@mnemonik/cli 7.112.1 → 7.112.4
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/auth/credentials.d.ts +7 -2
- package/dist/auth/credentials.d.ts.map +1 -1
- package/dist/auth/credentials.js +308 -96
- package/dist/auth/index.d.ts +15 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +69 -13
- package/dist/auth/index.js.map +1 -1
- package/dist/diagnostics.js +312 -100
- package/dist/digests.json +45 -45
- package/dist/identity/migrate.js +37 -0
- package/dist/install/journal.js +37 -0
- package/dist/install/journey.js +37 -0
- package/dist/install/ownership.js +37 -0
- package/dist/install/transaction.d.ts +1 -3
- package/dist/install/transaction.d.ts.map +1 -1
- package/dist/install/transaction.js +64 -14
- package/dist/install/transaction.js.map +1 -1
- package/dist/install/ui.js +37 -0
- package/dist/installation.js +37 -0
- package/dist/launcher.js +37 -0
- package/dist/preflight.js +1 -1
- package/dist/project/eligibility.js +37 -0
- package/dist/project/records.js +37 -0
- package/dist/project.d.ts +11 -2
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +43 -2
- package/dist/project.js.map +1 -1
- package/dist/router.js +38 -1
- package/dist/runtime/releaseSource.js +37 -0
- package/dist/runtime/selfUpdate.js +37 -0
- package/dist/runtime/signers.js +242 -50
- package/dist/runtime/store.js +34 -0
- package/dist/runtime/updateCheck.js +37 -0
- package/dist/sbom.json +21 -21
- package/dist/scanner/control.js +37 -0
- package/dist/scanner/discover.js +1 -1
- package/dist/scanner/enable.js +37 -0
- package/dist/scanner/service.js +37 -0
- package/dist/scanner/update.js +37 -0
- package/dist/scanner-release.json +29 -29
- package/dist/status.js +41 -4
- package/dist/transport/server.d.ts +21 -2
- package/dist/transport/server.d.ts.map +1 -1
- package/dist/transport/server.js +15 -1
- package/dist/transport/server.js.map +1 -1
- package/dist/vendor/local-setup.js +37 -0
- package/package.json +31 -31
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { type CredentialAdapterOptions, type CredentialStoreKind } from '@mnemonik/credentials';
|
|
2
2
|
/** Every CLI OAuth consumer uses the same process-scoped OS store. */
|
|
3
3
|
export declare function createCliCredentials(options?: CredentialAdapterOptions): {
|
|
4
|
-
putCliOAuth: (metadata: import("@mnemonik/credentials").CliOAuthMetadata, tokens: import("@mnemonik/credentials").CliOAuthTokens | string) => Promise<{
|
|
4
|
+
putCliOAuth: (metadata: import("@mnemonik/credentials").CliOAuthMetadata, tokens: import("@mnemonik/credentials").CliOAuthTokens | string) => ReturnType<(metadata: import("@mnemonik/credentials").CliOAuthMetadata, tokens: import("@mnemonik/credentials").CliOAuthTokens | string) => Promise<{
|
|
5
5
|
store: string;
|
|
6
|
-
}
|
|
6
|
+
}>>;
|
|
7
7
|
readCliOAuth: () => Promise<import("@mnemonik/credentials").CliOAuthCredential | null>;
|
|
8
8
|
rotateCli: (transport: import("@mnemonik/credentials").CliCredentialTransport) => Promise<import("@mnemonik/credentials").ActionRequired | import("@mnemonik/credentials").RetryLater | import("@mnemonik/credentials").CliOAuthCredential>;
|
|
9
9
|
withCliCredential: <T>(transport: import("@mnemonik/credentials").CliCredentialTransport, work: (accessToken: string) => Promise<import("@mnemonik/credentials").WorkResponse<T>>) => Promise<import("@mnemonik/credentials").WorkResponse<T> | import("@mnemonik/credentials").ActionRequired | import("@mnemonik/credentials").RetryLater>;
|
|
10
10
|
removeCliOAuth: () => Promise<void>;
|
|
11
|
+
revokeCli: (transport: import("@mnemonik/credentials").CliRevocationTransport) => Promise<import("@mnemonik/credentials").ActionRequired | import("@mnemonik/credentials").RetryLater | {
|
|
12
|
+
status: 'revoked';
|
|
13
|
+
familyId: string;
|
|
14
|
+
}>;
|
|
15
|
+
waitForCliLease: () => Promise<void>;
|
|
11
16
|
putFamily: (componentKind: import("@mnemonik/credentials").ComponentKind, response: import("@mnemonik/credentials").ComponentCredentialResponse) => Promise<import("@mnemonik/credentials").FamilyCredential>;
|
|
12
17
|
readFamily: (familyId: string) => Promise<import("@mnemonik/credentials").FamilyCredential | null>;
|
|
13
18
|
rotateFamily: (familyId: string, transport: import("@mnemonik/credentials").CredentialTransport) => Promise<import("@mnemonik/credentials").RotationResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/auth/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACzB,MAAM,uBAAuB,CAAC;AAG/B,sEAAsE;AACtE,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,wBAA6B
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/auth/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACzB,MAAM,uBAAuB,CAAC;AAG/B,sEAAsE;AACtE,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,wBAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQ1E;AAED,wBAAsB,mBAAmB,CAAC,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC;IACzF,KAAK,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC,CAmBD"}
|
package/dist/auth/credentials.js
CHANGED
|
@@ -501,6 +501,13 @@ var require_ignore = __commonJS({
|
|
|
501
501
|
}
|
|
502
502
|
});
|
|
503
503
|
|
|
504
|
+
// packages/shared/dist/hookTimeouts.js
|
|
505
|
+
var init_hookTimeouts = __esm({
|
|
506
|
+
"packages/shared/dist/hookTimeouts.js"() {
|
|
507
|
+
"use strict";
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
|
|
504
511
|
// packages/shared/dist/runtimeSigners.js
|
|
505
512
|
import { lstatSync, mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync } from "node:fs";
|
|
506
513
|
import { basename, dirname as dirname2, join as join2, resolve, sep, win32 } from "node:path";
|
|
@@ -747,11 +754,37 @@ var init_runtimeReader = __esm({
|
|
|
747
754
|
});
|
|
748
755
|
|
|
749
756
|
// packages/shared/dist/projectSetupHandoff.js
|
|
757
|
+
var PROJECT_ENSURE_TIMEOUT_MS, SUPERVISOR, RUN_STALE_MS;
|
|
750
758
|
var init_projectSetupHandoff = __esm({
|
|
751
759
|
"packages/shared/dist/projectSetupHandoff.js"() {
|
|
752
760
|
"use strict";
|
|
753
761
|
init_runtimeSigners();
|
|
762
|
+
init_hookTimeouts();
|
|
754
763
|
init_runtimeReader();
|
|
764
|
+
PROJECT_ENSURE_TIMEOUT_MS = 3e4;
|
|
765
|
+
SUPERVISOR = `
|
|
766
|
+
const { execFile } = require('node:child_process');
|
|
767
|
+
const { writeFileSync, renameSync } = require('node:fs');
|
|
768
|
+
const [out, bin, ...args] = process.argv.slice(1);
|
|
769
|
+
let input = '';
|
|
770
|
+
process.stdin.setEncoding('utf8');
|
|
771
|
+
process.stdin.on('data', (chunk) => (input += chunk));
|
|
772
|
+
process.stdin.on('end', () => {
|
|
773
|
+
const child = execFile(
|
|
774
|
+
process.execPath,
|
|
775
|
+
[bin, ...args],
|
|
776
|
+
{ shell: false, timeout: ${PROJECT_ENSURE_TIMEOUT_MS}, killSignal: 'SIGKILL', maxBuffer: 64 * 1024, env: process.env },
|
|
777
|
+
(error, stdout) => {
|
|
778
|
+
const result = { code: error ? (typeof error.code === 'number' ? error.code : null) : 0, killed: !!(error && error.killed), stdout: String(stdout) };
|
|
779
|
+
writeFileSync(out + '.tmp', JSON.stringify(result), { mode: 0o600 });
|
|
780
|
+
renameSync(out + '.tmp', out);
|
|
781
|
+
}
|
|
782
|
+
);
|
|
783
|
+
child.stdin.on('error', () => {});
|
|
784
|
+
child.stdin.end(input);
|
|
785
|
+
});
|
|
786
|
+
`;
|
|
787
|
+
RUN_STALE_MS = PROJECT_ENSURE_TIMEOUT_MS + 1e4;
|
|
755
788
|
}
|
|
756
789
|
});
|
|
757
790
|
|
|
@@ -760,6 +793,7 @@ var init_hookRuntime = __esm({
|
|
|
760
793
|
"packages/shared/dist/hookRuntime.js"() {
|
|
761
794
|
"use strict";
|
|
762
795
|
init_runtimeSigners();
|
|
796
|
+
init_hookTimeouts();
|
|
763
797
|
init_repositoryRoot();
|
|
764
798
|
init_runtimeReader();
|
|
765
799
|
init_runtimeSigners();
|
|
@@ -2345,8 +2379,9 @@ var require_proper_lockfile = __commonJS({
|
|
|
2345
2379
|
});
|
|
2346
2380
|
|
|
2347
2381
|
// packages/credentials/dist/index.js
|
|
2348
|
-
import { createHmac, randomBytes } from "node:crypto";
|
|
2349
|
-
import {
|
|
2382
|
+
import { createHash as createHash4, createHmac, randomBytes } from "node:crypto";
|
|
2383
|
+
import { realpath } from "node:fs/promises";
|
|
2384
|
+
import { basename as basename3, dirname as dirname5, join as join6 } from "node:path";
|
|
2350
2385
|
|
|
2351
2386
|
// packages/shared/dist/instructions.js
|
|
2352
2387
|
var INSTRUCTIONS_CONTENT = `Mnemonik provides persistent project memory: decisions, rationale, tasks, and policies across sessions. If no block begins "PROJECT_CONTEXT schemaVersion=", session_bootstrap({ cwd }) loads project context when cwd is the real absolute path of the project root; a placeholder or relative path fails. With that block, context is loaded and no session_bootstrap call is needed. Code and docs show current artifacts, git shows changes, and memory carries the why; memory_search recalls rationale or intent, then Grep or Read verifies current source.
|
|
@@ -6531,6 +6566,9 @@ var DEFAULT_OPTIONS = {
|
|
|
6531
6566
|
};
|
|
6532
6567
|
var BUILT_IN_IGNORE_DIR_SET = new Set(BUILT_IN_IGNORE_DIRS);
|
|
6533
6568
|
|
|
6569
|
+
// packages/shared/dist/index.js
|
|
6570
|
+
init_hookTimeouts();
|
|
6571
|
+
|
|
6534
6572
|
// packages/shared/dist/jitDeliveryAck.js
|
|
6535
6573
|
init_runtimeSigners();
|
|
6536
6574
|
|
|
@@ -6818,16 +6856,17 @@ function isCredentialSessionUnavailableError(error) {
|
|
|
6818
6856
|
// packages/credentials/dist/storage.js
|
|
6819
6857
|
import { createHash as createHash3 } from "node:crypto";
|
|
6820
6858
|
import { lstat as nodeLstat, mkdir as mkdir2, open as open2, readdir, rmdir as rmdir2, unlink as unlink2 } from "node:fs/promises";
|
|
6821
|
-
import { constants } from "node:fs";
|
|
6859
|
+
import { constants, realpathSync as realpathSync2 } from "node:fs";
|
|
6822
6860
|
import { basename as basename2, dirname as dirname4, isAbsolute, join as join5, parse, relative, resolve as resolve2, sep as sep2 } from "node:path";
|
|
6823
6861
|
var CredentialError = class extends Error {
|
|
6824
6862
|
reason;
|
|
6825
|
-
constructor(reason) {
|
|
6826
|
-
super(
|
|
6863
|
+
constructor(reason, message = reason) {
|
|
6864
|
+
super(message);
|
|
6827
6865
|
this.reason = reason;
|
|
6828
6866
|
this.name = "CredentialError";
|
|
6829
6867
|
}
|
|
6830
6868
|
};
|
|
6869
|
+
var STATE_ROOT_LINK_MESSAGE = "The Mnemonik state directory is a symbolic link. Replace it with a real directory, or set MNEMONIK_STATE_DIR to one.";
|
|
6831
6870
|
var digest = (value) => createHash3("sha256").update(value).digest("hex");
|
|
6832
6871
|
function credentialPaths(stateDir = stateDirectory(), familyId) {
|
|
6833
6872
|
const root = join5(stateDir, "credentials");
|
|
@@ -6847,8 +6886,25 @@ function credentialPaths(stateDir = stateDirectory(), familyId) {
|
|
|
6847
6886
|
};
|
|
6848
6887
|
}
|
|
6849
6888
|
var codeIs2 = (error, code) => error.code === code;
|
|
6889
|
+
function canonicalDirectory(path) {
|
|
6890
|
+
const missing = [];
|
|
6891
|
+
let current2 = resolve2(path);
|
|
6892
|
+
for (; ; ) {
|
|
6893
|
+
try {
|
|
6894
|
+
return join5(realpathSync2.native(current2), ...missing.reverse());
|
|
6895
|
+
} catch (error) {
|
|
6896
|
+
const parent = dirname4(current2);
|
|
6897
|
+
if (!codeIs2(error, "ENOENT") || parent === current2)
|
|
6898
|
+
return resolve2(path);
|
|
6899
|
+
missing.push(basename2(current2));
|
|
6900
|
+
current2 = parent;
|
|
6901
|
+
}
|
|
6902
|
+
}
|
|
6903
|
+
}
|
|
6850
6904
|
var SecureFiles = class {
|
|
6851
6905
|
stateDir;
|
|
6906
|
+
/** The root as configured; paths spelled through it are rebased onto `stateDir`. */
|
|
6907
|
+
configuredStateDir;
|
|
6852
6908
|
platform;
|
|
6853
6909
|
lstat;
|
|
6854
6910
|
uid;
|
|
@@ -6857,7 +6913,8 @@ var SecureFiles = class {
|
|
|
6857
6913
|
username;
|
|
6858
6914
|
aclRun;
|
|
6859
6915
|
constructor(options = {}) {
|
|
6860
|
-
this.
|
|
6916
|
+
this.configuredStateDir = resolve2(options.stateDir ?? stateDirectory());
|
|
6917
|
+
this.stateDir = join5(canonicalDirectory(dirname4(this.configuredStateDir)), basename2(this.configuredStateDir));
|
|
6861
6918
|
this.platform = options.platform ?? process.platform;
|
|
6862
6919
|
this.lstat = options.lstat ?? nodeLstat;
|
|
6863
6920
|
this.uid = options.uid ?? process.getuid?.();
|
|
@@ -6867,7 +6924,10 @@ var SecureFiles = class {
|
|
|
6867
6924
|
this.aclRun = options.aclRun;
|
|
6868
6925
|
}
|
|
6869
6926
|
assertInsideState(path) {
|
|
6870
|
-
|
|
6927
|
+
let absolute = resolve2(path);
|
|
6928
|
+
const fromConfigured = relative(this.configuredStateDir, absolute);
|
|
6929
|
+
if (this.configuredStateDir !== this.stateDir && fromConfigured !== ".." && !fromConfigured.startsWith(`..${sep2}`) && !isAbsolute(fromConfigured))
|
|
6930
|
+
absolute = join5(this.stateDir, fromConfigured);
|
|
6871
6931
|
const fromState = relative(this.stateDir, absolute);
|
|
6872
6932
|
if (fromState === ".." || fromState.startsWith(`..${sep2}`) || isAbsolute(fromState))
|
|
6873
6933
|
throw new CredentialError("path_outside_state");
|
|
@@ -6888,6 +6948,8 @@ var SecureFiles = class {
|
|
|
6888
6948
|
async inspect(path, expectFile, allowMissing) {
|
|
6889
6949
|
const absolute = this.assertInsideState(path);
|
|
6890
6950
|
for (const component of this.components(absolute)) {
|
|
6951
|
+
if (component !== this.stateDir && !component.startsWith(`${this.stateDir}${sep2}`))
|
|
6952
|
+
continue;
|
|
6891
6953
|
let value;
|
|
6892
6954
|
try {
|
|
6893
6955
|
value = await this.lstat(component);
|
|
@@ -6897,10 +6959,7 @@ var SecureFiles = class {
|
|
|
6897
6959
|
throw error;
|
|
6898
6960
|
}
|
|
6899
6961
|
if (value.isSymbolicLink())
|
|
6900
|
-
throw new CredentialError("symlink_rejected");
|
|
6901
|
-
const protectedComponent = component === this.stateDir || component.startsWith(`${this.stateDir}${sep2}`);
|
|
6902
|
-
if (!protectedComponent)
|
|
6903
|
-
continue;
|
|
6962
|
+
throw new CredentialError("symlink_rejected", component === this.stateDir ? STATE_ROOT_LINK_MESSAGE : void 0);
|
|
6904
6963
|
const final = component === absolute;
|
|
6905
6964
|
if (this.platform === "win32") {
|
|
6906
6965
|
if (final)
|
|
@@ -7238,6 +7297,7 @@ var action = (familyId, reason) => ({
|
|
|
7238
7297
|
reason,
|
|
7239
7298
|
familyId
|
|
7240
7299
|
});
|
|
7300
|
+
var isSecretMissing = (error) => error instanceof Error && error.message === "credential_secret_missing";
|
|
7241
7301
|
var retryLater = (familyId, reason) => ({
|
|
7242
7302
|
status: "RETRY_LATER",
|
|
7243
7303
|
reason,
|
|
@@ -7245,12 +7305,15 @@ var retryLater = (familyId, reason) => ({
|
|
|
7245
7305
|
});
|
|
7246
7306
|
var isFailure = (result) => "status" in result && (result.status === "ACTION_REQUIRED" || result.status === "RETRY_LATER");
|
|
7247
7307
|
var diagnosedStores = /* @__PURE__ */ new WeakSet();
|
|
7308
|
+
var familyFileName = /^([0-9a-f]{64})\.json(?:\.[0-9a-f-]{36}\.tmp)?$/;
|
|
7309
|
+
var temporaryOf = (base, name2) => name2.startsWith(`${base}.`) && /^\.[0-9a-f-]{36}\.tmp$/.test(name2.slice(base.length));
|
|
7248
7310
|
function createCredentialAdapter(options = {}) {
|
|
7249
7311
|
const files = new SecureFiles({ ...options, stateDir: options.stateDir ?? stateDirectory() });
|
|
7250
7312
|
const paths = credentialPaths(files.stateDir);
|
|
7251
7313
|
const now = options.now ?? Date.now;
|
|
7252
7314
|
const sleep = options.sleep ?? wait;
|
|
7253
7315
|
const retryLimit = options.retryLimit ?? 2;
|
|
7316
|
+
const lockWaitMs = options.lockWaitMs ?? 6e4;
|
|
7254
7317
|
const store = options.secretStore;
|
|
7255
7318
|
const rotations = /* @__PURE__ */ new Map();
|
|
7256
7319
|
let osAvailable;
|
|
@@ -7266,7 +7329,10 @@ function createCredentialAdapter(options = {}) {
|
|
|
7266
7329
|
disable();
|
|
7267
7330
|
return ok;
|
|
7268
7331
|
}) : Promise.resolve(false);
|
|
7269
|
-
const reference = (name2) =>
|
|
7332
|
+
const reference = async (name2) => {
|
|
7333
|
+
const directory = createHash4("sha256").update(await realpath(files.stateDir));
|
|
7334
|
+
return `mnemonik.credentials.v1:${name2}:${directory.digest("hex").slice(0, 32)}`;
|
|
7335
|
+
};
|
|
7270
7336
|
const relativeSecret = (path) => path.slice(files.stateDir.length + 1);
|
|
7271
7337
|
const fileBackend = {
|
|
7272
7338
|
store: "file",
|
|
@@ -7298,7 +7364,7 @@ function createCredentialAdapter(options = {}) {
|
|
|
7298
7364
|
async function locator(name2, file, existing) {
|
|
7299
7365
|
if (existing?.store === "os")
|
|
7300
7366
|
return existing;
|
|
7301
|
-
return name2 === "cli-oauth" && await osBackend.isAvailable() ? { store: "os", secretRef: reference(name2) } : { store: "file", secretFile: relativeSecret(file) };
|
|
7367
|
+
return name2 === "cli-oauth" && await osBackend.isAvailable() ? { store: "os", secretRef: await reference(name2) } : { store: "file", secretFile: relativeSecret(file) };
|
|
7302
7368
|
}
|
|
7303
7369
|
const useFile = (location, file) => {
|
|
7304
7370
|
Object.assign(location, { store: "file", secretFile: relativeSecret(file) });
|
|
@@ -7352,6 +7418,14 @@ function createCredentialAdapter(options = {}) {
|
|
|
7352
7418
|
return bytes ? parse2(bytes, kind) : null;
|
|
7353
7419
|
}
|
|
7354
7420
|
async function putCliOAuth(metadata2, tokens) {
|
|
7421
|
+
await files.ensureParent(paths.cliRecord);
|
|
7422
|
+
return leased(paths.cliRecord, () => writeCliOAuth(metadata2, tokens), () => putCliOAuth(metadata2, tokens));
|
|
7423
|
+
}
|
|
7424
|
+
async function waitForCliLease() {
|
|
7425
|
+
await files.ensureParent(paths.cliRecord);
|
|
7426
|
+
await leased(paths.cliRecord, async () => void 0, () => void 0);
|
|
7427
|
+
}
|
|
7428
|
+
async function writeCliOAuth(metadata2, tokens) {
|
|
7355
7429
|
const current2 = await readRecord(paths.cliRecord, "cli-oauth");
|
|
7356
7430
|
const location = await locator("cli-oauth", paths.cliSecret, current2 ?? void 0);
|
|
7357
7431
|
const secret = typeof tokens === "string" ? { accessToken: "", refreshToken: tokens, accessExpiresAt: (/* @__PURE__ */ new Date(0)).toISOString() } : tokens;
|
|
@@ -7418,8 +7492,11 @@ function createCredentialAdapter(options = {}) {
|
|
|
7418
7492
|
}
|
|
7419
7493
|
async function putFamily(componentKind, response) {
|
|
7420
7494
|
const familyPaths = credentialPaths(files.stateDir, response.id);
|
|
7421
|
-
|
|
7422
|
-
return
|
|
7495
|
+
await files.ensureParent(familyPaths.record);
|
|
7496
|
+
return leased(familyPaths.record, async () => {
|
|
7497
|
+
const current2 = await readRecord(familyPaths.record, "component");
|
|
7498
|
+
return persistFamily(componentKind, response, current2 ?? void 0);
|
|
7499
|
+
}, () => putFamily(componentKind, response));
|
|
7423
7500
|
}
|
|
7424
7501
|
async function readFamily(familyId) {
|
|
7425
7502
|
const familyPaths = credentialPaths(files.stateDir, familyId);
|
|
@@ -7439,49 +7516,73 @@ function createCredentialAdapter(options = {}) {
|
|
|
7439
7516
|
void _file;
|
|
7440
7517
|
return { ...publicMetadata, ...secret };
|
|
7441
7518
|
}
|
|
7442
|
-
async function requestRotation(familyId, request) {
|
|
7443
|
-
const started = now();
|
|
7444
|
-
let lostResponseRetried = false;
|
|
7445
|
-
let retryableAttempt = 0;
|
|
7519
|
+
async function requestRotation(familyId, request, state) {
|
|
7446
7520
|
while (true) {
|
|
7447
7521
|
let response;
|
|
7448
7522
|
try {
|
|
7449
7523
|
response = await request();
|
|
7450
7524
|
} catch {
|
|
7451
|
-
if (lostResponseRetried)
|
|
7525
|
+
if (state.lostResponseRetried)
|
|
7452
7526
|
return action(familyId, "rotation_response_lost");
|
|
7453
|
-
lostResponseRetried = true;
|
|
7527
|
+
state.lostResponseRetried = true;
|
|
7454
7528
|
await sleep(250);
|
|
7455
|
-
if (now() > started + 6e4)
|
|
7529
|
+
if (now() > state.started + 6e4)
|
|
7456
7530
|
return action(familyId, "rotation_response_lost");
|
|
7457
7531
|
continue;
|
|
7458
7532
|
}
|
|
7459
7533
|
if (response.status === 400 && response.body.error === "invalid_grant")
|
|
7460
7534
|
return action(familyId, "invalid_grant");
|
|
7461
7535
|
if (response.status === 429 || response.status >= 500) {
|
|
7462
|
-
if (lostResponseRetried)
|
|
7536
|
+
if (state.lostResponseRetried)
|
|
7463
7537
|
return action(familyId, "rotation_response_lost");
|
|
7464
|
-
if (retryableAttempt >= retryLimit)
|
|
7538
|
+
if (state.retryableAttempt >= retryLimit)
|
|
7465
7539
|
return retryLater(familyId, response.status === 429 ? "rate_limited" : "server_error");
|
|
7466
|
-
const delay2 = response.retryAfterMs ?? 250 * 2 ** retryableAttempt;
|
|
7467
|
-
retryableAttempt += 1;
|
|
7468
|
-
|
|
7469
|
-
continue;
|
|
7540
|
+
const delay2 = response.retryAfterMs ?? 250 * 2 ** state.retryableAttempt;
|
|
7541
|
+
state.retryableAttempt += 1;
|
|
7542
|
+
return { backoffMs: delay2 };
|
|
7470
7543
|
}
|
|
7471
7544
|
return response;
|
|
7472
7545
|
}
|
|
7473
7546
|
}
|
|
7547
|
+
async function leased(recordPath2, work, vanished) {
|
|
7548
|
+
let entered = false;
|
|
7549
|
+
try {
|
|
7550
|
+
return await withLock(recordPath2, lockWaitMs, () => {
|
|
7551
|
+
entered = true;
|
|
7552
|
+
return work();
|
|
7553
|
+
});
|
|
7554
|
+
} catch (error) {
|
|
7555
|
+
if (entered || error.code !== "ENOENT")
|
|
7556
|
+
throw error;
|
|
7557
|
+
return vanished();
|
|
7558
|
+
}
|
|
7559
|
+
}
|
|
7474
7560
|
async function rotateStored(lockPath, missingId, read, request, decode, persist) {
|
|
7475
7561
|
const predecessor = await read();
|
|
7476
7562
|
if (!predecessor)
|
|
7477
7563
|
return action(missingId, "family_missing");
|
|
7478
|
-
|
|
7564
|
+
const attempts = {
|
|
7565
|
+
started: now(),
|
|
7566
|
+
lostResponseRetried: false,
|
|
7567
|
+
retryableAttempt: 0
|
|
7568
|
+
};
|
|
7569
|
+
for (; ; ) {
|
|
7570
|
+
const outcome = await rotateStoredOnce(lockPath, predecessor, read, request, decode, persist, attempts);
|
|
7571
|
+
if (!("backoffMs" in outcome))
|
|
7572
|
+
return outcome;
|
|
7573
|
+
await sleep(outcome.backoffMs);
|
|
7574
|
+
}
|
|
7575
|
+
}
|
|
7576
|
+
async function rotateStoredOnce(lockPath, predecessor, read, request, decode, persist, attempts) {
|
|
7577
|
+
return leased(lockPath, async () => {
|
|
7479
7578
|
const current2 = await read();
|
|
7480
7579
|
if (!current2)
|
|
7481
7580
|
return action(predecessor.familyId, "family_missing");
|
|
7482
7581
|
if (current2.refreshToken !== predecessor.refreshToken)
|
|
7483
7582
|
return current2;
|
|
7484
|
-
const response = await requestRotation(current2.familyId, () => request(current2));
|
|
7583
|
+
const response = await requestRotation(current2.familyId, () => request(current2), attempts);
|
|
7584
|
+
if ("backoffMs" in response)
|
|
7585
|
+
return response;
|
|
7485
7586
|
if ("status" in response && (response.status === "ACTION_REQUIRED" || response.status === "RETRY_LATER"))
|
|
7486
7587
|
return response;
|
|
7487
7588
|
if (response.status !== 200)
|
|
@@ -7494,21 +7595,19 @@ function createCredentialAdapter(options = {}) {
|
|
|
7494
7595
|
} catch {
|
|
7495
7596
|
return action(current2.familyId, "persistence_failed");
|
|
7496
7597
|
}
|
|
7497
|
-
});
|
|
7598
|
+
}, () => action(predecessor.familyId, "family_missing"));
|
|
7498
7599
|
}
|
|
7499
7600
|
function rotateLocked(familyId, transport) {
|
|
7500
7601
|
const familyPaths = credentialPaths(files.stateDir, familyId);
|
|
7501
|
-
return rotateStored(
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
...current2.store === "os" ? { store: "os", secretRef: reference(`component:${familyId}`) } : { store: "file", secretFile: relativeSecret(familyPaths.secret) }
|
|
7511
|
-
}));
|
|
7602
|
+
return rotateStored(
|
|
7603
|
+
familyPaths.record,
|
|
7604
|
+
familyId,
|
|
7605
|
+
() => readFamily(familyId),
|
|
7606
|
+
(current2) => transport.rotateFamily(familyId, current2.refreshToken),
|
|
7607
|
+
(body2) => "access_token" in body2 ? body2 : null,
|
|
7608
|
+
// The stored record's own locator: a reference rebuilt from the name could differ.
|
|
7609
|
+
async (current2, body2) => persistFamily(current2.componentKind, body2, await readRecord(familyPaths.record, "component") ?? void 0)
|
|
7610
|
+
);
|
|
7512
7611
|
}
|
|
7513
7612
|
function rotateFamily(familyId, transport) {
|
|
7514
7613
|
const active = rotations.get(familyId);
|
|
@@ -7526,7 +7625,7 @@ function createCredentialAdapter(options = {}) {
|
|
|
7526
7625
|
function rotateCliLocked(transport) {
|
|
7527
7626
|
return rotateStored(paths.cliRecord, "cli", readCliOAuth, (current2) => transport.rotateCli(current2), (body2) => "access_token" in body2 && typeof body2.access_token === "string" && "refresh_token" in body2 && typeof body2.refresh_token === "string" && "expires_in" in body2 && typeof body2.expires_in === "number" && "scope" in body2 && typeof body2.scope === "string" ? body2 : null, async (current2, body2) => {
|
|
7528
7627
|
const rotatedAt = now();
|
|
7529
|
-
await
|
|
7628
|
+
await writeCliOAuth({
|
|
7530
7629
|
issuer: current2.issuer,
|
|
7531
7630
|
clientId: current2.clientId,
|
|
7532
7631
|
familyId: current2.familyId,
|
|
@@ -7577,11 +7676,33 @@ function createCredentialAdapter(options = {}) {
|
|
|
7577
7676
|
return second.status === 401 ? action(rotated.familyId, "protected_unauthorized") : second;
|
|
7578
7677
|
}
|
|
7579
7678
|
async function removeCliOAuth() {
|
|
7580
|
-
|
|
7581
|
-
|
|
7582
|
-
|
|
7583
|
-
await
|
|
7584
|
-
|
|
7679
|
+
await removeLeased(paths.cliRecord, "cli-oauth", paths.cliSecret);
|
|
7680
|
+
}
|
|
7681
|
+
async function revokeCli(transport) {
|
|
7682
|
+
if (!await readRecord(paths.cliRecord, "cli-oauth"))
|
|
7683
|
+
return action("cli", "family_missing");
|
|
7684
|
+
return leased(paths.cliRecord, async () => {
|
|
7685
|
+
let current2;
|
|
7686
|
+
try {
|
|
7687
|
+
current2 = await readCliOAuth();
|
|
7688
|
+
} catch (error) {
|
|
7689
|
+
if (!isSecretMissing(error))
|
|
7690
|
+
throw error;
|
|
7691
|
+
await removeStored(paths.cliRecord, "cli-oauth", paths.cliSecret);
|
|
7692
|
+
return action("cli", "credential_secret_missing");
|
|
7693
|
+
}
|
|
7694
|
+
if (!current2)
|
|
7695
|
+
return action("cli", "family_missing");
|
|
7696
|
+
const response = await transport.revokeCli(current2);
|
|
7697
|
+
if (response.status === 429)
|
|
7698
|
+
return retryLater(current2.familyId, "rate_limited");
|
|
7699
|
+
if (response.status >= 500)
|
|
7700
|
+
return retryLater(current2.familyId, "server_error");
|
|
7701
|
+
if (response.status !== 200)
|
|
7702
|
+
return action(current2.familyId, "error" in response.body ? response.body.error : "revoke_refused");
|
|
7703
|
+
await removeStored(paths.cliRecord, "cli-oauth", paths.cliSecret);
|
|
7704
|
+
return { status: "revoked", familyId: current2.familyId };
|
|
7705
|
+
}, () => action("cli", "family_missing"));
|
|
7585
7706
|
}
|
|
7586
7707
|
async function withCredential(familyId, transport, work) {
|
|
7587
7708
|
let current2 = await readFamily(familyId);
|
|
@@ -7602,43 +7723,98 @@ function createCredentialAdapter(options = {}) {
|
|
|
7602
7723
|
const second = await work(rotated.accessToken);
|
|
7603
7724
|
return second.status === 401 ? action(familyId, "protected_unauthorized") : second;
|
|
7604
7725
|
}
|
|
7726
|
+
async function removeStored(recordPath2, kind, secretPath) {
|
|
7727
|
+
const record = await readRecord(recordPath2, kind);
|
|
7728
|
+
if (!record)
|
|
7729
|
+
return null;
|
|
7730
|
+
await files.remove(recordPath2);
|
|
7731
|
+
await deleteSecret(record, secretPath);
|
|
7732
|
+
return record;
|
|
7733
|
+
}
|
|
7734
|
+
async function removeLeased(recordPath2, kind, secretPath) {
|
|
7735
|
+
if (!await readRecord(recordPath2, kind))
|
|
7736
|
+
return null;
|
|
7737
|
+
return leased(recordPath2, () => removeStored(recordPath2, kind, secretPath), () => null);
|
|
7738
|
+
}
|
|
7739
|
+
async function orphanRefreshToken(secretPath) {
|
|
7740
|
+
const bytes = await files.read(secretPath);
|
|
7741
|
+
if (!bytes)
|
|
7742
|
+
return null;
|
|
7743
|
+
const secret = JSON.parse(bytes.toString());
|
|
7744
|
+
if (typeof secret.refreshToken !== "string")
|
|
7745
|
+
throw new Error("credential_record_invalid");
|
|
7746
|
+
return secret.refreshToken;
|
|
7747
|
+
}
|
|
7605
7748
|
async function removeFamily(familyId) {
|
|
7606
7749
|
const familyPaths = credentialPaths(files.stateDir, familyId);
|
|
7607
|
-
|
|
7608
|
-
if (!record)
|
|
7609
|
-
return;
|
|
7610
|
-
await deleteSecret(record, familyPaths.secret);
|
|
7611
|
-
await files.remove(familyPaths.record);
|
|
7750
|
+
await removeStored(familyPaths.record, "component", familyPaths.secret);
|
|
7612
7751
|
}
|
|
7613
7752
|
async function revokeFamily(familyId, transport) {
|
|
7614
|
-
const
|
|
7615
|
-
if (!
|
|
7753
|
+
const familyPaths = credentialPaths(files.stateDir, familyId);
|
|
7754
|
+
if (!await readRecord(familyPaths.record, "component") && !await files.read(familyPaths.secret))
|
|
7616
7755
|
return action(familyId, "family_missing");
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
|
|
7620
|
-
|
|
7621
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
7624
|
-
|
|
7625
|
-
|
|
7756
|
+
await files.ensureParent(familyPaths.record);
|
|
7757
|
+
return leased(familyPaths.record, async () => {
|
|
7758
|
+
let current2;
|
|
7759
|
+
try {
|
|
7760
|
+
current2 = await readFamily(familyId);
|
|
7761
|
+
} catch (error) {
|
|
7762
|
+
if (!isSecretMissing(error))
|
|
7763
|
+
throw error;
|
|
7764
|
+
await removeFamily(familyId);
|
|
7765
|
+
return action(familyId, "credential_secret_missing");
|
|
7766
|
+
}
|
|
7767
|
+
const refreshToken = current2?.refreshToken ?? await orphanRefreshToken(familyPaths.secret);
|
|
7768
|
+
if (!refreshToken)
|
|
7769
|
+
return action(familyId, "family_missing");
|
|
7770
|
+
const response = await transport.revokeFamily(familyId, refreshToken);
|
|
7771
|
+
if (response.status === 429)
|
|
7772
|
+
return retryLater(familyId, "rate_limited");
|
|
7773
|
+
if (response.status >= 500)
|
|
7774
|
+
return retryLater(familyId, "server_error");
|
|
7775
|
+
const refusal = response.status === 200 ? null : "error" in response.body ? response.body.error : "revoke_refused";
|
|
7776
|
+
if (refusal)
|
|
7777
|
+
return action(familyId, refusal);
|
|
7778
|
+
if (current2)
|
|
7779
|
+
await removeFamily(familyId);
|
|
7780
|
+
else
|
|
7781
|
+
await files.remove(familyPaths.secret);
|
|
7782
|
+
return { status: "revoked", familyId };
|
|
7783
|
+
}, () => action(familyId, "family_missing"));
|
|
7626
7784
|
}
|
|
7627
|
-
async function
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
}
|
|
7636
|
-
const location = await locator("root-binding", paths.rootSecret);
|
|
7637
|
-
const key = randomBytes(32);
|
|
7638
|
-
await writeSecret(location, paths.rootSecret, key);
|
|
7639
|
-
await files.write(paths.rootRecord, json({ schemaVersion: 1, kind: "root-binding", ...location }), "record");
|
|
7640
|
-
return key;
|
|
7785
|
+
async function storedRootKey() {
|
|
7786
|
+
const record = await readRecord(paths.rootRecord, "root-binding");
|
|
7787
|
+
if (!record)
|
|
7788
|
+
return null;
|
|
7789
|
+
const key = await readSecret(record, paths.rootSecret).catch((error) => {
|
|
7790
|
+
if (isSecretMissing(error))
|
|
7791
|
+
return null;
|
|
7792
|
+
throw error;
|
|
7641
7793
|
});
|
|
7794
|
+
if (key && key.length !== 32)
|
|
7795
|
+
throw new Error("root_binding_key_invalid");
|
|
7796
|
+
return key;
|
|
7797
|
+
}
|
|
7798
|
+
async function rootKey() {
|
|
7799
|
+
const existing = await storedRootKey();
|
|
7800
|
+
if (existing)
|
|
7801
|
+
return existing;
|
|
7802
|
+
await files.ensureParent(paths.rootRecord);
|
|
7803
|
+
return leased(
|
|
7804
|
+
paths.rootRecord,
|
|
7805
|
+
async () => {
|
|
7806
|
+
const stored = await storedRootKey();
|
|
7807
|
+
if (stored)
|
|
7808
|
+
return stored;
|
|
7809
|
+
const location = await locator("root-binding", paths.rootSecret);
|
|
7810
|
+
const key = randomBytes(32);
|
|
7811
|
+
await writeSecret(location, paths.rootSecret, key);
|
|
7812
|
+
await files.write(paths.rootRecord, json({ schemaVersion: 1, kind: "root-binding", ...location }), "record");
|
|
7813
|
+
return key;
|
|
7814
|
+
},
|
|
7815
|
+
// forget erased the binding and its directory while this call waited: create afresh.
|
|
7816
|
+
rootKey
|
|
7817
|
+
);
|
|
7642
7818
|
}
|
|
7643
7819
|
async function hmacRootBinding(version, input) {
|
|
7644
7820
|
if (version !== 1)
|
|
@@ -7646,26 +7822,60 @@ function createCredentialAdapter(options = {}) {
|
|
|
7646
7822
|
const key = await rootKey();
|
|
7647
7823
|
return { version, hmac: createHmac("sha256", key).update(input).digest("base64url") };
|
|
7648
7824
|
}
|
|
7825
|
+
async function eraseEntry(recordPath2, kind, secretPath) {
|
|
7826
|
+
const record = await removeStored(recordPath2, kind, secretPath);
|
|
7827
|
+
if (!record)
|
|
7828
|
+
await files.remove(secretPath);
|
|
7829
|
+
for (const path of [recordPath2, secretPath])
|
|
7830
|
+
for (const entry of await files.list(dirname5(path)))
|
|
7831
|
+
if (temporaryOf(basename3(path), entry))
|
|
7832
|
+
await files.remove(join6(dirname5(path), entry));
|
|
7833
|
+
return record;
|
|
7834
|
+
}
|
|
7835
|
+
async function forgetCliOsOrphan() {
|
|
7836
|
+
if (!store)
|
|
7837
|
+
return;
|
|
7838
|
+
const recordPath2 = paths.cliRecord;
|
|
7839
|
+
const ref = await reference("cli-oauth").catch(() => null);
|
|
7840
|
+
if (!ref || !await available())
|
|
7841
|
+
return;
|
|
7842
|
+
if (!await osBackend.read(ref, "").catch(() => null))
|
|
7843
|
+
return;
|
|
7844
|
+
await files.ensureParent(recordPath2);
|
|
7845
|
+
await leased(recordPath2, async () => {
|
|
7846
|
+
const record = await readRecord(recordPath2, "cli-oauth");
|
|
7847
|
+
if (record?.store === "os" && record.secretRef === ref)
|
|
7848
|
+
return;
|
|
7849
|
+
await osBackend.delete(ref, "");
|
|
7850
|
+
}, () => void 0);
|
|
7851
|
+
}
|
|
7852
|
+
async function forgetEntry(recordPath2, kind, secretPath) {
|
|
7853
|
+
let present = false;
|
|
7854
|
+
for (const path of [recordPath2, secretPath])
|
|
7855
|
+
for (const entry of await files.list(dirname5(path)))
|
|
7856
|
+
present ||= entry === basename3(path) || temporaryOf(basename3(path), entry);
|
|
7857
|
+
if (!present)
|
|
7858
|
+
return null;
|
|
7859
|
+
await files.ensureParent(recordPath2);
|
|
7860
|
+
return leased(recordPath2, () => eraseEntry(recordPath2, kind, secretPath), () => null);
|
|
7861
|
+
}
|
|
7649
7862
|
async function forget() {
|
|
7650
|
-
const cli = await readRecord(paths.cliRecord, "cli-oauth");
|
|
7651
7863
|
const componentFamilyIds = [];
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
const
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
|
|
7660
|
-
|
|
7661
|
-
|
|
7662
|
-
|
|
7663
|
-
}
|
|
7664
|
-
const
|
|
7665
|
-
|
|
7666
|
-
|
|
7667
|
-
await files.remove(paths.rootRecord);
|
|
7668
|
-
}
|
|
7864
|
+
const digests = /* @__PURE__ */ new Set();
|
|
7865
|
+
for (const directory of [paths.familyRecords, paths.familySecrets])
|
|
7866
|
+
for (const name2 of await files.list(directory)) {
|
|
7867
|
+
const digest2 = familyFileName.exec(name2)?.[1];
|
|
7868
|
+
if (digest2)
|
|
7869
|
+
digests.add(digest2);
|
|
7870
|
+
}
|
|
7871
|
+
for (const digest2 of [...digests].sort()) {
|
|
7872
|
+
const record = await forgetEntry(join6(paths.familyRecords, `${digest2}.json`), "component", join6(paths.familySecrets, `${digest2}.json`));
|
|
7873
|
+
if (record)
|
|
7874
|
+
componentFamilyIds.push(record.familyId);
|
|
7875
|
+
}
|
|
7876
|
+
const cli = await forgetEntry(paths.cliRecord, "cli-oauth", paths.cliSecret);
|
|
7877
|
+
await forgetCliOsOrphan();
|
|
7878
|
+
await forgetEntry(paths.rootRecord, "root-binding", paths.rootSecret);
|
|
7669
7879
|
await files.removeEmptyDirectories([
|
|
7670
7880
|
paths.familySecrets,
|
|
7671
7881
|
paths.familyRecords,
|
|
@@ -7687,6 +7897,8 @@ function createCredentialAdapter(options = {}) {
|
|
|
7687
7897
|
rotateCli,
|
|
7688
7898
|
withCliCredential,
|
|
7689
7899
|
removeCliOAuth,
|
|
7900
|
+
revokeCli,
|
|
7901
|
+
waitForCliLease,
|
|
7690
7902
|
putFamily,
|
|
7691
7903
|
readFamily,
|
|
7692
7904
|
rotateFamily,
|