@todoist/outline-cli 0.0.1 → 3.0.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 +359 -0
- package/README.md +145 -29
- 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 +2 -0
- package/dist/commands/auth.js +169 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/changelog.d.ts +2 -0
- package/dist/commands/changelog.js +15 -0
- package/dist/commands/changelog.js.map +1 -0
- package/dist/commands/collection.d.ts +2 -0
- package/dist/commands/collection.js +106 -0
- package/dist/commands/collection.js.map +1 -0
- package/dist/commands/document.d.ts +2 -0
- package/dist/commands/document.js +291 -0
- package/dist/commands/document.js.map +1 -0
- package/dist/commands/search.d.ts +2 -0
- package/dist/commands/search.js +49 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/skill.d.ts +2 -0
- package/dist/commands/skill.js +80 -0
- package/dist/commands/skill.js.map +1 -0
- package/dist/commands/update/index.d.ts +2 -0
- package/dist/commands/update/index.js +14 -0
- package/dist/commands/update/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api.d.ts +18 -0
- package/dist/lib/api.js +107 -0
- package/dist/lib/api.js.map +1 -0
- 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-output.d.ts +21 -0
- package/dist/lib/auth-output.js +38 -0
- package/dist/lib/auth-output.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 +85 -0
- package/dist/lib/auth-provider.js +363 -0
- package/dist/lib/auth-provider.js.map +1 -0
- package/dist/lib/auth.d.ts +73 -0
- package/dist/lib/auth.js +188 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/config.d.ts +45 -0
- package/dist/lib/config.js +20 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/errors.d.ts +8 -0
- package/dist/lib/errors.js +9 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/global-args.d.ts +30 -0
- package/dist/lib/global-args.js +66 -0
- package/dist/lib/global-args.js.map +1 -0
- package/dist/lib/markdown.d.ts +2 -0
- package/dist/lib/markdown.js +12 -0
- package/dist/lib/markdown.js.map +1 -0
- 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 +32 -0
- package/dist/lib/outline-account.js +22 -0
- package/dist/lib/outline-account.js.map +1 -0
- package/dist/lib/output.d.ts +15 -0
- package/dist/lib/output.js +85 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/refs.d.ts +33 -0
- package/dist/lib/refs.js +182 -0
- package/dist/lib/refs.js.map +1 -0
- package/dist/lib/skills/content.d.ts +3 -0
- package/dist/lib/skills/content.js +160 -0
- package/dist/lib/skills/content.js.map +1 -0
- package/dist/lib/skills/create-installer.d.ts +9 -0
- package/dist/lib/skills/create-installer.js +77 -0
- package/dist/lib/skills/create-installer.js.map +1 -0
- package/dist/lib/skills/index.d.ts +5 -0
- package/dist/lib/skills/index.js +40 -0
- package/dist/lib/skills/index.js.map +1 -0
- package/dist/lib/skills/types.d.ts +10 -0
- package/dist/lib/skills/types.js +2 -0
- package/dist/lib/skills/types.js.map +1 -0
- package/dist/lib/skills/update-installed.d.ts +6 -0
- package/dist/lib/skills/update-installed.js +23 -0
- package/dist/lib/skills/update-installed.js.map +1 -0
- package/dist/lib/spinner.d.ts +2 -0
- package/dist/lib/spinner.js +5 -0
- package/dist/lib/spinner.js.map +1 -0
- package/dist/lib/user-records.d.ts +21 -0
- package/dist/lib/user-records.js +126 -0
- package/dist/lib/user-records.js.map +1 -0
- 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/dist/postinstall.d.ts +1 -0
- package/dist/postinstall.js +16 -0
- package/dist/postinstall.js.map +1 -0
- package/dist/transport/fetch-with-retry.d.ts +15 -0
- package/dist/transport/fetch-with-retry.js +144 -0
- package/dist/transport/fetch-with-retry.js.map +1 -0
- package/dist/transport/http-dispatcher.d.ts +37 -0
- package/dist/transport/http-dispatcher.js +95 -0
- package/dist/transport/http-dispatcher.js.map +1 -0
- package/package.json +71 -8
- package/scripts/check-skill-sync.js +40 -0
- package/scripts/postinstall.js +12 -0
- package/scripts/sync-skill.js +30 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type AccountRef, type AuthProvider, type KeyringTokenStore } from '@todoist/cli-core/auth';
|
|
2
|
+
import { type OutlineAccount } from './outline-account.js';
|
|
3
|
+
export type { OutlineAccount } from './outline-account.js';
|
|
4
|
+
export { matchOutlineAccount } from './outline-account.js';
|
|
5
|
+
export type AuthInfoResponse = {
|
|
6
|
+
user: {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
email: string;
|
|
10
|
+
};
|
|
11
|
+
team: {
|
|
12
|
+
name: string;
|
|
13
|
+
subdomain: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export type OutlineTokenStore = KeyringTokenStore<OutlineAccount>;
|
|
17
|
+
/**
|
|
18
|
+
* Read a line from stdin, prompting on stderr so `--json` / `--ndjson`
|
|
19
|
+
* envelopes on stdout stay clean. With `hidden: true` the typed characters are
|
|
20
|
+
* masked (for secrets): the prompt label is shown, then readline's echo is
|
|
21
|
+
* routed through a muted `Writable` so keystrokes don't leak to the terminal.
|
|
22
|
+
*/
|
|
23
|
+
export declare function prompt(question: string, options?: {
|
|
24
|
+
hidden?: boolean;
|
|
25
|
+
}): Promise<string>;
|
|
26
|
+
export declare function resolveBaseUrl(options?: {
|
|
27
|
+
baseUrl?: unknown;
|
|
28
|
+
}): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Verify a token by probing `auth.info` and map the response to the persisted
|
|
31
|
+
* account shape. Shared by the OAuth `validate` hook and `auth token` so the
|
|
32
|
+
* identity-resolution rules live in one place.
|
|
33
|
+
*/
|
|
34
|
+
export declare function identifyAccount(token: string, baseUrl: string, oauthClientId?: string): Promise<OutlineAccount>;
|
|
35
|
+
export declare function createOutlineAuthProvider(): AuthProvider<OutlineAccount>;
|
|
36
|
+
/**
|
|
37
|
+
* True when the v2 store is empty but a legacy v1 token snapshot is still
|
|
38
|
+
* the only thing keeping the CLI authenticated — typically because
|
|
39
|
+
* `migrateLegacyAuth` couldn't reach the Outline API to identify the
|
|
40
|
+
* account (`MigrateSkipReason: 'identify-failed'`) or the OS keyring is
|
|
41
|
+
* unreachable (`'legacy-keyring-unreachable'`, although outline has no
|
|
42
|
+
* v1 keyring slot to read from). Useful for downstream diagnostics.
|
|
43
|
+
*/
|
|
44
|
+
export declare function isLegacyAuthActive(): Promise<boolean>;
|
|
45
|
+
/** Where `ol account current` resolves the active credential from. */
|
|
46
|
+
export type ActiveAccountSource = {
|
|
47
|
+
source: 'stored';
|
|
48
|
+
account: OutlineAccount;
|
|
49
|
+
isDefault: boolean;
|
|
50
|
+
} | {
|
|
51
|
+
source: 'env';
|
|
52
|
+
} | {
|
|
53
|
+
source: 'legacy';
|
|
54
|
+
} | null;
|
|
55
|
+
/**
|
|
56
|
+
* Authoritatively classify the active credential without re-deriving the
|
|
57
|
+
* cascade from membership heuristics. Mirrors `active()`'s precedence — env
|
|
58
|
+
* (only when no explicit ref) → stored v2 → legacy snapshot — but reports
|
|
59
|
+
* *which* branch matched, so `ol account current` can render the right shape.
|
|
60
|
+
*
|
|
61
|
+
* The v2 lookup uses the bare keyring store (no legacy fallback) and matches by
|
|
62
|
+
* `ref`, so a renamed legacy snapshot sharing a UUID with a v2 record can't be
|
|
63
|
+
* misreported as `stored`: a `--user <old-name>` that the v2 record no longer
|
|
64
|
+
* matches falls through to the legacy branch.
|
|
65
|
+
*/
|
|
66
|
+
export declare function resolveActiveAccountSource(ref?: AccountRef): Promise<ActiveAccountSource>;
|
|
67
|
+
export declare function createOutlineTokenStore(): OutlineTokenStore;
|
|
68
|
+
/**
|
|
69
|
+
* Where the currently-active token lives. Mirrors `active()`'s resolution
|
|
70
|
+
* order — env → v2 record → legacy plaintext — so the answer can never
|
|
71
|
+
* contradict the token the runtime is actually using.
|
|
72
|
+
*
|
|
73
|
+
* Pass `ref` to report the source of a specific `--user` account (used by
|
|
74
|
+
* `auth status --user <ref>`): a ref selects the matching record and skips the
|
|
75
|
+
* env short-circuit, so the source reflects the account being shown rather
|
|
76
|
+
* than an ambient env token. Without a ref the default/env cascade applies.
|
|
77
|
+
*
|
|
78
|
+
* The precedence cascade is intentionally duplicated with `active()`:
|
|
79
|
+
* the only true dedupe options either (a) add an extra config read on
|
|
80
|
+
* every `apiRequest` call (regressing the request hot path) or (b)
|
|
81
|
+
* augment cli-core's `TokenStore` contract. The drift the duplication
|
|
82
|
+
* invites is guarded by the `getActiveTokenSource` regression test that
|
|
83
|
+
* asserts v2-record presence wins over a lingering v1 `api_token`.
|
|
84
|
+
*/
|
|
85
|
+
export declare function getActiveTokenSource(ref?: AccountRef): Promise<'env' | 'secure-store' | 'config-file'>;
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline/promises';
|
|
2
|
+
import { Writable } from 'node:stream';
|
|
3
|
+
import { createKeyringTokenStore, createPkceProvider, } from '@todoist/cli-core/auth';
|
|
4
|
+
import { fetchWithRetry } from '../transport/fetch-with-retry.js';
|
|
5
|
+
import { apiRequest } from './api.js';
|
|
6
|
+
import { LEGACY_CLEAR_PAYLOAD, SECURE_STORE_SERVICE, TOKEN_ENV_VAR } from './auth-constants.js';
|
|
7
|
+
import { getBaseUrl, getOAuthClientId } from './auth.js';
|
|
8
|
+
import { getConfig, getConfigPath, updateConfig } from './config.js';
|
|
9
|
+
import { runMigrateLegacyAuth } from './migrate-auth.js';
|
|
10
|
+
import { makeOutlineAccount, matchOutlineAccount } from './outline-account.js';
|
|
11
|
+
import { createOutlineUserRecordStore, getDefaultUserRecord, recordForRef } from './user-records.js';
|
|
12
|
+
export { matchOutlineAccount } from './outline-account.js';
|
|
13
|
+
function stringFlag(value) {
|
|
14
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Read a line from stdin, prompting on stderr so `--json` / `--ndjson`
|
|
18
|
+
* envelopes on stdout stay clean. With `hidden: true` the typed characters are
|
|
19
|
+
* masked (for secrets): the prompt label is shown, then readline's echo is
|
|
20
|
+
* routed through a muted `Writable` so keystrokes don't leak to the terminal.
|
|
21
|
+
*/
|
|
22
|
+
export async function prompt(question, options = {}) {
|
|
23
|
+
if (!options.hidden) {
|
|
24
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
25
|
+
try {
|
|
26
|
+
return (await rl.question(question)).trim();
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
rl.close();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Echo the prompt label, then mute so typed characters aren't shown. A muted
|
|
33
|
+
// `Writable` (public API) avoids poking readline's private `_writeToOutput`.
|
|
34
|
+
let muted = false;
|
|
35
|
+
const output = new Writable({
|
|
36
|
+
write(chunk, _encoding, callback) {
|
|
37
|
+
if (!muted)
|
|
38
|
+
process.stderr.write(chunk);
|
|
39
|
+
callback();
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
const rl = createInterface({ input: process.stdin, output, terminal: true });
|
|
43
|
+
try {
|
|
44
|
+
const pending = rl.question(question);
|
|
45
|
+
muted = true;
|
|
46
|
+
const answer = (await pending).trim();
|
|
47
|
+
process.stderr.write('\n');
|
|
48
|
+
return answer;
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
rl.close();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export async function resolveBaseUrl(options = {}) {
|
|
55
|
+
const fromFlag = stringFlag(options.baseUrl);
|
|
56
|
+
if (fromFlag)
|
|
57
|
+
return fromFlag.replace(/\/$/, '');
|
|
58
|
+
const fromEnv = process.env.OUTLINE_URL?.trim();
|
|
59
|
+
if (fromEnv)
|
|
60
|
+
return fromEnv.replace(/\/$/, '');
|
|
61
|
+
const configured = await getBaseUrl();
|
|
62
|
+
// Never block a non-interactive shell (CI, piped input) on a prompt —
|
|
63
|
+
// `auth token` is meant to be scriptable, so fall back to the default.
|
|
64
|
+
if (!process.stdin.isTTY)
|
|
65
|
+
return configured.replace(/\/$/, '');
|
|
66
|
+
const answered = await prompt(`Base URL (default: ${configured}): `);
|
|
67
|
+
return (answered || configured).replace(/\/$/, '');
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Verify a token by probing `auth.info` and map the response to the persisted
|
|
71
|
+
* account shape. Shared by the OAuth `validate` hook and `auth token` so the
|
|
72
|
+
* identity-resolution rules live in one place.
|
|
73
|
+
*/
|
|
74
|
+
export async function identifyAccount(token, baseUrl, oauthClientId) {
|
|
75
|
+
const { data } = await apiRequest('auth.info', {}, { token, baseUrl });
|
|
76
|
+
return makeOutlineAccount({
|
|
77
|
+
id: data.user.id,
|
|
78
|
+
label: data.user.name,
|
|
79
|
+
baseUrl,
|
|
80
|
+
oauthClientId,
|
|
81
|
+
teamName: data.team.name,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
async function resolveClientId(flags) {
|
|
85
|
+
const fromFlag = stringFlag(flags.clientId);
|
|
86
|
+
if (fromFlag)
|
|
87
|
+
return fromFlag;
|
|
88
|
+
const existing = await getOAuthClientId();
|
|
89
|
+
if (existing)
|
|
90
|
+
return existing;
|
|
91
|
+
const answered = await prompt('OAuth Client ID: ');
|
|
92
|
+
if (!answered) {
|
|
93
|
+
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.');
|
|
94
|
+
}
|
|
95
|
+
return answered;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Routes cli-core's OAuth HTTP through outline's transport so the proxy /
|
|
99
|
+
* decompression dispatcher applies — to the token exchange AND the
|
|
100
|
+
* oauth4webapi refresh grant (cli-core threads this into oauth4webapi's
|
|
101
|
+
* `customFetch`), which would otherwise capture the bare global `fetch`.
|
|
102
|
+
*/
|
|
103
|
+
const outlineFetch = (input, init) => fetchWithRetry({ url: input, options: init ?? {} });
|
|
104
|
+
export function createOutlineAuthProvider() {
|
|
105
|
+
// Captured at `authorize` (which may prompt for it) and reused by
|
|
106
|
+
// `exchangeCode` / `validate` so a single login can't double-prompt. A
|
|
107
|
+
// provider built only for silent refresh never runs `authorize`, so this
|
|
108
|
+
// stays undefined there and `tokenUrl` falls back to stored config — the
|
|
109
|
+
// refresh path must never prompt.
|
|
110
|
+
let baseUrl;
|
|
111
|
+
return createPkceProvider({
|
|
112
|
+
authorizeUrl: async ({ flags }) => {
|
|
113
|
+
baseUrl = await resolveBaseUrl(flags);
|
|
114
|
+
return `${baseUrl}/oauth/authorize`;
|
|
115
|
+
},
|
|
116
|
+
tokenUrl: async ({ handshake }) => {
|
|
117
|
+
// `handshake.baseUrl` lets a caller scope refresh to a specific
|
|
118
|
+
// account's instance (status --user); otherwise the authorize-time
|
|
119
|
+
// value, then the default config.
|
|
120
|
+
const base = baseUrl ?? handshake.baseUrl ?? (await getBaseUrl());
|
|
121
|
+
return `${base}/oauth/token`;
|
|
122
|
+
},
|
|
123
|
+
// Prefer a handshake-scoped client id (account-aware refresh); else
|
|
124
|
+
// resolve from flag/config (only prompts when neither is set, so it's
|
|
125
|
+
// safe on the refresh path — the logged-in record carries one).
|
|
126
|
+
clientId: ({ handshake, flags }) => {
|
|
127
|
+
const fromHandshake = handshake.clientId;
|
|
128
|
+
return typeof fromHandshake === 'string' && fromHandshake
|
|
129
|
+
? fromHandshake
|
|
130
|
+
: resolveClientId(flags);
|
|
131
|
+
},
|
|
132
|
+
validate: async ({ token, handshake }) => {
|
|
133
|
+
const base = baseUrl ?? (await getBaseUrl());
|
|
134
|
+
return identifyAccount(token, base, handshake.clientId);
|
|
135
|
+
},
|
|
136
|
+
fetchImpl: outlineFetch,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/** True when the v2 store is the authoritative source. */
|
|
140
|
+
function migrationIsConclusive(result) {
|
|
141
|
+
return (result.status === 'migrated' ||
|
|
142
|
+
result.status === 'already-migrated' ||
|
|
143
|
+
result.status === 'no-legacy-state');
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Synthesise a snapshot from v1 plaintext state still on disk. Fallback for
|
|
147
|
+
* when migration can't complete (offline `identifyAccount`, WSL
|
|
148
|
+
* `legacy-keyring-unreachable`). Returns `null` when no legacy token is
|
|
149
|
+
* present. The synthesised account uses whatever identity fields the v1
|
|
150
|
+
* config has — for pre-#71 configs the id/label may be empty, but the
|
|
151
|
+
* runtime never renders those (status's `fetchLive` re-derives from the
|
|
152
|
+
* API), so empty placeholders are safe.
|
|
153
|
+
*/
|
|
154
|
+
async function readLegacyTokenSnapshot() {
|
|
155
|
+
const config = await getConfig();
|
|
156
|
+
const token = config.api_token?.trim() || null;
|
|
157
|
+
if (!token)
|
|
158
|
+
return null;
|
|
159
|
+
return {
|
|
160
|
+
token,
|
|
161
|
+
account: makeOutlineAccount({
|
|
162
|
+
id: config.auth_user_id ?? '',
|
|
163
|
+
label: config.auth_user_name ?? '',
|
|
164
|
+
baseUrl: config.base_url,
|
|
165
|
+
oauthClientId: config.oauth_client_id,
|
|
166
|
+
teamName: config.auth_team_name,
|
|
167
|
+
}),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Discharge v1 plaintext state. Runs **after** a successful v2 write/clear
|
|
172
|
+
* — never before — so a v2-op failure doesn't strand the user with no
|
|
173
|
+
* recoverable credentials. Caller decides whether to swallow or propagate
|
|
174
|
+
* the `updateConfig` failure:
|
|
175
|
+
* - `set()` swallows (v2 record will win in `active()` regardless).
|
|
176
|
+
* - `clear()` propagates (v2 is empty, so a stale legacy token would
|
|
177
|
+
* shadow the logout via the fallback).
|
|
178
|
+
*/
|
|
179
|
+
async function dischargeLegacyState() {
|
|
180
|
+
await updateConfig(LEGACY_CLEAR_PAYLOAD);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Memoised one-shot migration trigger. Resolves with `null` on rejection so
|
|
184
|
+
* the CLI never fails to start because of a migration error — the legacy
|
|
185
|
+
* snapshot fallback handles that case. Tests reset the memo with
|
|
186
|
+
* `vi.resetModules()` + a dynamic re-import.
|
|
187
|
+
*/
|
|
188
|
+
let migrationPromise;
|
|
189
|
+
function ensureMigrated() {
|
|
190
|
+
if (!migrationPromise) {
|
|
191
|
+
migrationPromise = runMigrateLegacyAuth({ silent: true }).catch(() => null);
|
|
192
|
+
}
|
|
193
|
+
return migrationPromise;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* True when the v2 store is empty but a legacy v1 token snapshot is still
|
|
197
|
+
* the only thing keeping the CLI authenticated — typically because
|
|
198
|
+
* `migrateLegacyAuth` couldn't reach the Outline API to identify the
|
|
199
|
+
* account (`MigrateSkipReason: 'identify-failed'`) or the OS keyring is
|
|
200
|
+
* unreachable (`'legacy-keyring-unreachable'`, although outline has no
|
|
201
|
+
* v1 keyring slot to read from). Useful for downstream diagnostics.
|
|
202
|
+
*/
|
|
203
|
+
export async function isLegacyAuthActive() {
|
|
204
|
+
const result = await ensureMigrated();
|
|
205
|
+
if (result !== null && migrationIsConclusive(result))
|
|
206
|
+
return false;
|
|
207
|
+
const legacy = await readLegacyTokenSnapshot();
|
|
208
|
+
return legacy !== null;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* `OUTLINE_API_TOKEN` short-circuits `active()` only when no explicit ref
|
|
212
|
+
* is supplied — cli-core's `KeyringTokenStore` doesn't know about the env
|
|
213
|
+
* var, and an explicit ref means the caller targets a specific stored
|
|
214
|
+
* account.
|
|
215
|
+
*
|
|
216
|
+
* `ensureMigrated()` runs **before** every mutating v2 op so the post-op
|
|
217
|
+
* legacy discharge can't race a still-pending migration into re-grabbing
|
|
218
|
+
* the legacy `api_token` we just consumed. `set()` / `clear()` then
|
|
219
|
+
* discharge legacy state **after** the v2 op succeeds. `set()` swallows
|
|
220
|
+
* the cleanup failure (v2 wins in `active()` regardless); `clear()`
|
|
221
|
+
* propagates it so a failed logout fails loudly instead of leaving the
|
|
222
|
+
* user authenticated via the legacy fallback.
|
|
223
|
+
*/
|
|
224
|
+
/** The bare cli-core keyring store (v2 records only — no env/legacy fallback). */
|
|
225
|
+
function createInnerStore() {
|
|
226
|
+
return createKeyringTokenStore({
|
|
227
|
+
serviceName: SECURE_STORE_SERVICE,
|
|
228
|
+
userRecords: createOutlineUserRecordStore(),
|
|
229
|
+
recordsLocation: getConfigPath(),
|
|
230
|
+
matchAccount: matchOutlineAccount,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Authoritatively classify the active credential without re-deriving the
|
|
235
|
+
* cascade from membership heuristics. Mirrors `active()`'s precedence — env
|
|
236
|
+
* (only when no explicit ref) → stored v2 → legacy snapshot — but reports
|
|
237
|
+
* *which* branch matched, so `ol account current` can render the right shape.
|
|
238
|
+
*
|
|
239
|
+
* The v2 lookup uses the bare keyring store (no legacy fallback) and matches by
|
|
240
|
+
* `ref`, so a renamed legacy snapshot sharing a UUID with a v2 record can't be
|
|
241
|
+
* misreported as `stored`: a `--user <old-name>` that the v2 record no longer
|
|
242
|
+
* matches falls through to the legacy branch.
|
|
243
|
+
*/
|
|
244
|
+
export async function resolveActiveAccountSource(ref) {
|
|
245
|
+
if (ref === undefined && process.env[TOKEN_ENV_VAR]?.trim())
|
|
246
|
+
return { source: 'env' };
|
|
247
|
+
await ensureMigrated();
|
|
248
|
+
const stored = await createInnerStore().activeAccount(ref);
|
|
249
|
+
if (stored)
|
|
250
|
+
return { source: 'stored', account: stored.account, isDefault: stored.isDefault };
|
|
251
|
+
const legacy = await readLegacyTokenSnapshot();
|
|
252
|
+
if (legacy && (ref === undefined || matchOutlineAccount(legacy.account, ref))) {
|
|
253
|
+
return { source: 'legacy' };
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
export function createOutlineTokenStore() {
|
|
258
|
+
const inner = createInnerStore();
|
|
259
|
+
async function migrationIsInconclusive() {
|
|
260
|
+
const result = await ensureMigrated(); // memoised
|
|
261
|
+
return result === null || !migrationIsConclusive(result);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Shared env → v2 → legacy resolution for `active` / `activeBundle`.
|
|
265
|
+
* `fromV2` reads the matching cli-core method; `fromEnvOrLegacy` maps an
|
|
266
|
+
* env/legacy token into the caller's shape (env + legacy carry no refresh
|
|
267
|
+
* material, so the bundle form surfaces as access-only).
|
|
268
|
+
*/
|
|
269
|
+
async function resolveAuth(ref, fromV2, fromEnvOrLegacy) {
|
|
270
|
+
if (ref === undefined) {
|
|
271
|
+
const envToken = process.env[TOKEN_ENV_VAR]?.trim();
|
|
272
|
+
if (envToken) {
|
|
273
|
+
return fromEnvOrLegacy(envToken, makeOutlineAccount({ id: '', label: '', baseUrl: await getBaseUrl() }));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
await ensureMigrated();
|
|
277
|
+
const fromStore = await fromV2();
|
|
278
|
+
if (fromStore)
|
|
279
|
+
return fromStore;
|
|
280
|
+
const legacy = await readLegacyTokenSnapshot();
|
|
281
|
+
if (legacy && (ref === undefined || matchOutlineAccount(legacy.account, ref))) {
|
|
282
|
+
return fromEnvOrLegacy(legacy.token, legacy.account);
|
|
283
|
+
}
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
// `ensureMigrated` runs before the v2 write so the post-write discharge
|
|
287
|
+
// can't race a pending migration into re-grabbing the legacy token. The
|
|
288
|
+
// discharge is best-effort: a lingering `api_token` is dormant because
|
|
289
|
+
// reads hit v2 first. (`clear` needs the stricter propagating variant.)
|
|
290
|
+
async function writeThenDischargeLegacy(write) {
|
|
291
|
+
await ensureMigrated();
|
|
292
|
+
await write();
|
|
293
|
+
if (await migrationIsInconclusive()) {
|
|
294
|
+
await dischargeLegacyState().catch(() => undefined);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
active(ref) {
|
|
299
|
+
return resolveAuth(ref, () => inner.active(ref), (token, account) => ({ token, account }));
|
|
300
|
+
},
|
|
301
|
+
activeAccount(ref) {
|
|
302
|
+
return resolveAuth(ref, () => inner.activeAccount(ref), (_token, account) => ({ account, isDefault: true }));
|
|
303
|
+
},
|
|
304
|
+
activeBundle(ref) {
|
|
305
|
+
return resolveAuth(ref, () => inner.activeBundle(ref), (token, account) => ({ account, bundle: { accessToken: token } }));
|
|
306
|
+
},
|
|
307
|
+
set(account, token) {
|
|
308
|
+
return writeThenDischargeLegacy(() => inner.set(account, token));
|
|
309
|
+
},
|
|
310
|
+
setBundle(account, bundle, options) {
|
|
311
|
+
return writeThenDischargeLegacy(() => inner.setBundle(account, bundle, options));
|
|
312
|
+
},
|
|
313
|
+
async clear(ref) {
|
|
314
|
+
await ensureMigrated();
|
|
315
|
+
const cleared = await inner.clear(ref);
|
|
316
|
+
if (await migrationIsInconclusive()) {
|
|
317
|
+
// Must succeed: v2 is now empty, so a surviving legacy
|
|
318
|
+
// token would shadow the logout via the fallback.
|
|
319
|
+
await dischargeLegacyState();
|
|
320
|
+
}
|
|
321
|
+
return cleared;
|
|
322
|
+
},
|
|
323
|
+
async list() {
|
|
324
|
+
await ensureMigrated();
|
|
325
|
+
return inner.list();
|
|
326
|
+
},
|
|
327
|
+
async setDefault(ref) {
|
|
328
|
+
await ensureMigrated();
|
|
329
|
+
return inner.setDefault(ref);
|
|
330
|
+
},
|
|
331
|
+
getLastStorageResult: () => inner.getLastStorageResult(),
|
|
332
|
+
getLastClearResult: () => inner.getLastClearResult(),
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Where the currently-active token lives. Mirrors `active()`'s resolution
|
|
337
|
+
* order — env → v2 record → legacy plaintext — so the answer can never
|
|
338
|
+
* contradict the token the runtime is actually using.
|
|
339
|
+
*
|
|
340
|
+
* Pass `ref` to report the source of a specific `--user` account (used by
|
|
341
|
+
* `auth status --user <ref>`): a ref selects the matching record and skips the
|
|
342
|
+
* env short-circuit, so the source reflects the account being shown rather
|
|
343
|
+
* than an ambient env token. Without a ref the default/env cascade applies.
|
|
344
|
+
*
|
|
345
|
+
* The precedence cascade is intentionally duplicated with `active()`:
|
|
346
|
+
* the only true dedupe options either (a) add an extra config read on
|
|
347
|
+
* every `apiRequest` call (regressing the request hot path) or (b)
|
|
348
|
+
* augment cli-core's `TokenStore` contract. The drift the duplication
|
|
349
|
+
* invites is guarded by the `getActiveTokenSource` regression test that
|
|
350
|
+
* asserts v2-record presence wins over a lingering v1 `api_token`.
|
|
351
|
+
*/
|
|
352
|
+
export async function getActiveTokenSource(ref) {
|
|
353
|
+
if (ref === undefined && process.env[TOKEN_ENV_VAR]?.trim())
|
|
354
|
+
return 'env';
|
|
355
|
+
const config = await getConfig();
|
|
356
|
+
const record = ref !== undefined ? recordForRef(config, ref) : getDefaultUserRecord(config);
|
|
357
|
+
if (record)
|
|
358
|
+
return record.fallbackToken ? 'config-file' : 'secure-store';
|
|
359
|
+
if (ref === undefined && config.api_token?.trim())
|
|
360
|
+
return 'config-file';
|
|
361
|
+
return 'secure-store';
|
|
362
|
+
}
|
|
363
|
+
//# 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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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { SecureStoreUnavailableError, type TokenRefreshOptions } from '@todoist/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
|
+
/** 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
|
+
};
|
|
14
|
+
export { SecureStoreUnavailableError, getActiveTokenSource, TOKEN_ENV_VAR };
|
|
15
|
+
export declare class NoTokenError extends CliError {
|
|
16
|
+
constructor();
|
|
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 wiring for cli-core's `auth status` / `auth token view` attachers,
|
|
30
|
+
* so both hand back a token that is usable right now rather than whatever
|
|
31
|
+
* was last stored. The attachers pass their own (`--user`-aware) store, so
|
|
32
|
+
* the handshake is resolved from the account cli-core actually selected —
|
|
33
|
+
* pinning the refresh to that account's instance and client id. Env, legacy
|
|
34
|
+
* and record-less accounts carry no refresh token and are served as-is.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getTokenRefreshOptions(): TokenRefreshOptions<OutlineAccount>;
|
|
37
|
+
/**
|
|
38
|
+
* Reactive rotation after a 401. Returns `true` when the token rotated (the
|
|
39
|
+
* caller retries once). A rejected/absent refresh token surfaces as
|
|
40
|
+
* `NoTokenError` (re-login); a transient failure propagates unchanged.
|
|
41
|
+
*/
|
|
42
|
+
export declare function reactiveRefresh(handshake?: RequestContext['handshake']): Promise<boolean>;
|
|
43
|
+
/**
|
|
44
|
+
* Read the active token. Hot path: when `OUTLINE_API_TOKEN` is set we
|
|
45
|
+
* return it directly without consulting the token store, since
|
|
46
|
+
* `apiRequest` already resolves the base URL separately — going through
|
|
47
|
+
* `store.active()` here would trigger a redundant `getBaseUrl()` lookup
|
|
48
|
+
* per request just to synthesise an account we don't need.
|
|
49
|
+
*/
|
|
50
|
+
export declare function getApiToken(): Promise<string>;
|
|
51
|
+
/**
|
|
52
|
+
* Account-agnostic base URL cascade: env var → default user record (v2) →
|
|
53
|
+
* legacy `base_url` config (v1) → built-in default. Deliberately ignores the
|
|
54
|
+
* global `--user` selector so the login flow (which resolves its default
|
|
55
|
+
* prompt through here) isn't skewed by account selection; the request path
|
|
56
|
+
* applies `--user` separately via {@link getRequestContext}.
|
|
57
|
+
*/
|
|
58
|
+
export declare function getBaseUrl(): Promise<string>;
|
|
59
|
+
/**
|
|
60
|
+
* Account-agnostic OAuth client id cascade: env var → default user record
|
|
61
|
+
* (v2) → legacy `oauth_client_id` config (v1) → undefined (caller prompts).
|
|
62
|
+
* Like {@link getBaseUrl}, ignores `--user` so login stays default-driven.
|
|
63
|
+
*/
|
|
64
|
+
export declare function getOAuthClientId(): Promise<string | undefined>;
|
|
65
|
+
/**
|
|
66
|
+
* Resolve the base URL — and, when a `--user` account is selected, the refresh
|
|
67
|
+
* handshake (its instance + client id) — for a single managed request. Falls
|
|
68
|
+
* back to the account-agnostic {@link getBaseUrl} cascade without `--user`.
|
|
69
|
+
* An unknown ref resolves no handshake and the default base URL; the wrapped
|
|
70
|
+
* token store then raises `ACCOUNT_NOT_FOUND` when the token is read, so the
|
|
71
|
+
* request never fires.
|
|
72
|
+
*/
|
|
73
|
+
export declare function getRequestContext(): Promise<RequestContext>;
|