@skrr-ai/cli 0.1.122 → 0.1.124
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/dist/commands/agents/chat.d.ts +7 -0
- package/dist/commands/agents/chat.js +32 -6
- package/dist/commands/agents/delete.d.ts +8 -0
- package/dist/commands/agents/delete.js +16 -0
- package/dist/commands/context.js +7 -0
- package/dist/commands/followups/act.js +3 -0
- package/dist/commands/followups/checkin.js +3 -0
- package/dist/commands/followups/remind.js +3 -0
- package/dist/commands/followups/show.js +1 -1
- package/dist/commands/followups/watch.js +3 -0
- package/dist/commands/search/workspace.js +1 -1
- package/dist/commands/store/gallery-admissions/approve.js +1 -1
- package/dist/commands/store/gallery-admissions/preview.js +1 -1
- package/dist/commands/store/gallery-admissions/refuse.js +1 -1
- package/dist/commands/store/gallery-admissions/show.js +1 -1
- package/dist/lib/agentic-stream.d.ts +46 -0
- package/dist/lib/agentic-stream.js +135 -14
- package/dist/lib/auth-core-init.d.ts +15 -2
- package/dist/lib/auth-core-init.js +47 -6
- package/dist/lib/auth-failure.js +12 -0
- package/dist/lib/command-hygiene.js +30 -1
- package/dist/lib/daemonBrokerRefusal.js +5 -1
- package/dist/lib/exec-runtime-binary.d.ts +9 -0
- package/dist/lib/exec-runtime-binary.js +15 -1
- package/dist/lib/followups.d.ts +13 -0
- package/dist/lib/followups.js +8 -0
- package/dist/lib/keychain.js +41 -4
- package/dist/lib/profile-machine-identity-guard.js +2 -1
- package/dist/lib/runtime-start-verification.js +6 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +5 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +69 -27
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +135 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +440 -83
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.d.ts +106 -7
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.js +77 -19
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHandle.d.ts +33 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHandle.js +77 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHardware.d.ts +68 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyHardware.js +264 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyServerSupport.d.ts +31 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKeyServerSupport.js +90 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dpop.js +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineReservation.js +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +7 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +110 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/index.d.ts +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/index.js +13 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/linux.d.ts +18 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/linux.js +11 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.d.ts +22 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.js +14 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/masterKekService.d.ts +69 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/masterKekService.js +67 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/liveState.d.ts +154 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/liveState.js +281 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +6 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +8 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/messages.js +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/profileStateDir.js +7 -27
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +24 -16
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/stateFiles.d.ts +31 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/stateFiles.js +89 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/telemetryRedaction.d.ts +3 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/telemetryRedaction.js +41 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/types.d.ts +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +5 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +4 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +122 -38
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +135 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +603 -90
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.d.ts +106 -7
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.js +131 -27
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHandle.d.ts +33 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHandle.js +81 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHardware.d.ts +68 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyHardware.js +317 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyServerSupport.d.ts +31 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKeyServerSupport.js +86 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dpop.js +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineReservation.js +4 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +7 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +14 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/index.d.ts +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/index.js +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/linux.d.ts +18 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/linux.js +18 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.d.ts +22 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.js +23 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/masterKekService.d.ts +69 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/masterKekService.js +134 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/liveState.d.ts +154 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/liveState.js +386 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +6 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +11 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/messages.js +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/profileStateDir.js +6 -35
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +45 -16
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/stateFiles.d.ts +31 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/stateFiles.js +73 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/telemetryRedaction.d.ts +3 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/telemetryRedaction.js +40 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/types.d.ts +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/package.json +21 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +7349 -7325
- package/oclif.manifest.json +11 -2
- package/package.json +4 -1
|
@@ -45,6 +45,7 @@ var import_node_crypto = require("node:crypto");
|
|
|
45
45
|
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
46
46
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
47
47
|
var import_runtime = require("./runtime.js");
|
|
48
|
+
var import_stateFiles = require("./stateFiles.js");
|
|
48
49
|
const PROFILES_SEGMENT = "profiles";
|
|
49
50
|
const SUPERSEDED_MARKER_SUFFIX = ".superseded.json";
|
|
50
51
|
const SUPERSEDED_COPY_SUFFIX = ".superseded";
|
|
@@ -190,28 +191,6 @@ function sha256OfFile(target) {
|
|
|
190
191
|
return null;
|
|
191
192
|
}
|
|
192
193
|
}
|
|
193
|
-
function atomicWrite0600(target, value) {
|
|
194
|
-
const dir = import_node_path.default.dirname(target);
|
|
195
|
-
import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
196
|
-
const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
|
|
197
|
-
const fd = import_node_fs.default.openSync(tmp, "w", 384);
|
|
198
|
-
try {
|
|
199
|
-
import_node_fs.default.writeSync(fd, value);
|
|
200
|
-
import_node_fs.default.fsyncSync(fd);
|
|
201
|
-
} finally {
|
|
202
|
-
import_node_fs.default.closeSync(fd);
|
|
203
|
-
}
|
|
204
|
-
import_node_fs.default.renameSync(tmp, target);
|
|
205
|
-
try {
|
|
206
|
-
const dirFd = import_node_fs.default.openSync(dir, "r");
|
|
207
|
-
try {
|
|
208
|
-
import_node_fs.default.fsyncSync(dirFd);
|
|
209
|
-
} finally {
|
|
210
|
-
import_node_fs.default.closeSync(dirFd);
|
|
211
|
-
}
|
|
212
|
-
} catch {
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
194
|
function adopt(fromDir, toDir, files, dryRun) {
|
|
216
195
|
const written = [];
|
|
217
196
|
for (const name of files) {
|
|
@@ -219,8 +198,9 @@ function adopt(fromDir, toDir, files, dryRun) {
|
|
|
219
198
|
if (!existsFile(source))
|
|
220
199
|
continue;
|
|
221
200
|
const target = import_node_path.default.join(toDir, name);
|
|
222
|
-
if (!dryRun)
|
|
223
|
-
atomicWrite0600(target, import_node_fs.default.readFileSync(source));
|
|
201
|
+
if (!dryRun) {
|
|
202
|
+
(0, import_stateFiles.atomicWrite0600)(target, import_node_fs.default.readFileSync(source), `adopt ${name} into ${toDir}`);
|
|
203
|
+
}
|
|
224
204
|
written.push(target);
|
|
225
205
|
}
|
|
226
206
|
return { written, supersededAside: [] };
|
|
@@ -232,7 +212,7 @@ function moveAside(dir, files, suffix = SUPERSEDED_COPY_SUFFIX) {
|
|
|
232
212
|
const target = import_node_path.default.join(dir, name);
|
|
233
213
|
if (!existsFile(target))
|
|
234
214
|
continue;
|
|
235
|
-
|
|
215
|
+
(0, import_stateFiles.renameStateFile)(target, `${target}${suffix}.${stamp}`, `set aside ${target}`);
|
|
236
216
|
aside.push(target);
|
|
237
217
|
}
|
|
238
218
|
return aside;
|
|
@@ -248,8 +228,8 @@ function writeMarker(markerPath, supersededBy, reason) {
|
|
|
248
228
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
249
229
|
note: "Kept deliberately. This file is the pre-consolidation copy (OSK-10314); older binaries read it and it is the only rollback that needs no operator step. Remove it only through logout."
|
|
250
230
|
};
|
|
251
|
-
atomicWrite0600(markerPath, Buffer.from(`${JSON.stringify(payload, null, 2)}
|
|
252
|
-
`, "utf-8"));
|
|
231
|
+
(0, import_stateFiles.atomicWrite0600)(markerPath, Buffer.from(`${JSON.stringify(payload, null, 2)}
|
|
232
|
+
`, "utf-8"), `write the consolidation marker ${markerPath}`);
|
|
253
233
|
}
|
|
254
234
|
function emitOutcome(spec, status, meta) {
|
|
255
235
|
spec.onEvent?.(`path_consolidation.${status}`, status !== "conflict", {
|
|
@@ -231,7 +231,7 @@ export type DeviceProofSigner = (params: {
|
|
|
231
231
|
method: string;
|
|
232
232
|
url: string;
|
|
233
233
|
refreshToken: string;
|
|
234
|
-
}) => string | null | undefined
|
|
234
|
+
}) => string | null | undefined | Promise<string | null | undefined>;
|
|
235
235
|
export declare function refreshDaemonToken(serverUrl: string, currentRefreshToken: string, daemonId: string, options?: {
|
|
236
236
|
machineUuid?: string;
|
|
237
237
|
origin?: string;
|
|
@@ -49,7 +49,9 @@ var import_node_fs = __toESM(require("node:fs"), 1);
|
|
|
49
49
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
50
50
|
var import_proper_lockfile = __toESM(require("proper-lockfile"), 1);
|
|
51
51
|
var import_runtime = require("./runtime.js");
|
|
52
|
+
var import_liveState = require("./liveState.js");
|
|
52
53
|
var import_refreshClassification = require("./refreshClassification.js");
|
|
54
|
+
var import_stateFiles = require("./stateFiles.js");
|
|
53
55
|
var import_types = require("./types.js");
|
|
54
56
|
const LOCK_STALE_MS = 5e3;
|
|
55
57
|
const LOCK_RETRY_MS = 100;
|
|
@@ -125,9 +127,9 @@ async function withAuthLock(body, daemonId) {
|
|
|
125
127
|
const lockPath = authLockPath();
|
|
126
128
|
const dir = import_node_path.default.dirname(lockPath);
|
|
127
129
|
if (!import_node_fs.default.existsSync(dir))
|
|
128
|
-
|
|
130
|
+
(0, import_stateFiles.ensureStateDir)(dir, "create the auth lock directory", null);
|
|
129
131
|
if (!import_node_fs.default.existsSync(lockPath)) {
|
|
130
|
-
|
|
132
|
+
(0, import_stateFiles.writeStateFile)(lockPath, "", { mode: 384 }, "create the auth lock");
|
|
131
133
|
}
|
|
132
134
|
const acquireStart = Date.now();
|
|
133
135
|
(0, import_runtime.emitAuthTelemetry)({
|
|
@@ -282,7 +284,9 @@ async function doRefresh(serverUrl, currentRefreshToken, daemonId, options = {})
|
|
|
282
284
|
if (options.machineUuid) {
|
|
283
285
|
body.machineUuid = options.machineUuid;
|
|
284
286
|
}
|
|
285
|
-
|
|
287
|
+
let proofAttached = false;
|
|
288
|
+
async function buildRequestInit() {
|
|
289
|
+
proofAttached = false;
|
|
286
290
|
const headers = { "Content-Type": "application/json" };
|
|
287
291
|
if (options.origin) {
|
|
288
292
|
headers["X-Oversky-Origin"] = options.origin;
|
|
@@ -290,7 +294,7 @@ async function doRefresh(serverUrl, currentRefreshToken, daemonId, options = {})
|
|
|
290
294
|
__refreshDiagnostics.requestAttemptCount += 1;
|
|
291
295
|
if (options.signRequestProof) {
|
|
292
296
|
try {
|
|
293
|
-
const proof = options.signRequestProof({
|
|
297
|
+
const proof = await options.signRequestProof({
|
|
294
298
|
method: "POST",
|
|
295
299
|
url,
|
|
296
300
|
refreshToken: currentRefreshToken
|
|
@@ -298,6 +302,7 @@ async function doRefresh(serverUrl, currentRefreshToken, daemonId, options = {})
|
|
|
298
302
|
__refreshDiagnostics.proofAttemptCount += 1;
|
|
299
303
|
if (typeof proof === "string" && proof.length > 0) {
|
|
300
304
|
headers["OverSky-DPoP"] = proof;
|
|
305
|
+
proofAttached = true;
|
|
301
306
|
}
|
|
302
307
|
} catch (err) {
|
|
303
308
|
__refreshDiagnostics.proofAttemptCount += 1;
|
|
@@ -312,7 +317,7 @@ async function doRefresh(serverUrl, currentRefreshToken, daemonId, options = {})
|
|
|
312
317
|
}
|
|
313
318
|
let response;
|
|
314
319
|
try {
|
|
315
|
-
response = await fetch(url, buildRequestInit());
|
|
320
|
+
response = await fetch(url, await buildRequestInit());
|
|
316
321
|
(0, import_runtime.notifyResponseObserver)(response);
|
|
317
322
|
} catch (err) {
|
|
318
323
|
log.warn(`[auth/refresh] network error: ${err?.message}`);
|
|
@@ -426,10 +431,13 @@ async function doRefresh(serverUrl, currentRefreshToken, daemonId, options = {})
|
|
|
426
431
|
});
|
|
427
432
|
throw new import_types.PermanentAuthFailure(code, parsedBody.message || code, revocationReason);
|
|
428
433
|
}
|
|
434
|
+
const noLocalProof = parsedBody.code === "DEVICE_PROOF_MISSING" && !proofAttached;
|
|
435
|
+
const latchReason = noLocalProof ? "DEVICE_KEY_UNAVAILABLE" : "UNKNOWN_401";
|
|
429
436
|
const message = parsedBody.message || "Unknown auth failure";
|
|
437
|
+
const latchMessage = noLocalProof ? `This computer has no usable device key, so the refresh carried no device proof (server: ${message})` : message;
|
|
430
438
|
const { exhausted, count } = recordUnknown401(daemonId, Date.now());
|
|
431
439
|
if (!exhausted) {
|
|
432
|
-
log.warn(`[auth/refresh] unknown-shape 401 (${count}/${UNKNOWN_401_BUDGET}) \u2014 treating as transient, will retry: ${message}`);
|
|
440
|
+
log.warn(`[auth/refresh] unknown-shape 401 (${count}/${UNKNOWN_401_BUDGET}) \u2014 treating as transient, will retry: ${message}` + (noLocalProof ? " (no device proof was sent: this machine has no usable device key)" : ""));
|
|
433
441
|
(0, import_runtime.emitAuthTelemetry)({
|
|
434
442
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
435
443
|
eventType: "auth",
|
|
@@ -448,7 +456,7 @@ async function doRefresh(serverUrl, currentRefreshToken, daemonId, options = {})
|
|
|
448
456
|
});
|
|
449
457
|
throw new import_types.TransientAuthFailure(`Unknown auth failure (transient, retrying): ${message}`);
|
|
450
458
|
}
|
|
451
|
-
log.warn(`[auth/refresh] unknown-shape 401 budget exhausted (${count}/${UNKNOWN_401_BUDGET}) \u2014 latching permanent: ${message}`);
|
|
459
|
+
log.warn(`[auth/refresh] unknown-shape 401 budget exhausted (${count}/${UNKNOWN_401_BUDGET}) \u2014 latching permanent ${latchReason}: ${message}`);
|
|
452
460
|
(0, import_runtime.emitAuthTelemetry)({
|
|
453
461
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
454
462
|
eventType: "auth",
|
|
@@ -457,18 +465,18 @@ async function doRefresh(serverUrl, currentRefreshToken, daemonId, options = {})
|
|
|
457
465
|
event: "refresh_failure",
|
|
458
466
|
daemonId,
|
|
459
467
|
durationMs: Date.now() - startMs,
|
|
460
|
-
errorCode:
|
|
468
|
+
errorCode: latchReason,
|
|
461
469
|
statusCode: 401,
|
|
462
470
|
transient: false,
|
|
463
471
|
unknown401Streak: count
|
|
464
472
|
}
|
|
465
473
|
});
|
|
466
474
|
persistReauthState({
|
|
467
|
-
code:
|
|
468
|
-
message,
|
|
475
|
+
code: latchReason,
|
|
476
|
+
message: latchMessage,
|
|
469
477
|
at: Date.now()
|
|
470
478
|
});
|
|
471
|
-
throw new import_types.PermanentAuthFailure(
|
|
479
|
+
throw new import_types.PermanentAuthFailure(latchReason, latchMessage);
|
|
472
480
|
}
|
|
473
481
|
let bodyText = "";
|
|
474
482
|
try {
|
|
@@ -495,11 +503,10 @@ function persistReauthState(state) {
|
|
|
495
503
|
try {
|
|
496
504
|
const dir = import_node_path.default.dirname(flagPath);
|
|
497
505
|
if (!import_node_fs.default.existsSync(dir))
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
mode: 384
|
|
501
|
-
});
|
|
506
|
+
(0, import_stateFiles.ensureStateDir)(dir, "create the re-auth latch directory");
|
|
507
|
+
(0, import_stateFiles.writeStateFile)(flagPath, JSON.stringify(state, null, 2) + "\n", { mode: 384 }, "write the re-auth latch");
|
|
502
508
|
} catch (err) {
|
|
509
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
503
510
|
log.debug(`[auth/refresh] failed to persist reauth flag: ${err?.message}`);
|
|
504
511
|
}
|
|
505
512
|
}
|
|
@@ -526,8 +533,9 @@ function clearReauthState() {
|
|
|
526
533
|
try {
|
|
527
534
|
const flagPath = reauthFlagPath();
|
|
528
535
|
if (import_node_fs.default.existsSync(flagPath))
|
|
529
|
-
|
|
536
|
+
(0, import_stateFiles.removeStateFile)(flagPath, "clear the re-auth latch");
|
|
530
537
|
} catch (err) {
|
|
538
|
+
(0, import_liveState.rethrowIfLiveStateRefusal)(err);
|
|
531
539
|
log.debug(`[auth/refresh] clearReauthState: ${err?.message}`);
|
|
532
540
|
}
|
|
533
541
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stateFiles.ts — the ONE place auth-core mutates credential state on disk.
|
|
3
|
+
*
|
|
4
|
+
* Every write, delete, rename and mkdir of a wrapped DEK, a device key, an enrollment marker, a
|
|
5
|
+
* lock sentinel, a re-auth latch or a machine identity goes through a function here, and each
|
|
6
|
+
* asks `assertMayMutateLiveState` first: a test process aimed at the account's real home is
|
|
7
|
+
* refused with a named error before a byte changes (`liveState.ts` says why that cannot be left
|
|
8
|
+
* to each runner).
|
|
9
|
+
*
|
|
10
|
+
* This is the "own a control surface, or do not touch it" rule applied to the real home. The
|
|
11
|
+
* atomic writer used to exist three times (device identity, credential envelope, profile
|
|
12
|
+
* consolidation), so a guard added to one left two doors open. `stateFileBoundary.spec.ts`
|
|
13
|
+
* fails when a file other than this one starts calling a raw `fs` mutator, so a fourth copy
|
|
14
|
+
* cannot appear unguarded.
|
|
15
|
+
*
|
|
16
|
+
* Outside a test process every function here is exactly the `fs` call it wraps.
|
|
17
|
+
*/
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
/** `mkdir -p` for a state directory, 0700 unless `mode` says otherwise. */
|
|
20
|
+
export declare function ensureStateDir(dir: string, what: string, mode?: number | null): void;
|
|
21
|
+
/**
|
|
22
|
+
* Atomic 0600 write — tmp+fsync+rename+dir-fsync. The same shape the daemon and CLI always used,
|
|
23
|
+
* so a partial-write race between `skrr` and `skrrd` produces the same on-disk transitions.
|
|
24
|
+
*/
|
|
25
|
+
export declare function atomicWrite0600(target: string, value: Buffer | string, what: string): void;
|
|
26
|
+
/** `fs.writeFileSync` for a state file: lock sentinels, a re-auth latch, a machine identity. */
|
|
27
|
+
export declare function writeStateFile(target: string, data: string | NodeJS.ArrayBufferView, options: fs.WriteFileOptions, what: string): void;
|
|
28
|
+
/** `fs.unlinkSync` for a state file. Throws what `unlinkSync` throws, ENOENT included. */
|
|
29
|
+
export declare function removeStateFile(target: string, what: string): void;
|
|
30
|
+
/** `fs.renameSync` between state paths; both ends are checked. */
|
|
31
|
+
export declare function renameStateFile(from: string, to: string, what: string): void;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var stateFiles_exports = {};
|
|
30
|
+
__export(stateFiles_exports, {
|
|
31
|
+
atomicWrite0600: () => atomicWrite0600,
|
|
32
|
+
ensureStateDir: () => ensureStateDir,
|
|
33
|
+
removeStateFile: () => removeStateFile,
|
|
34
|
+
renameStateFile: () => renameStateFile,
|
|
35
|
+
writeStateFile: () => writeStateFile
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(stateFiles_exports);
|
|
38
|
+
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
39
|
+
var import_node_path = __toESM(require("node:path"), 1);
|
|
40
|
+
var import_liveState = require("./liveState.js");
|
|
41
|
+
function ensureStateDir(dir, what, mode = 448) {
|
|
42
|
+
(0, import_liveState.assertMayMutateLiveState)(dir, what);
|
|
43
|
+
import_node_fs.default.mkdirSync(dir, mode === null ? { recursive: true } : { recursive: true, mode });
|
|
44
|
+
}
|
|
45
|
+
function atomicWrite0600(target, value, what) {
|
|
46
|
+
(0, import_liveState.assertMayMutateLiveState)(target, what);
|
|
47
|
+
const dir = import_node_path.default.dirname(target);
|
|
48
|
+
import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
49
|
+
const tmp = `${target}.tmp.${process.pid}.${Date.now()}`;
|
|
50
|
+
const buf = typeof value === "string" ? Buffer.from(value, "utf-8") : value;
|
|
51
|
+
const fd = import_node_fs.default.openSync(tmp, "w", 384);
|
|
52
|
+
try {
|
|
53
|
+
import_node_fs.default.writeSync(fd, buf);
|
|
54
|
+
import_node_fs.default.fsyncSync(fd);
|
|
55
|
+
} finally {
|
|
56
|
+
import_node_fs.default.closeSync(fd);
|
|
57
|
+
}
|
|
58
|
+
import_node_fs.default.renameSync(tmp, target);
|
|
59
|
+
try {
|
|
60
|
+
const dirFd = import_node_fs.default.openSync(dir, "r");
|
|
61
|
+
try {
|
|
62
|
+
import_node_fs.default.fsyncSync(dirFd);
|
|
63
|
+
} finally {
|
|
64
|
+
import_node_fs.default.closeSync(dirFd);
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function writeStateFile(target, data, options, what) {
|
|
70
|
+
(0, import_liveState.assertMayMutateLiveState)(target, what);
|
|
71
|
+
import_node_fs.default.writeFileSync(target, data, options);
|
|
72
|
+
}
|
|
73
|
+
function removeStateFile(target, what) {
|
|
74
|
+
(0, import_liveState.assertMayMutateLiveState)(target, what);
|
|
75
|
+
import_node_fs.default.unlinkSync(target);
|
|
76
|
+
}
|
|
77
|
+
function renameStateFile(from, to, what) {
|
|
78
|
+
(0, import_liveState.assertMayMutateLiveState)(from, what);
|
|
79
|
+
(0, import_liveState.assertMayMutateLiveState)(to, what);
|
|
80
|
+
import_node_fs.default.renameSync(from, to);
|
|
81
|
+
}
|
|
82
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
83
|
+
0 && (module.exports = {
|
|
84
|
+
atomicWrite0600,
|
|
85
|
+
ensureStateDir,
|
|
86
|
+
removeStateFile,
|
|
87
|
+
renameStateFile,
|
|
88
|
+
writeStateFile
|
|
89
|
+
});
|
|
@@ -61,6 +61,9 @@ export declare const PREVIEW_TOKEN_REDACTION: "pvw_[redacted]";
|
|
|
61
61
|
export declare const QR_SIGN_IN_REDACTION: "[redacted]";
|
|
62
62
|
/** Fields are protocol-specific; preserve ordinary request ids, error codes and metrics. */
|
|
63
63
|
export declare function isQrSignInSecretField(key: string): boolean;
|
|
64
|
+
export declare function isOAuthQuerySecretField(key: string): boolean;
|
|
65
|
+
export declare function isOAuthQueryContainerField(key: string): boolean;
|
|
66
|
+
export declare function isGraphHandoffCookieField(key: string): boolean;
|
|
64
67
|
/**
|
|
65
68
|
* Replace every credential that travels in a URL with a marker: a Hosted Machine preview token with
|
|
66
69
|
* {@link PREVIEW_TOKEN_REDACTION}, and a workspace invitation, platform invitation, member link or operator code with
|
|
@@ -20,6 +20,9 @@ var telemetryRedaction_exports = {};
|
|
|
20
20
|
__export(telemetryRedaction_exports, {
|
|
21
21
|
PREVIEW_TOKEN_REDACTION: () => PREVIEW_TOKEN_REDACTION,
|
|
22
22
|
QR_SIGN_IN_REDACTION: () => QR_SIGN_IN_REDACTION,
|
|
23
|
+
isGraphHandoffCookieField: () => isGraphHandoffCookieField,
|
|
24
|
+
isOAuthQueryContainerField: () => isOAuthQueryContainerField,
|
|
25
|
+
isOAuthQuerySecretField: () => isOAuthQuerySecretField,
|
|
23
26
|
isQrSignInSecretField: () => isQrSignInSecretField,
|
|
24
27
|
redactPreviewTokens: () => redactPreviewTokens,
|
|
25
28
|
redactPreviewTokensDeep: () => redactPreviewTokensDeep,
|
|
@@ -53,6 +56,18 @@ function isQrSignInSecretField(key) {
|
|
|
53
56
|
return QR_SECRET_FIELDS.has(leaf.replace(/[_-]/g, "").toLowerCase());
|
|
54
57
|
}
|
|
55
58
|
const QR_SECRET_ASSIGNMENT = /((?:["']?)(?:statusToken|claimToken|codeVerifier|codeChallenge|comparisonCode|status_token|claim_token|code_verifier|code_challenge|comparison_code)(?:["']?)\s*[:=]\s*["']?)([A-Za-z0-9._~%+-]+)/gi;
|
|
59
|
+
const OAUTH_QUERY_SECRET = /(^|[?&]|%(?:25)*26)(code|state|error_description|handoff)(=|%(?:25)*3[dD])([^&#\s"'<>]*)/gi;
|
|
60
|
+
function isOAuthQuerySecretField(key) {
|
|
61
|
+
return ["code", "state", "error_description", "handoff"].includes(key.toLowerCase());
|
|
62
|
+
}
|
|
63
|
+
function isOAuthQueryContainerField(key) {
|
|
64
|
+
const leaf = (key.split(".").pop() || "").replace(/[_-]/g, "").toLowerCase();
|
|
65
|
+
return leaf === "query" || leaf === "querystring";
|
|
66
|
+
}
|
|
67
|
+
function isGraphHandoffCookieField(key) {
|
|
68
|
+
return key.toLowerCase() === "__secure-pc-graph-handoff";
|
|
69
|
+
}
|
|
70
|
+
const GRAPH_HANDOFF_COOKIE = /((?:_|%(?:25)*5[fF]){2}secure(?:-|%(?:25)*2[dD])pc(?:-|%(?:25)*2[dD])graph(?:-|%(?:25)*2[dD])handoff(?:=|%(?:25)*3[dD]))([^;,&\s"'<>]+)/gi;
|
|
56
71
|
function redactQrSignInText(value) {
|
|
57
72
|
const standaloneQuery = /(?:^|[?&])direction=(?:mobile_approves_web|web_approves_mobile)(?:&|$)/.test(value) ? value.replace(QR_REQUEST_QUERY, `$1$2${QR_SIGN_IN_REDACTION}`) : value;
|
|
58
73
|
return standaloneQuery.replace(QR_REQUEST_PATH, `$1${QR_SIGN_IN_REDACTION}`).replace(QR_LINK_QUERY, (_match, prefix, query) => prefix + query.replace(QR_REQUEST_QUERY, `$1$2${QR_SIGN_IN_REDACTION}`)).replace(QR_SECRET_ASSIGNMENT, `$1${QR_SIGN_IN_REDACTION}`);
|
|
@@ -62,7 +77,7 @@ const credentialMarker = (_match, prefix) => `${prefix}_[redacted]`;
|
|
|
62
77
|
function redactPreviewTokens(value) {
|
|
63
78
|
if (typeof value !== "string")
|
|
64
79
|
return value;
|
|
65
|
-
const qrSafe = redactQrSignInText(value);
|
|
80
|
+
const qrSafe = redactQrSignInText(value).replace(OAUTH_QUERY_SECRET, "$1$2$3[redacted]").replace(GRAPH_HANDOFF_COOKIE, "$1[redacted]");
|
|
66
81
|
if (!CREDENTIAL_HINTS.some((hint) => qrSafe.indexOf(hint) !== -1))
|
|
67
82
|
return qrSafe;
|
|
68
83
|
return qrSafe.replace(PREVIEW_TOKEN, PREVIEW_TOKEN_REDACTION).replace(SIGNED_CREDENTIAL, credentialMarker).replace(LEGACY_OPERATOR_CODE, credentialMarker);
|
|
@@ -81,7 +96,7 @@ function redactPreviewTokensDeep(root, limits = {}) {
|
|
|
81
96
|
let visited = 0;
|
|
82
97
|
let complete = true;
|
|
83
98
|
const rewrite = (holder, key, current) => {
|
|
84
|
-
const next = isQrSignInSecretField(key) ? QR_SIGN_IN_REDACTION : redactPreviewTokens(current);
|
|
99
|
+
const next = isQrSignInSecretField(key) || isGraphHandoffCookieField(key) ? QR_SIGN_IN_REDACTION : redactPreviewTokens(current);
|
|
85
100
|
if (next === current) {
|
|
86
101
|
return;
|
|
87
102
|
}
|
|
@@ -119,7 +134,27 @@ function redactPreviewTokensDeep(root, limits = {}) {
|
|
|
119
134
|
complete = false;
|
|
120
135
|
continue;
|
|
121
136
|
}
|
|
122
|
-
if (
|
|
137
|
+
if (isOAuthQueryContainerField(key) && Array.isArray(child)) {
|
|
138
|
+
try {
|
|
139
|
+
holder[key] = "[redacted]";
|
|
140
|
+
} catch {
|
|
141
|
+
complete = false;
|
|
142
|
+
}
|
|
143
|
+
if (holder[key] !== "[redacted]")
|
|
144
|
+
complete = false;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (isOAuthQueryContainerField(key) && child && typeof child === "object" && !Array.isArray(child)) {
|
|
148
|
+
const query = child;
|
|
149
|
+
for (const queryKey of Object.keys(query)) {
|
|
150
|
+
if (isOAuthQuerySecretField(queryKey)) {
|
|
151
|
+
query[queryKey] = "[redacted]";
|
|
152
|
+
if (query[queryKey] !== "[redacted]")
|
|
153
|
+
complete = false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (isQrSignInSecretField(key) || isGraphHandoffCookieField(key) || typeof child === "string") {
|
|
123
158
|
rewrite(holder, key, child);
|
|
124
159
|
} else if (child !== null && typeof child === "object") {
|
|
125
160
|
if (depth + 1 > maxDepth) {
|
|
@@ -166,6 +201,9 @@ function withPreviewTokenRedaction(options) {
|
|
|
166
201
|
0 && (module.exports = {
|
|
167
202
|
PREVIEW_TOKEN_REDACTION,
|
|
168
203
|
QR_SIGN_IN_REDACTION,
|
|
204
|
+
isGraphHandoffCookieField,
|
|
205
|
+
isOAuthQueryContainerField,
|
|
206
|
+
isOAuthQuerySecretField,
|
|
169
207
|
isQrSignInSecretField,
|
|
170
208
|
redactPreviewTokens,
|
|
171
209
|
redactPreviewTokensDeep,
|
|
@@ -67,6 +67,12 @@ export interface TokenPair {
|
|
|
67
67
|
* (server code `DEVICE_PROOF_THUMBPRINT_MISMATCH`).
|
|
68
68
|
* The daemon signs every retry with the same key, so
|
|
69
69
|
* retrying can never succeed.
|
|
70
|
+
* - DEVICE_KEY_UNAVAILABLE — the server demanded a device proof
|
|
71
|
+
* (`DEVICE_PROOF_MISSING`) and this process had none
|
|
72
|
+
* to send: the key is unreadable, never created, or no
|
|
73
|
+
* signer is wired. Unlike `DEVICE_KEY_MISMATCH` there is
|
|
74
|
+
* no second key to reconcile; the fix is an interactive
|
|
75
|
+
* daemon sign-in, which mints and re-pins a new one.
|
|
70
76
|
* - UNKNOWN_401 — server returned 401 with a code we don't
|
|
71
77
|
* recognize. Treated as permanent (better to prompt
|
|
72
78
|
* re-auth than to loop forever) but tagged
|
|
@@ -79,7 +85,7 @@ export interface TokenPair {
|
|
|
79
85
|
* Without this, the client `describeAuthFailure` exhaustiveness check
|
|
80
86
|
* was silently broken for the non-REFRESH branches.
|
|
81
87
|
*/
|
|
82
|
-
export type PermanentAuthReason = 'REFRESH_INVALID' | 'REFRESH_REUSED' | 'REFRESH_EXPIRED' | 'SESSION_REVOKED' | 'DEVICE_KEY_MISMATCH' | 'TOKEN_INVALID' | 'TOKEN_EXPIRED' | 'UNKNOWN_401';
|
|
88
|
+
export type PermanentAuthReason = 'REFRESH_INVALID' | 'REFRESH_REUSED' | 'REFRESH_EXPIRED' | 'SESSION_REVOKED' | 'DEVICE_KEY_MISMATCH' | 'DEVICE_KEY_UNAVAILABLE' | 'TOKEN_INVALID' | 'TOKEN_EXPIRED' | 'UNKNOWN_401';
|
|
83
89
|
export declare class PermanentAuthFailure extends Error {
|
|
84
90
|
readonly code: PermanentAuthReason;
|
|
85
91
|
/**
|
|
@@ -161,7 +161,11 @@ export type DaemonSupervisor = 'desktop' | 'service';
|
|
|
161
161
|
export declare function daemonSignInRemedy(opts?: {
|
|
162
162
|
/** How the daemon is run; absent when the caller cannot tell. */
|
|
163
163
|
supervisor?: DaemonSupervisor;
|
|
164
|
-
/**
|
|
164
|
+
/**
|
|
165
|
+
* The exact login command for this profile (`skrrd … login`, or
|
|
166
|
+
* `skrr daemon login --profile …`), when the caller knows it. Used when the
|
|
167
|
+
* supervisor is `service` or unknown; a desktop-hosted daemon is never told one.
|
|
168
|
+
*/
|
|
165
169
|
loginCommand?: string;
|
|
166
170
|
/** The refusal was DEVICE_PROOF_PIN_MISMATCH: say that a sign-in re-pins. */
|
|
167
171
|
repinsDeviceKey?: boolean;
|
|
@@ -167,7 +167,10 @@ export function daemonSignInRemedy(opts = {}) {
|
|
|
167
167
|
const login = opts.loginCommand ? `\`${opts.loginCommand}\`` : '`skrr daemon login`';
|
|
168
168
|
return `run ${login}${repin ? ' — an interactive login re-pins the device key' : ''}`;
|
|
169
169
|
}
|
|
170
|
+
// A caller that cannot tell who runs the daemon may still know which profile it
|
|
171
|
+
// is on: `skrr daemon login` with no `--profile` signs the DEFAULT daemon in
|
|
172
|
+
// (OSK-16435), so the command it names is the one it was given.
|
|
170
173
|
return ('sign the daemon in again on the machine running it: if the Skrr desktop app runs it, quit ' +
|
|
171
|
-
|
|
174
|
+
`and reopen the app; otherwise run \`${opts.loginCommand ?? 'skrr daemon login'}\`` +
|
|
172
175
|
(repin ? ' (either one re-pins the device key)' : ''));
|
|
173
176
|
}
|