返回 CodeWhale
secrets.ts
根目录 / extensions / vscode / src / secrets.ts
1 import * as vscode from "vscode";
2
3 const SECRET_KEY = "codewhale.runtimeToken";
4 const SETTING_KEY = "runtimeToken";
5
6 let warnedAboutWorkspaceToken = false;
7
8 /** Trim a setting value that arrived as `unknown` from a trust boundary. */
9 function nonEmptyString(value: unknown): string | undefined {
10 if (typeof value !== "string") {
11 return undefined;
12 }
13 const trimmed = value.trim();
14 return trimmed.length > 0 ? trimmed : undefined;
15 }
16
17 /**
18 * Resolve the runtime bearer token. SecretStorage is authoritative; the legacy
19 * `codewhale.runtimeToken` setting is a one-way migration source only, which is
20 * what `package.json`'s deprecation message and `README.md` promise.
21 *
22 * Only a *user-level* value is migrated. A workspace- or folder-scoped value is
23 * attacker-controlled input: `codewhale.runtimeHost` is workspace-settable too,
24 * so a repo-local `.vscode/settings.json` that supplied both would make merely
25 * opening the repository ship a bearer token to a host of its choosing. Such a
26 * value is ignored, never adopted.
27 */
28 export async function resolveToken(context: vscode.ExtensionContext): Promise<string | undefined> {
29 const stored = nonEmptyString(await context.secrets.get(SECRET_KEY));
30 if (stored) {
31 return stored;
32 }
33
34 const config = vscode.workspace.getConfiguration("codewhale");
35 const inspected = config.inspect<string>(SETTING_KEY);
36 const userToken = nonEmptyString(inspected?.globalValue);
37 if (!userToken) {
38 const workspaceToken =
39 nonEmptyString(inspected?.workspaceValue) ??
40 nonEmptyString(inspected?.workspaceFolderValue);
41 if (workspaceToken && !warnedAboutWorkspaceToken) {
42 warnedAboutWorkspaceToken = true;
43 void vscode.window.showWarningMessage(
44 "Ignoring codewhale.runtimeToken from workspace settings: a workspace cannot supply the runtime bearer token. Use CodeWhale: Set Runtime Token.",
45 );
46 }
47 return undefined;
48 }
49
50 await context.secrets.store(SECRET_KEY, userToken);
51 // One-way migration: drop the plaintext copy so it stops riding Settings Sync.
52 try {
53 await config.update(SETTING_KEY, undefined, vscode.ConfigurationTarget.Global);
54 } catch {
55 // A read-only settings.json must not cost the user a working token; the
56 // secret is already stored, so keep going and leave the plaintext behind.
57 }
58 return userToken;
59 }
60
61 export async function storeToken(context: vscode.ExtensionContext, token: string): Promise<void> {
62 await context.secrets.store(SECRET_KEY, token.trim());
63 }
64
65 export async function promptForToken(context: vscode.ExtensionContext): Promise<string | undefined> {
66 const entered = await vscode.window.showInputBox({
67 prompt: "Codewhale runtime bearer token (stored in VS Code secret storage)",
68 password: true,
69 ignoreFocusOut: true,
70 });
71 if (entered === undefined) {
72 return undefined;
73 }
74 const token = entered.trim();
75 if (token.length === 0) {
76 await context.secrets.delete(SECRET_KEY);
77 return undefined;
78 }
79 await storeToken(context, token);
80 return token;
81 }
82
82 lines TYPESCRIPT