@doist/outline-cli 1.5.3 → 1.7.0
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/CHANGELOG.md +12 -0
- package/dist/commands/auth.d.ts +12 -0
- package/dist/commands/auth.js +110 -158
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/changelog.d.ts +1 -6
- package/dist/commands/changelog.js +8 -88
- package/dist/commands/changelog.js.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +5 -1
- package/dist/lib/api.js +9 -6
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/auth-constants.d.ts +18 -0
- package/dist/lib/auth-constants.js +25 -0
- package/dist/lib/auth-constants.js.map +1 -0
- package/dist/lib/auth-pages.d.ts +2 -0
- package/dist/lib/auth-pages.js +101 -0
- package/dist/lib/auth-pages.js.map +1 -0
- package/dist/lib/auth-provider.d.ts +59 -0
- package/dist/lib/auth-provider.js +299 -0
- package/dist/lib/auth-provider.js.map +1 -0
- package/dist/lib/auth.d.ts +24 -7
- package/dist/lib/auth.js +53 -36
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/config.d.ts +23 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/errors.d.ts +3 -1
- package/dist/lib/errors.js +1 -0
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/global-args.d.ts +1 -0
- package/dist/lib/global-args.js +4 -0
- package/dist/lib/global-args.js.map +1 -1
- package/dist/lib/migrate-auth.d.ts +11 -0
- package/dist/lib/migrate-auth.js +92 -0
- package/dist/lib/migrate-auth.js.map +1 -0
- package/dist/lib/outline-account.d.ts +26 -0
- package/dist/lib/outline-account.js +12 -0
- package/dist/lib/outline-account.js.map +1 -0
- package/dist/lib/output.d.ts +6 -0
- package/dist/lib/output.js +16 -4
- package/dist/lib/output.js.map +1 -1
- package/dist/lib/skills/content.d.ts +1 -1
- package/dist/lib/skills/content.js +10 -3
- package/dist/lib/skills/content.js.map +1 -1
- package/dist/lib/user-records.d.ts +14 -0
- package/dist/lib/user-records.js +89 -0
- package/dist/lib/user-records.js.map +1 -0
- package/package.json +2 -2
- package/dist/lib/oauth-server.d.ts +0 -14
- package/dist/lib/oauth-server.js +0 -203
- package/dist/lib/oauth-server.js.map +0 -1
- package/dist/lib/oauth.d.ts +0 -17
- package/dist/lib/oauth.js +0 -42
- package/dist/lib/oauth.js.map +0 -1
- package/dist/lib/pkce.d.ts +0 -3
- package/dist/lib/pkce.js +0 -16
- package/dist/lib/pkce.js.map +0 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** OS keyring `service` identifier for every outline-cli secret. */
|
|
2
|
+
export const SECURE_STORE_SERVICE = 'outline-cli';
|
|
3
|
+
/** Env var that short-circuits the token store with a manually-supplied token. */
|
|
4
|
+
export const TOKEN_ENV_VAR = 'OUTLINE_API_TOKEN';
|
|
5
|
+
/**
|
|
6
|
+
* Legacy single-user keyring slot. Outline never wrote a token to the
|
|
7
|
+
* keyring before this migration, so there is no real legacy slot to read —
|
|
8
|
+
* `migrateLegacyAuth` still expects the option, and its best-effort delete
|
|
9
|
+
* after migration is a harmless no-op on an empty entry.
|
|
10
|
+
*/
|
|
11
|
+
export const LEGACY_KEYRING_ACCOUNT = 'api-token';
|
|
12
|
+
/**
|
|
13
|
+
* `updateConfig` payload that wipes every v1 auth key. Used by both the
|
|
14
|
+
* one-shot migration `cleanupLegacyConfig` and the runtime
|
|
15
|
+
* `dischargeLegacyState` so both paths stay in lockstep.
|
|
16
|
+
*/
|
|
17
|
+
export const LEGACY_CLEAR_PAYLOAD = {
|
|
18
|
+
api_token: undefined,
|
|
19
|
+
base_url: undefined,
|
|
20
|
+
oauth_client_id: undefined,
|
|
21
|
+
auth_user_id: undefined,
|
|
22
|
+
auth_user_name: undefined,
|
|
23
|
+
auth_team_name: undefined,
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=auth-constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-constants.js","sourceRoot":"","sources":["../../src/lib/auth-constants.ts"],"names":[],"mappings":"AAEA,oEAAoE;AACpE,MAAM,CAAC,MAAM,oBAAoB,GAAG,aAAa,CAAA;AAEjD,kFAAkF;AAClF,MAAM,CAAC,MAAM,aAAa,GAAG,mBAAmB,CAAA;AAEhD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,WAAW,CAAA;AAEjD;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAoB;IACjD,SAAS,EAAE,SAAS;IACpB,QAAQ,EAAE,SAAS;IACnB,eAAe,EAAE,SAAS;IAC1B,YAAY,EAAE,SAAS;IACvB,cAAc,EAAE,SAAS;IACzB,cAAc,EAAE,SAAS;CAC5B,CAAA"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
function escapeHtml(text) {
|
|
2
|
+
return text
|
|
3
|
+
.replaceAll('&', '&')
|
|
4
|
+
.replaceAll('<', '<')
|
|
5
|
+
.replaceAll('>', '>')
|
|
6
|
+
.replaceAll('"', '"')
|
|
7
|
+
.replaceAll("'", ''');
|
|
8
|
+
}
|
|
9
|
+
function renderPage(title, subtitle, body) {
|
|
10
|
+
return `<!doctype html>
|
|
11
|
+
<html lang="en">
|
|
12
|
+
<head>
|
|
13
|
+
<meta charset="utf-8">
|
|
14
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
15
|
+
<title>${escapeHtml(title)} - Outline CLI</title>
|
|
16
|
+
<style>
|
|
17
|
+
:root {
|
|
18
|
+
--bg: #f5f7fb;
|
|
19
|
+
--surface: #ffffff;
|
|
20
|
+
--border: #e6e9f0;
|
|
21
|
+
--text: #1e2430;
|
|
22
|
+
--muted: #586178;
|
|
23
|
+
--ok: #15803d;
|
|
24
|
+
--ok-soft: #dcfce7;
|
|
25
|
+
--err: #b91c1c;
|
|
26
|
+
--err-soft: #fee2e2;
|
|
27
|
+
--radius: 14px;
|
|
28
|
+
}
|
|
29
|
+
* { box-sizing: border-box; }
|
|
30
|
+
body {
|
|
31
|
+
margin: 0;
|
|
32
|
+
min-height: 100vh;
|
|
33
|
+
display: grid;
|
|
34
|
+
place-items: center;
|
|
35
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
36
|
+
background: radial-gradient(1200px 500px at 50% -10%, #dbeafe 0%, var(--bg) 60%);
|
|
37
|
+
color: var(--text);
|
|
38
|
+
line-height: 1.5;
|
|
39
|
+
padding: 20px;
|
|
40
|
+
}
|
|
41
|
+
.card {
|
|
42
|
+
width: min(560px, 100%);
|
|
43
|
+
background: var(--surface);
|
|
44
|
+
border: 1px solid var(--border);
|
|
45
|
+
border-radius: var(--radius);
|
|
46
|
+
padding: 24px;
|
|
47
|
+
box-shadow: 0 16px 40px rgba(15, 23, 42, 0.08);
|
|
48
|
+
}
|
|
49
|
+
h1 {
|
|
50
|
+
margin: 0 0 8px;
|
|
51
|
+
font-size: 24px;
|
|
52
|
+
letter-spacing: -0.01em;
|
|
53
|
+
}
|
|
54
|
+
.subtitle {
|
|
55
|
+
margin: 0 0 16px;
|
|
56
|
+
color: var(--muted);
|
|
57
|
+
font-size: 15px;
|
|
58
|
+
}
|
|
59
|
+
.message {
|
|
60
|
+
border-radius: 12px;
|
|
61
|
+
padding: 14px 16px;
|
|
62
|
+
font-size: 14px;
|
|
63
|
+
}
|
|
64
|
+
.success {
|
|
65
|
+
background: var(--ok-soft);
|
|
66
|
+
color: #14532d;
|
|
67
|
+
}
|
|
68
|
+
.error {
|
|
69
|
+
background: var(--err-soft);
|
|
70
|
+
color: #7f1d1d;
|
|
71
|
+
}
|
|
72
|
+
.hint {
|
|
73
|
+
margin-top: 14px;
|
|
74
|
+
color: var(--muted);
|
|
75
|
+
font-size: 13px;
|
|
76
|
+
}
|
|
77
|
+
code {
|
|
78
|
+
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
|
|
79
|
+
background: #eef2ff;
|
|
80
|
+
border-radius: 6px;
|
|
81
|
+
padding: 2px 6px;
|
|
82
|
+
}
|
|
83
|
+
</style>
|
|
84
|
+
</head>
|
|
85
|
+
<body>
|
|
86
|
+
<main class="card">
|
|
87
|
+
<h1>${escapeHtml(title)}</h1>
|
|
88
|
+
<p class="subtitle">${escapeHtml(subtitle)}</p>
|
|
89
|
+
${body}
|
|
90
|
+
<p class="hint">Return to your terminal and continue with <code>ol</code> commands.</p>
|
|
91
|
+
</main>
|
|
92
|
+
</body>
|
|
93
|
+
</html>`;
|
|
94
|
+
}
|
|
95
|
+
export function renderSuccess() {
|
|
96
|
+
return renderPage('Login complete', 'Outline CLI is now authenticated.', '<div class="message success">You can close this tab now.</div>');
|
|
97
|
+
}
|
|
98
|
+
export function renderError(message) {
|
|
99
|
+
return renderPage('Authentication failed', 'Outline CLI could not finish OAuth login.', `<div class="message error">${escapeHtml(message)}</div>`);
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=auth-pages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-pages.js","sourceRoot":"","sources":["../../src/lib/auth-pages.ts"],"names":[],"mappings":"AAAA,SAAS,UAAU,CAAC,IAAY;IAC5B,OAAO,IAAI;SACN,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;SACxB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC;SACzB,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;AACjC,CAAC;AAED,SAAS,UAAU,CAAC,KAAa,EAAE,QAAgB,EAAE,IAAY;IAC7D,OAAO;;;;;WAKA,UAAU,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAwElB,UAAU,CAAC,KAAK,CAAC;0BACD,UAAU,CAAC,QAAQ,CAAC;MACxC,IAAI;;;;QAIF,CAAA;AACR,CAAC;AAED,MAAM,UAAU,aAAa;IACzB,OAAO,UAAU,CACb,gBAAgB,EAChB,mCAAmC,EACnC,gEAAgE,CACnE,CAAA;AACL,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAe;IACvC,OAAO,UAAU,CACb,uBAAuB,EACvB,2CAA2C,EAC3C,8BAA8B,UAAU,CAAC,OAAO,CAAC,QAAQ,CAC5D,CAAA;AACL,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type AccountRef, type AuthProvider, type KeyringTokenStore } from '@doist/cli-core/auth';
|
|
2
|
+
import { type OutlineAccount } from './outline-account.js';
|
|
3
|
+
export type { OutlineAccount } from './outline-account.js';
|
|
4
|
+
export type AuthInfoResponse = {
|
|
5
|
+
user: {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
email: string;
|
|
9
|
+
};
|
|
10
|
+
team: {
|
|
11
|
+
name: string;
|
|
12
|
+
subdomain: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export type OutlineTokenStore = KeyringTokenStore<OutlineAccount>;
|
|
16
|
+
export declare function createOutlineAuthProvider(): AuthProvider<OutlineAccount>;
|
|
17
|
+
/**
|
|
18
|
+
* Accepts the Outline user UUID or display name. Id matches are
|
|
19
|
+
* case-sensitive (UUIDs are canonical); label matches are
|
|
20
|
+
* case-insensitive so users can pass the name they see in `auth status`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function matchOutlineAccount(account: OutlineAccount, ref: AccountRef): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* True when the v2 store is empty but a legacy v1 token snapshot is still
|
|
25
|
+
* the only thing keeping the CLI authenticated — typically because
|
|
26
|
+
* `migrateLegacyAuth` couldn't reach the Outline API to identify the
|
|
27
|
+
* account (`MigrateSkipReason: 'identify-failed'`) or the OS keyring is
|
|
28
|
+
* unreachable (`'legacy-keyring-unreachable'`, although outline has no
|
|
29
|
+
* v1 keyring slot to read from). Useful for downstream diagnostics.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isLegacyAuthActive(): Promise<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* `OUTLINE_API_TOKEN` short-circuits `active()` only when no explicit ref
|
|
34
|
+
* is supplied — cli-core's `KeyringTokenStore` doesn't know about the env
|
|
35
|
+
* var, and an explicit ref means the caller targets a specific stored
|
|
36
|
+
* account.
|
|
37
|
+
*
|
|
38
|
+
* `ensureMigrated()` runs **before** every mutating v2 op so the post-op
|
|
39
|
+
* legacy discharge can't race a still-pending migration into re-grabbing
|
|
40
|
+
* the legacy `api_token` we just consumed. `set()` / `clear()` then
|
|
41
|
+
* discharge legacy state **after** the v2 op succeeds. `set()` swallows
|
|
42
|
+
* the cleanup failure (v2 wins in `active()` regardless); `clear()`
|
|
43
|
+
* propagates it so a failed logout fails loudly instead of leaving the
|
|
44
|
+
* user authenticated via the legacy fallback.
|
|
45
|
+
*/
|
|
46
|
+
export declare function createOutlineTokenStore(): OutlineTokenStore;
|
|
47
|
+
/**
|
|
48
|
+
* Where the currently-active token lives. Mirrors `active()`'s resolution
|
|
49
|
+
* order — env → v2 record → legacy plaintext — so the answer can never
|
|
50
|
+
* contradict the token the runtime is actually using.
|
|
51
|
+
*
|
|
52
|
+
* The precedence cascade is intentionally duplicated with `active()`:
|
|
53
|
+
* the only true dedupe options either (a) add an extra config read on
|
|
54
|
+
* every `apiRequest` call (regressing the request hot path) or (b)
|
|
55
|
+
* augment cli-core's `TokenStore` contract. The drift the duplication
|
|
56
|
+
* invites is guarded by the `getActiveTokenSource` regression test that
|
|
57
|
+
* asserts v2-record presence wins over a lingering v1 `api_token`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function getActiveTokenSource(): Promise<'env' | 'secure-store' | 'config-file'>;
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline/promises';
|
|
2
|
+
import { createKeyringTokenStore, deriveChallenge, generateVerifier, } from '@doist/cli-core/auth';
|
|
3
|
+
import { fetchWithRetry } from '../transport/fetch-with-retry.js';
|
|
4
|
+
import { apiRequest } from './api.js';
|
|
5
|
+
import { LEGACY_CLEAR_PAYLOAD, SECURE_STORE_SERVICE, TOKEN_ENV_VAR } from './auth-constants.js';
|
|
6
|
+
import { getBaseUrl, getOAuthClientId } from './auth.js';
|
|
7
|
+
import { getConfig, getConfigPath, updateConfig } from './config.js';
|
|
8
|
+
import { runMigrateLegacyAuth } from './migrate-auth.js';
|
|
9
|
+
import { makeOutlineAccount } from './outline-account.js';
|
|
10
|
+
import { createOutlineUserRecordStore, getDefaultUserRecord } from './user-records.js';
|
|
11
|
+
function asHandshake(value) {
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
function stringFlag(flags, key) {
|
|
15
|
+
const value = flags[key];
|
|
16
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
17
|
+
}
|
|
18
|
+
async function prompt(question) {
|
|
19
|
+
// Output to stderr so `--json` / `--ndjson` envelopes on stdout stay clean.
|
|
20
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
21
|
+
try {
|
|
22
|
+
return (await rl.question(question)).trim();
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
rl.close();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function resolveBaseUrl(flags) {
|
|
29
|
+
const fromFlag = stringFlag(flags, 'baseUrl');
|
|
30
|
+
if (fromFlag)
|
|
31
|
+
return fromFlag.replace(/\/$/, '');
|
|
32
|
+
const fromEnv = process.env.OUTLINE_URL?.trim();
|
|
33
|
+
if (fromEnv)
|
|
34
|
+
return fromEnv.replace(/\/$/, '');
|
|
35
|
+
const configured = await getBaseUrl();
|
|
36
|
+
const answered = await prompt(`Base URL (default: ${configured}): `);
|
|
37
|
+
return (answered || configured).replace(/\/$/, '');
|
|
38
|
+
}
|
|
39
|
+
async function resolveClientId(flags) {
|
|
40
|
+
const fromFlag = stringFlag(flags, 'clientId');
|
|
41
|
+
if (fromFlag)
|
|
42
|
+
return fromFlag;
|
|
43
|
+
const existing = await getOAuthClientId();
|
|
44
|
+
if (existing)
|
|
45
|
+
return existing;
|
|
46
|
+
const answered = await prompt('OAuth Client ID: ');
|
|
47
|
+
if (!answered) {
|
|
48
|
+
throw new Error('OAuth client ID is required. Create a public OAuth app in Outline settings, then pass --client-id <id> or set OUTLINE_OAUTH_CLIENT_ID.');
|
|
49
|
+
}
|
|
50
|
+
return answered;
|
|
51
|
+
}
|
|
52
|
+
export function createOutlineAuthProvider() {
|
|
53
|
+
return {
|
|
54
|
+
async authorize({ redirectUri, state, flags }) {
|
|
55
|
+
const baseUrl = await resolveBaseUrl(flags);
|
|
56
|
+
const clientId = await resolveClientId(flags);
|
|
57
|
+
const codeVerifier = generateVerifier();
|
|
58
|
+
const codeChallenge = deriveChallenge(codeVerifier);
|
|
59
|
+
const url = new URL(`${baseUrl}/oauth/authorize`);
|
|
60
|
+
url.searchParams.set('client_id', clientId);
|
|
61
|
+
url.searchParams.set('response_type', 'code');
|
|
62
|
+
url.searchParams.set('code_challenge', codeChallenge);
|
|
63
|
+
url.searchParams.set('code_challenge_method', 'S256');
|
|
64
|
+
url.searchParams.set('redirect_uri', redirectUri);
|
|
65
|
+
url.searchParams.set('state', state);
|
|
66
|
+
const handshake = { baseUrl, clientId, codeVerifier };
|
|
67
|
+
return { authorizeUrl: url.toString(), handshake };
|
|
68
|
+
},
|
|
69
|
+
async exchangeCode({ code, redirectUri, handshake }) {
|
|
70
|
+
const hs = asHandshake(handshake);
|
|
71
|
+
if (!hs.codeVerifier) {
|
|
72
|
+
throw new Error('Missing PKCE code verifier from authorize step.');
|
|
73
|
+
}
|
|
74
|
+
const params = new URLSearchParams({
|
|
75
|
+
grant_type: 'authorization_code',
|
|
76
|
+
client_id: hs.clientId,
|
|
77
|
+
redirect_uri: redirectUri,
|
|
78
|
+
code_verifier: hs.codeVerifier,
|
|
79
|
+
code,
|
|
80
|
+
});
|
|
81
|
+
const res = await fetchWithRetry({
|
|
82
|
+
url: `${hs.baseUrl}/oauth/token`,
|
|
83
|
+
options: {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
86
|
+
body: params.toString(),
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
const json = (await res.json().catch(() => ({})));
|
|
90
|
+
if (!res.ok) {
|
|
91
|
+
const message = json.error_description || json.message || json.error || res.statusText;
|
|
92
|
+
throw new Error(`OAuth token exchange failed: ${message}`);
|
|
93
|
+
}
|
|
94
|
+
if (!json.access_token) {
|
|
95
|
+
throw new Error('OAuth token exchange did not return an access token.');
|
|
96
|
+
}
|
|
97
|
+
return { accessToken: json.access_token };
|
|
98
|
+
},
|
|
99
|
+
async validateToken({ token, handshake }) {
|
|
100
|
+
const hs = asHandshake(handshake);
|
|
101
|
+
const { data } = await apiRequest('auth.info', {}, {
|
|
102
|
+
token,
|
|
103
|
+
baseUrl: hs.baseUrl,
|
|
104
|
+
});
|
|
105
|
+
return makeOutlineAccount({
|
|
106
|
+
id: data.user.id,
|
|
107
|
+
label: data.user.name,
|
|
108
|
+
baseUrl: hs.baseUrl,
|
|
109
|
+
oauthClientId: hs.clientId,
|
|
110
|
+
teamName: data.team.name,
|
|
111
|
+
});
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Accepts the Outline user UUID or display name. Id matches are
|
|
117
|
+
* case-sensitive (UUIDs are canonical); label matches are
|
|
118
|
+
* case-insensitive so users can pass the name they see in `auth status`.
|
|
119
|
+
*/
|
|
120
|
+
export function matchOutlineAccount(account, ref) {
|
|
121
|
+
if (account.id === ref)
|
|
122
|
+
return true;
|
|
123
|
+
return account.label.toLowerCase() === ref.toLowerCase();
|
|
124
|
+
}
|
|
125
|
+
/** True when the v2 store is the authoritative source. */
|
|
126
|
+
function migrationIsConclusive(result) {
|
|
127
|
+
return (result.status === 'migrated' ||
|
|
128
|
+
result.status === 'already-migrated' ||
|
|
129
|
+
result.status === 'no-legacy-state');
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Synthesise a snapshot from v1 plaintext state still on disk. Fallback for
|
|
133
|
+
* when migration can't complete (offline `identifyAccount`, WSL
|
|
134
|
+
* `legacy-keyring-unreachable`). Returns `null` when no legacy token is
|
|
135
|
+
* present. The synthesised account uses whatever identity fields the v1
|
|
136
|
+
* config has — for pre-#71 configs the id/label may be empty, but the
|
|
137
|
+
* runtime never renders those (status's `fetchLive` re-derives from the
|
|
138
|
+
* API), so empty placeholders are safe.
|
|
139
|
+
*/
|
|
140
|
+
async function readLegacyTokenSnapshot() {
|
|
141
|
+
const config = await getConfig();
|
|
142
|
+
const token = config.api_token?.trim() || null;
|
|
143
|
+
if (!token)
|
|
144
|
+
return null;
|
|
145
|
+
return {
|
|
146
|
+
token,
|
|
147
|
+
account: makeOutlineAccount({
|
|
148
|
+
id: config.auth_user_id ?? '',
|
|
149
|
+
label: config.auth_user_name ?? '',
|
|
150
|
+
baseUrl: config.base_url,
|
|
151
|
+
oauthClientId: config.oauth_client_id,
|
|
152
|
+
teamName: config.auth_team_name,
|
|
153
|
+
}),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Discharge v1 plaintext state. Runs **after** a successful v2 write/clear
|
|
158
|
+
* — never before — so a v2-op failure doesn't strand the user with no
|
|
159
|
+
* recoverable credentials. Caller decides whether to swallow or propagate
|
|
160
|
+
* the `updateConfig` failure:
|
|
161
|
+
* - `set()` swallows (v2 record will win in `active()` regardless).
|
|
162
|
+
* - `clear()` propagates (v2 is empty, so a stale legacy token would
|
|
163
|
+
* shadow the logout via the fallback).
|
|
164
|
+
*/
|
|
165
|
+
async function dischargeLegacyState() {
|
|
166
|
+
await updateConfig(LEGACY_CLEAR_PAYLOAD);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Memoised one-shot migration trigger. Resolves with `null` on rejection so
|
|
170
|
+
* the CLI never fails to start because of a migration error — the legacy
|
|
171
|
+
* snapshot fallback handles that case. Tests reset the memo with
|
|
172
|
+
* `vi.resetModules()` + a dynamic re-import.
|
|
173
|
+
*/
|
|
174
|
+
let migrationPromise;
|
|
175
|
+
function ensureMigrated() {
|
|
176
|
+
if (!migrationPromise) {
|
|
177
|
+
migrationPromise = runMigrateLegacyAuth({ silent: true }).catch(() => null);
|
|
178
|
+
}
|
|
179
|
+
return migrationPromise;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* True when the v2 store is empty but a legacy v1 token snapshot is still
|
|
183
|
+
* the only thing keeping the CLI authenticated — typically because
|
|
184
|
+
* `migrateLegacyAuth` couldn't reach the Outline API to identify the
|
|
185
|
+
* account (`MigrateSkipReason: 'identify-failed'`) or the OS keyring is
|
|
186
|
+
* unreachable (`'legacy-keyring-unreachable'`, although outline has no
|
|
187
|
+
* v1 keyring slot to read from). Useful for downstream diagnostics.
|
|
188
|
+
*/
|
|
189
|
+
export async function isLegacyAuthActive() {
|
|
190
|
+
const result = await ensureMigrated();
|
|
191
|
+
if (result !== null && migrationIsConclusive(result))
|
|
192
|
+
return false;
|
|
193
|
+
const legacy = await readLegacyTokenSnapshot();
|
|
194
|
+
return legacy !== null;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* `OUTLINE_API_TOKEN` short-circuits `active()` only when no explicit ref
|
|
198
|
+
* is supplied — cli-core's `KeyringTokenStore` doesn't know about the env
|
|
199
|
+
* var, and an explicit ref means the caller targets a specific stored
|
|
200
|
+
* account.
|
|
201
|
+
*
|
|
202
|
+
* `ensureMigrated()` runs **before** every mutating v2 op so the post-op
|
|
203
|
+
* legacy discharge can't race a still-pending migration into re-grabbing
|
|
204
|
+
* the legacy `api_token` we just consumed. `set()` / `clear()` then
|
|
205
|
+
* discharge legacy state **after** the v2 op succeeds. `set()` swallows
|
|
206
|
+
* the cleanup failure (v2 wins in `active()` regardless); `clear()`
|
|
207
|
+
* propagates it so a failed logout fails loudly instead of leaving the
|
|
208
|
+
* user authenticated via the legacy fallback.
|
|
209
|
+
*/
|
|
210
|
+
export function createOutlineTokenStore() {
|
|
211
|
+
const inner = createKeyringTokenStore({
|
|
212
|
+
serviceName: SECURE_STORE_SERVICE,
|
|
213
|
+
userRecords: createOutlineUserRecordStore(),
|
|
214
|
+
recordsLocation: getConfigPath(),
|
|
215
|
+
matchAccount: matchOutlineAccount,
|
|
216
|
+
});
|
|
217
|
+
async function migrationIsInconclusive() {
|
|
218
|
+
const result = await ensureMigrated(); // memoised
|
|
219
|
+
return result === null || !migrationIsConclusive(result);
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
async active(ref) {
|
|
223
|
+
if (ref === undefined) {
|
|
224
|
+
const envToken = process.env[TOKEN_ENV_VAR]?.trim();
|
|
225
|
+
if (envToken) {
|
|
226
|
+
return {
|
|
227
|
+
token: envToken,
|
|
228
|
+
account: makeOutlineAccount({
|
|
229
|
+
id: '',
|
|
230
|
+
label: '',
|
|
231
|
+
baseUrl: await getBaseUrl(),
|
|
232
|
+
}),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
await ensureMigrated();
|
|
237
|
+
const fromStore = await inner.active(ref);
|
|
238
|
+
if (fromStore)
|
|
239
|
+
return fromStore;
|
|
240
|
+
const legacy = await readLegacyTokenSnapshot();
|
|
241
|
+
if (legacy && (ref === undefined || matchOutlineAccount(legacy.account, ref))) {
|
|
242
|
+
return legacy;
|
|
243
|
+
}
|
|
244
|
+
return null;
|
|
245
|
+
},
|
|
246
|
+
async set(account, token) {
|
|
247
|
+
await ensureMigrated();
|
|
248
|
+
await inner.set(account, token);
|
|
249
|
+
if (await migrationIsInconclusive()) {
|
|
250
|
+
// Best-effort: a lingering `api_token` is dormant because
|
|
251
|
+
// `active()` reads v2 first.
|
|
252
|
+
await dischargeLegacyState().catch(() => undefined);
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
async clear(ref) {
|
|
256
|
+
await ensureMigrated();
|
|
257
|
+
await inner.clear(ref);
|
|
258
|
+
if (await migrationIsInconclusive()) {
|
|
259
|
+
// Must succeed: v2 is now empty, so a surviving legacy
|
|
260
|
+
// token would shadow the logout via the fallback.
|
|
261
|
+
await dischargeLegacyState();
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
async list() {
|
|
265
|
+
await ensureMigrated();
|
|
266
|
+
return inner.list();
|
|
267
|
+
},
|
|
268
|
+
async setDefault(ref) {
|
|
269
|
+
await ensureMigrated();
|
|
270
|
+
return inner.setDefault(ref);
|
|
271
|
+
},
|
|
272
|
+
getLastStorageResult: () => inner.getLastStorageResult(),
|
|
273
|
+
getLastClearResult: () => inner.getLastClearResult(),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Where the currently-active token lives. Mirrors `active()`'s resolution
|
|
278
|
+
* order — env → v2 record → legacy plaintext — so the answer can never
|
|
279
|
+
* contradict the token the runtime is actually using.
|
|
280
|
+
*
|
|
281
|
+
* The precedence cascade is intentionally duplicated with `active()`:
|
|
282
|
+
* the only true dedupe options either (a) add an extra config read on
|
|
283
|
+
* every `apiRequest` call (regressing the request hot path) or (b)
|
|
284
|
+
* augment cli-core's `TokenStore` contract. The drift the duplication
|
|
285
|
+
* invites is guarded by the `getActiveTokenSource` regression test that
|
|
286
|
+
* asserts v2-record presence wins over a lingering v1 `api_token`.
|
|
287
|
+
*/
|
|
288
|
+
export async function getActiveTokenSource() {
|
|
289
|
+
if (process.env[TOKEN_ENV_VAR]?.trim())
|
|
290
|
+
return 'env';
|
|
291
|
+
const config = await getConfig();
|
|
292
|
+
const record = getDefaultUserRecord(config);
|
|
293
|
+
if (record)
|
|
294
|
+
return record.fallbackToken ? 'config-file' : 'secure-store';
|
|
295
|
+
if (config.api_token?.trim())
|
|
296
|
+
return 'config-file';
|
|
297
|
+
return 'secure-store';
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=auth-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-provider.js","sourceRoot":"","sources":["../../src/lib/auth-provider.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/auth.d.ts
CHANGED
|
@@ -1,11 +1,28 @@
|
|
|
1
|
+
import { SecureStoreUnavailableError } from '@doist/cli-core/auth';
|
|
2
|
+
import { TOKEN_ENV_VAR } from './auth-constants.js';
|
|
3
|
+
import { getActiveTokenSource } from './auth-provider.js';
|
|
4
|
+
import { CliError } from './errors.js';
|
|
5
|
+
export { SecureStoreUnavailableError, getActiveTokenSource, TOKEN_ENV_VAR };
|
|
6
|
+
export declare class NoTokenError extends CliError {
|
|
7
|
+
constructor();
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Read the active token. Hot path: when `OUTLINE_API_TOKEN` is set we
|
|
11
|
+
* return it directly without consulting the token store, since
|
|
12
|
+
* `apiRequest` already resolves the base URL separately — going through
|
|
13
|
+
* `store.active()` here would trigger a redundant `getBaseUrl()` lookup
|
|
14
|
+
* per request just to synthesise an account we don't need.
|
|
15
|
+
*/
|
|
1
16
|
export declare function getApiToken(): Promise<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Base URL cascade: env var → default user record (v2) → legacy
|
|
19
|
+
* `base_url` config (v1) → built-in default. The record takes priority
|
|
20
|
+
* over the legacy slot so post-migration logins keep defaulting to the
|
|
21
|
+
* same Outline instance.
|
|
22
|
+
*/
|
|
2
23
|
export declare function getBaseUrl(): Promise<string>;
|
|
3
|
-
export declare function getOAuthClientId(): Promise<string | undefined>;
|
|
4
|
-
export declare function getTokenSource(): Promise<'env' | 'config' | null>;
|
|
5
|
-
export declare function saveConfig(token: string, baseUrl?: string, oauthClientId?: string): Promise<void>;
|
|
6
24
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* would silently reset the user's update-channel preference too.
|
|
25
|
+
* OAuth client id cascade: env var → default user record (v2) → legacy
|
|
26
|
+
* `oauth_client_id` config (v1) → undefined (caller prompts).
|
|
10
27
|
*/
|
|
11
|
-
export declare function
|
|
28
|
+
export declare function getOAuthClientId(): Promise<string | undefined>;
|
package/dist/lib/auth.js
CHANGED
|
@@ -1,57 +1,74 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { SecureStoreUnavailableError } from '@doist/cli-core/auth';
|
|
2
|
+
import { TOKEN_ENV_VAR } from './auth-constants.js';
|
|
3
|
+
import { createOutlineTokenStore, getActiveTokenSource, } from './auth-provider.js';
|
|
4
|
+
import { getConfig } from './config.js';
|
|
5
|
+
import { CliError } from './errors.js';
|
|
6
|
+
import { DEFAULT_BASE_URL } from './outline-account.js';
|
|
7
|
+
import { getDefaultUserRecord } from './user-records.js';
|
|
8
|
+
export { SecureStoreUnavailableError, getActiveTokenSource, TOKEN_ENV_VAR };
|
|
9
|
+
export class NoTokenError extends CliError {
|
|
10
|
+
constructor() {
|
|
11
|
+
super('NO_TOKEN', `No API token found. Set ${TOKEN_ENV_VAR} env var or run: ol auth login`, [`Set ${TOKEN_ENV_VAR} or run: ol auth login`], 'info');
|
|
12
|
+
this.name = 'NoTokenError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Module-level token-store singleton. Built lazily on first call; reused
|
|
17
|
+
* across every `apiRequest` so the request hot path doesn't reconstruct
|
|
18
|
+
* the keyring + user-record adapters per POST.
|
|
19
|
+
*/
|
|
20
|
+
let storeSingleton;
|
|
21
|
+
function tokenStore() {
|
|
22
|
+
if (!storeSingleton)
|
|
23
|
+
storeSingleton = createOutlineTokenStore();
|
|
24
|
+
return storeSingleton;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Read the active token. Hot path: when `OUTLINE_API_TOKEN` is set we
|
|
28
|
+
* return it directly without consulting the token store, since
|
|
29
|
+
* `apiRequest` already resolves the base URL separately — going through
|
|
30
|
+
* `store.active()` here would trigger a redundant `getBaseUrl()` lookup
|
|
31
|
+
* per request just to synthesise an account we don't need.
|
|
32
|
+
*/
|
|
3
33
|
export async function getApiToken() {
|
|
4
|
-
const envToken = process.env
|
|
34
|
+
const envToken = process.env[TOKEN_ENV_VAR]?.trim();
|
|
5
35
|
if (envToken)
|
|
6
36
|
return envToken;
|
|
7
|
-
const
|
|
8
|
-
if (
|
|
9
|
-
|
|
10
|
-
|
|
37
|
+
const snapshot = await tokenStore().active();
|
|
38
|
+
if (!snapshot?.token)
|
|
39
|
+
throw new NoTokenError();
|
|
40
|
+
return snapshot.token;
|
|
11
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Base URL cascade: env var → default user record (v2) → legacy
|
|
44
|
+
* `base_url` config (v1) → built-in default. The record takes priority
|
|
45
|
+
* over the legacy slot so post-migration logins keep defaulting to the
|
|
46
|
+
* same Outline instance.
|
|
47
|
+
*/
|
|
12
48
|
export async function getBaseUrl() {
|
|
13
49
|
const envUrl = process.env.OUTLINE_URL;
|
|
14
50
|
if (envUrl)
|
|
15
51
|
return envUrl.replace(/\/$/, '');
|
|
16
52
|
const config = await getConfig();
|
|
53
|
+
const record = getDefaultUserRecord(config);
|
|
54
|
+
if (record?.account.baseUrl)
|
|
55
|
+
return record.account.baseUrl.replace(/\/$/, '');
|
|
17
56
|
if (config.base_url)
|
|
18
57
|
return config.base_url.replace(/\/$/, '');
|
|
19
58
|
return DEFAULT_BASE_URL;
|
|
20
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* OAuth client id cascade: env var → default user record (v2) → legacy
|
|
62
|
+
* `oauth_client_id` config (v1) → undefined (caller prompts).
|
|
63
|
+
*/
|
|
21
64
|
export async function getOAuthClientId() {
|
|
22
65
|
const envClientId = process.env.OUTLINE_OAUTH_CLIENT_ID;
|
|
23
66
|
if (envClientId)
|
|
24
67
|
return envClientId;
|
|
25
68
|
const config = await getConfig();
|
|
69
|
+
const record = getDefaultUserRecord(config);
|
|
70
|
+
if (record?.account.oauthClientId)
|
|
71
|
+
return record.account.oauthClientId;
|
|
26
72
|
return config.oauth_client_id;
|
|
27
73
|
}
|
|
28
|
-
export async function getTokenSource() {
|
|
29
|
-
if (process.env.OUTLINE_API_TOKEN)
|
|
30
|
-
return 'env';
|
|
31
|
-
const config = await getConfig();
|
|
32
|
-
if (config.api_token)
|
|
33
|
-
return 'config';
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
export async function saveConfig(token, baseUrl, oauthClientId) {
|
|
37
|
-
const updates = { api_token: token };
|
|
38
|
-
if (baseUrl)
|
|
39
|
-
updates.base_url = baseUrl.replace(/\/$/, '');
|
|
40
|
-
if (oauthClientId)
|
|
41
|
-
updates.oauth_client_id = oauthClientId;
|
|
42
|
-
await updateConfig(updates);
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Clear the auth-related keys without deleting the file. The config is now
|
|
46
|
-
* shared with non-auth settings (notably `update_channel`); a blanket unlink
|
|
47
|
-
* would silently reset the user's update-channel preference too.
|
|
48
|
-
*/
|
|
49
|
-
export async function clearConfig() {
|
|
50
|
-
const existing = await getConfig();
|
|
51
|
-
const { api_token, base_url, oauth_client_id, ...rest } = existing;
|
|
52
|
-
void api_token;
|
|
53
|
-
void base_url;
|
|
54
|
-
void oauth_client_id;
|
|
55
|
-
await setConfig(rest);
|
|
56
|
-
}
|
|
57
74
|
//# sourceMappingURL=auth.js.map
|