@seliseblocks/cli-os 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI_USAGE_GUIDE.md +18 -38
- package/README.md +13 -36
- package/dist/commands/auth/status.js +16 -55
- package/dist/commands/doctor.js +3 -4
- package/dist/commands/init.js +4 -2
- package/dist/commands/login.d.ts +0 -1
- package/dist/commands/login.js +15 -55
- package/dist/commands/new/web.js +1 -1
- package/dist/index.js +10 -56
- package/dist/lib/auth.d.ts +4 -11
- package/dist/lib/auth.js +43 -18
- package/dist/lib/config.d.ts +1 -2
- package/dist/lib/config.js +45 -13
- package/dist/lib/open-browser.js +26 -3
- package/dist/lib/secret-store.d.ts +0 -1
- package/dist/lib/secret-store.js +1 -4
- package/dist/lib/token-store.d.ts +0 -1
- package/dist/lib/token-store.js +1 -5
- package/dist/lib/token.d.ts +0 -1
- package/dist/lib/token.js +0 -4
- package/package.json +1 -1
- package/dist/commands/auth/add.d.ts +0 -1
- package/dist/commands/auth/add.js +0 -51
- package/dist/commands/auth/list.d.ts +0 -1
- package/dist/commands/auth/list.js +0 -33
- package/dist/commands/auth/repair.d.ts +0 -1
- package/dist/commands/auth/repair.js +0 -39
- package/dist/commands/auth/show.d.ts +0 -1
- package/dist/commands/auth/show.js +0 -23
- package/dist/commands/auth/use.d.ts +0 -1
- package/dist/commands/auth/use.js +0 -15
- package/dist/commands/login-device.d.ts +0 -1
- package/dist/commands/login-device.js +0 -6
- package/dist/lib/login-server.d.ts +0 -1
- package/dist/lib/login-server.js +0 -43
- package/dist/lib/pkce.d.ts +0 -4
- package/dist/lib/pkce.js +0 -9
- package/dist/lib/prompt.d.ts +0 -2
- package/dist/lib/prompt.js +0 -36
package/dist/lib/auth.js
CHANGED
|
@@ -5,18 +5,7 @@ import { getClientSecret } from "./secret-store.js";
|
|
|
5
5
|
import { CliActionableError } from "./errors.js";
|
|
6
6
|
const DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
|
|
7
7
|
const FALLBACK_IMPERSONATION_CLIENT_ID = "57214b67-aa9c-4307-92ab-a25e35180fac";
|
|
8
|
-
|
|
9
|
-
const body = new URLSearchParams({
|
|
10
|
-
client_id: args.clientId,
|
|
11
|
-
code: args.code,
|
|
12
|
-
grant_type: "authorization_code",
|
|
13
|
-
redirect_uri: args.redirectUri
|
|
14
|
-
});
|
|
15
|
-
if (args.codeVerifier)
|
|
16
|
-
body.set("code_verifier", args.codeVerifier);
|
|
17
|
-
applyClientSecret(body, args.clientSecret);
|
|
18
|
-
return postFormToken(args.oidcUrl, body, args.rootTenantId);
|
|
19
|
-
}
|
|
8
|
+
const MAX_CONSECUTIVE_TRANSIENT_ERRORS = 3;
|
|
20
9
|
export async function requestDeviceAuthorization(profile) {
|
|
21
10
|
const rootTenantId = await resolveRootTenantForDevice(profile);
|
|
22
11
|
if (!rootTenantId) {
|
|
@@ -45,34 +34,70 @@ export async function requestDeviceAuthorization(profile) {
|
|
|
45
34
|
}
|
|
46
35
|
return data;
|
|
47
36
|
}
|
|
48
|
-
export async function pollDeviceToken(profile, device) {
|
|
37
|
+
export async function pollDeviceToken(profile, device, options = {}) {
|
|
49
38
|
const rootTenantId = await resolveRootTenantForDevice(profile);
|
|
50
39
|
if (!rootTenantId) {
|
|
51
40
|
throw new Error("Device token polling requires rootTenantId in the account profile.");
|
|
52
41
|
}
|
|
53
42
|
let intervalSeconds = Math.max(device.interval ?? 5, 1);
|
|
54
43
|
const deadline = Date.now() + device.expires_in * 1000;
|
|
44
|
+
let consecutiveTransientErrors = 0;
|
|
55
45
|
while (Date.now() < deadline) {
|
|
56
|
-
|
|
46
|
+
options.onWait?.(intervalSeconds);
|
|
47
|
+
await delay(Math.min(intervalSeconds * 1000, Math.max(deadline - Date.now(), 0)));
|
|
57
48
|
const body = new URLSearchParams({
|
|
58
49
|
client_id: profile.clientId,
|
|
59
50
|
device_code: device.device_code,
|
|
60
51
|
grant_type: DEVICE_GRANT
|
|
61
52
|
});
|
|
62
53
|
applyClientSecret(body, await getSecretForProfile(profile));
|
|
63
|
-
|
|
64
|
-
|
|
54
|
+
let response;
|
|
55
|
+
try {
|
|
56
|
+
response = await postFormToken(profile.oidcUrl, body, rootTenantId, false);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
// The token endpoint itself never throws (postFormToken with
|
|
60
|
+
// throwOnOAuthError=false only returns error payloads) -- a thrown
|
|
61
|
+
// error here means fetch() failed before any response came back
|
|
62
|
+
// (DNS, connection reset, etc.). Treat it as transient and keep
|
|
63
|
+
// polling rather than aborting a multi-minute wait on one blip.
|
|
64
|
+
throwIfTooManyTransientErrors(++consecutiveTransientErrors, error);
|
|
65
65
|
continue;
|
|
66
|
+
}
|
|
67
|
+
if (response.error === "authorization_pending") {
|
|
68
|
+
consecutiveTransientErrors = 0;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
66
71
|
if (response.error === "slow_down") {
|
|
72
|
+
consecutiveTransientErrors = 0;
|
|
67
73
|
intervalSeconds += 5;
|
|
68
74
|
continue;
|
|
69
75
|
}
|
|
76
|
+
if (response.error === "token_request_failed") {
|
|
77
|
+
// Synthetic error from postFormToken for a non-JSON HTTP failure
|
|
78
|
+
// (e.g. a 502 from an upstream proxy) -- not a real OAuth rejection,
|
|
79
|
+
// so treat it the same as a network blip.
|
|
80
|
+
throwIfTooManyTransientErrors(++consecutiveTransientErrors, response.error_description ?? response.error);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (response.error === "access_denied") {
|
|
84
|
+
throw new CliActionableError("Device authorization was denied.", "device_login_denied", "blocks-os login");
|
|
85
|
+
}
|
|
86
|
+
if (response.error === "expired_token") {
|
|
87
|
+
throw new CliActionableError("Device login expired before approval.", "device_login_expired", "blocks-os login");
|
|
88
|
+
}
|
|
70
89
|
if (response.error) {
|
|
71
|
-
throw new
|
|
90
|
+
throw new CliActionableError(response.error_description ?? response.error, "device_login_failed", "blocks-os login");
|
|
72
91
|
}
|
|
73
92
|
return response;
|
|
74
93
|
}
|
|
75
|
-
throw new
|
|
94
|
+
throw new CliActionableError("Device login expired before approval.", "device_login_expired", "blocks-os login");
|
|
95
|
+
}
|
|
96
|
+
function throwIfTooManyTransientErrors(count, cause) {
|
|
97
|
+
if (count <= MAX_CONSECUTIVE_TRANSIENT_ERRORS)
|
|
98
|
+
return;
|
|
99
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
100
|
+
throw new CliActionableError(`Could not reach the identity provider while waiting for device approval (${detail}).`, "device_login_network_error", "Check your network connection and run 'blocks-os login' again.");
|
|
76
101
|
}
|
|
77
102
|
export async function getAccountSession(accountOverride) {
|
|
78
103
|
let config = await readConfig();
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -4,7 +4,6 @@ export type AccountProfile = {
|
|
|
4
4
|
createdAt: string;
|
|
5
5
|
oidcUrl: string;
|
|
6
6
|
osUrl: string;
|
|
7
|
-
redirectUri: string;
|
|
8
7
|
rootTenantId?: string;
|
|
9
8
|
scope: string;
|
|
10
9
|
updatedAt: string;
|
|
@@ -31,9 +30,9 @@ export type BlocksCliConfig = {
|
|
|
31
30
|
};
|
|
32
31
|
export declare function defaults(): {
|
|
33
32
|
apiUrl: string;
|
|
33
|
+
osClientId: string;
|
|
34
34
|
oidcUrl: string;
|
|
35
35
|
osUrl: string;
|
|
36
|
-
redirectUri: string;
|
|
37
36
|
rootTenantId: string;
|
|
38
37
|
scope: string;
|
|
39
38
|
};
|
package/dist/lib/config.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { homedir, platform } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
const
|
|
4
|
+
const BAD_GATEWAY_OS_URL = "https://api.seliseblocks.com/os/v4";
|
|
5
|
+
const DEFAULT_API_URL = "https://os.seliseblocks.com";
|
|
6
|
+
const DEFAULT_OS_CLIENT_ID = "4a633b13-1108-4fbf-84fd-b196c9dcdee2";
|
|
5
7
|
const DEFAULT_OIDC_URL = "https://iam.seliseblocks.com";
|
|
6
8
|
const DEFAULT_OS_URL = "https://os.seliseblocks.com";
|
|
7
|
-
const
|
|
9
|
+
const DEFAULT_PROFILE_TIMESTAMP = "2026-01-01T00:00:00.000Z";
|
|
8
10
|
const DEFAULT_ROOT_TENANT_ID = "d7e5554c758541db8a18694b64ef423d";
|
|
9
11
|
const DEFAULT_SCOPE = "openid profile offline_access";
|
|
10
|
-
const DEFAULT_REDIRECT_URI = "http://127.0.0.1:8976/callback";
|
|
11
12
|
export function defaults() {
|
|
12
13
|
return {
|
|
13
14
|
apiUrl: DEFAULT_API_URL,
|
|
15
|
+
osClientId: DEFAULT_OS_CLIENT_ID,
|
|
14
16
|
oidcUrl: DEFAULT_OIDC_URL,
|
|
15
17
|
osUrl: DEFAULT_OS_URL,
|
|
16
|
-
redirectUri: DEFAULT_REDIRECT_URI,
|
|
17
18
|
rootTenantId: DEFAULT_ROOT_TENANT_ID,
|
|
18
19
|
scope: DEFAULT_SCOPE
|
|
19
20
|
};
|
|
@@ -40,9 +41,7 @@ export async function readConfig() {
|
|
|
40
41
|
}
|
|
41
42
|
catch (error) {
|
|
42
43
|
if (error.code === "ENOENT") {
|
|
43
|
-
return
|
|
44
|
-
accounts: {}
|
|
45
|
-
};
|
|
44
|
+
return defaultConfig();
|
|
46
45
|
}
|
|
47
46
|
throw error;
|
|
48
47
|
}
|
|
@@ -61,7 +60,7 @@ export function normalizeAccountName(account) {
|
|
|
61
60
|
export function getActiveAccountName(config, override) {
|
|
62
61
|
const account = normalizeAccountName(override ?? config.activeAccount);
|
|
63
62
|
if (!config.accounts[account]) {
|
|
64
|
-
throw new Error("OIDC account is not configured.
|
|
63
|
+
throw new Error("OIDC account is not configured.");
|
|
65
64
|
}
|
|
66
65
|
return account;
|
|
67
66
|
}
|
|
@@ -73,21 +72,54 @@ export function getAccountProfile(config, account) {
|
|
|
73
72
|
};
|
|
74
73
|
}
|
|
75
74
|
function normalizeConfig(config) {
|
|
75
|
+
const env = defaults();
|
|
76
76
|
const accounts = {};
|
|
77
77
|
for (const [name, profile] of Object.entries(config.accounts ?? {})) {
|
|
78
|
-
accounts[name] = {
|
|
78
|
+
accounts[name] = name === "default" ? defaultProfile(profile) : {
|
|
79
79
|
...profile,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
apiUrl: profile.apiUrl ?? env.apiUrl,
|
|
81
|
+
clientId: profile.clientId ?? env.osClientId,
|
|
82
|
+
createdAt: profile.createdAt ?? DEFAULT_PROFILE_TIMESTAMP,
|
|
83
|
+
oidcUrl: profile.oidcUrl ?? env.oidcUrl,
|
|
84
|
+
osUrl: !profile.osUrl || profile.osUrl === BAD_GATEWAY_OS_URL ? env.osUrl : profile.osUrl,
|
|
85
|
+
rootTenantId: profile.rootTenantId ?? env.rootTenantId,
|
|
86
|
+
scope: profile.scope ?? env.scope,
|
|
87
|
+
updatedAt: profile.updatedAt ?? DEFAULT_PROFILE_TIMESTAMP
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (Object.keys(accounts).length === 0) {
|
|
91
|
+
return {
|
|
92
|
+
...defaultConfig(),
|
|
93
|
+
selectedProject: config.selectedProject
|
|
83
94
|
};
|
|
84
95
|
}
|
|
85
96
|
return {
|
|
86
|
-
activeAccount: config.activeAccount,
|
|
97
|
+
activeAccount: config.activeAccount ?? "default",
|
|
87
98
|
accounts,
|
|
88
99
|
selectedProject: config.selectedProject
|
|
89
100
|
};
|
|
90
101
|
}
|
|
102
|
+
function defaultConfig() {
|
|
103
|
+
return {
|
|
104
|
+
activeAccount: "default",
|
|
105
|
+
accounts: {
|
|
106
|
+
default: defaultProfile()
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function defaultProfile(existing) {
|
|
111
|
+
const env = defaults();
|
|
112
|
+
return {
|
|
113
|
+
apiUrl: env.apiUrl,
|
|
114
|
+
clientId: env.osClientId,
|
|
115
|
+
createdAt: existing?.createdAt ?? DEFAULT_PROFILE_TIMESTAMP,
|
|
116
|
+
oidcUrl: env.oidcUrl,
|
|
117
|
+
osUrl: env.osUrl,
|
|
118
|
+
rootTenantId: env.rootTenantId,
|
|
119
|
+
scope: env.scope,
|
|
120
|
+
updatedAt: DEFAULT_PROFILE_TIMESTAMP
|
|
121
|
+
};
|
|
122
|
+
}
|
|
91
123
|
function nonEmptyEnv(name) {
|
|
92
124
|
const value = process.env[name]?.trim();
|
|
93
125
|
return value || undefined;
|
package/dist/lib/open-browser.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { platform } from "node:os";
|
|
3
|
+
const LAUNCH_GRACE_PERIOD_MS = 500;
|
|
3
4
|
export async function openBrowser(url) {
|
|
4
5
|
const os = platform();
|
|
5
6
|
const command = os === "win32" ? "rundll32" : os === "darwin" ? "open" : "xdg-open";
|
|
@@ -10,10 +11,32 @@ export async function openBrowser(url) {
|
|
|
10
11
|
stdio: "ignore",
|
|
11
12
|
windowsHide: true
|
|
12
13
|
});
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
let settled = false;
|
|
15
|
+
const settle = (result) => {
|
|
16
|
+
if (settled)
|
|
17
|
+
return;
|
|
18
|
+
settled = true;
|
|
19
|
+
resolve(result);
|
|
20
|
+
};
|
|
21
|
+
child.once("error", () => settle(false));
|
|
22
|
+
// A launcher like xdg-open can spawn successfully and still exit
|
|
23
|
+
// non-zero almost immediately when no browser/display session is
|
|
24
|
+
// available (headless SSH, missing DISPLAY, etc.). Give it a short
|
|
25
|
+
// grace period to fail fast before treating the launch as a success
|
|
26
|
+
// and detaching the process.
|
|
27
|
+
const grace = setTimeout(() => {
|
|
15
28
|
child.unref();
|
|
16
|
-
|
|
29
|
+
settle(true);
|
|
30
|
+
}, LAUNCH_GRACE_PERIOD_MS);
|
|
31
|
+
child.once("exit", (code) => {
|
|
32
|
+
clearTimeout(grace);
|
|
33
|
+
if (code === 0 || code === null) {
|
|
34
|
+
child.unref();
|
|
35
|
+
settle(true);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
settle(false);
|
|
39
|
+
}
|
|
17
40
|
});
|
|
18
41
|
});
|
|
19
42
|
}
|
|
@@ -17,7 +17,6 @@ export declare function writeSecretStore(store: BlocksSecretStore): Promise<void
|
|
|
17
17
|
export declare function setClientSecret(account: string, clientSecret: string): Promise<void>;
|
|
18
18
|
export declare function getClientSecret(account: string): Promise<string | undefined>;
|
|
19
19
|
export declare function removeAccountSecrets(account: string): Promise<void>;
|
|
20
|
-
export declare function clearSecretStore(): Promise<void>;
|
|
21
20
|
export declare function setSecretValue(key: string, value: string): Promise<void>;
|
|
22
21
|
export declare function getSecretValue(key: string): Promise<string | undefined>;
|
|
23
22
|
export declare function removeSecretValue(key: string): Promise<void>;
|
package/dist/lib/secret-store.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFile, spawn } from "node:child_process";
|
|
2
|
-
import { mkdir, readFile,
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { platform } from "node:os";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
@@ -57,9 +57,6 @@ export async function removeAccountSecrets(account) {
|
|
|
57
57
|
await removeSecretValue(`client-secret:${account}`);
|
|
58
58
|
await removeFallbackSecret(account);
|
|
59
59
|
}
|
|
60
|
-
export async function clearSecretStore() {
|
|
61
|
-
await rm(secretPath(), { force: true });
|
|
62
|
-
}
|
|
63
60
|
export async function setSecretValue(key, value) {
|
|
64
61
|
const backend = await resolveBackend();
|
|
65
62
|
if (backend === "macos-keychain") {
|
|
@@ -15,4 +15,3 @@ export declare function tokenStoreInfo(): Promise<{
|
|
|
15
15
|
export declare function readTokenStore(): Promise<BlocksTokenStore>;
|
|
16
16
|
export declare function writeTokenStore(store: BlocksTokenStore): Promise<void>;
|
|
17
17
|
export declare function removeAccountTokens(account: string): Promise<void>;
|
|
18
|
-
export declare function clearTokenStore(): Promise<void>;
|
package/dist/lib/token-store.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { configDir, configPath } from "./config.js";
|
|
4
|
-
import { getSecretValue,
|
|
4
|
+
import { getSecretValue, secretStoreInfo, setSecretValue } from "./secret-store.js";
|
|
5
5
|
const TOKEN_SECRET_KEY = "oauth-token-store";
|
|
6
6
|
export function tokenPath() {
|
|
7
7
|
return join(configDir(), "tokens.json");
|
|
@@ -56,10 +56,6 @@ export async function removeAccountTokens(account) {
|
|
|
56
56
|
const { [account]: _, ...accounts } = store.accounts;
|
|
57
57
|
await writeTokenStore({ accounts });
|
|
58
58
|
}
|
|
59
|
-
export async function clearTokenStore() {
|
|
60
|
-
await removeSecretValue(TOKEN_SECRET_KEY);
|
|
61
|
-
await rm(tokenPath(), { force: true });
|
|
62
|
-
}
|
|
63
59
|
function normalizeTokenStore(store) {
|
|
64
60
|
return {
|
|
65
61
|
accounts: store?.accounts ?? {}
|
package/dist/lib/token.d.ts
CHANGED
|
@@ -13,7 +13,6 @@ export type TokenResponse = {
|
|
|
13
13
|
scope?: string;
|
|
14
14
|
token_type?: string;
|
|
15
15
|
};
|
|
16
|
-
export declare function randomState(): string;
|
|
17
16
|
export declare function expiresAtFromToken(response: TokenResponse, accessToken: string): string | undefined;
|
|
18
17
|
export declare function expiresAtFromJwtFirst(response: TokenResponse, accessToken: string): string | undefined;
|
|
19
18
|
export declare function refreshExpiresAtFromToken(response: TokenResponse, refreshToken?: string): string | undefined;
|
package/dist/lib/token.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import { randomBytes } from "node:crypto";
|
|
2
1
|
import { decodeJwtPayload, tenantFromToken } from "./jwt.js";
|
|
3
2
|
const EXPIRY_SKEW_MS = 60_000;
|
|
4
3
|
const DEFAULT_REFRESH_TOKEN_LIFETIME_SECONDS = 30 * 60;
|
|
5
|
-
export function randomState() {
|
|
6
|
-
return randomBytes(24).toString("base64url");
|
|
7
|
-
}
|
|
8
4
|
export function expiresAtFromToken(response, accessToken) {
|
|
9
5
|
if (typeof response.expires_in === "number" && response.expires_in > 0) {
|
|
10
6
|
return new Date(Date.now() + response.expires_in * 1000).toISOString();
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function authAdd(argv: string[]): Promise<void>;
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { parseFlags, stringFlag } from "../../lib/args.js";
|
|
2
|
-
import { defaults, normalizeAccountName, readConfig, writeConfig } from "../../lib/config.js";
|
|
3
|
-
import { promptSecret, promptValue } from "../../lib/prompt.js";
|
|
4
|
-
import { getClientSecret, setClientSecret } from "../../lib/secret-store.js";
|
|
5
|
-
export async function authAdd(argv) {
|
|
6
|
-
const { args, flags } = parseFlags(argv);
|
|
7
|
-
const account = normalizeAccountName(args[0]);
|
|
8
|
-
const current = await readConfig();
|
|
9
|
-
const previous = current.accounts[account];
|
|
10
|
-
const now = new Date().toISOString();
|
|
11
|
-
const apiUrl = stringFlag(flags, "api-url") || previous?.apiUrl || defaults().apiUrl;
|
|
12
|
-
const oidcUrl = stringFlag(flags, "oidc-url") || previous?.oidcUrl || defaults().oidcUrl;
|
|
13
|
-
const osUrl = stringFlag(flags, "os-url") || defaults().osUrl;
|
|
14
|
-
const clientId = await value(flags, "client-id", "OIDC client id", previous?.clientId);
|
|
15
|
-
const existingSecret = await getClientSecret(account);
|
|
16
|
-
const clientSecret = await value(flags, "client-secret", "OIDC client secret", existingSecret, true);
|
|
17
|
-
const scope = stringFlag(flags, "scope") || previous?.scope || defaults().scope;
|
|
18
|
-
const redirectUri = stringFlag(flags, "redirect-uri") || previous?.redirectUri || defaults().redirectUri;
|
|
19
|
-
const rootTenantId = optionalStringFlag(flags, "root-tenant") ?? previous?.rootTenantId ?? defaults().rootTenantId;
|
|
20
|
-
await writeConfig({
|
|
21
|
-
...current,
|
|
22
|
-
activeAccount: current.activeAccount ?? account,
|
|
23
|
-
accounts: {
|
|
24
|
-
...current.accounts,
|
|
25
|
-
[account]: {
|
|
26
|
-
apiUrl,
|
|
27
|
-
clientId,
|
|
28
|
-
createdAt: previous?.createdAt ?? now,
|
|
29
|
-
oidcUrl,
|
|
30
|
-
osUrl,
|
|
31
|
-
redirectUri,
|
|
32
|
-
rootTenantId,
|
|
33
|
-
scope,
|
|
34
|
-
updatedAt: now
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
await setClientSecret(account, clientSecret);
|
|
39
|
-
console.log(`Saved OIDC account '${account}'.`);
|
|
40
|
-
}
|
|
41
|
-
async function value(flags, name, label, defaultValue, secret = false) {
|
|
42
|
-
const flag = stringFlag(flags, name);
|
|
43
|
-
const resolved = flag || await (secret ? promptSecret(label, defaultValue) : promptValue(label, defaultValue));
|
|
44
|
-
if (!resolved)
|
|
45
|
-
throw new Error(`Missing --${name}`);
|
|
46
|
-
return resolved;
|
|
47
|
-
}
|
|
48
|
-
function optionalStringFlag(flags, name) {
|
|
49
|
-
const value = flags[name];
|
|
50
|
-
return typeof value === "string" && value ? value : undefined;
|
|
51
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function authList(argv?: string[]): Promise<void>;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { parseFlags } from "../../lib/args.js";
|
|
2
|
-
import { readConfig } from "../../lib/config.js";
|
|
3
|
-
import { writeOutput } from "../../lib/output.js";
|
|
4
|
-
export async function authList(argv = []) {
|
|
5
|
-
const { flags } = parseFlags(argv);
|
|
6
|
-
const config = await readConfig();
|
|
7
|
-
const names = Object.keys(config.accounts);
|
|
8
|
-
if (names.length === 0) {
|
|
9
|
-
if (flags.json) {
|
|
10
|
-
writeOutput({ activeAccount: config.activeAccount ?? null, accounts: [] }, flags);
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
console.log("No OIDC accounts configured.");
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
if (flags.json) {
|
|
17
|
-
writeOutput({
|
|
18
|
-
activeAccount: config.activeAccount ?? null,
|
|
19
|
-
accounts: names.map((name) => ({
|
|
20
|
-
name,
|
|
21
|
-
active: config.activeAccount === name,
|
|
22
|
-
apiUrl: config.accounts[name].apiUrl,
|
|
23
|
-
oidcUrl: config.accounts[name].oidcUrl
|
|
24
|
-
}))
|
|
25
|
-
}, flags);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
for (const name of names) {
|
|
29
|
-
const profile = config.accounts[name];
|
|
30
|
-
const active = config.activeAccount === name ? "*" : " ";
|
|
31
|
-
console.log(`${active} ${name} ${profile.apiUrl} ${profile.oidcUrl}`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function authRepair(argv: string[]): Promise<void>;
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { booleanFlag, parseFlags } from "../../lib/args.js";
|
|
2
|
-
import { confirmMutation } from "../../lib/confirm.js";
|
|
3
|
-
import { getAccountProfile, normalizeAccountName, readConfig } from "../../lib/config.js";
|
|
4
|
-
import { writeOutput } from "../../lib/output.js";
|
|
5
|
-
import { removeAccountSecrets } from "../../lib/secret-store.js";
|
|
6
|
-
import { clearTokenStore } from "../../lib/token-store.js";
|
|
7
|
-
export async function authRepair(argv) {
|
|
8
|
-
const { args, flags } = parseFlags(argv);
|
|
9
|
-
const config = await readConfig();
|
|
10
|
-
const account = args[0] ? normalizeAccountName(args[0]) : getAccountProfile(config).name;
|
|
11
|
-
if (!config.accounts[account]) {
|
|
12
|
-
throw new Error(`OIDC account '${account}' is not configured.`);
|
|
13
|
-
}
|
|
14
|
-
const result = {
|
|
15
|
-
account,
|
|
16
|
-
clearedClientSecret: true,
|
|
17
|
-
clearedTokenCache: "all accounts",
|
|
18
|
-
nextSteps: [
|
|
19
|
-
["blocks-os auth:add", account === "default" ? "" : account, "--client-id <id>", "--client-secret <secret>"].filter(Boolean).join(" "),
|
|
20
|
-
`blocks-os login${account === "default" ? "" : ` --account ${account}`}`
|
|
21
|
-
]
|
|
22
|
-
};
|
|
23
|
-
if (booleanFlag(flags, "dry-run")) {
|
|
24
|
-
writeOutput({ dryRun: true, ...result }, flags);
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
await confirmMutation(flags, `Repair local auth state for '${account}'. This clears the account client secret and all cached OAuth tokens, but keeps account profile config.`);
|
|
28
|
-
await removeAccountSecrets(account);
|
|
29
|
-
await clearTokenStore();
|
|
30
|
-
if (flags.json) {
|
|
31
|
-
writeOutput(result, flags);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
console.log(`Repaired local auth state for '${account}'.`);
|
|
35
|
-
console.log("Client secret and cached OAuth tokens were cleared.");
|
|
36
|
-
console.log("Next:");
|
|
37
|
-
for (const step of result.nextSteps)
|
|
38
|
-
console.log(` ${step}`);
|
|
39
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function authShow(argv: string[]): Promise<void>;
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { parseFlags, stringFlag } from "../../lib/args.js";
|
|
2
|
-
import { getAccountProfile, readConfig } from "../../lib/config.js";
|
|
3
|
-
import { getClientSecret, secretStoreInfo } from "../../lib/secret-store.js";
|
|
4
|
-
export async function authShow(argv) {
|
|
5
|
-
const { args, flags } = parseFlags(argv);
|
|
6
|
-
const config = await readConfig();
|
|
7
|
-
const account = args[0] || stringFlag(flags, "account") || config.activeAccount;
|
|
8
|
-
const { name, profile } = getAccountProfile(config, account);
|
|
9
|
-
const secretInfo = await secretStoreInfo();
|
|
10
|
-
console.log(JSON.stringify({
|
|
11
|
-
account: name,
|
|
12
|
-
active: config.activeAccount === name,
|
|
13
|
-
apiUrl: profile.apiUrl,
|
|
14
|
-
clientId: profile.clientId,
|
|
15
|
-
clientSecret: await getClientSecret(name) ? "configured" : "missing",
|
|
16
|
-
secretStorage: secretInfo,
|
|
17
|
-
oidcUrl: profile.oidcUrl,
|
|
18
|
-
osUrl: profile.osUrl,
|
|
19
|
-
redirectUri: profile.redirectUri,
|
|
20
|
-
rootTenantId: profile.rootTenantId,
|
|
21
|
-
scope: profile.scope
|
|
22
|
-
}, null, 2));
|
|
23
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function authUse(argv: string[]): Promise<void>;
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { parseFlags } from "../../lib/args.js";
|
|
2
|
-
import { normalizeAccountName, readConfig, writeConfig } from "../../lib/config.js";
|
|
3
|
-
export async function authUse(argv) {
|
|
4
|
-
const { args } = parseFlags(argv);
|
|
5
|
-
const account = normalizeAccountName(args[0]);
|
|
6
|
-
const config = await readConfig();
|
|
7
|
-
if (!config.accounts[account]) {
|
|
8
|
-
throw new Error("OIDC account is not configured. Run 'blocks-os auth:add --client-id <id> --client-secret <secret>' first.");
|
|
9
|
-
}
|
|
10
|
-
await writeConfig({
|
|
11
|
-
...config,
|
|
12
|
-
activeAccount: account
|
|
13
|
-
});
|
|
14
|
-
console.log(`Active account is now '${account}'.`);
|
|
15
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function loginDevice(argv: string[]): Promise<void>;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function waitForAuthorizationCode(port: number, expectedState?: string): Promise<string>;
|
package/dist/lib/login-server.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { createServer } from "node:http";
|
|
2
|
-
export async function waitForAuthorizationCode(port, expectedState) {
|
|
3
|
-
return new Promise((resolve, reject) => {
|
|
4
|
-
const server = createServer((request, response) => {
|
|
5
|
-
const url = new URL(request.url ?? "/", `http://127.0.0.1:${port}`);
|
|
6
|
-
if (url.pathname !== "/callback") {
|
|
7
|
-
response.writeHead(404);
|
|
8
|
-
response.end("Not found");
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
const error = url.searchParams.get("error");
|
|
12
|
-
const code = url.searchParams.get("code");
|
|
13
|
-
const state = url.searchParams.get("state");
|
|
14
|
-
if (error) {
|
|
15
|
-
response.writeHead(400, { "Content-Type": "text/plain" });
|
|
16
|
-
response.end(`Blocks OS login failed: ${error}`);
|
|
17
|
-
server.close();
|
|
18
|
-
reject(new Error(error));
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
if (expectedState && state !== expectedState) {
|
|
22
|
-
response.writeHead(400, { "Content-Type": "text/plain" });
|
|
23
|
-
response.end("Blocks OS login failed: invalid state");
|
|
24
|
-
server.close();
|
|
25
|
-
reject(new Error("Invalid authorization state"));
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
if (!code) {
|
|
29
|
-
response.writeHead(400, { "Content-Type": "text/plain" });
|
|
30
|
-
response.end("Blocks OS login failed: missing code");
|
|
31
|
-
server.close();
|
|
32
|
-
reject(new Error("Missing authorization code"));
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
response.writeHead(200, { "Content-Type": "text/plain" });
|
|
36
|
-
response.end("Blocks OS login complete. You can close this tab.");
|
|
37
|
-
server.close();
|
|
38
|
-
resolve(code);
|
|
39
|
-
});
|
|
40
|
-
server.on("error", reject);
|
|
41
|
-
server.listen(port, "127.0.0.1");
|
|
42
|
-
});
|
|
43
|
-
}
|
package/dist/lib/pkce.d.ts
DELETED
package/dist/lib/pkce.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
-
function base64Url(input) {
|
|
3
|
-
return input.toString("base64").replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
4
|
-
}
|
|
5
|
-
export function createPkcePair() {
|
|
6
|
-
const verifier = base64Url(randomBytes(32));
|
|
7
|
-
const challenge = base64Url(createHash("sha256").update(verifier).digest());
|
|
8
|
-
return { challenge, verifier };
|
|
9
|
-
}
|
package/dist/lib/prompt.d.ts
DELETED
package/dist/lib/prompt.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { createInterface } from "node:readline";
|
|
2
|
-
import { createInterface as createPromiseInterface } from "node:readline/promises";
|
|
3
|
-
import { stdin as input, stdout as output } from "node:process";
|
|
4
|
-
export async function promptValue(label, defaultValue) {
|
|
5
|
-
const rl = createPromiseInterface({ input, output });
|
|
6
|
-
try {
|
|
7
|
-
const suffix = defaultValue ? ` (${defaultValue})` : "";
|
|
8
|
-
const answer = await rl.question(`${label}${suffix}: `);
|
|
9
|
-
return answer.trim() || defaultValue || "";
|
|
10
|
-
}
|
|
11
|
-
finally {
|
|
12
|
-
rl.close();
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
export async function promptSecret(label, defaultValue) {
|
|
16
|
-
if (!input.isTTY || !output.isTTY)
|
|
17
|
-
return promptValue(label, defaultValue);
|
|
18
|
-
return new Promise((resolve) => {
|
|
19
|
-
const rl = createInterface({ input, output, terminal: true });
|
|
20
|
-
const muted = rl;
|
|
21
|
-
muted._writeToOutput = (value) => {
|
|
22
|
-
if (value === "\r\n" || value === "\n" || value === "\r") {
|
|
23
|
-
output.write(value);
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
output.write("*".repeat(value.length));
|
|
27
|
-
};
|
|
28
|
-
const suffix = defaultValue ? " (configured, press Enter to keep)" : "";
|
|
29
|
-
output.write(`${label}${suffix}: `);
|
|
30
|
-
rl.question("", (answer) => {
|
|
31
|
-
rl.close();
|
|
32
|
-
output.write("\n");
|
|
33
|
-
resolve(answer.trim() || defaultValue || "");
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
}
|