@doist/outline-cli 1.6.0 → 1.8.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 +101 -35
- package/dist/commands/auth.js.map +1 -1
- package/dist/lib/api.js +32 -4
- 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-provider.d.ts +57 -8
- package/dist/lib/auth-provider.js +226 -129
- package/dist/lib/auth-provider.js.map +1 -1
- package/dist/lib/auth.d.ts +47 -11
- package/dist/lib/auth.js +135 -35
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/config.d.ts +27 -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/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/skills/content.d.ts +1 -1
- package/dist/lib/skills/content.js +2 -0
- package/dist/lib/skills/content.js.map +1 -1
- package/dist/lib/user-records.d.ts +14 -0
- package/dist/lib/user-records.js +110 -0
- package/dist/lib/user-records.js.map +1 -0
- package/package.json +3 -2
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { createInterface } from 'node:readline/promises';
|
|
2
|
-
import {
|
|
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
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
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';
|
|
12
11
|
function stringFlag(flags, key) {
|
|
13
12
|
const value = flags[key];
|
|
14
13
|
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
@@ -47,152 +46,250 @@ async function resolveClientId(flags) {
|
|
|
47
46
|
}
|
|
48
47
|
return answered;
|
|
49
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Routes cli-core's OAuth HTTP through outline's transport so the proxy /
|
|
51
|
+
* decompression dispatcher applies — to the token exchange AND the
|
|
52
|
+
* oauth4webapi refresh grant (cli-core threads this into oauth4webapi's
|
|
53
|
+
* `customFetch`), which would otherwise capture the bare global `fetch`.
|
|
54
|
+
*/
|
|
55
|
+
const outlineFetch = (input, init) => fetchWithRetry({ url: input, options: init ?? {} });
|
|
50
56
|
export function createOutlineAuthProvider() {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
url.searchParams.set('code_challenge_method', 'S256');
|
|
62
|
-
url.searchParams.set('redirect_uri', redirectUri);
|
|
63
|
-
url.searchParams.set('state', state);
|
|
64
|
-
const handshake = { baseUrl, clientId, codeVerifier };
|
|
65
|
-
return { authorizeUrl: url.toString(), handshake };
|
|
57
|
+
// Captured at `authorize` (which may prompt for it) and reused by
|
|
58
|
+
// `exchangeCode` / `validate` so a single login can't double-prompt. A
|
|
59
|
+
// provider built only for silent refresh never runs `authorize`, so this
|
|
60
|
+
// stays undefined there and `tokenUrl` falls back to stored config — the
|
|
61
|
+
// refresh path must never prompt.
|
|
62
|
+
let baseUrl;
|
|
63
|
+
return createPkceProvider({
|
|
64
|
+
authorizeUrl: async ({ flags }) => {
|
|
65
|
+
baseUrl = await resolveBaseUrl(flags);
|
|
66
|
+
return `${baseUrl}/oauth/authorize`;
|
|
66
67
|
},
|
|
67
|
-
async
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
grant_type: 'authorization_code',
|
|
74
|
-
client_id: hs.clientId,
|
|
75
|
-
redirect_uri: redirectUri,
|
|
76
|
-
code_verifier: hs.codeVerifier,
|
|
77
|
-
code,
|
|
78
|
-
});
|
|
79
|
-
const res = await fetchWithRetry({
|
|
80
|
-
url: `${hs.baseUrl}/oauth/token`,
|
|
81
|
-
options: {
|
|
82
|
-
method: 'POST',
|
|
83
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
84
|
-
body: params.toString(),
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
const json = (await res.json().catch(() => ({})));
|
|
88
|
-
if (!res.ok) {
|
|
89
|
-
const message = json.error_description || json.message || json.error || res.statusText;
|
|
90
|
-
throw new Error(`OAuth token exchange failed: ${message}`);
|
|
91
|
-
}
|
|
92
|
-
if (!json.access_token) {
|
|
93
|
-
throw new Error('OAuth token exchange did not return an access token.');
|
|
94
|
-
}
|
|
95
|
-
return { accessToken: json.access_token };
|
|
68
|
+
tokenUrl: async ({ handshake }) => {
|
|
69
|
+
// `handshake.baseUrl` lets a caller scope refresh to a specific
|
|
70
|
+
// account's instance (status --user); otherwise the authorize-time
|
|
71
|
+
// value, then the default config.
|
|
72
|
+
const base = baseUrl ?? handshake.baseUrl ?? (await getBaseUrl());
|
|
73
|
+
return `${base}/oauth/token`;
|
|
96
74
|
},
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
75
|
+
// Prefer a handshake-scoped client id (account-aware refresh); else
|
|
76
|
+
// resolve from flag/config (only prompts when neither is set, so it's
|
|
77
|
+
// safe on the refresh path — the logged-in record carries one).
|
|
78
|
+
clientId: ({ handshake, flags }) => {
|
|
79
|
+
const fromHandshake = handshake.clientId;
|
|
80
|
+
return typeof fromHandshake === 'string' && fromHandshake
|
|
81
|
+
? fromHandshake
|
|
82
|
+
: resolveClientId(flags);
|
|
83
|
+
},
|
|
84
|
+
validate: async ({ token, handshake }) => {
|
|
85
|
+
const base = baseUrl ?? (await getBaseUrl());
|
|
86
|
+
const { data } = await apiRequest('auth.info', {}, { token, baseUrl: base });
|
|
87
|
+
return makeOutlineAccount({
|
|
104
88
|
id: data.user.id,
|
|
105
89
|
label: data.user.name,
|
|
106
|
-
baseUrl:
|
|
107
|
-
oauthClientId:
|
|
90
|
+
baseUrl: base,
|
|
91
|
+
oauthClientId: handshake.clientId,
|
|
108
92
|
teamName: data.team.name,
|
|
109
|
-
};
|
|
93
|
+
});
|
|
110
94
|
},
|
|
95
|
+
fetchImpl: outlineFetch,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Accepts the Outline user UUID or display name. Id matches are
|
|
100
|
+
* case-sensitive (UUIDs are canonical); label matches are
|
|
101
|
+
* case-insensitive so users can pass the name they see in `auth status`.
|
|
102
|
+
*/
|
|
103
|
+
export function matchOutlineAccount(account, ref) {
|
|
104
|
+
if (account.id === ref)
|
|
105
|
+
return true;
|
|
106
|
+
return account.label.toLowerCase() === ref.toLowerCase();
|
|
107
|
+
}
|
|
108
|
+
/** True when the v2 store is the authoritative source. */
|
|
109
|
+
function migrationIsConclusive(result) {
|
|
110
|
+
return (result.status === 'migrated' ||
|
|
111
|
+
result.status === 'already-migrated' ||
|
|
112
|
+
result.status === 'no-legacy-state');
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Synthesise a snapshot from v1 plaintext state still on disk. Fallback for
|
|
116
|
+
* when migration can't complete (offline `identifyAccount`, WSL
|
|
117
|
+
* `legacy-keyring-unreachable`). Returns `null` when no legacy token is
|
|
118
|
+
* present. The synthesised account uses whatever identity fields the v1
|
|
119
|
+
* config has — for pre-#71 configs the id/label may be empty, but the
|
|
120
|
+
* runtime never renders those (status's `fetchLive` re-derives from the
|
|
121
|
+
* API), so empty placeholders are safe.
|
|
122
|
+
*/
|
|
123
|
+
async function readLegacyTokenSnapshot() {
|
|
124
|
+
const config = await getConfig();
|
|
125
|
+
const token = config.api_token?.trim() || null;
|
|
126
|
+
if (!token)
|
|
127
|
+
return null;
|
|
128
|
+
return {
|
|
129
|
+
token,
|
|
130
|
+
account: makeOutlineAccount({
|
|
131
|
+
id: config.auth_user_id ?? '',
|
|
132
|
+
label: config.auth_user_name ?? '',
|
|
133
|
+
baseUrl: config.base_url,
|
|
134
|
+
oauthClientId: config.oauth_client_id,
|
|
135
|
+
teamName: config.auth_team_name,
|
|
136
|
+
}),
|
|
111
137
|
};
|
|
112
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Discharge v1 plaintext state. Runs **after** a successful v2 write/clear
|
|
141
|
+
* — never before — so a v2-op failure doesn't strand the user with no
|
|
142
|
+
* recoverable credentials. Caller decides whether to swallow or propagate
|
|
143
|
+
* the `updateConfig` failure:
|
|
144
|
+
* - `set()` swallows (v2 record will win in `active()` regardless).
|
|
145
|
+
* - `clear()` propagates (v2 is empty, so a stale legacy token would
|
|
146
|
+
* shadow the logout via the fallback).
|
|
147
|
+
*/
|
|
148
|
+
async function dischargeLegacyState() {
|
|
149
|
+
await updateConfig(LEGACY_CLEAR_PAYLOAD);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Memoised one-shot migration trigger. Resolves with `null` on rejection so
|
|
153
|
+
* the CLI never fails to start because of a migration error — the legacy
|
|
154
|
+
* snapshot fallback handles that case. Tests reset the memo with
|
|
155
|
+
* `vi.resetModules()` + a dynamic re-import.
|
|
156
|
+
*/
|
|
157
|
+
let migrationPromise;
|
|
158
|
+
function ensureMigrated() {
|
|
159
|
+
if (!migrationPromise) {
|
|
160
|
+
migrationPromise = runMigrateLegacyAuth({ silent: true }).catch(() => null);
|
|
161
|
+
}
|
|
162
|
+
return migrationPromise;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* True when the v2 store is empty but a legacy v1 token snapshot is still
|
|
166
|
+
* the only thing keeping the CLI authenticated — typically because
|
|
167
|
+
* `migrateLegacyAuth` couldn't reach the Outline API to identify the
|
|
168
|
+
* account (`MigrateSkipReason: 'identify-failed'`) or the OS keyring is
|
|
169
|
+
* unreachable (`'legacy-keyring-unreachable'`, although outline has no
|
|
170
|
+
* v1 keyring slot to read from). Useful for downstream diagnostics.
|
|
171
|
+
*/
|
|
172
|
+
export async function isLegacyAuthActive() {
|
|
173
|
+
const result = await ensureMigrated();
|
|
174
|
+
if (result !== null && migrationIsConclusive(result))
|
|
175
|
+
return false;
|
|
176
|
+
const legacy = await readLegacyTokenSnapshot();
|
|
177
|
+
return legacy !== null;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* `OUTLINE_API_TOKEN` short-circuits `active()` only when no explicit ref
|
|
181
|
+
* is supplied — cli-core's `KeyringTokenStore` doesn't know about the env
|
|
182
|
+
* var, and an explicit ref means the caller targets a specific stored
|
|
183
|
+
* account.
|
|
184
|
+
*
|
|
185
|
+
* `ensureMigrated()` runs **before** every mutating v2 op so the post-op
|
|
186
|
+
* legacy discharge can't race a still-pending migration into re-grabbing
|
|
187
|
+
* the legacy `api_token` we just consumed. `set()` / `clear()` then
|
|
188
|
+
* discharge legacy state **after** the v2 op succeeds. `set()` swallows
|
|
189
|
+
* the cleanup failure (v2 wins in `active()` regardless); `clear()`
|
|
190
|
+
* propagates it so a failed logout fails loudly instead of leaving the
|
|
191
|
+
* user authenticated via the legacy fallback.
|
|
192
|
+
*/
|
|
113
193
|
export function createOutlineTokenStore() {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
if (!id || !label) {
|
|
124
|
-
// Stored token predates this adapter (env var, pre-upgrade
|
|
125
|
-
// config). No persisted identity to round-trip.
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
return {
|
|
129
|
-
token: config.api_token,
|
|
130
|
-
account: {
|
|
131
|
-
id,
|
|
132
|
-
label,
|
|
133
|
-
baseUrl: config.base_url ?? DEFAULT_BASE_URL,
|
|
134
|
-
oauthClientId: config.oauth_client_id ?? '',
|
|
135
|
-
teamName: config.auth_team_name,
|
|
136
|
-
},
|
|
137
|
-
};
|
|
194
|
+
const inner = createKeyringTokenStore({
|
|
195
|
+
serviceName: SECURE_STORE_SERVICE,
|
|
196
|
+
userRecords: createOutlineUserRecordStore(),
|
|
197
|
+
recordsLocation: getConfigPath(),
|
|
198
|
+
matchAccount: matchOutlineAccount,
|
|
199
|
+
});
|
|
200
|
+
async function migrationIsInconclusive() {
|
|
201
|
+
const result = await ensureMigrated(); // memoised
|
|
202
|
+
return result === null || !migrationIsConclusive(result);
|
|
138
203
|
}
|
|
139
204
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
205
|
+
* Shared env → v2 → legacy resolution for `active` / `activeBundle`.
|
|
206
|
+
* `fromV2` reads the matching cli-core method; `fromEnvOrLegacy` maps an
|
|
207
|
+
* env/legacy token into the caller's shape (env + legacy carry no refresh
|
|
208
|
+
* material, so the bundle form surfaces as access-only).
|
|
144
209
|
*/
|
|
145
|
-
function
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
210
|
+
async function resolveAuth(ref, fromV2, fromEnvOrLegacy) {
|
|
211
|
+
if (ref === undefined) {
|
|
212
|
+
const envToken = process.env[TOKEN_ENV_VAR]?.trim();
|
|
213
|
+
if (envToken) {
|
|
214
|
+
return fromEnvOrLegacy(envToken, makeOutlineAccount({ id: '', label: '', baseUrl: await getBaseUrl() }));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
await ensureMigrated();
|
|
218
|
+
const fromStore = await fromV2();
|
|
219
|
+
if (fromStore)
|
|
220
|
+
return fromStore;
|
|
221
|
+
const legacy = await readLegacyTokenSnapshot();
|
|
222
|
+
if (legacy && (ref === undefined || matchOutlineAccount(legacy.account, ref))) {
|
|
223
|
+
return fromEnvOrLegacy(legacy.token, legacy.account);
|
|
224
|
+
}
|
|
225
|
+
return null;
|
|
149
226
|
}
|
|
150
|
-
|
|
151
|
-
|
|
227
|
+
// `ensureMigrated` runs before the v2 write so the post-write discharge
|
|
228
|
+
// can't race a pending migration into re-grabbing the legacy token. The
|
|
229
|
+
// discharge is best-effort: a lingering `api_token` is dormant because
|
|
230
|
+
// reads hit v2 first. (`clear` needs the stricter propagating variant.)
|
|
231
|
+
async function writeThenDischargeLegacy(write) {
|
|
232
|
+
await ensureMigrated();
|
|
233
|
+
await write();
|
|
234
|
+
if (await migrationIsInconclusive()) {
|
|
235
|
+
await dischargeLegacyState().catch(() => undefined);
|
|
236
|
+
}
|
|
152
237
|
}
|
|
153
238
|
return {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (ref === undefined)
|
|
157
|
-
return snapshot;
|
|
158
|
-
if (!snapshot || !matchesRef(snapshot.account, ref))
|
|
159
|
-
throw refMismatch(ref);
|
|
160
|
-
return snapshot;
|
|
239
|
+
active(ref) {
|
|
240
|
+
return resolveAuth(ref, () => inner.active(ref), (token, account) => ({ token, account }));
|
|
161
241
|
},
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
});
|
|
242
|
+
activeBundle(ref) {
|
|
243
|
+
return resolveAuth(ref, () => inner.activeBundle(ref), (token, account) => ({ account, bundle: { accessToken: token } }));
|
|
244
|
+
},
|
|
245
|
+
set(account, token) {
|
|
246
|
+
return writeThenDischargeLegacy(() => inner.set(account, token));
|
|
247
|
+
},
|
|
248
|
+
setBundle(account, bundle, options) {
|
|
249
|
+
return writeThenDischargeLegacy(() => inner.setBundle(account, bundle, options));
|
|
171
250
|
},
|
|
172
251
|
async clear(ref) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (ref !== undefined) {
|
|
180
|
-
const snapshot = deriveSnapshot(config);
|
|
181
|
-
if (!snapshot || !matchesRef(snapshot.account, ref))
|
|
182
|
-
throw refMismatch(ref);
|
|
252
|
+
await ensureMigrated();
|
|
253
|
+
await inner.clear(ref);
|
|
254
|
+
if (await migrationIsInconclusive()) {
|
|
255
|
+
// Must succeed: v2 is now empty, so a surviving legacy
|
|
256
|
+
// token would shadow the logout via the fallback.
|
|
257
|
+
await dischargeLegacyState();
|
|
183
258
|
}
|
|
184
|
-
await clearConfig(config);
|
|
185
259
|
},
|
|
186
260
|
async list() {
|
|
187
|
-
|
|
188
|
-
return
|
|
261
|
+
await ensureMigrated();
|
|
262
|
+
return inner.list();
|
|
189
263
|
},
|
|
190
264
|
async setDefault(ref) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
throw refMismatch(ref);
|
|
194
|
-
// Single-user store — already the default once `ref` matches.
|
|
265
|
+
await ensureMigrated();
|
|
266
|
+
return inner.setDefault(ref);
|
|
195
267
|
},
|
|
268
|
+
getLastStorageResult: () => inner.getLastStorageResult(),
|
|
269
|
+
getLastClearResult: () => inner.getLastClearResult(),
|
|
196
270
|
};
|
|
197
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* Where the currently-active token lives. Mirrors `active()`'s resolution
|
|
274
|
+
* order — env → v2 record → legacy plaintext — so the answer can never
|
|
275
|
+
* contradict the token the runtime is actually using.
|
|
276
|
+
*
|
|
277
|
+
* The precedence cascade is intentionally duplicated with `active()`:
|
|
278
|
+
* the only true dedupe options either (a) add an extra config read on
|
|
279
|
+
* every `apiRequest` call (regressing the request hot path) or (b)
|
|
280
|
+
* augment cli-core's `TokenStore` contract. The drift the duplication
|
|
281
|
+
* invites is guarded by the `getActiveTokenSource` regression test that
|
|
282
|
+
* asserts v2-record presence wins over a lingering v1 `api_token`.
|
|
283
|
+
*/
|
|
284
|
+
export async function getActiveTokenSource() {
|
|
285
|
+
if (process.env[TOKEN_ENV_VAR]?.trim())
|
|
286
|
+
return 'env';
|
|
287
|
+
const config = await getConfig();
|
|
288
|
+
const record = getDefaultUserRecord(config);
|
|
289
|
+
if (record)
|
|
290
|
+
return record.fallbackToken ? 'config-file' : 'secure-store';
|
|
291
|
+
if (config.api_token?.trim())
|
|
292
|
+
return 'config-file';
|
|
293
|
+
return 'secure-store';
|
|
294
|
+
}
|
|
198
295
|
//# sourceMappingURL=auth-provider.js.map
|
|
@@ -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,
|
|
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,15 +1,51 @@
|
|
|
1
|
-
import {
|
|
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
|
+
import { type OutlineAccount } from './outline-account.js';
|
|
6
|
+
export { SecureStoreUnavailableError, getActiveTokenSource, TOKEN_ENV_VAR };
|
|
7
|
+
export declare class NoTokenError extends CliError {
|
|
8
|
+
constructor();
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Best-effort proactive rotation before a request. Returns the token to use
|
|
12
|
+
* (the rotated one, or the current one if no rotation was needed) so the
|
|
13
|
+
* caller doesn't re-read the store; `undefined` when the default account
|
|
14
|
+
* isn't refreshable (no refresh token / env / failure) — the caller then
|
|
15
|
+
* falls back to `getApiToken()` and the 401 path stays authoritative.
|
|
16
|
+
*/
|
|
17
|
+
export declare function proactiveRefresh(): Promise<string | undefined>;
|
|
18
|
+
/**
|
|
19
|
+
* Refresh the *selected* account (not the default) for `auth status`, returning
|
|
20
|
+
* its current-or-rotated token. Scoped via the account's id + base URL/client
|
|
21
|
+
* id through the refresh handshake, so `--user <other>` checks and rotates the
|
|
22
|
+
* right account at the right instance. Env / legacy / record-less accounts
|
|
23
|
+
* aren't refreshable — the snapshot `fallback` token is returned as-is.
|
|
24
|
+
*/
|
|
25
|
+
export declare function refreshedTokenForStatus(account: OutlineAccount, fallback: string): Promise<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Reactive rotation after a 401. Returns `true` when the token rotated (the
|
|
28
|
+
* caller retries once). A rejected/absent refresh token surfaces as
|
|
29
|
+
* `NoTokenError` (re-login); a transient failure propagates unchanged.
|
|
30
|
+
*/
|
|
31
|
+
export declare function reactiveRefresh(): Promise<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* Read the active token. Hot path: when `OUTLINE_API_TOKEN` is set we
|
|
34
|
+
* return it directly without consulting the token store, since
|
|
35
|
+
* `apiRequest` already resolves the base URL separately — going through
|
|
36
|
+
* `store.active()` here would trigger a redundant `getBaseUrl()` lookup
|
|
37
|
+
* per request just to synthesise an account we don't need.
|
|
38
|
+
*/
|
|
2
39
|
export declare function getApiToken(): Promise<string>;
|
|
40
|
+
/**
|
|
41
|
+
* Base URL cascade: env var → default user record (v2) → legacy
|
|
42
|
+
* `base_url` config (v1) → built-in default. The record takes priority
|
|
43
|
+
* over the legacy slot so post-migration logins keep defaulting to the
|
|
44
|
+
* same Outline instance.
|
|
45
|
+
*/
|
|
3
46
|
export declare function getBaseUrl(): Promise<string>;
|
|
4
|
-
export declare function getOAuthClientId(): Promise<string | undefined>;
|
|
5
|
-
export declare function getTokenSource(): Promise<'env' | 'config' | null>;
|
|
6
47
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* would silently reset the user's update-channel preference too.
|
|
10
|
-
*
|
|
11
|
-
* Pass `existing` to skip the read when the caller has already loaded the
|
|
12
|
-
* config (e.g. ref-validated logout) — keeps that flow at one read + one
|
|
13
|
-
* write instead of two reads.
|
|
48
|
+
* OAuth client id cascade: env var → default user record (v2) → legacy
|
|
49
|
+
* `oauth_client_id` config (v1) → undefined (caller prompts).
|
|
14
50
|
*/
|
|
15
|
-
export declare function
|
|
51
|
+
export declare function getOAuthClientId(): Promise<string | undefined>;
|