add login with oskeychain
This commit is contained in:
@@ -17,7 +17,7 @@
|
||||
action: "push" | "pull";
|
||||
error: string;
|
||||
isBusy: boolean;
|
||||
onSubmit: (username: string, password: string, save: boolean) => void;
|
||||
onSubmit: (username: string, password: string, save: boolean, expiresAt: string | null) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
let password = $state("");
|
||||
let showPassword = $state(false);
|
||||
let saveSession = $state(false);
|
||||
let expiresAt = $state("");
|
||||
|
||||
let canSubmit = $derived(
|
||||
!isBusy &&
|
||||
@@ -51,7 +52,12 @@
|
||||
function handleSubmit(e: SubmitEvent) {
|
||||
e.preventDefault();
|
||||
if (!canSubmit) return;
|
||||
onSubmit(mode === "token" ? "oauth2" : username, password, saveSession);
|
||||
onSubmit(
|
||||
mode === "token" ? "oauth2" : username,
|
||||
password,
|
||||
saveSession,
|
||||
saveSession && expiresAt ? expiresAt : null,
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -83,7 +89,7 @@
|
||||
|
||||
<div class="cred-security-note">
|
||||
<ShieldCheck size={14} aria-hidden="true" />
|
||||
<span>Wird nur an Git fuer diese Remote-Operation weitergegeben.</span>
|
||||
<span>Beim Speichern landet der Token verschluesselt im Schluesselbund des Betriebssystems – nie im Klartext.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -176,10 +182,23 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if saveSession}
|
||||
<div class="cred-expiry">
|
||||
<label class="cred-field-label" for="cred-expiry">Ablaufdatum (optional)</label>
|
||||
<input
|
||||
id="cred-expiry"
|
||||
type="date"
|
||||
bind:value={expiresAt}
|
||||
disabled={isBusy}
|
||||
/>
|
||||
<span class="cred-expiry-hint">Nach diesem Datum wird automatisch erneut nach dem Login gefragt.</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="cred-footer">
|
||||
<label class="cred-save">
|
||||
<input type="checkbox" bind:checked={saveSession} disabled={isBusy} />
|
||||
<span>Fuer diese Sitzung merken</span>
|
||||
<span>Im Schluesselbund speichern</span>
|
||||
</label>
|
||||
|
||||
<div class="cred-btns">
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { StoredCredential } from "./types";
|
||||
|
||||
/**
|
||||
* Derives a credential key from a remote URL, scoped to host + organisation —
|
||||
* the same granularity Azure DevOps / GitHub use. Examples:
|
||||
* https://github.com/owner/repo.git -> github.com/owner
|
||||
* https://dev.azure.com/org/project/_git/repo -> dev.azure.com/org
|
||||
* git@github.com:owner/repo.git -> github.com/owner
|
||||
* ssh://git@host:2222/owner/repo -> host/owner
|
||||
* Returns null when nothing usable can be parsed.
|
||||
*/
|
||||
export function orgKeyFromUrl(raw: string): string | null {
|
||||
const url = raw.trim();
|
||||
if (!url) return null;
|
||||
|
||||
let host = "";
|
||||
let path = "";
|
||||
|
||||
// scp-like syntax: user@host:owner/repo.git (no scheme, single colon segment)
|
||||
const scp = url.match(/^[^@/]+@([^:/]+):(.+)$/);
|
||||
if (scp && !url.includes("://")) {
|
||||
host = scp[1];
|
||||
path = scp[2];
|
||||
} else {
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
host = parsed.host; // host:port, without userinfo
|
||||
path = parsed.pathname;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
host = host.toLowerCase();
|
||||
const org = path.replace(/^\/+/, "").split("/").filter(Boolean)[0] ?? "";
|
||||
if (!host) return null;
|
||||
return org ? `${host}/${org}` : host;
|
||||
}
|
||||
|
||||
/** A stored credential is expired only if it carries a past expiry date. */
|
||||
export function isCredentialExpired(cred: StoredCredential): boolean {
|
||||
if (!cred.expiresAt) return false;
|
||||
const time = new Date(cred.expiresAt).getTime();
|
||||
return !Number.isNaN(time) && time < Date.now();
|
||||
}
|
||||
|
||||
const AUTH_PREFIX = "AUTH_FAILED:";
|
||||
|
||||
export function isAuthError(message: string): boolean {
|
||||
return message.startsWith(AUTH_PREFIX);
|
||||
}
|
||||
|
||||
export function stripAuthPrefix(message: string): string {
|
||||
return message.startsWith(AUTH_PREFIX)
|
||||
? message.slice(AUTH_PREFIX.length).trim()
|
||||
: message;
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
GitRepositoryFile,
|
||||
GitSearchHit,
|
||||
GitStatus,
|
||||
StoredCredential,
|
||||
} from "./types";
|
||||
|
||||
export function openRepository(path: string): Promise<GitStatus> {
|
||||
@@ -54,6 +55,27 @@ export function push(path: string, username?: string, password?: string): Promis
|
||||
return invoke<GitStatus>("push", { path, username: username ?? null, password: password ?? null });
|
||||
}
|
||||
|
||||
export function getRemoteUrl(path: string): Promise<string | null> {
|
||||
return invoke<string | null>("get_remote_url", { path });
|
||||
}
|
||||
|
||||
export function credLoad(key: string): Promise<StoredCredential | null> {
|
||||
return invoke<StoredCredential | null>("cred_load", { key });
|
||||
}
|
||||
|
||||
export function credSave(
|
||||
key: string,
|
||||
username: string,
|
||||
password: string,
|
||||
expiresAt: string | null,
|
||||
): Promise<void> {
|
||||
return invoke<void>("cred_save", { key, username, password, expiresAt });
|
||||
}
|
||||
|
||||
export function credDelete(key: string): Promise<void> {
|
||||
return invoke<void>("cred_delete", { key });
|
||||
}
|
||||
|
||||
export function listCommits(path: string, limit = 100): Promise<GitCommit[]> {
|
||||
return invoke<GitCommit[]>("list_commits", { path, limit });
|
||||
}
|
||||
|
||||
@@ -117,3 +117,9 @@ export interface ConflictFile {
|
||||
ours_size: number | null;
|
||||
theirs_size: number | null;
|
||||
}
|
||||
|
||||
export interface StoredCredential {
|
||||
username: string;
|
||||
password: string;
|
||||
expiresAt?: string | null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user