-
Notifications
You must be signed in to change notification settings - Fork 42
Expand file tree
/
Copy pathcliCredentialManager.ts
More file actions
131 lines (116 loc) · 3.35 KB
/
cliCredentialManager.ts
File metadata and controls
131 lines (116 loc) · 3.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { isKeyringEnabled } from "../cliConfig";
import { getHeaderArgs } from "../headers";
import type { WorkspaceConfiguration } from "vscode";
import type { Logger } from "../logging/logger";
const execFileAsync = promisify(execFile);
/**
* Resolves a CLI binary path for a given deployment URL, fetching/downloading
* if needed. Returns the path or throws if unavailable.
*/
export type BinaryResolver = (url: string) => Promise<string>;
/**
* Returns true on platforms where the OS keyring is supported (macOS, Windows).
*/
export function isKeyringSupported(): boolean {
return process.platform === "darwin" || process.platform === "win32";
}
/**
* Delegates credential storage to the Coder CLI. All operations resolve the
* binary via the injected BinaryResolver before invoking it.
*/
export class CliCredentialManager {
constructor(
private readonly logger: Logger,
private readonly resolveBinary: BinaryResolver,
) {}
/**
* Store a token via `coder login --use-token-as-session`.
* Token is passed via CODER_SESSION_TOKEN env var, never in args.
*/
public async storeToken(
url: string,
token: string,
configs: Pick<WorkspaceConfiguration, "get">,
): Promise<void> {
let binPath: string;
try {
binPath = await this.resolveBinary(url);
} catch (error) {
this.logger.debug("Could not resolve CLI binary for token store:", error);
throw error;
}
const args = [
...getHeaderArgs(configs),
"login",
"--use-token-as-session",
url,
];
try {
await execFileAsync(binPath, args, {
env: { ...process.env, CODER_SESSION_TOKEN: token },
});
this.logger.info("Stored token via CLI for", url);
} catch (error) {
this.logger.debug("Failed to store token via CLI:", error);
throw error;
}
}
/**
* Read a token via `coder login token --url`. Returns trimmed stdout,
* or undefined on any failure (resolver, CLI, empty output).
*/
public async readToken(
url: string,
configs: Pick<WorkspaceConfiguration, "get">,
): Promise<string | undefined> {
if (!isKeyringEnabled(configs)) {
return undefined;
}
let binPath: string;
try {
binPath = await this.resolveBinary(url);
} catch (error) {
this.logger.debug("Could not resolve CLI binary for token read:", error);
return undefined;
}
const args = [...getHeaderArgs(configs), "login", "token", "--url", url];
try {
const { stdout } = await execFileAsync(binPath, args);
const token = stdout.trim();
return token || undefined;
} catch (error) {
this.logger.debug("Failed to read token via CLI:", error);
return undefined;
}
}
/**
* Delete a token via `coder logout --url`. Best-effort: never throws.
*/
public async deleteToken(
url: string,
configs: Pick<WorkspaceConfiguration, "get">,
): Promise<void> {
if (!isKeyringEnabled(configs)) {
return;
}
let binPath: string;
try {
binPath = await this.resolveBinary(url);
} catch (error) {
this.logger.debug(
"Could not resolve CLI binary for token delete:",
error,
);
return;
}
const args = [...getHeaderArgs(configs), "logout", "--url", url, "--yes"];
try {
await execFileAsync(binPath, args);
this.logger.info("Deleted token via CLI for", url);
} catch (error) {
this.logger.debug("Failed to delete token via CLI:", error);
}
}
}