Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

auth.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

auth.tsBlame86 lines · 1 contributor
63fe719Claude1/**
2 * Token storage + sign-in flow.
3 *
4 * Tokens are persisted via `vscode.SecretStorage` (OS keychain on
5 * macOS/Windows/Linux). We never write them to settings.json or disk.
6 */
7
8import * as vscode from "vscode";
9
10export const SECRET_KEY = "gluecron.pat";
11const CONTEXT_KEY = "gluecron:signedIn";
12
13export async function getToken(context: vscode.ExtensionContext): Promise<string | undefined> {
14 return context.secrets.get(SECRET_KEY);
15}
16
17export async function setToken(
18 context: vscode.ExtensionContext,
19 token: string
20): Promise<void> {
21 await context.secrets.store(SECRET_KEY, token);
22 await vscode.commands.executeCommand("setContext", CONTEXT_KEY, true);
23}
24
25export async function clearToken(context: vscode.ExtensionContext): Promise<void> {
26 await context.secrets.delete(SECRET_KEY);
27 await vscode.commands.executeCommand("setContext", CONTEXT_KEY, false);
28}
29
30/**
31 * Prompt the user for a PAT, validate it against `/api/v2/user`, and store it.
32 */
33export async function signInFlow(
34 context: vscode.ExtensionContext,
35 host: string
36): Promise<boolean> {
37 const url = `${host.replace(/\/+$/, "")}/settings/tokens`;
38 const token = await vscode.window.showInputBox({
39 prompt: `Paste a Gluecron PAT (create one at ${url})`,
40 placeHolder: "glc_...",
41 password: true,
42 ignoreFocusOut: true,
43 validateInput: (v) => {
44 const t = (v || "").trim();
45 if (!t) return "Token is required";
46 if (!/^glc_/.test(t)) return "Token should start with 'glc_'";
47 return null;
48 },
49 });
50 if (!token) return false;
51
52 // Best-effort validation; don't block sign-in if the network is flaky —
53 // the user can still use the extension and we'll surface 401s later.
54 try {
55 const me = await fetch(`${host.replace(/\/+$/, "")}/api/v2/user`, {
56 headers: { authorization: `Bearer ${token.trim()}` },
57 });
58 if (me.ok) {
59 const body = (await me.json()) as { username?: string };
60 vscode.window.showInformationMessage(
61 `Gluecron: signed in as ${body.username || "(unknown)"}`
62 );
63 } else if (me.status === 401) {
64 vscode.window.showWarningMessage(
65 "Gluecron: token was rejected (401). Saving anyway — you can re-run sign-in."
66 );
67 }
68 } catch {
69 vscode.window.showWarningMessage(
70 "Gluecron: couldn't reach the server to validate your token. Saving locally."
71 );
72 }
73
74 await setToken(context, token.trim());
75 return true;
76}
77
78/**
79 * Restore the signed-in context flag at activation time.
80 */
81export async function restoreSignedInContext(
82 context: vscode.ExtensionContext
83): Promise<void> {
84 const t = await getToken(context);
85 await vscode.commands.executeCommand("setContext", CONTEXT_KEY, !!t);
86}