overlord-cli 5.1.0 → 5.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/_cli/credentials.mjs +72 -29
- package/package.json +1 -1
package/bin/_cli/credentials.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
9
9
|
|
|
10
10
|
const CREDENTIALS_DIR = path.join(os.homedir(), '.ovld');
|
|
11
11
|
const CLI_CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, 'credentials.cli.json');
|
|
12
|
+
const DESKTOP_CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, 'credentials.desktop.json');
|
|
12
13
|
const LEGACY_CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, 'credentials.json');
|
|
13
14
|
const LEGACY_ELECTRON_CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, 'electron-credentials.json');
|
|
14
15
|
const LEGACY_MIGRATION_MARKER = path.join(CREDENTIALS_DIR, '.cli-migrated');
|
|
@@ -24,7 +25,8 @@ const LOCAL_SECRET_HEADER = 'X-Overlord-Local-Secret';
|
|
|
24
25
|
* refresh_token?: string,
|
|
25
26
|
* organization_id?: number | null,
|
|
26
27
|
* platform_url: string,
|
|
27
|
-
* user_email?: string
|
|
28
|
+
* user_email?: string,
|
|
29
|
+
* updated_at?: string
|
|
28
30
|
* }} Credentials
|
|
29
31
|
*/
|
|
30
32
|
|
|
@@ -93,6 +95,9 @@ function parseStoredCredentialsData(parsed, { requireAuthData = false } = {}) {
|
|
|
93
95
|
...(organizationId ? { organization_id: organizationId } : {}),
|
|
94
96
|
...(typeof parsed.user_email === 'string' && parsed.user_email.trim()
|
|
95
97
|
? { user_email: parsed.user_email.trim() }
|
|
98
|
+
: {}),
|
|
99
|
+
...(typeof parsed.updated_at === 'string' && parsed.updated_at.trim()
|
|
100
|
+
? { updated_at: parsed.updated_at.trim() }
|
|
96
101
|
: {})
|
|
97
102
|
};
|
|
98
103
|
}
|
|
@@ -112,7 +117,8 @@ function normalizeCredentialsForSave(data) {
|
|
|
112
117
|
? { access_token_expires_at: parsed.access_token_expires_at }
|
|
113
118
|
: {}),
|
|
114
119
|
...(parsed.organization_id ? { organization_id: parsed.organization_id } : {}),
|
|
115
|
-
...(parsed.user_email ? { user_email: parsed.user_email } : {})
|
|
120
|
+
...(parsed.user_email ? { user_email: parsed.user_email } : {}),
|
|
121
|
+
...(parsed.updated_at ? { updated_at: parsed.updated_at } : {})
|
|
116
122
|
};
|
|
117
123
|
}
|
|
118
124
|
|
|
@@ -140,13 +146,57 @@ function migrateLegacyCredentials() {
|
|
|
140
146
|
return source;
|
|
141
147
|
}
|
|
142
148
|
|
|
149
|
+
function resolveAccessTokenExpiry(credentials) {
|
|
150
|
+
if (!credentials?.access_token) return null;
|
|
151
|
+
if (credentials.access_token_expires_at) {
|
|
152
|
+
const parsed = Date.parse(credentials.access_token_expires_at);
|
|
153
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
154
|
+
}
|
|
155
|
+
const jwtExp = decodeJwtExpiry(credentials.access_token);
|
|
156
|
+
return jwtExp ? jwtExp * 1000 : null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function isAccessTokenFresh(credentials) {
|
|
160
|
+
const expiresAt = resolveAccessTokenExpiry(credentials);
|
|
161
|
+
if (expiresAt === null) return false;
|
|
162
|
+
return expiresAt - Date.now() > 60_000;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function credentialsUpdatedAt(credentials) {
|
|
166
|
+
const parsed = Date.parse(credentials?.updated_at ?? '');
|
|
167
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function selectStoredCredentials() {
|
|
171
|
+
const candidates = [
|
|
172
|
+
{
|
|
173
|
+
source: 'credentials.cli.json',
|
|
174
|
+
credentials: parseStoredCredentialsData(readJsonFile(CLI_CREDENTIALS_FILE), {
|
|
175
|
+
requireAuthData: true
|
|
176
|
+
})
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
source: 'credentials.desktop.json',
|
|
180
|
+
credentials: parseStoredCredentialsData(readJsonFile(DESKTOP_CREDENTIALS_FILE), {
|
|
181
|
+
requireAuthData: true
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
].filter(candidate => candidate.credentials?.refresh_token);
|
|
185
|
+
|
|
186
|
+
if (candidates.length === 0) return null;
|
|
187
|
+
|
|
188
|
+
const fresh = candidates.filter(candidate => isAccessTokenFresh(candidate.credentials));
|
|
189
|
+
const pool = fresh.length > 0 ? fresh : candidates;
|
|
190
|
+
return pool.sort(
|
|
191
|
+
(left, right) =>
|
|
192
|
+
credentialsUpdatedAt(right.credentials) - credentialsUpdatedAt(left.credentials)
|
|
193
|
+
)[0];
|
|
194
|
+
}
|
|
195
|
+
|
|
143
196
|
/** @returns {Credentials | null} */
|
|
144
197
|
export function loadCredentials() {
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
if (cliCredentials?.refresh_token) return cliCredentials;
|
|
198
|
+
const selected = selectStoredCredentials();
|
|
199
|
+
if (selected?.credentials) return selected.credentials;
|
|
150
200
|
|
|
151
201
|
return migrateLegacyCredentials();
|
|
152
202
|
}
|
|
@@ -161,6 +211,16 @@ export function saveCredentials(data) {
|
|
|
161
211
|
writeJsonFileAtomic(CLI_CREDENTIALS_FILE, { ...credentials, updated_at: new Date().toISOString() });
|
|
162
212
|
}
|
|
163
213
|
|
|
214
|
+
function saveCredentialsToSource(data, source) {
|
|
215
|
+
const credentials = normalizeCredentialsForSave(data);
|
|
216
|
+
if (!credentials) {
|
|
217
|
+
throw new Error('Cannot save empty Overlord credentials.');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const filePath = source === 'credentials.desktop.json' ? DESKTOP_CREDENTIALS_FILE : CLI_CREDENTIALS_FILE;
|
|
221
|
+
writeJsonFileAtomic(filePath, { ...credentials, updated_at: new Date().toISOString() });
|
|
222
|
+
}
|
|
223
|
+
|
|
164
224
|
export function clearCredentials() {
|
|
165
225
|
try {
|
|
166
226
|
fs.unlinkSync(CLI_CREDENTIALS_FILE);
|
|
@@ -170,10 +230,8 @@ export function clearCredentials() {
|
|
|
170
230
|
}
|
|
171
231
|
|
|
172
232
|
function getCredentialFileSource() {
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
});
|
|
176
|
-
if (cliCredentials?.refresh_token) return 'credentials.cli.json';
|
|
233
|
+
const selected = selectStoredCredentials();
|
|
234
|
+
if (selected) return selected.source;
|
|
177
235
|
|
|
178
236
|
if (fileExists(LEGACY_CREDENTIALS_FILE)) {
|
|
179
237
|
const legacyShared = parseStoredCredentialsData(readJsonFile(LEGACY_CREDENTIALS_FILE), {
|
|
@@ -338,22 +396,6 @@ function computeAccessTokenExpiry(data) {
|
|
|
338
396
|
return jwtExp ? new Date(jwtExp * 1000).toISOString() : null;
|
|
339
397
|
}
|
|
340
398
|
|
|
341
|
-
function resolveAccessTokenExpiry(credentials) {
|
|
342
|
-
if (!credentials?.access_token) return null;
|
|
343
|
-
if (credentials.access_token_expires_at) {
|
|
344
|
-
const parsed = Date.parse(credentials.access_token_expires_at);
|
|
345
|
-
if (Number.isFinite(parsed)) return parsed;
|
|
346
|
-
}
|
|
347
|
-
const jwtExp = decodeJwtExpiry(credentials.access_token);
|
|
348
|
-
return jwtExp ? jwtExp * 1000 : null;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function isAccessTokenFresh(credentials) {
|
|
352
|
-
const expiresAt = resolveAccessTokenExpiry(credentials);
|
|
353
|
-
if (expiresAt === null) return false;
|
|
354
|
-
return expiresAt - Date.now() > 60_000;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
399
|
function describeNetworkError(error, context) {
|
|
358
400
|
const cause = error?.cause;
|
|
359
401
|
const details = [cause?.code, cause?.message].filter(Boolean).join(': ');
|
|
@@ -483,7 +525,8 @@ function isLocalDevCli() {
|
|
|
483
525
|
* Refreshes OAuth access tokens when possible.
|
|
484
526
|
*/
|
|
485
527
|
export async function resolveAuth() {
|
|
486
|
-
const
|
|
528
|
+
const selectedCredentials = selectStoredCredentials();
|
|
529
|
+
const creds = selectedCredentials?.credentials ?? migrateLegacyCredentials();
|
|
487
530
|
const overlordUrlFromEnv = normalizePlatformUrl(process.env.OVERLORD_URL);
|
|
488
531
|
const overlordUrlFromCreds = normalizeStoredPlatformUrl(creds?.platform_url);
|
|
489
532
|
|
|
@@ -540,7 +583,7 @@ export async function resolveAuth() {
|
|
|
540
583
|
access_token_expires_at: refreshed.access_token_expires_at,
|
|
541
584
|
refresh_token: refreshed.refresh_token || creds.refresh_token
|
|
542
585
|
};
|
|
543
|
-
|
|
586
|
+
saveCredentialsToSource(nextCredentials, selectedCredentials?.source);
|
|
544
587
|
} catch (refreshError) {
|
|
545
588
|
throw new Error(
|
|
546
589
|
`Stored Overlord session expired and refresh failed. ${refreshError instanceof Error ? refreshError.message : String(refreshError)} Run \`ovld auth login\` again.`
|