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.
| 63fe719 | 1 | /** |
| 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 | ||
| 8 | import * as vscode from "vscode"; | |
| 9 | ||
| 10 | export const SECRET_KEY = "gluecron.pat"; | |
| 11 | const CONTEXT_KEY = "gluecron:signedIn"; | |
| 12 | ||
| 13 | export async function getToken(context: vscode.ExtensionContext): Promise<string | undefined> { | |
| 14 | return context.secrets.get(SECRET_KEY); | |
| 15 | } | |
| 16 | ||
| 17 | export 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 | ||
| 25 | export 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 | */ | |
| 33 | export 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 | */ | |
| 81 | export 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 | } |