@doist/outline-cli 1.7.0 → 1.9.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/account.d.ts +2 -0
- package/dist/commands/account.js +107 -0
- package/dist/commands/account.js.map +1 -0
- package/dist/commands/auth.d.ts +0 -12
- package/dist/commands/auth.js +25 -28
- package/dist/commands/auth.js.map +1 -1
- package/dist/index.js +19 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/api.js +46 -7
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/auth-output.d.ts +15 -0
- package/dist/lib/auth-output.js +27 -0
- package/dist/lib/auth-output.js.map +1 -0
- package/dist/lib/auth-provider.d.ts +26 -18
- package/dist/lib/auth-provider.js +131 -107
- package/dist/lib/auth-provider.js.map +1 -1
- package/dist/lib/auth.d.ts +50 -6
- package/dist/lib/auth.js +134 -11
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/config.d.ts +7 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/errors.d.ts +1 -1
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/global-args.d.ts +25 -0
- package/dist/lib/global-args.js +50 -1
- package/dist/lib/global-args.js.map +1 -1
- package/dist/lib/outline-account.d.ts +7 -1
- package/dist/lib/outline-account.js +10 -0
- package/dist/lib/outline-account.js.map +1 -1
- package/dist/lib/skills/content.d.ts +1 -1
- package/dist/lib/skills/content.js +18 -0
- package/dist/lib/skills/content.js.map +1 -1
- package/dist/lib/user-records.d.ts +8 -1
- package/dist/lib/user-records.js +46 -9
- package/dist/lib/user-records.js.map +1 -1
- package/dist/lib/user-ref-store.d.ts +16 -0
- package/dist/lib/user-ref-store.js +37 -0
- package/dist/lib/user-ref-store.js.map +1 -0
- package/package.json +3 -2
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { createInterface } from 'node:readline/promises';
|
|
2
|
-
import { createKeyringTokenStore,
|
|
2
|
+
import { createKeyringTokenStore, createPkceProvider, } from '@doist/cli-core/auth';
|
|
3
3
|
import { fetchWithRetry } from '../transport/fetch-with-retry.js';
|
|
4
4
|
import { apiRequest } from './api.js';
|
|
5
5
|
import { LEGACY_CLEAR_PAYLOAD, SECURE_STORE_SERVICE, TOKEN_ENV_VAR } from './auth-constants.js';
|
|
6
6
|
import { getBaseUrl, getOAuthClientId } from './auth.js';
|
|
7
7
|
import { getConfig, getConfigPath, updateConfig } from './config.js';
|
|
8
8
|
import { runMigrateLegacyAuth } from './migrate-auth.js';
|
|
9
|
-
import { makeOutlineAccount } from './outline-account.js';
|
|
10
|
-
import { createOutlineUserRecordStore, getDefaultUserRecord } from './user-records.js';
|
|
11
|
-
|
|
12
|
-
return value;
|
|
13
|
-
}
|
|
9
|
+
import { makeOutlineAccount, matchOutlineAccount } from './outline-account.js';
|
|
10
|
+
import { createOutlineUserRecordStore, getDefaultUserRecord, recordForRef } from './user-records.js';
|
|
11
|
+
export { matchOutlineAccount } from './outline-account.js';
|
|
14
12
|
function stringFlag(flags, key) {
|
|
15
13
|
const value = flags[key];
|
|
16
14
|
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
@@ -49,78 +47,54 @@ async function resolveClientId(flags) {
|
|
|
49
47
|
}
|
|
50
48
|
return answered;
|
|
51
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Routes cli-core's OAuth HTTP through outline's transport so the proxy /
|
|
52
|
+
* decompression dispatcher applies — to the token exchange AND the
|
|
53
|
+
* oauth4webapi refresh grant (cli-core threads this into oauth4webapi's
|
|
54
|
+
* `customFetch`), which would otherwise capture the bare global `fetch`.
|
|
55
|
+
*/
|
|
56
|
+
const outlineFetch = (input, init) => fetchWithRetry({ url: input, options: init ?? {} });
|
|
52
57
|
export function createOutlineAuthProvider() {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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 };
|
|
58
|
+
// Captured at `authorize` (which may prompt for it) and reused by
|
|
59
|
+
// `exchangeCode` / `validate` so a single login can't double-prompt. A
|
|
60
|
+
// provider built only for silent refresh never runs `authorize`, so this
|
|
61
|
+
// stays undefined there and `tokenUrl` falls back to stored config — the
|
|
62
|
+
// refresh path must never prompt.
|
|
63
|
+
let baseUrl;
|
|
64
|
+
return createPkceProvider({
|
|
65
|
+
authorizeUrl: async ({ flags }) => {
|
|
66
|
+
baseUrl = await resolveBaseUrl(flags);
|
|
67
|
+
return `${baseUrl}/oauth/authorize`;
|
|
68
68
|
},
|
|
69
|
-
async
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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 };
|
|
69
|
+
tokenUrl: async ({ handshake }) => {
|
|
70
|
+
// `handshake.baseUrl` lets a caller scope refresh to a specific
|
|
71
|
+
// account's instance (status --user); otherwise the authorize-time
|
|
72
|
+
// value, then the default config.
|
|
73
|
+
const base = baseUrl ?? handshake.baseUrl ?? (await getBaseUrl());
|
|
74
|
+
return `${base}/oauth/token`;
|
|
98
75
|
},
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
76
|
+
// Prefer a handshake-scoped client id (account-aware refresh); else
|
|
77
|
+
// resolve from flag/config (only prompts when neither is set, so it's
|
|
78
|
+
// safe on the refresh path — the logged-in record carries one).
|
|
79
|
+
clientId: ({ handshake, flags }) => {
|
|
80
|
+
const fromHandshake = handshake.clientId;
|
|
81
|
+
return typeof fromHandshake === 'string' && fromHandshake
|
|
82
|
+
? fromHandshake
|
|
83
|
+
: resolveClientId(flags);
|
|
84
|
+
},
|
|
85
|
+
validate: async ({ token, handshake }) => {
|
|
86
|
+
const base = baseUrl ?? (await getBaseUrl());
|
|
87
|
+
const { data } = await apiRequest('auth.info', {}, { token, baseUrl: base });
|
|
105
88
|
return makeOutlineAccount({
|
|
106
89
|
id: data.user.id,
|
|
107
90
|
label: data.user.name,
|
|
108
|
-
baseUrl:
|
|
109
|
-
oauthClientId:
|
|
91
|
+
baseUrl: base,
|
|
92
|
+
oauthClientId: handshake.clientId,
|
|
110
93
|
teamName: data.team.name,
|
|
111
94
|
});
|
|
112
95
|
},
|
|
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();
|
|
96
|
+
fetchImpl: outlineFetch,
|
|
97
|
+
});
|
|
124
98
|
}
|
|
125
99
|
/** True when the v2 store is the authoritative source. */
|
|
126
100
|
function migrationIsConclusive(result) {
|
|
@@ -207,59 +181,104 @@ export async function isLegacyAuthActive() {
|
|
|
207
181
|
* propagates it so a failed logout fails loudly instead of leaving the
|
|
208
182
|
* user authenticated via the legacy fallback.
|
|
209
183
|
*/
|
|
210
|
-
|
|
211
|
-
|
|
184
|
+
/** The bare cli-core keyring store (v2 records only — no env/legacy fallback). */
|
|
185
|
+
function createInnerStore() {
|
|
186
|
+
return createKeyringTokenStore({
|
|
212
187
|
serviceName: SECURE_STORE_SERVICE,
|
|
213
188
|
userRecords: createOutlineUserRecordStore(),
|
|
214
189
|
recordsLocation: getConfigPath(),
|
|
215
190
|
matchAccount: matchOutlineAccount,
|
|
216
191
|
});
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Authoritatively classify the active credential without re-deriving the
|
|
195
|
+
* cascade from membership heuristics. Mirrors `active()`'s precedence — env
|
|
196
|
+
* (only when no explicit ref) → stored v2 → legacy snapshot — but reports
|
|
197
|
+
* *which* branch matched, so `ol account current` can render the right shape.
|
|
198
|
+
*
|
|
199
|
+
* The v2 lookup uses the bare keyring store (no legacy fallback) and matches by
|
|
200
|
+
* `ref`, so a renamed legacy snapshot sharing a UUID with a v2 record can't be
|
|
201
|
+
* misreported as `stored`: a `--user <old-name>` that the v2 record no longer
|
|
202
|
+
* matches falls through to the legacy branch.
|
|
203
|
+
*/
|
|
204
|
+
export async function resolveActiveAccountSource(ref) {
|
|
205
|
+
if (ref === undefined && process.env[TOKEN_ENV_VAR]?.trim())
|
|
206
|
+
return { source: 'env' };
|
|
207
|
+
await ensureMigrated();
|
|
208
|
+
const stored = await createInnerStore().activeAccount(ref);
|
|
209
|
+
if (stored)
|
|
210
|
+
return { source: 'stored', account: stored.account, isDefault: stored.isDefault };
|
|
211
|
+
const legacy = await readLegacyTokenSnapshot();
|
|
212
|
+
if (legacy && (ref === undefined || matchOutlineAccount(legacy.account, ref))) {
|
|
213
|
+
return { source: 'legacy' };
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
export function createOutlineTokenStore() {
|
|
218
|
+
const inner = createInnerStore();
|
|
217
219
|
async function migrationIsInconclusive() {
|
|
218
220
|
const result = await ensureMigrated(); // memoised
|
|
219
221
|
return result === null || !migrationIsConclusive(result);
|
|
220
222
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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;
|
|
223
|
+
/**
|
|
224
|
+
* Shared env → v2 → legacy resolution for `active` / `activeBundle`.
|
|
225
|
+
* `fromV2` reads the matching cli-core method; `fromEnvOrLegacy` maps an
|
|
226
|
+
* env/legacy token into the caller's shape (env + legacy carry no refresh
|
|
227
|
+
* material, so the bundle form surfaces as access-only).
|
|
228
|
+
*/
|
|
229
|
+
async function resolveAuth(ref, fromV2, fromEnvOrLegacy) {
|
|
230
|
+
if (ref === undefined) {
|
|
231
|
+
const envToken = process.env[TOKEN_ENV_VAR]?.trim();
|
|
232
|
+
if (envToken) {
|
|
233
|
+
return fromEnvOrLegacy(envToken, makeOutlineAccount({ id: '', label: '', baseUrl: await getBaseUrl() }));
|
|
243
234
|
}
|
|
244
|
-
|
|
235
|
+
}
|
|
236
|
+
await ensureMigrated();
|
|
237
|
+
const fromStore = await fromV2();
|
|
238
|
+
if (fromStore)
|
|
239
|
+
return fromStore;
|
|
240
|
+
const legacy = await readLegacyTokenSnapshot();
|
|
241
|
+
if (legacy && (ref === undefined || matchOutlineAccount(legacy.account, ref))) {
|
|
242
|
+
return fromEnvOrLegacy(legacy.token, legacy.account);
|
|
243
|
+
}
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
// `ensureMigrated` runs before the v2 write so the post-write discharge
|
|
247
|
+
// can't race a pending migration into re-grabbing the legacy token. The
|
|
248
|
+
// discharge is best-effort: a lingering `api_token` is dormant because
|
|
249
|
+
// reads hit v2 first. (`clear` needs the stricter propagating variant.)
|
|
250
|
+
async function writeThenDischargeLegacy(write) {
|
|
251
|
+
await ensureMigrated();
|
|
252
|
+
await write();
|
|
253
|
+
if (await migrationIsInconclusive()) {
|
|
254
|
+
await dischargeLegacyState().catch(() => undefined);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
active(ref) {
|
|
259
|
+
return resolveAuth(ref, () => inner.active(ref), (token, account) => ({ token, account }));
|
|
245
260
|
},
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
261
|
+
activeAccount(ref) {
|
|
262
|
+
return resolveAuth(ref, () => inner.activeAccount(ref), (_token, account) => ({ account, isDefault: true }));
|
|
263
|
+
},
|
|
264
|
+
activeBundle(ref) {
|
|
265
|
+
return resolveAuth(ref, () => inner.activeBundle(ref), (token, account) => ({ account, bundle: { accessToken: token } }));
|
|
266
|
+
},
|
|
267
|
+
set(account, token) {
|
|
268
|
+
return writeThenDischargeLegacy(() => inner.set(account, token));
|
|
269
|
+
},
|
|
270
|
+
setBundle(account, bundle, options) {
|
|
271
|
+
return writeThenDischargeLegacy(() => inner.setBundle(account, bundle, options));
|
|
254
272
|
},
|
|
255
273
|
async clear(ref) {
|
|
256
274
|
await ensureMigrated();
|
|
257
|
-
await inner.clear(ref);
|
|
275
|
+
const cleared = await inner.clear(ref);
|
|
258
276
|
if (await migrationIsInconclusive()) {
|
|
259
277
|
// Must succeed: v2 is now empty, so a surviving legacy
|
|
260
278
|
// token would shadow the logout via the fallback.
|
|
261
279
|
await dischargeLegacyState();
|
|
262
280
|
}
|
|
281
|
+
return cleared;
|
|
263
282
|
},
|
|
264
283
|
async list() {
|
|
265
284
|
await ensureMigrated();
|
|
@@ -278,6 +297,11 @@ export function createOutlineTokenStore() {
|
|
|
278
297
|
* order — env → v2 record → legacy plaintext — so the answer can never
|
|
279
298
|
* contradict the token the runtime is actually using.
|
|
280
299
|
*
|
|
300
|
+
* Pass `ref` to report the source of a specific `--user` account (used by
|
|
301
|
+
* `auth status --user <ref>`): a ref selects the matching record and skips the
|
|
302
|
+
* env short-circuit, so the source reflects the account being shown rather
|
|
303
|
+
* than an ambient env token. Without a ref the default/env cascade applies.
|
|
304
|
+
*
|
|
281
305
|
* The precedence cascade is intentionally duplicated with `active()`:
|
|
282
306
|
* the only true dedupe options either (a) add an extra config read on
|
|
283
307
|
* every `apiRequest` call (regressing the request hot path) or (b)
|
|
@@ -285,14 +309,14 @@ export function createOutlineTokenStore() {
|
|
|
285
309
|
* invites is guarded by the `getActiveTokenSource` regression test that
|
|
286
310
|
* asserts v2-record presence wins over a lingering v1 `api_token`.
|
|
287
311
|
*/
|
|
288
|
-
export async function getActiveTokenSource() {
|
|
289
|
-
if (process.env[TOKEN_ENV_VAR]?.trim())
|
|
312
|
+
export async function getActiveTokenSource(ref) {
|
|
313
|
+
if (ref === undefined && process.env[TOKEN_ENV_VAR]?.trim())
|
|
290
314
|
return 'env';
|
|
291
315
|
const config = await getConfig();
|
|
292
|
-
const record = getDefaultUserRecord(config);
|
|
316
|
+
const record = ref !== undefined ? recordForRef(config, ref) : getDefaultUserRecord(config);
|
|
293
317
|
if (record)
|
|
294
318
|
return record.fallbackToken ? 'config-file' : 'secure-store';
|
|
295
|
-
if (config.api_token?.trim())
|
|
319
|
+
if (ref === undefined && config.api_token?.trim())
|
|
296
320
|
return 'config-file';
|
|
297
321
|
return 'secure-store';
|
|
298
322
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-provider.js","sourceRoot":"","sources":["../../src/lib/auth-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAGH,uBAAuB,EACvB,
|
|
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
|
@@ -2,10 +2,43 @@ import { SecureStoreUnavailableError } from '@doist/cli-core/auth';
|
|
|
2
2
|
import { TOKEN_ENV_VAR } from './auth-constants.js';
|
|
3
3
|
import { getActiveTokenSource } from './auth-provider.js';
|
|
4
4
|
import { CliError } from './errors.js';
|
|
5
|
+
import { type OutlineAccount } from './outline-account.js';
|
|
6
|
+
/** Base URL + optional refresh handshake for a single request. */
|
|
7
|
+
type RequestContext = {
|
|
8
|
+
baseUrl: string;
|
|
9
|
+
handshake?: {
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
clientId: string;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
5
14
|
export { SecureStoreUnavailableError, getActiveTokenSource, TOKEN_ENV_VAR };
|
|
6
15
|
export declare class NoTokenError extends CliError {
|
|
7
16
|
constructor();
|
|
8
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Best-effort proactive rotation before a request. Returns the token to use
|
|
20
|
+
* (the rotated one, or the current one if no rotation was needed) so the
|
|
21
|
+
* caller doesn't re-read the store; `undefined` when the account isn't
|
|
22
|
+
* refreshable (no refresh token / env / failure) — the caller then falls back
|
|
23
|
+
* to `getApiToken()` and the 401 path stays authoritative. The wrapped store
|
|
24
|
+
* scopes the bundle to any global `--user`; `handshake` pins the token
|
|
25
|
+
* endpoint to that account's instance + client id.
|
|
26
|
+
*/
|
|
27
|
+
export declare function proactiveRefresh(handshake?: RequestContext['handshake']): Promise<string | undefined>;
|
|
28
|
+
/**
|
|
29
|
+
* Refresh the *selected* account (not the default) for `auth status`, returning
|
|
30
|
+
* its current-or-rotated token. Scoped via the account's id + base URL/client
|
|
31
|
+
* id through the refresh handshake, so `--user <other>` checks and rotates the
|
|
32
|
+
* right account at the right instance. Env / legacy / record-less accounts
|
|
33
|
+
* aren't refreshable — the snapshot `fallback` token is returned as-is.
|
|
34
|
+
*/
|
|
35
|
+
export declare function refreshedTokenForStatus(account: OutlineAccount, fallback: string): Promise<string>;
|
|
36
|
+
/**
|
|
37
|
+
* Reactive rotation after a 401. Returns `true` when the token rotated (the
|
|
38
|
+
* caller retries once). A rejected/absent refresh token surfaces as
|
|
39
|
+
* `NoTokenError` (re-login); a transient failure propagates unchanged.
|
|
40
|
+
*/
|
|
41
|
+
export declare function reactiveRefresh(handshake?: RequestContext['handshake']): Promise<boolean>;
|
|
9
42
|
/**
|
|
10
43
|
* Read the active token. Hot path: when `OUTLINE_API_TOKEN` is set we
|
|
11
44
|
* return it directly without consulting the token store, since
|
|
@@ -15,14 +48,25 @@ export declare class NoTokenError extends CliError {
|
|
|
15
48
|
*/
|
|
16
49
|
export declare function getApiToken(): Promise<string>;
|
|
17
50
|
/**
|
|
18
|
-
*
|
|
19
|
-
* `base_url` config (v1) → built-in default.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
51
|
+
* Account-agnostic base URL cascade: env var → default user record (v2) →
|
|
52
|
+
* legacy `base_url` config (v1) → built-in default. Deliberately ignores the
|
|
53
|
+
* global `--user` selector so the login flow (which resolves its default
|
|
54
|
+
* prompt through here) isn't skewed by account selection; the request path
|
|
55
|
+
* applies `--user` separately via {@link getRequestContext}.
|
|
22
56
|
*/
|
|
23
57
|
export declare function getBaseUrl(): Promise<string>;
|
|
24
58
|
/**
|
|
25
|
-
* OAuth client id cascade: env var → default user record
|
|
26
|
-
* `oauth_client_id` config (v1) → undefined (caller prompts).
|
|
59
|
+
* Account-agnostic OAuth client id cascade: env var → default user record
|
|
60
|
+
* (v2) → legacy `oauth_client_id` config (v1) → undefined (caller prompts).
|
|
61
|
+
* Like {@link getBaseUrl}, ignores `--user` so login stays default-driven.
|
|
27
62
|
*/
|
|
28
63
|
export declare function getOAuthClientId(): Promise<string | undefined>;
|
|
64
|
+
/**
|
|
65
|
+
* Resolve the base URL — and, when a `--user` account is selected, the refresh
|
|
66
|
+
* handshake (its instance + client id) — for a single managed request. Falls
|
|
67
|
+
* back to the account-agnostic {@link getBaseUrl} cascade without `--user`.
|
|
68
|
+
* An unknown ref resolves no handshake and the default base URL; the wrapped
|
|
69
|
+
* token store then raises `ACCOUNT_NOT_FOUND` when the token is read, so the
|
|
70
|
+
* request never fires.
|
|
71
|
+
*/
|
|
72
|
+
export declare function getRequestContext(): Promise<RequestContext>;
|
package/dist/lib/auth.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { SecureStoreUnavailableError } from '@doist/cli-core/auth';
|
|
1
|
+
import { refreshAccessToken, SecureStoreUnavailableError } from '@doist/cli-core/auth';
|
|
2
2
|
import { TOKEN_ENV_VAR } from './auth-constants.js';
|
|
3
|
-
import { createOutlineTokenStore, getActiveTokenSource, } from './auth-provider.js';
|
|
4
|
-
import { getConfig } from './config.js';
|
|
3
|
+
import { createOutlineAuthProvider, createOutlineTokenStore, getActiveTokenSource, } from './auth-provider.js';
|
|
4
|
+
import { getConfig, getConfigPath } from './config.js';
|
|
5
5
|
import { CliError } from './errors.js';
|
|
6
|
+
import { getRequestedUserRef } from './global-args.js';
|
|
6
7
|
import { DEFAULT_BASE_URL } from './outline-account.js';
|
|
7
|
-
import { getDefaultUserRecord } from './user-records.js';
|
|
8
|
+
import { getDefaultUserRecord, recordForRef } from './user-records.js';
|
|
9
|
+
import { withUserRefAware } from './user-ref-store.js';
|
|
8
10
|
export { SecureStoreUnavailableError, getActiveTokenSource, TOKEN_ENV_VAR };
|
|
9
11
|
export class NoTokenError extends CliError {
|
|
10
12
|
constructor() {
|
|
@@ -19,10 +21,110 @@ export class NoTokenError extends CliError {
|
|
|
19
21
|
*/
|
|
20
22
|
let storeSingleton;
|
|
21
23
|
function tokenStore() {
|
|
24
|
+
// Wrapped so a global `--user <ref>` flows into every no-ref store read on
|
|
25
|
+
// the request hot path (token lookup + refresh). Without `--user`,
|
|
26
|
+
// `getRequestedUserRef()` is undefined and the wrap is a pass-through.
|
|
22
27
|
if (!storeSingleton)
|
|
23
|
-
storeSingleton = createOutlineTokenStore();
|
|
28
|
+
storeSingleton = withUserRefAware(createOutlineTokenStore());
|
|
24
29
|
return storeSingleton;
|
|
25
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* The `--user` ref to apply on the request path, or `undefined` when an
|
|
33
|
+
* `OUTLINE_API_TOKEN` is set — an env token wins outright, so base URL and
|
|
34
|
+
* client id resolve from the default account rather than the named one,
|
|
35
|
+
* keeping token / base URL / client id pinned to the same source.
|
|
36
|
+
*/
|
|
37
|
+
function requestPathUserRef() {
|
|
38
|
+
if (process.env[TOKEN_ENV_VAR]?.trim())
|
|
39
|
+
return undefined;
|
|
40
|
+
return getRequestedUserRef();
|
|
41
|
+
}
|
|
42
|
+
let providerSingleton;
|
|
43
|
+
function authProvider() {
|
|
44
|
+
if (!providerSingleton)
|
|
45
|
+
providerSingleton = createOutlineAuthProvider();
|
|
46
|
+
return providerSingleton;
|
|
47
|
+
}
|
|
48
|
+
// Caller-provided O_EXCL lock so concurrent `ol` invocations don't issue
|
|
49
|
+
// parallel refresh grants. Resolved per-call (not cached) to honour test
|
|
50
|
+
// config-path mocking, mirroring `getConfigPath`.
|
|
51
|
+
function refreshLockPath() {
|
|
52
|
+
return `${getConfigPath()}.refresh.lock`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Best-effort proactive rotation before a request. Returns the token to use
|
|
56
|
+
* (the rotated one, or the current one if no rotation was needed) so the
|
|
57
|
+
* caller doesn't re-read the store; `undefined` when the account isn't
|
|
58
|
+
* refreshable (no refresh token / env / failure) — the caller then falls back
|
|
59
|
+
* to `getApiToken()` and the 401 path stays authoritative. The wrapped store
|
|
60
|
+
* scopes the bundle to any global `--user`; `handshake` pins the token
|
|
61
|
+
* endpoint to that account's instance + client id.
|
|
62
|
+
*/
|
|
63
|
+
export async function proactiveRefresh(handshake) {
|
|
64
|
+
try {
|
|
65
|
+
const { bundle } = await refreshAccessToken({
|
|
66
|
+
store: tokenStore(),
|
|
67
|
+
provider: authProvider(),
|
|
68
|
+
lockPath: refreshLockPath(),
|
|
69
|
+
handshake,
|
|
70
|
+
});
|
|
71
|
+
return bundle.accessToken;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Refresh the *selected* account (not the default) for `auth status`, returning
|
|
79
|
+
* its current-or-rotated token. Scoped via the account's id + base URL/client
|
|
80
|
+
* id through the refresh handshake, so `--user <other>` checks and rotates the
|
|
81
|
+
* right account at the right instance. Env / legacy / record-less accounts
|
|
82
|
+
* aren't refreshable — the snapshot `fallback` token is returned as-is.
|
|
83
|
+
*/
|
|
84
|
+
export async function refreshedTokenForStatus(account, fallback) {
|
|
85
|
+
if (process.env[TOKEN_ENV_VAR]?.trim() || !account.id)
|
|
86
|
+
return fallback;
|
|
87
|
+
try {
|
|
88
|
+
const { bundle } = await refreshAccessToken({
|
|
89
|
+
store: tokenStore(),
|
|
90
|
+
provider: authProvider(),
|
|
91
|
+
lockPath: refreshLockPath(),
|
|
92
|
+
ref: account.id,
|
|
93
|
+
handshake: { baseUrl: account.baseUrl, clientId: account.oauthClientId },
|
|
94
|
+
});
|
|
95
|
+
return bundle.accessToken;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return fallback;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Reactive rotation after a 401. Returns `true` when the token rotated (the
|
|
103
|
+
* caller retries once). A rejected/absent refresh token surfaces as
|
|
104
|
+
* `NoTokenError` (re-login); a transient failure propagates unchanged.
|
|
105
|
+
*/
|
|
106
|
+
export async function reactiveRefresh(handshake) {
|
|
107
|
+
try {
|
|
108
|
+
const result = await refreshAccessToken({
|
|
109
|
+
store: tokenStore(),
|
|
110
|
+
provider: authProvider(),
|
|
111
|
+
lockPath: refreshLockPath(),
|
|
112
|
+
force: true,
|
|
113
|
+
handshake,
|
|
114
|
+
});
|
|
115
|
+
return result.rotated;
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
// Match on the structural `.code` rather than `instanceof`: cli-core
|
|
119
|
+
// throws its own CliError, and class identity isn't reliable across
|
|
120
|
+
// package boundaries (duplicate module instances under linking).
|
|
121
|
+
const code = err?.code;
|
|
122
|
+
if (code === 'AUTH_REFRESH_EXPIRED' || code === 'AUTH_REFRESH_UNAVAILABLE') {
|
|
123
|
+
throw new NoTokenError();
|
|
124
|
+
}
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
26
128
|
/**
|
|
27
129
|
* Read the active token. Hot path: when `OUTLINE_API_TOKEN` is set we
|
|
28
130
|
* return it directly without consulting the token store, since
|
|
@@ -40,10 +142,11 @@ export async function getApiToken() {
|
|
|
40
142
|
return snapshot.token;
|
|
41
143
|
}
|
|
42
144
|
/**
|
|
43
|
-
*
|
|
44
|
-
* `base_url` config (v1) → built-in default.
|
|
45
|
-
*
|
|
46
|
-
*
|
|
145
|
+
* Account-agnostic base URL cascade: env var → default user record (v2) →
|
|
146
|
+
* legacy `base_url` config (v1) → built-in default. Deliberately ignores the
|
|
147
|
+
* global `--user` selector so the login flow (which resolves its default
|
|
148
|
+
* prompt through here) isn't skewed by account selection; the request path
|
|
149
|
+
* applies `--user` separately via {@link getRequestContext}.
|
|
47
150
|
*/
|
|
48
151
|
export async function getBaseUrl() {
|
|
49
152
|
const envUrl = process.env.OUTLINE_URL;
|
|
@@ -58,8 +161,9 @@ export async function getBaseUrl() {
|
|
|
58
161
|
return DEFAULT_BASE_URL;
|
|
59
162
|
}
|
|
60
163
|
/**
|
|
61
|
-
* OAuth client id cascade: env var → default user record
|
|
62
|
-
* `oauth_client_id` config (v1) → undefined (caller prompts).
|
|
164
|
+
* Account-agnostic OAuth client id cascade: env var → default user record
|
|
165
|
+
* (v2) → legacy `oauth_client_id` config (v1) → undefined (caller prompts).
|
|
166
|
+
* Like {@link getBaseUrl}, ignores `--user` so login stays default-driven.
|
|
63
167
|
*/
|
|
64
168
|
export async function getOAuthClientId() {
|
|
65
169
|
const envClientId = process.env.OUTLINE_OAUTH_CLIENT_ID;
|
|
@@ -71,4 +175,23 @@ export async function getOAuthClientId() {
|
|
|
71
175
|
return record.account.oauthClientId;
|
|
72
176
|
return config.oauth_client_id;
|
|
73
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Resolve the base URL — and, when a `--user` account is selected, the refresh
|
|
180
|
+
* handshake (its instance + client id) — for a single managed request. Falls
|
|
181
|
+
* back to the account-agnostic {@link getBaseUrl} cascade without `--user`.
|
|
182
|
+
* An unknown ref resolves no handshake and the default base URL; the wrapped
|
|
183
|
+
* token store then raises `ACCOUNT_NOT_FOUND` when the token is read, so the
|
|
184
|
+
* request never fires.
|
|
185
|
+
*/
|
|
186
|
+
export async function getRequestContext() {
|
|
187
|
+
const ref = requestPathUserRef();
|
|
188
|
+
if (ref !== undefined) {
|
|
189
|
+
const record = recordForRef(await getConfig(), ref);
|
|
190
|
+
if (record) {
|
|
191
|
+
const baseUrl = record.account.baseUrl.replace(/\/$/, '');
|
|
192
|
+
return { baseUrl, handshake: { baseUrl, clientId: record.account.oauthClientId } };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return { baseUrl: await getBaseUrl() };
|
|
196
|
+
}
|
|
74
197
|
//# sourceMappingURL=auth.js.map
|
package/dist/lib/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -10,7 +10,14 @@ export type StoredUser = {
|
|
|
10
10
|
base_url?: string;
|
|
11
11
|
oauth_client_id?: string;
|
|
12
12
|
team_name?: string;
|
|
13
|
+
/** Plaintext access-token fallback, present only when the keyring was unavailable at write time. */
|
|
13
14
|
token?: string;
|
|
15
|
+
/** Access-token expiry (unix-epoch ms); drives proactive refresh. */
|
|
16
|
+
access_token_expires_at?: number;
|
|
17
|
+
/** Refresh-token expiry (unix-epoch ms), when the server returns one. */
|
|
18
|
+
refresh_token_expires_at?: number;
|
|
19
|
+
/** Whether a refresh token is stored (keyring slot or `refresh_token`). */
|
|
20
|
+
has_refresh_token?: boolean;
|
|
14
21
|
};
|
|
15
22
|
export type Config = CoreConfig & {
|
|
16
23
|
config_version?: number;
|