add login with oskeychain
This commit is contained in:
+115
-25
@@ -32,6 +32,10 @@
|
||||
openRepository,
|
||||
pull,
|
||||
push,
|
||||
getRemoteUrl,
|
||||
credLoad,
|
||||
credSave,
|
||||
credDelete,
|
||||
readConflict,
|
||||
resolveConflict,
|
||||
resolveConflictSide,
|
||||
@@ -57,8 +61,16 @@
|
||||
GitSearchHit,
|
||||
GitStatus,
|
||||
PreparedResolution,
|
||||
StoredCredential,
|
||||
} from "./lib/types";
|
||||
|
||||
import {
|
||||
orgKeyFromUrl,
|
||||
isCredentialExpired,
|
||||
isAuthError,
|
||||
stripAuthPrefix,
|
||||
} from "./lib/credentials";
|
||||
|
||||
// ── State ──────────────────────────────────────────────────────────────────
|
||||
|
||||
let repoPath = "";
|
||||
@@ -95,7 +107,7 @@
|
||||
let credDialogOpen = false;
|
||||
let credDialogAction: "push" | "pull" | null = null;
|
||||
let credDialogError = "";
|
||||
let sessionCredentials: { username: string; password: string } | null = null;
|
||||
let credDialogKey: string | null = null;
|
||||
let lastStatusFingerprint = "";
|
||||
const AUTO_REFRESH_INTERVAL = 4000;
|
||||
let autoRefreshTimer: ReturnType<typeof setInterval> | undefined;
|
||||
@@ -306,14 +318,69 @@
|
||||
});
|
||||
}
|
||||
|
||||
function openCredentialDialog(action: "push" | "pull") {
|
||||
// Resolve the keychain key (host/org) for the active repo's remote.
|
||||
async function currentCredKey(): Promise<string | null> {
|
||||
if (!activeRepoPath) return null;
|
||||
try {
|
||||
const url = await getRemoteUrl(activeRepoPath);
|
||||
return url ? orgKeyFromUrl(url) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadStoredCredential(key: string | null): Promise<StoredCredential | null> {
|
||||
if (!key) return null;
|
||||
try {
|
||||
return await credLoad(key);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function openCredentialDialog(action: "push" | "pull", key?: string | null) {
|
||||
if (!activeRepoPath) return;
|
||||
credDialogError = "";
|
||||
credDialogAction = action;
|
||||
credDialogKey = key === undefined ? await currentCredKey() : key;
|
||||
credDialogOpen = true;
|
||||
}
|
||||
|
||||
async function doActualPull(username: string, password: string) {
|
||||
// Post-process a pull/push result: surface errors, and on rejected/expired
|
||||
// credentials drop the stored entry and re-open the login dialog.
|
||||
function handleRemoteResult(action: "push" | "pull", key: string | null, fromStore: boolean) {
|
||||
if (!errorMessage) {
|
||||
credDialogOpen = false;
|
||||
credDialogAction = null;
|
||||
return;
|
||||
}
|
||||
const auth = isAuthError(errorMessage);
|
||||
const message = stripAuthPrefix(errorMessage);
|
||||
errorMessage = "";
|
||||
|
||||
if (fromStore) {
|
||||
if (auth) {
|
||||
if (key) void credDelete(key).catch(() => {});
|
||||
credDialogError =
|
||||
"Zugangsdaten wurden abgelehnt oder sind abgelaufen. Bitte erneut anmelden.";
|
||||
credDialogAction = action;
|
||||
credDialogKey = key;
|
||||
credDialogOpen = true;
|
||||
} else {
|
||||
// Non-auth failure (e.g. network) – keep the stored credential, show it inline.
|
||||
errorMessage = message;
|
||||
}
|
||||
} else {
|
||||
credDialogError = message || "Anmeldung fehlgeschlagen.";
|
||||
}
|
||||
}
|
||||
|
||||
async function doActualPull(
|
||||
username: string,
|
||||
password: string,
|
||||
key: string | null,
|
||||
fromStore: boolean,
|
||||
) {
|
||||
errorMessage = "";
|
||||
await runOperation("Pulling", async () => {
|
||||
applyStatus(await pull(activeRepoPath, username, password));
|
||||
@@ -322,11 +389,15 @@
|
||||
await refreshExplorerFiles(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
});
|
||||
if (errorMessage) { credDialogError = errorMessage; errorMessage = ""; }
|
||||
else { credDialogOpen = false; credDialogAction = null; }
|
||||
handleRemoteResult("pull", key, fromStore);
|
||||
}
|
||||
|
||||
async function doActualPush(username: string, password: string) {
|
||||
async function doActualPush(
|
||||
username: string,
|
||||
password: string,
|
||||
key: string | null,
|
||||
fromStore: boolean,
|
||||
) {
|
||||
errorMessage = "";
|
||||
await runOperation("Pushing", async () => {
|
||||
applyStatus(await push(activeRepoPath, username, password));
|
||||
@@ -334,32 +405,51 @@
|
||||
await refreshCommitHistory(activeRepoPath);
|
||||
await refreshFileHistory(activeRepoPath);
|
||||
});
|
||||
if (errorMessage) { credDialogError = errorMessage; errorMessage = ""; }
|
||||
else { credDialogOpen = false; credDialogAction = null; }
|
||||
handleRemoteResult("push", key, fromStore);
|
||||
}
|
||||
|
||||
async function handleCredentialSubmit(username: string, password: string, save: boolean) {
|
||||
if (credDialogAction === "pull") await doActualPull(username, password);
|
||||
else if (credDialogAction === "push") await doActualPush(username, password);
|
||||
if (!credDialogOpen && save) sessionCredentials = { username, password };
|
||||
async function handleCredentialSubmit(
|
||||
username: string,
|
||||
password: string,
|
||||
save: boolean,
|
||||
expiresAt: string | null,
|
||||
) {
|
||||
const key = credDialogKey;
|
||||
if (credDialogAction === "pull") await doActualPull(username, password, key, false);
|
||||
else if (credDialogAction === "push") await doActualPush(username, password, key, false);
|
||||
|
||||
// Only persist once the operation actually succeeded (dialog has closed).
|
||||
if (!credDialogOpen && save && key) {
|
||||
try {
|
||||
await credSave(key, username, password, expiresAt);
|
||||
} catch (error) {
|
||||
errorMessage = errorToMessage(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function startRemoteAction(action: "push" | "pull") {
|
||||
if (!activeRepoPath) return;
|
||||
const key = await currentCredKey();
|
||||
const stored = await loadStoredCredential(key);
|
||||
|
||||
if (stored && !isCredentialExpired(stored)) {
|
||||
if (action === "pull") await doActualPull(stored.username, stored.password, key, true);
|
||||
else await doActualPush(stored.username, stored.password, key, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Expired entry → clean it up before prompting again.
|
||||
if (stored && key) await credDelete(key).catch(() => {});
|
||||
await openCredentialDialog(action, key);
|
||||
}
|
||||
|
||||
async function pullRepo() {
|
||||
if (!activeRepoPath) return;
|
||||
if (sessionCredentials) {
|
||||
await doActualPull(sessionCredentials.username, sessionCredentials.password);
|
||||
} else {
|
||||
openCredentialDialog("pull");
|
||||
}
|
||||
await startRemoteAction("pull");
|
||||
}
|
||||
|
||||
async function pushRepo() {
|
||||
if (!activeRepoPath) return;
|
||||
if (sessionCredentials) {
|
||||
await doActualPush(sessionCredentials.username, sessionCredentials.password);
|
||||
} else {
|
||||
openCredentialDialog("push");
|
||||
}
|
||||
await startRemoteAction("push");
|
||||
}
|
||||
|
||||
// ── File staging / restore ─────────────────────────────────────────────────
|
||||
@@ -881,7 +971,7 @@
|
||||
error={credDialogError}
|
||||
{isBusy}
|
||||
onSubmit={handleCredentialSubmit}
|
||||
onCancel={() => { credDialogOpen = false; credDialogAction = null; credDialogError = ""; }}
|
||||
onCancel={() => { credDialogOpen = false; credDialogAction = null; credDialogError = ""; credDialogKey = null; }}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user