@vigilbase/cli 0.1.4 → 0.2.1
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/CLOUDFLARE.md +3 -4
- package/COMMANDS.md +158 -83
- package/LOCAL.md +721 -0
- package/ONE.md +2 -4
- package/README.md +341 -123
- package/SECURITY.md +2 -2
- package/dist/auth/device-auth.d.ts +0 -1
- package/dist/auth/device-auth.js +13 -8
- package/dist/auth/index.d.ts +4 -2
- package/dist/auth/index.js +3 -2
- package/dist/auth/provider.d.ts +18 -0
- package/dist/auth/provider.js +71 -0
- package/dist/auth/refresh.d.ts +29 -0
- package/dist/auth/refresh.js +199 -0
- package/dist/auth/store.d.ts +103 -4
- package/dist/auth/store.js +400 -74
- package/dist/auth/token-metadata.d.ts +9 -0
- package/dist/auth/token-metadata.js +21 -0
- package/dist/auth/types.d.ts +8 -2
- package/dist/auth/types.js +0 -1
- package/dist/bin.d.ts +0 -1
- package/dist/bin.js +0 -1
- package/dist/cli.d.ts +4 -2
- package/dist/cli.js +45 -15
- package/dist/commands/account.d.ts +0 -1
- package/dist/commands/account.js +0 -1
- package/dist/commands/auth.d.ts +0 -1
- package/dist/commands/auth.js +82 -22
- package/dist/commands/billing.d.ts +0 -1
- package/dist/commands/billing.js +0 -1
- package/dist/commands/cloudflare.d.ts +1 -2
- package/dist/commands/cloudflare.js +6 -6
- package/dist/commands/helpers.d.ts +15 -1
- package/dist/commands/helpers.js +2 -3
- package/dist/commands/index.d.ts +3 -2
- package/dist/commands/index.js +6 -2
- package/dist/commands/legacy-validation.d.ts +0 -1
- package/dist/commands/legacy-validation.js +0 -1
- package/dist/commands/local.d.ts +35 -0
- package/dist/commands/local.js +1543 -0
- package/dist/commands/one.d.ts +0 -1
- package/dist/commands/one.js +6 -7
- package/dist/commands/public-tools.d.ts +0 -1
- package/dist/commands/public-tools.js +55 -17
- package/dist/commands/runs.d.ts +0 -1
- package/dist/commands/runs.js +0 -1
- package/dist/commands/security.d.ts +1 -2
- package/dist/commands/security.js +1 -2
- package/dist/commands/watchdog.d.ts +0 -1
- package/dist/commands/watchdog.js +0 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -2
- package/dist/lib/banner.d.ts +0 -1
- package/dist/lib/banner.js +0 -1
- package/dist/lib/client.d.ts +4 -2
- package/dist/lib/client.js +42 -18
- package/dist/lib/cloudflare-client.d.ts +0 -1
- package/dist/lib/cloudflare-client.js +0 -1
- package/dist/lib/config.d.ts +31 -3
- package/dist/lib/config.js +72 -13
- package/dist/lib/format.d.ts +2 -1
- package/dist/lib/format.js +27 -7
- package/dist/lib/help.d.ts +1 -2
- package/dist/lib/help.js +37 -17
- package/dist/lib/network.d.ts +10 -1
- package/dist/lib/network.js +33 -5
- package/dist/lib/one-client.d.ts +5 -3
- package/dist/lib/one-client.js +49 -3
- package/dist/lib/one-operations.d.ts +0 -1
- package/dist/lib/one-operations.js +0 -1
- package/dist/lib/open-url.d.ts +0 -1
- package/dist/lib/open-url.js +0 -1
- package/dist/lib/platform.d.ts +2 -0
- package/dist/lib/platform.js +4 -0
- package/dist/lib/progress.d.ts +0 -1
- package/dist/lib/progress.js +0 -1
- package/dist/lib/security-client.d.ts +1 -2
- package/dist/lib/security-client.js +0 -1
- package/dist/lib/skill-fetch.d.ts +0 -1
- package/dist/lib/skill-fetch.js +0 -1
- package/dist/lib/system-trust.d.ts +0 -1
- package/dist/lib/system-trust.js +0 -1
- package/dist/lib/tui.d.ts +308 -0
- package/dist/lib/tui.js +846 -0
- package/dist/local/cloudflare-checkup/cf/allowlist.d.ts +94 -0
- package/dist/local/cloudflare-checkup/cf/allowlist.js +183 -0
- package/dist/local/cloudflare-checkup/cf/auth-lifecycle.d.ts +112 -0
- package/dist/local/cloudflare-checkup/cf/auth-lifecycle.js +312 -0
- package/dist/local/cloudflare-checkup/cf/child-env.d.ts +31 -0
- package/dist/local/cloudflare-checkup/cf/child-env.js +80 -0
- package/dist/local/cloudflare-checkup/cf/install.d.ts +105 -0
- package/dist/local/cloudflare-checkup/cf/install.js +308 -0
- package/dist/local/cloudflare-checkup/cf/locate.d.ts +43 -0
- package/dist/local/cloudflare-checkup/cf/locate.js +227 -0
- package/dist/local/cloudflare-checkup/cf/run-cf.d.ts +88 -0
- package/dist/local/cloudflare-checkup/cf/run-cf.js +274 -0
- package/dist/local/cloudflare-checkup/collector.d.ts +47 -0
- package/dist/local/cloudflare-checkup/collector.js +295 -0
- package/dist/local/cloudflare-checkup/contract.d.ts +2804 -0
- package/dist/local/cloudflare-checkup/contract.js +1620 -0
- package/dist/local/cloudflare-checkup/email-verification.d.ts +124 -0
- package/dist/local/cloudflare-checkup/email-verification.js +211 -0
- package/dist/local/cloudflare-checkup/errors.d.ts +29 -0
- package/dist/local/cloudflare-checkup/errors.js +103 -0
- package/dist/local/cloudflare-checkup/output.d.ts +23 -0
- package/dist/local/cloudflare-checkup/output.js +88 -0
- package/dist/local/cloudflare-checkup/prompts.d.ts +40 -0
- package/dist/local/cloudflare-checkup/prompts.js +139 -0
- package/dist/local/cloudflare-checkup/rate-budget.d.ts +41 -0
- package/dist/local/cloudflare-checkup/rate-budget.js +79 -0
- package/dist/local/cloudflare-checkup/snapshot-file.d.ts +52 -0
- package/dist/local/cloudflare-checkup/snapshot-file.js +185 -0
- package/dist/local/cloudflare-checkup/submit-client.d.ts +112 -0
- package/dist/local/cloudflare-checkup/submit-client.js +395 -0
- package/dist/local/cloudflare-checkup/terminal-ui.d.ts +170 -0
- package/dist/local/cloudflare-checkup/terminal-ui.js +403 -0
- package/dist/types.d.ts +25 -3
- package/dist/types.js +16 -1
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -1
- package/package.json +12 -12
- package/dist/auth/device-auth.d.ts.map +0 -1
- package/dist/auth/device-auth.js.map +0 -1
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/store.d.ts.map +0 -1
- package/dist/auth/store.js.map +0 -1
- package/dist/auth/types.d.ts.map +0 -1
- package/dist/auth/types.js.map +0 -1
- package/dist/bin.d.ts.map +0 -1
- package/dist/bin.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/commands/account.d.ts.map +0 -1
- package/dist/commands/account.js.map +0 -1
- package/dist/commands/auth.d.ts.map +0 -1
- package/dist/commands/auth.js.map +0 -1
- package/dist/commands/billing.d.ts.map +0 -1
- package/dist/commands/billing.js.map +0 -1
- package/dist/commands/cloudflare.d.ts.map +0 -1
- package/dist/commands/cloudflare.js.map +0 -1
- package/dist/commands/helpers.d.ts.map +0 -1
- package/dist/commands/helpers.js.map +0 -1
- package/dist/commands/index.d.ts.map +0 -1
- package/dist/commands/index.js.map +0 -1
- package/dist/commands/legacy-validation.d.ts.map +0 -1
- package/dist/commands/legacy-validation.js.map +0 -1
- package/dist/commands/one.d.ts.map +0 -1
- package/dist/commands/one.js.map +0 -1
- package/dist/commands/public-tools.d.ts.map +0 -1
- package/dist/commands/public-tools.js.map +0 -1
- package/dist/commands/runs.d.ts.map +0 -1
- package/dist/commands/runs.js.map +0 -1
- package/dist/commands/security.d.ts.map +0 -1
- package/dist/commands/security.js.map +0 -1
- package/dist/commands/watchdog.d.ts.map +0 -1
- package/dist/commands/watchdog.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/banner.d.ts.map +0 -1
- package/dist/lib/banner.js.map +0 -1
- package/dist/lib/client.d.ts.map +0 -1
- package/dist/lib/client.js.map +0 -1
- package/dist/lib/cloudflare-client.d.ts.map +0 -1
- package/dist/lib/cloudflare-client.js.map +0 -1
- package/dist/lib/config.d.ts.map +0 -1
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/format.d.ts.map +0 -1
- package/dist/lib/format.js.map +0 -1
- package/dist/lib/help.d.ts.map +0 -1
- package/dist/lib/help.js.map +0 -1
- package/dist/lib/network.d.ts.map +0 -1
- package/dist/lib/network.js.map +0 -1
- package/dist/lib/one-client.d.ts.map +0 -1
- package/dist/lib/one-client.js.map +0 -1
- package/dist/lib/one-operations.d.ts.map +0 -1
- package/dist/lib/one-operations.js.map +0 -1
- package/dist/lib/open-url.d.ts.map +0 -1
- package/dist/lib/open-url.js.map +0 -1
- package/dist/lib/progress.d.ts.map +0 -1
- package/dist/lib/progress.js.map +0 -1
- package/dist/lib/security-client.d.ts.map +0 -1
- package/dist/lib/security-client.js.map +0 -1
- package/dist/lib/skill-fetch.d.ts.map +0 -1
- package/dist/lib/skill-fetch.js.map +0 -1
- package/dist/lib/system-trust.d.ts.map +0 -1
- package/dist/lib/system-trust.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
package/dist/auth/device-auth.js
CHANGED
|
@@ -27,26 +27,31 @@ function isRetryableTokenStatus(status) {
|
|
|
27
27
|
status >= 500);
|
|
28
28
|
}
|
|
29
29
|
async function postJson(config, path, body) {
|
|
30
|
-
const
|
|
30
|
+
const provider = config.authProvider;
|
|
31
|
+
const url = provider ? (path === '/device/code' ? provider.deviceEndpoint : provider.tokenEndpoint) : `${authBase(config)}${path}`;
|
|
32
|
+
const form = provider && path === '/device/token';
|
|
31
33
|
const { response, text } = await requestText(config, url, {
|
|
32
34
|
method: 'POST',
|
|
33
35
|
headers: {
|
|
34
36
|
Accept: 'application/json',
|
|
35
|
-
'Content-Type': 'application/json',
|
|
37
|
+
'Content-Type': form ? 'application/x-www-form-urlencoded' : 'application/json',
|
|
36
38
|
'User-Agent': config.userAgent,
|
|
37
39
|
},
|
|
38
|
-
body: JSON.stringify(body),
|
|
40
|
+
body: form ? new URLSearchParams(Object.entries(body).map(([key, value]) => [key, String(value)])) : JSON.stringify(body),
|
|
39
41
|
});
|
|
40
|
-
return { status: response.status, json: parseJsonResponse(response, text) };
|
|
42
|
+
return { status: response.status, json: parseJsonResponse(response, text), origin: new URL(url).origin };
|
|
41
43
|
}
|
|
42
44
|
export async function requestDeviceCode(config, options) {
|
|
43
|
-
const { status, json } = await postJson(config, '/device/code', {
|
|
45
|
+
const { status, json, origin } = await postJson(config, '/device/code', {
|
|
44
46
|
client_id: config.clientId,
|
|
47
|
+
...(config.authProvider ? { resource: config.authProvider.resource, scope: 'openid profile email offline_access one:api' } : {}),
|
|
45
48
|
...(options?.scope ? { scope: options.scope } : {}),
|
|
46
49
|
});
|
|
50
|
+
// origin is the Identity issuer, or One for legacy sign-in.
|
|
47
51
|
if (status === 404) {
|
|
48
|
-
throw new DeviceAuthError(`Device
|
|
49
|
-
'
|
|
52
|
+
throw new DeviceAuthError(`Device sign-in isn't available at ${origin} right now. ` +
|
|
53
|
+
'If you set --one-url or --auth-url, check that it\'s correct; otherwise try again later or email help@vigilbase.com. ' +
|
|
54
|
+
'Public tools work without signing in.', { status, code: 'not_enabled' });
|
|
50
55
|
}
|
|
51
56
|
if (status < 200 || status >= 300) {
|
|
52
57
|
throw new DeviceAuthError(`Device code request failed (HTTP ${status})`, { status });
|
|
@@ -75,6 +80,7 @@ export async function pollDeviceToken(config, deviceCode) {
|
|
|
75
80
|
grant_type: DEVICE_GRANT,
|
|
76
81
|
device_code: deviceCode,
|
|
77
82
|
client_id: config.clientId,
|
|
83
|
+
...(config.authProvider ? { resource: config.authProvider.resource } : {}),
|
|
78
84
|
}));
|
|
79
85
|
}
|
|
80
86
|
catch (error) {
|
|
@@ -222,4 +228,3 @@ export async function waitForDeviceAuthorization(config, device, options) {
|
|
|
222
228
|
}
|
|
223
229
|
throw new DeviceAuthError('Timed out waiting for browser authorization. Run `vigilbase login` again.', { status: 0, code: 'timeout' });
|
|
224
230
|
}
|
|
225
|
-
//# sourceMappingURL=device-auth.js.map
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export { clearOneCredentials, credentialsPath, getOneAccessToken, readCredentials, writeOneCredentials, } from './store.js';
|
|
1
|
+
export { clearOneCredentials, credentialsPath, getOneAccessToken, getOptionalOneAccessToken, hasUsableSignIn, identityCredentialsPath, readCredentials, writeOneCredentials, } from './store.js';
|
|
2
2
|
export { DeviceAuthError, pollDeviceToken, requestDeviceCode, waitForDeviceAuthorization, } from './device-auth.js';
|
|
3
3
|
export type { CredentialsFile, DeviceCodeResponse, DeviceTokenSuccess, OneCredentials, StoredUser, } from './types.js';
|
|
4
|
-
|
|
4
|
+
export { discoverAuthProvider, identityProvider } from './provider.js';
|
|
5
|
+
export { managedAccessToken, revokeSavedCredentials } from './refresh.js';
|
|
6
|
+
export type { AuthProvider } from './provider.js';
|
package/dist/auth/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export { clearOneCredentials, credentialsPath, getOneAccessToken, readCredentials, writeOneCredentials, } from './store.js';
|
|
1
|
+
export { clearOneCredentials, credentialsPath, getOneAccessToken, getOptionalOneAccessToken, hasUsableSignIn, identityCredentialsPath, readCredentials, writeOneCredentials, } from './store.js';
|
|
2
2
|
export { DeviceAuthError, pollDeviceToken, requestDeviceCode, waitForDeviceAuthorization, } from './device-auth.js';
|
|
3
|
-
|
|
3
|
+
export { discoverAuthProvider, identityProvider } from './provider.js';
|
|
4
|
+
export { managedAccessToken, revokeSavedCredentials } from './refresh.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type CliConfig } from '../lib/config.js';
|
|
2
|
+
export interface AuthProvider {
|
|
3
|
+
issuer: string;
|
|
4
|
+
tokenEndpoint: string;
|
|
5
|
+
deviceEndpoint: string;
|
|
6
|
+
userinfoEndpoint: string;
|
|
7
|
+
revocationEndpoint: string;
|
|
8
|
+
resource: string;
|
|
9
|
+
}
|
|
10
|
+
/** An issuer (discovered from One, or saved with credentials) is trusted only when its origin is exactly trustedIdentityIssuer(config). */
|
|
11
|
+
export declare function validateIdentityIssuer(raw: string, config: Pick<CliConfig, 'oneBaseUrl' | 'identityBaseUrl'>): string;
|
|
12
|
+
export declare function identityProvider(config: CliConfig, issuer: string): Promise<AuthProvider>;
|
|
13
|
+
/**
|
|
14
|
+
* Older One deployments answer the protected-resource path with a sign-in
|
|
15
|
+
* redirect or an HTML page; those mean "no Identity metadata" (legacy login).
|
|
16
|
+
* JSON that is present but malformed or names another resource still aborts.
|
|
17
|
+
*/
|
|
18
|
+
export declare function discoverAuthProvider(config: CliConfig): Promise<AuthProvider | undefined>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { DeviceAuthError } from './device-auth.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { DEFAULT_IDENTITY_ISSUER, parseOrigin, STAGING_IDENTITY_ISSUER, trustedIdentityIssuer, validateOneOrigin } from '../lib/config.js';
|
|
4
|
+
import { requestText, parseJsonResponse } from '../lib/network.js';
|
|
5
|
+
/** An issuer (discovered from One, or saved with credentials) is trusted only when its origin is exactly trustedIdentityIssuer(config). */
|
|
6
|
+
export function validateIdentityIssuer(raw, config) {
|
|
7
|
+
const trusted = trustedIdentityIssuer(config);
|
|
8
|
+
let origin;
|
|
9
|
+
// Do not echo a malformed issuer: it can contain credentials.
|
|
10
|
+
try {
|
|
11
|
+
origin = parseOrigin(raw, 'The Identity issuer');
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
throw new Error('Untrusted Identity issuer: not an HTTP(S) origin.');
|
|
15
|
+
}
|
|
16
|
+
if (origin === trusted)
|
|
17
|
+
return origin;
|
|
18
|
+
throw new Error(config.identityBaseUrl
|
|
19
|
+
? `Untrusted Identity issuer ${origin}: --auth-url / VIGILBASE_AUTH_URL is ${trusted}. Set it to the issuer for this One, or unset it to use ${DEFAULT_IDENTITY_ISSUER}.`
|
|
20
|
+
: `Untrusted Identity issuer ${origin}: only ${DEFAULT_IDENTITY_ISSUER} is trusted by default. For staging, pass --auth-url ${STAGING_IDENTITY_ISSUER} or set VIGILBASE_AUTH_URL.`);
|
|
21
|
+
}
|
|
22
|
+
async function json(config, url) { const { response, text } = await requestText(config, url, { headers: { Accept: 'application/json', 'User-Agent': config.userAgent } }); return { response, body: parseJsonResponse(response, text) }; }
|
|
23
|
+
export async function identityProvider(config, issuer) {
|
|
24
|
+
issuer = validateIdentityIssuer(issuer, config);
|
|
25
|
+
const { response, body } = await json(config, `${issuer}/.well-known/openid-configuration`);
|
|
26
|
+
const data = z.object({ issuer: z.literal(issuer), token_endpoint: z.string(), device_authorization_endpoint: z.string(), userinfo_endpoint: z.string(), revocation_endpoint: z.string() }).safeParse(body);
|
|
27
|
+
if (!response.ok || !data.success)
|
|
28
|
+
throw new Error('Invalid Identity discovery response.');
|
|
29
|
+
const endpoint = (raw, paths) => { let url; try {
|
|
30
|
+
url = new URL(raw);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
throw new Error('Invalid Identity endpoint.');
|
|
34
|
+
} ; if (url.origin !== issuer || url.username || url.password || url.search || url.hash || !paths.includes(url.pathname))
|
|
35
|
+
throw new Error('Untrusted Identity endpoint.'); return url.href; };
|
|
36
|
+
return { issuer, resource: validateOneOrigin(config.oneBaseUrl), tokenEndpoint: endpoint(data.data.token_endpoint, ['/api/auth/oauth2/token', '/token']), deviceEndpoint: endpoint(data.data.device_authorization_endpoint, ['/api/auth/device/code']), userinfoEndpoint: endpoint(data.data.userinfo_endpoint, ['/api/auth/oauth2/userinfo', '/userinfo']), revocationEndpoint: endpoint(data.data.revocation_endpoint, ['/api/auth/oauth2/revoke', '/revoke']) };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Older One deployments answer the protected-resource path with a sign-in
|
|
40
|
+
* redirect or an HTML page; those mean "no Identity metadata" (legacy login).
|
|
41
|
+
* JSON that is present but malformed or names another resource still aborts.
|
|
42
|
+
*/
|
|
43
|
+
export async function discoverAuthProvider(config) {
|
|
44
|
+
const one = validateOneOrigin(config.oneBaseUrl);
|
|
45
|
+
const { response, text } = await requestText(config, `${one}/.well-known/oauth-protected-resource`, { headers: { Accept: 'application/json', 'User-Agent': config.userAgent }, redirect: 'manual' });
|
|
46
|
+
if (response.type === 'opaqueredirect' || response.status === 0 || (response.status >= 300 && response.status < 400))
|
|
47
|
+
return undefined;
|
|
48
|
+
if (response.status === 404)
|
|
49
|
+
return undefined;
|
|
50
|
+
if (response.status === 429)
|
|
51
|
+
throw new DeviceAuthError('Discovery rate limit exceeded.', { status: 429, code: 'rate_limited' });
|
|
52
|
+
let body;
|
|
53
|
+
if (response.ok) {
|
|
54
|
+
const contentType = (response.headers.get('content-type') ?? '').split(';')[0].trim().toLowerCase();
|
|
55
|
+
if (contentType && contentType !== 'application/json' && !contentType.endsWith('+json'))
|
|
56
|
+
return undefined;
|
|
57
|
+
try {
|
|
58
|
+
body = JSON.parse(text);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const metadata = z.object({ resource: z.literal(one), authorization_servers: z.array(z.string()).length(1) }).safeParse(body);
|
|
65
|
+
if (!response.ok || !metadata.success)
|
|
66
|
+
throw new Error('Invalid One protected-resource discovery response.');
|
|
67
|
+
const issuer = metadata.data.authorization_servers[0];
|
|
68
|
+
if (issuer === `${one}/api/auth`)
|
|
69
|
+
return undefined;
|
|
70
|
+
return identityProvider(config, issuer);
|
|
71
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CliConfig } from '../lib/config.js';
|
|
2
|
+
import type { OneCredentials } from './types.js';
|
|
3
|
+
export declare function boundIdentityCredentials(config: CliConfig, one: OneCredentials): boolean;
|
|
4
|
+
/** Library callers may omit or mistype timeoutMs; NaN would make the wait unbounded. */
|
|
5
|
+
export declare function credentialsLockWaitMs(timeoutMs: unknown): number;
|
|
6
|
+
/** Another command holds credentials.json.lock. */
|
|
7
|
+
export declare class CredentialsLockedError extends Error {
|
|
8
|
+
constructor();
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* credentials.json.lock guards both credentials files; CLI 0.2.0 takes the same lock.
|
|
12
|
+
* Never steal a stale lock: a crashed refresh may have consumed the rotating token.
|
|
13
|
+
* With `wait` false, a held lock fails at once instead of after the lock wait.
|
|
14
|
+
*/
|
|
15
|
+
export declare function withCredentialsLock<T>(config: Pick<CliConfig, 'configDir'> & Partial<Pick<CliConfig, 'timeoutMs'>>, callback: () => Promise<T>, replaceDeadLock?: boolean, wait?: boolean): Promise<T>;
|
|
16
|
+
export declare function managedAccessToken(config: CliConfig, failedToken?: string): Promise<string | null>;
|
|
17
|
+
/**
|
|
18
|
+
* Revoke one saved refresh grant. Identity answers 400 (`invalid_request` or `invalid_grant`) for a token it no
|
|
19
|
+
* longer knows or has already revoked, for example after a browser sign-out ended the session that approved it,
|
|
20
|
+
* or a refresh rotated it; nothing is left to revoke then. Other failures throw.
|
|
21
|
+
*/
|
|
22
|
+
export declare function revokeIdentityGrant(config: CliConfig, one: OneCredentials): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Revoke every saved Identity grant, then remove its file. A failed revocation keeps every file. A safe file that
|
|
25
|
+
* can't be parsed has no grant to revoke; the login that calls this replaces it. Callers hold the lock.
|
|
26
|
+
*/
|
|
27
|
+
export declare function revokeIdentityCredentials(config: CliConfig): Promise<boolean>;
|
|
28
|
+
/** Revoke and remove every credentials file. A failed Identity revocation keeps them all. */
|
|
29
|
+
export declare function revokeSavedCredentials(config: CliConfig, onWarning?: (message: string) => void): Promise<boolean>;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { readIdentityTokenMetadata } from './token-metadata.js';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { open, rm, lstat } from 'node:fs/promises';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { DEFAULT_TIMEOUT_MS, validateOneOrigin } from '../lib/config.js';
|
|
7
|
+
import { requestText, parseJsonResponse } from '../lib/network.js';
|
|
8
|
+
import { assertFile, checkDirectory, credentialsPath, readCredentials, readLockedCredentials, readStoredCredentials, writeOneCredentials, clearOneCredentials, clearIdentityCredentials, DEFAULT_ONE_ORIGIN, UNCERTAIN_REFRESH } from './store.js';
|
|
9
|
+
import { identityProvider, validateIdentityIssuer } from './provider.js';
|
|
10
|
+
export function boundIdentityCredentials(config, one) {
|
|
11
|
+
try {
|
|
12
|
+
return one.clientId === config.clientId && one.resource === validateOneOrigin(config.oneBaseUrl) && one.oneBaseUrl === one.resource && validateIdentityIssuer(one.issuer, config) === one.issuer;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const MAX_LOCK_WAIT_MS = 15_000;
|
|
19
|
+
/** Library callers may omit or mistype timeoutMs; NaN would make the wait unbounded. */
|
|
20
|
+
export function credentialsLockWaitMs(timeoutMs) {
|
|
21
|
+
const requested = typeof timeoutMs === 'number' && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
22
|
+
return Math.min(requested, MAX_LOCK_WAIT_MS);
|
|
23
|
+
}
|
|
24
|
+
/** Another command holds credentials.json.lock. */
|
|
25
|
+
export class CredentialsLockedError extends Error {
|
|
26
|
+
constructor() { super('Credentials are locked by another command. A crashed refresh requires a new login; do not reuse its refresh token.'); this.name = 'CredentialsLockedError'; }
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* credentials.json.lock guards both credentials files; CLI 0.2.0 takes the same lock.
|
|
30
|
+
* Never steal a stale lock: a crashed refresh may have consumed the rotating token.
|
|
31
|
+
* With `wait` false, a held lock fails at once instead of after the lock wait.
|
|
32
|
+
*/
|
|
33
|
+
export async function withCredentialsLock(config, callback, replaceDeadLock = false, wait = true) {
|
|
34
|
+
const path = `${credentialsPath(config)}.lock`;
|
|
35
|
+
await checkDirectory(dirname(path), true);
|
|
36
|
+
const deadline = Date.now() + credentialsLockWaitMs(config.timeoutMs);
|
|
37
|
+
let handle;
|
|
38
|
+
for (;;) {
|
|
39
|
+
try {
|
|
40
|
+
handle = await open(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (!(error instanceof Error && 'code' in error && error.code === 'EEXIST'))
|
|
45
|
+
throw error;
|
|
46
|
+
const existing = await lstat(path);
|
|
47
|
+
assertFile(existing, path);
|
|
48
|
+
if (replaceDeadLock) {
|
|
49
|
+
const existingHandle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
50
|
+
let owner = '';
|
|
51
|
+
try {
|
|
52
|
+
assertFile(await existingHandle.stat(), path);
|
|
53
|
+
owner = await existingHandle.readFile('utf8');
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
await existingHandle.close();
|
|
57
|
+
}
|
|
58
|
+
const pid = Number(owner);
|
|
59
|
+
let dead = false;
|
|
60
|
+
if (Number.isSafeInteger(pid) && pid > 0) {
|
|
61
|
+
try {
|
|
62
|
+
process.kill(pid, 0);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
dead = error instanceof Error && 'code' in error && error.code === 'ESRCH';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (dead) {
|
|
69
|
+
const current = await lstat(path);
|
|
70
|
+
if (current.ino === existing.ino && current.dev === existing.dev) {
|
|
71
|
+
await rm(path);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (!wait || Date.now() >= deadline)
|
|
77
|
+
throw new CredentialsLockedError();
|
|
78
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
await handle.chmod(0o600);
|
|
82
|
+
const info = await handle.stat();
|
|
83
|
+
assertFile(info, path);
|
|
84
|
+
try {
|
|
85
|
+
await handle.writeFile(String(process.pid));
|
|
86
|
+
await handle.sync();
|
|
87
|
+
return await callback();
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
await handle.close();
|
|
91
|
+
const current = await lstat(path).catch(() => null);
|
|
92
|
+
if (current?.ino === info.ino && current.dev === info.dev)
|
|
93
|
+
await rm(path);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export async function managedAccessToken(config, failedToken) {
|
|
97
|
+
const initial = await readCredentials(config);
|
|
98
|
+
if (initial.version !== 2 || !initial.one || !boundIdentityCredentials(config, initial.one))
|
|
99
|
+
return null;
|
|
100
|
+
if (!failedToken && Date.parse(initial.one.expiresAt ?? '') - Date.now() > 60_000) {
|
|
101
|
+
readIdentityTokenMetadata(initial.one.accessToken, { issuer: initial.one.issuer, clientId: initial.one.clientId, resource: initial.one.resource });
|
|
102
|
+
return initial.one.accessToken;
|
|
103
|
+
}
|
|
104
|
+
return withCredentialsLock(config, async () => {
|
|
105
|
+
const current = await readLockedCredentials(config), one = current.one;
|
|
106
|
+
if (current.version !== 2 || !one || !boundIdentityCredentials(config, one))
|
|
107
|
+
return null;
|
|
108
|
+
if (failedToken && one.accessToken !== failedToken) {
|
|
109
|
+
readIdentityTokenMetadata(one.accessToken, { issuer: one.issuer, clientId: one.clientId, resource: one.resource });
|
|
110
|
+
return one.accessToken;
|
|
111
|
+
}
|
|
112
|
+
if (!failedToken && Date.parse(one.expiresAt ?? '') - Date.now() > 60_000) {
|
|
113
|
+
readIdentityTokenMetadata(one.accessToken, { issuer: one.issuer, clientId: one.clientId, resource: one.resource });
|
|
114
|
+
return one.accessToken;
|
|
115
|
+
}
|
|
116
|
+
if (one.refreshPending)
|
|
117
|
+
throw new Error(UNCERTAIN_REFRESH);
|
|
118
|
+
const provider = await identityProvider(config, one.issuer);
|
|
119
|
+
await writeOneCredentials(config, { ...one, refreshPending: true });
|
|
120
|
+
const { response, text } = await requestText(config, provider.tokenEndpoint, { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': config.userAgent }, body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: one.refreshToken, client_id: one.clientId, resource: one.resource }) });
|
|
121
|
+
const body = parseJsonResponse(response, text);
|
|
122
|
+
if (!response.ok) {
|
|
123
|
+
if (z.object({ error: z.literal('invalid_grant') }).safeParse(body).success)
|
|
124
|
+
await clearIdentityCredentials(config);
|
|
125
|
+
throw new Error('Identity refresh failed. Run `vigilbase login` again.');
|
|
126
|
+
}
|
|
127
|
+
const token = z.object({ access_token: z.string().min(1), refresh_token: z.string().min(1), expires_in: z.number().int().positive().max(300), scope: z.string().optional() }).safeParse(body);
|
|
128
|
+
if (!token.success)
|
|
129
|
+
throw new Error('Invalid Identity refresh response.');
|
|
130
|
+
const metadata = readIdentityTokenMetadata(token.data.access_token, { issuer: one.issuer, clientId: one.clientId, resource: one.resource });
|
|
131
|
+
if (one.orgId && metadata.organizationId !== one.orgId)
|
|
132
|
+
throw new Error('Identity refresh changed its bound organization. Run `vigilbase login` again.');
|
|
133
|
+
await writeOneCredentials(config, { ...one, orgId: metadata.organizationId, refreshPending: false, accessToken: token.data.access_token, refreshToken: token.data.refresh_token, expiresAt: new Date(Date.now() + token.data.expires_in * 1000).toISOString(), scope: token.data.scope ?? one.scope });
|
|
134
|
+
return token.data.access_token;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
const SAVED_FOR_OTHER_CONFIGURATION = 'Saved credentials belong to a different issuer, resource, or client. Use the matching configuration to log out.';
|
|
138
|
+
/**
|
|
139
|
+
* Revoke one saved refresh grant. Identity answers 400 (`invalid_request` or `invalid_grant`) for a token it no
|
|
140
|
+
* longer knows or has already revoked, for example after a browser sign-out ended the session that approved it,
|
|
141
|
+
* or a refresh rotated it; nothing is left to revoke then. Other failures throw.
|
|
142
|
+
*/
|
|
143
|
+
export async function revokeIdentityGrant(config, one) {
|
|
144
|
+
if (!boundIdentityCredentials(config, one))
|
|
145
|
+
throw new Error(SAVED_FOR_OTHER_CONFIGURATION);
|
|
146
|
+
const provider = await identityProvider(config, one.issuer);
|
|
147
|
+
const { response, text } = await requestText(config, provider.revocationEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': config.userAgent }, body: new URLSearchParams({ token: one.refreshToken, token_type_hint: 'refresh_token', client_id: one.clientId }) });
|
|
148
|
+
if (response.ok)
|
|
149
|
+
return;
|
|
150
|
+
if (response.status === 400 && z.object({ error: z.enum(['invalid_request', 'invalid_grant']) }).safeParse(parseJsonResponse(response, text)).success)
|
|
151
|
+
return;
|
|
152
|
+
throw new Error('Identity revocation failed, so your saved sign-in was kept. Try again later.');
|
|
153
|
+
}
|
|
154
|
+
async function revokeIdentityGrants(config, { identity, shared }) {
|
|
155
|
+
const grants = [identity, shared].flatMap((saved) => saved?.version === 2 && saved.one ? [saved.one] : []);
|
|
156
|
+
// Check every grant first, so a mismatch can't leave one revoked while its file stays.
|
|
157
|
+
if (grants.some((one) => !boundIdentityCredentials(config, one)))
|
|
158
|
+
throw new Error(SAVED_FOR_OTHER_CONFIGURATION);
|
|
159
|
+
for (const one of grants)
|
|
160
|
+
await revokeIdentityGrant(config, one);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Revoke every saved Identity grant, then remove its file. A failed revocation keeps every file. A safe file that
|
|
164
|
+
* can't be parsed has no grant to revoke; the login that calls this replaces it. Callers hold the lock.
|
|
165
|
+
*/
|
|
166
|
+
export async function revokeIdentityCredentials(config) {
|
|
167
|
+
const stored = await readStoredCredentials(config, () => { });
|
|
168
|
+
await revokeIdentityGrants(config, stored);
|
|
169
|
+
// credentials.json goes too only when it held a CLI 0.2.0 grant.
|
|
170
|
+
return stored.shared?.version === 2 ? clearOneCredentials(config) : clearIdentityCredentials(config);
|
|
171
|
+
}
|
|
172
|
+
/** Best effort: legacy One auth retires on 2026-10-31, and its session expires on its own. */
|
|
173
|
+
async function signOutLegacySession(config, one) {
|
|
174
|
+
const origin = validateOneOrigin(one.oneBaseUrl ?? DEFAULT_ONE_ORIGIN);
|
|
175
|
+
const { response } = await requestText(config, `${origin}/api/auth/sign-out`, { method: 'POST', headers: { Accept: 'application/json', Authorization: `Bearer ${one.accessToken}`, 'User-Agent': config.userAgent }, redirect: 'manual', credentials: 'omit' });
|
|
176
|
+
if (!response.ok)
|
|
177
|
+
throw new Error('Legacy One sign-out failed.');
|
|
178
|
+
}
|
|
179
|
+
/** Revoke and remove every credentials file. A failed Identity revocation keeps them all. */
|
|
180
|
+
export async function revokeSavedCredentials(config, onWarning) {
|
|
181
|
+
return withCredentialsLock(config, async () => {
|
|
182
|
+
// A safe file that cannot be parsed cannot be revoked either; removing it keeps logout from dead-ending.
|
|
183
|
+
const unreadable = [];
|
|
184
|
+
const stored = await readStoredCredentials(config, (path) => unreadable.push(path));
|
|
185
|
+
await revokeIdentityGrants(config, stored);
|
|
186
|
+
if (stored.shared?.version === 1 && stored.shared.one) {
|
|
187
|
+
try {
|
|
188
|
+
await signOutLegacySession(config, stored.shared.one);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
onWarning?.('Could not sign out the legacy One session. Local credentials are removed anyway.');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const cleared = await clearOneCredentials(config);
|
|
195
|
+
if (unreadable.length)
|
|
196
|
+
onWarning?.(`Couldn't read the saved sign-in in ${unreadable.join(' and ')}, so it was removed without revoking it. It will expire on its own.`);
|
|
197
|
+
return cleared;
|
|
198
|
+
}, true);
|
|
199
|
+
}
|
package/dist/auth/store.d.ts
CHANGED
|
@@ -1,10 +1,109 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Stats } from 'node:fs';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { type CliConfig } from '../lib/config.js';
|
|
2
4
|
import type { CredentialsFile, OneCredentials } from './types.js';
|
|
3
|
-
|
|
5
|
+
export declare const DEFAULT_ONE_ORIGIN = "https://one.vigilbase.com";
|
|
6
|
+
export declare const UNCERTAIN_REFRESH = "A previous refresh has an uncertain result. Run `vigilbase login` again; its token cannot be retried safely.";
|
|
7
|
+
type CredentialsConfig = Pick<CliConfig, 'configDir'> & Partial<Pick<CliConfig, 'oneBaseUrl' | 'clientId' | 'identityBaseUrl' | 'timeoutMs' | 'userAgent'>>;
|
|
8
|
+
export declare const OneCredentialsSchema: z.ZodObject<{
|
|
9
|
+
issuer: z.ZodOptional<z.ZodString>;
|
|
10
|
+
clientId: z.ZodOptional<z.ZodString>;
|
|
11
|
+
resource: z.ZodOptional<z.ZodString>;
|
|
12
|
+
refreshToken: z.ZodOptional<z.ZodString>;
|
|
13
|
+
refreshTokenExpiresAt: z.ZodOptional<z.ZodString>;
|
|
14
|
+
refreshPending: z.ZodOptional<z.ZodBoolean>;
|
|
15
|
+
orgId: z.ZodOptional<z.ZodString>;
|
|
16
|
+
oneBaseUrl: z.ZodOptional<z.ZodString>;
|
|
17
|
+
accessToken: z.ZodString;
|
|
18
|
+
tokenType: z.ZodOptional<z.ZodString>;
|
|
19
|
+
scope: z.ZodOptional<z.ZodString>;
|
|
20
|
+
expiresAt: z.ZodOptional<z.ZodString>;
|
|
21
|
+
obtainedAt: z.ZodString;
|
|
22
|
+
user: z.ZodOptional<z.ZodObject<{
|
|
23
|
+
id: z.ZodOptional<z.ZodString>;
|
|
24
|
+
email: z.ZodOptional<z.ZodString>;
|
|
25
|
+
name: z.ZodOptional<z.ZodString>;
|
|
26
|
+
}, z.core.$strip>>;
|
|
27
|
+
}, z.core.$strip>;
|
|
28
|
+
/** Version 1 One session credentials, shared with CLI 0.1.x. */
|
|
4
29
|
export declare function credentialsPath(config: Pick<CliConfig, 'configDir'>): string;
|
|
5
|
-
|
|
30
|
+
/** Version 2 Identity credentials with the rotating refresh grant. */
|
|
31
|
+
export declare function identityCredentialsPath(config: Pick<CliConfig, 'configDir'>): string;
|
|
32
|
+
/** A credentials file that passed the safety checks but cannot be parsed or is incomplete. */
|
|
33
|
+
export declare class InvalidCredentialsError extends Error {
|
|
34
|
+
constructor(message: string);
|
|
35
|
+
}
|
|
36
|
+
/** Credentials cannot be protected by modes and owners on Windows yet. */
|
|
37
|
+
export declare class SignInUnsupportedError extends Error {
|
|
38
|
+
readonly code = "SIGN_IN_UNSUPPORTED";
|
|
39
|
+
constructor();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A credentials directory or file the CLI refuses to use. `reason` names the path that failed and `remedy`
|
|
43
|
+
* says how to fix it; `vigilbase login` can't, because it runs the same checks.
|
|
44
|
+
*/
|
|
45
|
+
export declare class CredentialsPathError extends Error {
|
|
46
|
+
readonly reason: string;
|
|
47
|
+
readonly remedy: string;
|
|
48
|
+
constructor(reason: string, remedy: string, options?: ErrorOptions);
|
|
49
|
+
}
|
|
50
|
+
export declare function assertFile(info: Stats, path: string): void;
|
|
51
|
+
/**
|
|
52
|
+
* Check every ancestor before traversing it; a writable non-sticky parent permits replacement.
|
|
53
|
+
* Every credentials read or write starts here, so on Windows they fail before touching the filesystem.
|
|
54
|
+
*/
|
|
55
|
+
export declare function checkDirectory(configDir: string, isCreate?: boolean): Promise<boolean>;
|
|
56
|
+
/** Both files as saved. credentials.json is version 1, or version 2 when CLI 0.2.0 wrote it. */
|
|
57
|
+
export type StoredCredentials = {
|
|
58
|
+
identity: CredentialsFile | null;
|
|
59
|
+
shared: CredentialsFile | null;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* With `onInvalid`, a safe file that cannot be parsed reads as absent and is reported; unsafe files always throw.
|
|
63
|
+
* Without it, an unparseable credentials.json reads as absent when identity-credentials.json holds a sign-in, which wins anyway.
|
|
64
|
+
*/
|
|
65
|
+
export declare function readStoredCredentials(config: Pick<CliConfig, 'configDir'>, onInvalid?: (path: string) => void): Promise<StoredCredentials>;
|
|
66
|
+
/** Identity credentials win. The first read moves a CLI 0.2.0 credentials.json aside under the lock. */
|
|
67
|
+
export declare function readCredentials(config: Pick<CliConfig, 'configDir'> & Partial<Pick<CliConfig, 'timeoutMs'>>): Promise<CredentialsFile>;
|
|
68
|
+
/** readCredentials for callers that already hold credentials.json.lock. */
|
|
69
|
+
export declare function readLockedCredentials(config: Pick<CliConfig, 'configDir'>): Promise<CredentialsFile>;
|
|
70
|
+
/**
|
|
71
|
+
* The Identity grant that saving a new one replaces: identity-credentials.json's, or that of a CLI 0.2.0
|
|
72
|
+
* credentials.json which saving moves there. An unparseable file has no grant to revoke. Callers hold the lock.
|
|
73
|
+
*/
|
|
74
|
+
export declare function readReplacedIdentityGrant(config: Pick<CliConfig, 'configDir'>): Promise<OneCredentials | null>;
|
|
75
|
+
/**
|
|
76
|
+
* Fail before a login starts, rather than after it was approved, when saving would fail: Windows, or an unsafe
|
|
77
|
+
* credentials directory or file. Unparseable files pass; login replaces them.
|
|
78
|
+
*/
|
|
79
|
+
export declare function checkCredentialsLocation(config: Pick<CliConfig, 'configDir'>): Promise<void>;
|
|
80
|
+
/** CLI 0.2.0 can write credentials.json again after migration; that grant is ignored until logout revokes it. */
|
|
81
|
+
export declare function hasIgnoredIdentityCredentials(config: Pick<CliConfig, 'configDir'>): Promise<boolean>;
|
|
6
82
|
export declare function writeOneCredentials(config: CredentialsConfig, one: OneCredentials): Promise<void>;
|
|
83
|
+
/** Remove every local credentials file without revoking anything. */
|
|
7
84
|
export declare function clearOneCredentials(config: Pick<CliConfig, 'configDir'>): Promise<boolean>;
|
|
85
|
+
/** Remove identity-credentials.json only; credentials.json stays for CLI 0.1.x. */
|
|
86
|
+
export declare function clearIdentityCredentials(config: Pick<CliConfig, 'configDir'>): Promise<boolean>;
|
|
8
87
|
export declare function getOneAccessToken(config: CredentialsConfig): Promise<string | null>;
|
|
88
|
+
/** Saved sign-in for doctor, which reports problems instead of failing on them. */
|
|
89
|
+
export type OptionalAccessToken = {
|
|
90
|
+
accessToken: string | null;
|
|
91
|
+
/** Why the saved sign-in could not be used: an unsafe credentials directory or file, unreadable credentials, a held lock, or a failed refresh. */
|
|
92
|
+
problem: string | null;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* getOneAccessToken for doctor: any problem with the saved sign-in is returned, not thrown.
|
|
96
|
+
* Lock waits, discovery and refresh use the configured timeout. Windows skips the credential store entirely.
|
|
97
|
+
* With no saved sign-in there is no problem, even under a config path that fails the directory checks.
|
|
98
|
+
*/
|
|
99
|
+
export declare function getOptionalOneAccessToken(config: CredentialsConfig): Promise<OptionalAccessToken>;
|
|
100
|
+
/**
|
|
101
|
+
* Whether list can show the saved sign-in as usable, judged from the files alone: no refresh, no network
|
|
102
|
+
* and no wait for the credentials lock. A legacy token must be unexpired; an Identity grant may hold an
|
|
103
|
+
* expired access token, which the next One command refreshes. Windows skips the credential store entirely.
|
|
104
|
+
* Any problem means signed out and one `warn` per process; an expired legacy token is not a problem.
|
|
105
|
+
* A credentials directory that fails the strict checks is never written to or opened, and is only warned
|
|
106
|
+
* about when a credentials file may exist there. Never throws.
|
|
107
|
+
*/
|
|
108
|
+
export declare function hasUsableSignIn(config: CliConfig, warn: (message: string) => void): Promise<boolean>;
|
|
9
109
|
export {};
|
|
10
|
-
//# sourceMappingURL=store.d.ts.map
|