@modelprofile.com/authswitch 7.0.0 → 8.1.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/authority-contract.d.ts +1 -3
- package/dist_ts/authority-contract.js +1 -1
- package/dist_ts/authority-import-contract.d.ts +207 -0
- package/dist_ts/authority-import-contract.js +18 -0
- package/dist_ts/classes.authoritybroker.js +1 -5
- package/dist_ts/classes.authoritycli.d.ts +4 -0
- package/dist_ts/classes.authoritycli.js +149 -12
- package/dist_ts/classes.authorityclient.d.ts +7 -0
- package/dist_ts/classes.authorityclient.js +13 -1
- package/dist_ts/classes.authoritydaemon.d.ts +4 -0
- package/dist_ts/classes.authoritydaemon.js +75 -9
- package/dist_ts/classes.authoritydatabase.d.ts +10 -26
- package/dist_ts/classes.authoritydatabase.js +68 -133
- package/dist_ts/classes.authorityimport.d.ts +161 -0
- package/dist_ts/classes.authorityimport.js +638 -0
- package/dist_ts/classes.authoritymodels.d.ts +39 -43
- package/dist_ts/classes.authoritymodels.js +209 -243
- package/dist_ts/classes.authoritypreuse.js +1 -2
- package/dist_ts/classes.authorityregistry.d.ts +2 -17
- package/dist_ts/classes.authorityregistry.js +18 -74
- package/dist_ts/classes.authoritysecrets.d.ts +1 -1
- package/dist_ts/classes.authoritysecrets.js +2 -2
- package/dist_ts/classes.authorityusage.d.ts +3 -3
- package/dist_ts/classes.claudeauthority.js +3 -7
- package/dist_ts/classes.claudenative.d.ts +1 -8
- package/dist_ts/classes.claudenative.js +2 -25
- package/dist_ts/classes.codexmanaged.d.ts +1 -1
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +6 -3
- package/dist_ts/plugins.d.ts +7 -7
- package/dist_ts/plugins.js +8 -8
- package/dist_ts/ts_migration/0001_authority_meta.d.ts +13 -3
- package/dist_ts/ts_migration/0001_authority_meta.js +29 -17
- package/dist_ts/ts_migration/0002_remove_backup_records.d.ts +17 -0
- package/dist_ts/ts_migration/0002_remove_backup_records.js +48 -0
- package/dist_ts/ts_migration/index.d.ts +6 -3
- package/dist_ts/ts_migration/index.js +10 -4
- package/dist_ts/ts_migration/legacysources/authswitchstores.d.ts +2 -0
- package/dist_ts/ts_migration/legacysources/authswitchstores.js +244 -0
- package/dist_ts/ts_migration/legacysources/index.d.ts +18 -0
- package/dist_ts/ts_migration/legacysources/index.js +24 -0
- package/dist_ts/ts_migration/legacysources/material.d.ts +37 -0
- package/dist_ts/ts_migration/legacysources/material.js +120 -0
- package/dist_ts/ts_migration/legacysources/nativestores.d.ts +2 -0
- package/dist_ts/ts_migration/legacysources/nativestores.js +207 -0
- package/dist_ts/ts_migration/legacysources/shared.d.ts +124 -0
- package/dist_ts/ts_migration/legacysources/shared.js +142 -0
- package/package.json +6 -5
- package/readme.md +68 -26
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/authority-contract.ts +1 -3
- package/ts/authority-import-contract.ts +216 -0
- package/ts/classes.authoritybroker.ts +0 -4
- package/ts/classes.authoritycli.ts +165 -11
- package/ts/classes.authorityclient.ts +22 -0
- package/ts/classes.authoritydaemon.ts +75 -8
- package/ts/classes.authoritydatabase.ts +70 -145
- package/ts/classes.authorityimport.ts +718 -0
- package/ts/classes.authoritymodels.ts +253 -269
- package/ts/classes.authoritypreuse.ts +0 -1
- package/ts/classes.authorityregistry.ts +20 -91
- package/ts/classes.authoritysecrets.ts +1 -1
- package/ts/classes.authorityusage.ts +3 -3
- package/ts/classes.claudeauthority.ts +2 -6
- package/ts/classes.claudenative.ts +1 -32
- package/ts/classes.codexmanaged.ts +1 -1
- package/ts/index.ts +5 -2
- package/ts/plugins.ts +7 -7
- package/ts/ts_migration/0001_authority_meta.ts +29 -17
- package/ts/ts_migration/0002_remove_backup_records.ts +49 -0
- package/ts/ts_migration/index.ts +10 -5
- package/ts/ts_migration/legacysources/authswitchstores.ts +211 -0
- package/ts/ts_migration/legacysources/index.ts +35 -0
- package/ts/ts_migration/legacysources/material.ts +134 -0
- package/ts/ts_migration/legacysources/nativestores.ts +204 -0
- package/ts/ts_migration/legacysources/shared.ts +220 -0
- package/dist_ts/authorityrehearsal.child.d.ts +0 -1
- package/dist_ts/authorityrehearsal.child.js +0 -395
- package/dist_ts/classes.authoritybackup.d.ts +0 -134
- package/dist_ts/classes.authoritybackup.js +0 -769
- package/dist_ts/classes.authorityrehearsal.d.ts +0 -127
- package/dist_ts/classes.authorityrehearsal.js +0 -632
- package/ts/authorityrehearsal.child.ts +0 -330
- package/ts/classes.authoritybackup.ts +0 -780
- package/ts/classes.authorityrehearsal.ts +0 -625
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import { readCredentialRaw } from '../../classes.credentialstore.js';
|
|
3
|
+
import type { TAuthSwitchImportProof, TAuthSwitchImportSourceKind,
|
|
4
|
+
TAuthSwitchImportTreatment } from '../../authority-import-contract.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Shared vocabulary of the legacy-source readers.
|
|
8
|
+
*
|
|
9
|
+
* These readers exist for one migration and are deliberately isolated from the runtime: nothing here is a
|
|
10
|
+
* fallback an ordinary command may reach for. They only read, and they report what they could not read
|
|
11
|
+
* instead of returning a shorter list -- a silently skipped entry is an account the owner would lose.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** One source as the readers see it. `path` stays inside the daemon; only `sourcePathHash` is published. */
|
|
15
|
+
export interface ILegacySourceRecord {
|
|
16
|
+
id: string;
|
|
17
|
+
sourceKind: TAuthSwitchImportSourceKind;
|
|
18
|
+
path: string;
|
|
19
|
+
sourcePathHash: string;
|
|
20
|
+
sourceDigest: string;
|
|
21
|
+
treatment: TAuthSwitchImportTreatment;
|
|
22
|
+
proof: TAuthSwitchImportProof;
|
|
23
|
+
accountId: string | null;
|
|
24
|
+
/** The provider's own stable identity, kept on the account so a sealed credential can be matched to it. */
|
|
25
|
+
subject: string | null;
|
|
26
|
+
workspaceId: string | null;
|
|
27
|
+
providerId: string | null;
|
|
28
|
+
email: string | null;
|
|
29
|
+
plan: string | null;
|
|
30
|
+
label: string | null;
|
|
31
|
+
accessExpiresAt: string | null;
|
|
32
|
+
enrollmentCount: number | null;
|
|
33
|
+
problems: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ILegacySourceReport {
|
|
37
|
+
sources: ILegacySourceRecord[];
|
|
38
|
+
/** Locations that could not be read at all, so an empty result is never mistaken for an empty store. */
|
|
39
|
+
problems: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Where the legacy stores live.
|
|
44
|
+
*
|
|
45
|
+
* Each field repeats the rule of the class that owns that location, so a fixture can point the whole reader
|
|
46
|
+
* at a temporary directory: `authSwitchHome` (ts/classes.credentialstore.ts), `CodexHome`
|
|
47
|
+
* (ts/classes.codexhome.ts), `ClaudeCodeHarness` (ts/classes.claudecodeharness.ts) and `OpenCodeHarness`
|
|
48
|
+
* (ts/classes.opencodeharness.ts). Drift in any of those is a bug here, not a reason to guess.
|
|
49
|
+
*/
|
|
50
|
+
export interface ILegacySourceLocations {
|
|
51
|
+
authSwitchHome: string;
|
|
52
|
+
codexHome: string;
|
|
53
|
+
claudeCredentialFile: string;
|
|
54
|
+
claudeConfigFile: string;
|
|
55
|
+
openCodeAuthFile: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const resolveLegacySourceLocations = (env: NodeJS.ProcessEnv,
|
|
59
|
+
homeDirectory: string): ILegacySourceLocations => {
|
|
60
|
+
const claudeConfigDirectory = env.CLAUDE_CONFIG_DIR || plugins.path.join(homeDirectory, '.claude');
|
|
61
|
+
const dataHome = env.XDG_DATA_HOME || plugins.path.join(homeDirectory, '.local', 'share');
|
|
62
|
+
return {
|
|
63
|
+
authSwitchHome: env.AUTHSWITCH_HOME || plugins.path.join(homeDirectory, '.authswitch'),
|
|
64
|
+
codexHome: env.CODEX_HOME || plugins.path.join(homeDirectory, '.codex'),
|
|
65
|
+
claudeCredentialFile: plugins.path.join(claudeConfigDirectory, '.credentials.json'),
|
|
66
|
+
// Claude Code keeps its account metadata beside the home directory, not inside a custom config directory.
|
|
67
|
+
claudeConfigFile: plugins.path.join(env.CLAUDE_CONFIG_DIR || homeDirectory, '.claude.json'),
|
|
68
|
+
openCodeAuthFile: plugins.path.join(dataHome, 'opencode', 'auth.json'),
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const legacyHash = (value: string): string =>
|
|
73
|
+
plugins.crypto.createHash('sha256').update(value).digest('hex');
|
|
74
|
+
|
|
75
|
+
/** The ledger row id of a source. Stable across inventory runs, so a resumed import finds its own row. */
|
|
76
|
+
export const legacySourceId = (sourceKind: TAuthSwitchImportSourceKind, sourcePathHash: string): string =>
|
|
77
|
+
legacyHash(JSON.stringify(['authswitch-migration-v1', sourceKind, sourcePathHash]));
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The digest that binds an import to exactly these bytes.
|
|
81
|
+
*
|
|
82
|
+
* A source is often more than one file -- a stash directory, or Claude's credential/config pair -- so the
|
|
83
|
+
* digest covers an ordered list of per-file digests rather than one file. The file names are part of it: a
|
|
84
|
+
* source that gains or loses a file is a different source.
|
|
85
|
+
*/
|
|
86
|
+
export const legacySourceDigest = (files: ReadonlyArray<{ name: string; raw: string }>): string =>
|
|
87
|
+
legacyHash(JSON.stringify([...files]
|
|
88
|
+
.sort((left, right) => left.name.localeCompare(right.name))
|
|
89
|
+
.map(file => [file.name, legacyHash(file.raw)])));
|
|
90
|
+
|
|
91
|
+
export interface ILegacySourceFile {
|
|
92
|
+
name: string;
|
|
93
|
+
raw: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** A bounded read that reports unreadable and absent apart, through the store's own reader. */
|
|
97
|
+
export const readLegacyFile = (file: string, name: string):
|
|
98
|
+
{ file: ILegacySourceFile | null; problem: string | null } => {
|
|
99
|
+
try {
|
|
100
|
+
const raw = readCredentialRaw(file);
|
|
101
|
+
return { file: raw === null ? null : { name, raw }, problem: null };
|
|
102
|
+
} catch {
|
|
103
|
+
return { file: null, problem: `${name} is unreadable, malformed or too large` };
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const parseLegacyJson = (raw: string): Record<string, unknown> | null => {
|
|
108
|
+
try {
|
|
109
|
+
const value: unknown = JSON.parse(raw);
|
|
110
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
111
|
+
? value as Record<string, unknown> : null;
|
|
112
|
+
} catch { return null; }
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export interface IOpenAiSourceIdentity {
|
|
116
|
+
accountId: string;
|
|
117
|
+
/** The stable provider identity the account record keeps, so a sealed credential can be matched to it. */
|
|
118
|
+
subject: string;
|
|
119
|
+
workspaceId: string;
|
|
120
|
+
email: string | null;
|
|
121
|
+
plan: string | null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The authority account a ChatGPT login resolves to.
|
|
126
|
+
*
|
|
127
|
+
* Derived from the id_token's claims exactly as `AuthSwitchAuthorityBroker.identity` derives it
|
|
128
|
+
* (ts/classes.authoritybroker.ts), because an import that produced a different account id would create a
|
|
129
|
+
* second account for a login the owner already has. No provider call is involved: this is a local read of
|
|
130
|
+
* claims the token already carries, which is what lets the inventory run offline.
|
|
131
|
+
*/
|
|
132
|
+
export const openAiSourceIdentity = (idToken: string): IOpenAiSourceIdentity | null => {
|
|
133
|
+
let info: ReturnType<typeof plugins.flexAuth.parseOpenAiChatGptTokenInfo>;
|
|
134
|
+
try { info = plugins.flexAuth.parseOpenAiChatGptTokenInfo(idToken); }
|
|
135
|
+
catch { return null; }
|
|
136
|
+
if (!info.chatgptUserId || !info.chatgptAccountId) return null;
|
|
137
|
+
return {
|
|
138
|
+
accountId: legacyHash(JSON.stringify(['authswitch-account-v1', 'openai',
|
|
139
|
+
info.chatgptUserId, info.chatgptAccountId])),
|
|
140
|
+
subject: info.chatgptUserId,
|
|
141
|
+
workspaceId: info.chatgptAccountId,
|
|
142
|
+
email: info.email?.toLowerCase() ?? null,
|
|
143
|
+
plan: info.chatgptPlanType ?? null,
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export interface IClaudeSourceIdentity {
|
|
148
|
+
accountId: string;
|
|
149
|
+
/** Claude's immutable pair, kept as the account's subject and workspace so a sealed login matches it. */
|
|
150
|
+
subject: string;
|
|
151
|
+
workspaceId: string;
|
|
152
|
+
email: string | null;
|
|
153
|
+
accessExpiresAt: string | null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The authority account a Claude subscriber login resolves to.
|
|
158
|
+
*
|
|
159
|
+
* Claude's immutable identity is the account/organization UUID pair that `ClaudeNativeAdapter` proves
|
|
160
|
+
* against the provider (ts/classes.claudenative.ts). Nothing in the package derived an authority account id
|
|
161
|
+
* from it before this import, so it follows the OpenAI rule -- provider, subject, workspace -- and stays the
|
|
162
|
+
* single formula both the inventory and the import use.
|
|
163
|
+
*/
|
|
164
|
+
export const claudeSourceIdentity = (credential: Record<string, unknown>): IClaudeSourceIdentity | null => {
|
|
165
|
+
const oauth = credential.claudeAiOauth;
|
|
166
|
+
const account = credential.oauthAccount;
|
|
167
|
+
if (!oauth || typeof oauth !== 'object' || Array.isArray(oauth)
|
|
168
|
+
|| !account || typeof account !== 'object' || Array.isArray(account)) return null;
|
|
169
|
+
const oauthRecord = oauth as Record<string, unknown>;
|
|
170
|
+
const accountRecord = account as Record<string, unknown>;
|
|
171
|
+
const accountUuid = accountRecord.accountUuid;
|
|
172
|
+
const organizationUuid = accountRecord.organizationUuid;
|
|
173
|
+
if (typeof accountUuid !== 'string' || !accountUuid || typeof organizationUuid !== 'string'
|
|
174
|
+
|| !organizationUuid) return null;
|
|
175
|
+
const expiresAt = oauthRecord.expiresAt;
|
|
176
|
+
const email = accountRecord.emailAddress;
|
|
177
|
+
return {
|
|
178
|
+
accountId: legacyHash(JSON.stringify(['authswitch-account-v1', 'anthropic',
|
|
179
|
+
accountUuid, organizationUuid])),
|
|
180
|
+
subject: accountUuid,
|
|
181
|
+
workspaceId: organizationUuid,
|
|
182
|
+
email: typeof email === 'string' && email ? email.toLowerCase() : null,
|
|
183
|
+
accessExpiresAt: typeof expiresAt === 'number' && Number.isFinite(expiresAt)
|
|
184
|
+
? new Date(expiresAt).toISOString() : null,
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The identity of an OpenCode OpenAI provider entry.
|
|
190
|
+
*
|
|
191
|
+
* OpenCode stores no id_token, so `OpenCodeHarness.openAiInfo` (ts/classes.opencodeharness.ts) reads the
|
|
192
|
+
* claims of the access token instead. An expired or opaque access token therefore leaves such an entry
|
|
193
|
+
* without a derivable identity, which is reported rather than guessed.
|
|
194
|
+
*/
|
|
195
|
+
export const openCodeOpenAiIdentity = (entry: Record<string, unknown>):
|
|
196
|
+
{ identity: IOpenAiSourceIdentity | null; accessExpiresAt: string | null } => {
|
|
197
|
+
const access = typeof entry.access === 'string' ? entry.access : null;
|
|
198
|
+
const expires = entry.expires;
|
|
199
|
+
const accessExpiresAt = typeof expires === 'number' && Number.isFinite(expires)
|
|
200
|
+
? new Date(expires).toISOString() : accessTokenExpiry(access);
|
|
201
|
+
return { identity: access === null ? null : openAiSourceIdentity(access), accessExpiresAt };
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/** The expiry claim of an access token, as the broker reads it. Absent or unparsable means unknown. */
|
|
205
|
+
export const accessTokenExpiry = (accessToken: string | null): string | null => {
|
|
206
|
+
if (!accessToken) return null;
|
|
207
|
+
try { return plugins.flexAuth.parseOpenAiChatGptTokenInfo(accessToken).expiresAt ?? null; }
|
|
208
|
+
catch { return null; }
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/** Whether a non-rotating proof can still be taken with this access token. */
|
|
212
|
+
export const accessProofAvailable = (expiresAt: string | null, now: number): boolean => {
|
|
213
|
+
if (expiresAt === null) return false;
|
|
214
|
+
const value = Date.parse(expiresAt);
|
|
215
|
+
return Number.isFinite(value) && value > now;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export const EXPIRED_ACCESS_PROBLEM =
|
|
219
|
+
'the stored access token has expired, so the non-rotating proof is unavailable until the native tool '
|
|
220
|
+
+ 'refreshes its own store';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
import * as plugins from './plugins.js';
|
|
2
|
-
import { AuthSwitchAuthorityDatabase } from './classes.authoritydatabase.js';
|
|
3
|
-
import { assertStoredAuthorityMeta } from './classes.authoritymodels.js';
|
|
4
|
-
import { AuthorityRehearsalFramedTransport, authorityRehearsalMaximumFrameBytes, parseAuthorityRehearsalChildMessage, parseAuthorityRehearsalParentMessage, parseAuthorityRehearsalWireEntry, } from './classes.authorityrehearsal.js';
|
|
5
|
-
const spoolHeaderBytes = 4 + 12 + 16;
|
|
6
|
-
const childReceiveTimeoutMs = 24 * 60 * 60 * 1000;
|
|
7
|
-
const writeAll = async (file, bytes) => {
|
|
8
|
-
let offset = 0;
|
|
9
|
-
while (offset < bytes.byteLength) {
|
|
10
|
-
const result = await file.write(bytes, offset, bytes.byteLength - offset, null);
|
|
11
|
-
if (result.bytesWritten < 1)
|
|
12
|
-
throw new Error('Encrypted rehearsal staging stopped making progress.');
|
|
13
|
-
offset += result.bytesWritten;
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
const readExact = async (file, bytes, position) => {
|
|
17
|
-
let offset = 0;
|
|
18
|
-
while (offset < bytes.byteLength) {
|
|
19
|
-
const result = await file.read(bytes, offset, bytes.byteLength - offset, position + offset);
|
|
20
|
-
if (result.bytesRead === 0) {
|
|
21
|
-
if (offset === 0)
|
|
22
|
-
return true;
|
|
23
|
-
throw new Error('Encrypted rehearsal staging ended within a frame.');
|
|
24
|
-
}
|
|
25
|
-
offset += result.bytesRead;
|
|
26
|
-
}
|
|
27
|
-
return false;
|
|
28
|
-
};
|
|
29
|
-
/** Append-only encrypted staging. The key exists only in this child and every plaintext buffer is wiped. */
|
|
30
|
-
class EncryptedRehearsalSpool {
|
|
31
|
-
path;
|
|
32
|
-
domain;
|
|
33
|
-
key;
|
|
34
|
-
parse;
|
|
35
|
-
appendFile;
|
|
36
|
-
readFiles = new Set();
|
|
37
|
-
appendSequence = 0;
|
|
38
|
-
finished = false;
|
|
39
|
-
constructor(path, domain, key, parse) {
|
|
40
|
-
this.path = path;
|
|
41
|
-
this.domain = domain;
|
|
42
|
-
this.key = key;
|
|
43
|
-
this.parse = parse;
|
|
44
|
-
}
|
|
45
|
-
async open() {
|
|
46
|
-
if (this.appendFile)
|
|
47
|
-
throw new Error('Encrypted rehearsal staging is already open.');
|
|
48
|
-
this.appendFile = await plugins.fs.promises.open(this.path, 'wx', 0o600);
|
|
49
|
-
}
|
|
50
|
-
async append(value) {
|
|
51
|
-
if (!this.appendFile || this.finished)
|
|
52
|
-
throw new Error('Encrypted rehearsal staging is not writable.');
|
|
53
|
-
const plaintext = Buffer.from(JSON.stringify(value), 'utf8');
|
|
54
|
-
if (plaintext.byteLength < 1 || plaintext.byteLength > authorityRehearsalMaximumFrameBytes) {
|
|
55
|
-
plaintext.fill(0);
|
|
56
|
-
throw new Error('Encrypted rehearsal staging entry exceeds its size bound.');
|
|
57
|
-
}
|
|
58
|
-
const nonce = plugins.crypto.randomBytes(12);
|
|
59
|
-
const aad = Buffer.from(JSON.stringify(['authswitch-rehearsal-stage-v1', this.domain,
|
|
60
|
-
this.appendSequence]), 'utf8');
|
|
61
|
-
let ciphertext;
|
|
62
|
-
try {
|
|
63
|
-
const cipher = plugins.crypto.createCipheriv('aes-256-gcm', this.key, nonce);
|
|
64
|
-
cipher.setAAD(aad);
|
|
65
|
-
ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
66
|
-
const tag = cipher.getAuthTag();
|
|
67
|
-
const frame = Buffer.allocUnsafe(spoolHeaderBytes + ciphertext.byteLength);
|
|
68
|
-
frame.writeUInt32BE(ciphertext.byteLength, 0);
|
|
69
|
-
nonce.copy(frame, 4);
|
|
70
|
-
tag.copy(frame, 16);
|
|
71
|
-
ciphertext.copy(frame, spoolHeaderBytes);
|
|
72
|
-
try {
|
|
73
|
-
await writeAll(this.appendFile, frame);
|
|
74
|
-
}
|
|
75
|
-
finally {
|
|
76
|
-
frame.fill(0);
|
|
77
|
-
tag.fill(0);
|
|
78
|
-
}
|
|
79
|
-
this.appendSequence++;
|
|
80
|
-
}
|
|
81
|
-
finally {
|
|
82
|
-
plaintext.fill(0);
|
|
83
|
-
nonce.fill(0);
|
|
84
|
-
aad.fill(0);
|
|
85
|
-
ciphertext?.fill(0);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
async finish() {
|
|
89
|
-
if (this.finished)
|
|
90
|
-
return;
|
|
91
|
-
this.finished = true;
|
|
92
|
-
const file = this.appendFile;
|
|
93
|
-
this.appendFile = undefined;
|
|
94
|
-
if (file)
|
|
95
|
-
await file.close();
|
|
96
|
-
}
|
|
97
|
-
async *values() {
|
|
98
|
-
if (!this.finished)
|
|
99
|
-
throw new Error('Encrypted rehearsal staging is not complete.');
|
|
100
|
-
const file = await plugins.fs.promises.open(this.path, 'r');
|
|
101
|
-
this.readFiles.add(file);
|
|
102
|
-
let position = 0;
|
|
103
|
-
let itemSequence = 0;
|
|
104
|
-
try {
|
|
105
|
-
while (true) {
|
|
106
|
-
const header = Buffer.alloc(spoolHeaderBytes);
|
|
107
|
-
const ended = await readExact(file, header, position);
|
|
108
|
-
if (ended)
|
|
109
|
-
return;
|
|
110
|
-
const length = header.readUInt32BE(0);
|
|
111
|
-
if (length < 1 || length > authorityRehearsalMaximumFrameBytes) {
|
|
112
|
-
header.fill(0);
|
|
113
|
-
throw new Error('Encrypted rehearsal staging frame exceeds its size bound.');
|
|
114
|
-
}
|
|
115
|
-
const ciphertext = Buffer.allocUnsafe(length);
|
|
116
|
-
try {
|
|
117
|
-
if (await readExact(file, ciphertext, position + spoolHeaderBytes)) {
|
|
118
|
-
throw new Error('Encrypted rehearsal staging ended within a frame.');
|
|
119
|
-
}
|
|
120
|
-
const aad = Buffer.from(JSON.stringify(['authswitch-rehearsal-stage-v1', this.domain,
|
|
121
|
-
itemSequence]), 'utf8');
|
|
122
|
-
let plaintext;
|
|
123
|
-
try {
|
|
124
|
-
const decipher = plugins.crypto.createDecipheriv('aes-256-gcm', this.key, header.subarray(4, 16));
|
|
125
|
-
decipher.setAAD(aad);
|
|
126
|
-
decipher.setAuthTag(header.subarray(16, spoolHeaderBytes));
|
|
127
|
-
plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
128
|
-
let decoded;
|
|
129
|
-
try {
|
|
130
|
-
decoded = JSON.parse(plaintext.toString('utf8'));
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
throw new Error('Encrypted rehearsal staging contains invalid JSON.');
|
|
134
|
-
}
|
|
135
|
-
yield this.parse(decoded);
|
|
136
|
-
}
|
|
137
|
-
finally {
|
|
138
|
-
aad.fill(0);
|
|
139
|
-
plaintext?.fill(0);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
finally {
|
|
143
|
-
header.fill(0);
|
|
144
|
-
ciphertext.fill(0);
|
|
145
|
-
}
|
|
146
|
-
position += spoolHeaderBytes + length;
|
|
147
|
-
itemSequence++;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
finally {
|
|
151
|
-
this.readFiles.delete(file);
|
|
152
|
-
await file.close();
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
async close() {
|
|
156
|
-
const errors = [];
|
|
157
|
-
const appendFile = this.appendFile;
|
|
158
|
-
this.appendFile = undefined;
|
|
159
|
-
if (appendFile)
|
|
160
|
-
try {
|
|
161
|
-
await appendFile.close();
|
|
162
|
-
}
|
|
163
|
-
catch (error) {
|
|
164
|
-
errors.push(error);
|
|
165
|
-
}
|
|
166
|
-
for (const file of this.readFiles) {
|
|
167
|
-
try {
|
|
168
|
-
await file.close();
|
|
169
|
-
}
|
|
170
|
-
catch (error) {
|
|
171
|
-
errors.push(error);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
this.readFiles.clear();
|
|
175
|
-
if (errors.length)
|
|
176
|
-
throw new AggregateError(errors, 'Encrypted rehearsal staging cleanup failed.');
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
const validateRoot = async (rootInput) => {
|
|
180
|
-
if (!plugins.path.isAbsolute(rootInput))
|
|
181
|
-
throw new Error('Rehearsal root must be absolute.');
|
|
182
|
-
const root = await plugins.fs.promises.realpath(rootInput);
|
|
183
|
-
if (root !== rootInput)
|
|
184
|
-
throw new Error('Rehearsal root must be canonical.');
|
|
185
|
-
const stat = await plugins.fs.promises.lstat(root);
|
|
186
|
-
if (!stat.isDirectory() || stat.isSymbolicLink() || stat.uid !== process.getuid?.()
|
|
187
|
-
|| (stat.mode & 0o077) !== 0)
|
|
188
|
-
throw new Error('Rehearsal root is not private.');
|
|
189
|
-
return root;
|
|
190
|
-
};
|
|
191
|
-
const recordKey = (record) => `${record.model}\u0000${record.id}`;
|
|
192
|
-
const parseRecord = (value) => {
|
|
193
|
-
const entry = parseAuthorityRehearsalWireEntry(value);
|
|
194
|
-
if ('kind' in entry)
|
|
195
|
-
throw new Error('Encrypted record staging contains a source.');
|
|
196
|
-
return entry;
|
|
197
|
-
};
|
|
198
|
-
const parseSource = (value) => {
|
|
199
|
-
const entry = parseAuthorityRehearsalWireEntry(value);
|
|
200
|
-
if (!('kind' in entry))
|
|
201
|
-
throw new Error('Encrypted source staging contains a record.');
|
|
202
|
-
return entry;
|
|
203
|
-
};
|
|
204
|
-
const run = async () => {
|
|
205
|
-
// tsrun deliberately presents script arguments from argv[0], while built Node entrypoints retain argv[0..1].
|
|
206
|
-
const root = await validateRoot(process.argv.at(-1) ?? '');
|
|
207
|
-
const reader = plugins.fs.createReadStream('', { fd: 3, autoClose: false });
|
|
208
|
-
const writer = plugins.fs.createWriteStream('', { fd: 4, autoClose: false });
|
|
209
|
-
const transport = new AuthorityRehearsalFramedTransport({ readable: reader, writable: writer,
|
|
210
|
-
parseIncoming: parseAuthorityRehearsalParentMessage,
|
|
211
|
-
validateOutgoing: parseAuthorityRehearsalChildMessage });
|
|
212
|
-
const key = plugins.crypto.randomBytes(32);
|
|
213
|
-
const metaSpool = new EncryptedRehearsalSpool(plugins.path.join(root, 'meta.stage'), 'meta', key, parseRecord);
|
|
214
|
-
const recordSpool = new EncryptedRehearsalSpool(plugins.path.join(root, 'records.stage'), 'records', key, parseRecord);
|
|
215
|
-
const sourceSpool = new EncryptedRehearsalSpool(plugins.path.join(root, 'sources.stage'), 'sources', key, parseSource);
|
|
216
|
-
let database;
|
|
217
|
-
let recordRestoreIterator;
|
|
218
|
-
let recordReadbackIterator;
|
|
219
|
-
let sourceReadbackIterator;
|
|
220
|
-
let expectedSequence = 0;
|
|
221
|
-
let metaCount = 0;
|
|
222
|
-
let recordsStarted = false;
|
|
223
|
-
let lastRecordKey = '';
|
|
224
|
-
let inputComplete = false;
|
|
225
|
-
let restored = false;
|
|
226
|
-
let recordsReadbackDone = false;
|
|
227
|
-
let phase = 'staging';
|
|
228
|
-
let cleanClose = false;
|
|
229
|
-
const cleanup = async () => {
|
|
230
|
-
const errors = [];
|
|
231
|
-
for (const iterator of [recordRestoreIterator, recordReadbackIterator, sourceReadbackIterator]) {
|
|
232
|
-
if (iterator?.return)
|
|
233
|
-
try {
|
|
234
|
-
await iterator.return();
|
|
235
|
-
}
|
|
236
|
-
catch (error) {
|
|
237
|
-
errors.push(error);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
if (database)
|
|
241
|
-
try {
|
|
242
|
-
await database.stop();
|
|
243
|
-
}
|
|
244
|
-
catch (error) {
|
|
245
|
-
errors.push(error);
|
|
246
|
-
}
|
|
247
|
-
database = undefined;
|
|
248
|
-
for (const spool of [metaSpool, recordSpool, sourceSpool]) {
|
|
249
|
-
try {
|
|
250
|
-
await spool.close();
|
|
251
|
-
}
|
|
252
|
-
catch (error) {
|
|
253
|
-
errors.push(error);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
key.fill(0);
|
|
257
|
-
if (errors.length)
|
|
258
|
-
throw new AggregateError(errors, 'Rehearsal child cleanup failed.');
|
|
259
|
-
};
|
|
260
|
-
try {
|
|
261
|
-
await Promise.all([metaSpool.open(), recordSpool.open(), sourceSpool.open()]);
|
|
262
|
-
await transport.send({ version: 1, type: 'ready' }, childReceiveTimeoutMs);
|
|
263
|
-
while (true) {
|
|
264
|
-
const message = await transport.receive(childReceiveTimeoutMs);
|
|
265
|
-
if (message.type === 'stage') {
|
|
266
|
-
phase = 'staging';
|
|
267
|
-
if (inputComplete || message.sequence !== expectedSequence) {
|
|
268
|
-
throw new Error('Rehearsal stage sequence is invalid.');
|
|
269
|
-
}
|
|
270
|
-
const entry = message.entry;
|
|
271
|
-
if ('kind' in entry) {
|
|
272
|
-
if (recordsStarted)
|
|
273
|
-
throw new Error('Rehearsal sources must precede records.');
|
|
274
|
-
await sourceSpool.append(entry);
|
|
275
|
-
}
|
|
276
|
-
else {
|
|
277
|
-
recordsStarted = true;
|
|
278
|
-
const keyValue = recordKey(entry);
|
|
279
|
-
if (keyValue <= lastRecordKey)
|
|
280
|
-
throw new Error('Rehearsal records must be strictly ordered.');
|
|
281
|
-
lastRecordKey = keyValue;
|
|
282
|
-
if (entry.model === 'authswitch_authority_meta') {
|
|
283
|
-
if (++metaCount !== 1)
|
|
284
|
-
throw new Error('Rehearsal input contains duplicate metadata.');
|
|
285
|
-
await metaSpool.append(entry);
|
|
286
|
-
}
|
|
287
|
-
else
|
|
288
|
-
await recordSpool.append(entry);
|
|
289
|
-
}
|
|
290
|
-
expectedSequence++;
|
|
291
|
-
await transport.send({ version: 1, type: 'staged', sequence: message.sequence }, childReceiveTimeoutMs);
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
if (message.type === 'finish') {
|
|
295
|
-
phase = 'staging';
|
|
296
|
-
if (inputComplete || message.sequence !== expectedSequence || metaCount !== 1) {
|
|
297
|
-
throw new Error('Rehearsal input is incomplete.');
|
|
298
|
-
}
|
|
299
|
-
await Promise.all([metaSpool.finish(), recordSpool.finish(), sourceSpool.finish()]);
|
|
300
|
-
inputComplete = true;
|
|
301
|
-
recordRestoreIterator = recordSpool.values()[Symbol.asyncIterator]();
|
|
302
|
-
await transport.send({ version: 1, type: 'input.complete' }, childReceiveTimeoutMs);
|
|
303
|
-
continue;
|
|
304
|
-
}
|
|
305
|
-
if (message.type === 'restore.next') {
|
|
306
|
-
phase = 'restore';
|
|
307
|
-
if (!inputComplete || restored)
|
|
308
|
-
throw new Error('Rehearsal restore state is invalid.');
|
|
309
|
-
if (!database) {
|
|
310
|
-
database = new AuthSwitchAuthorityDatabase({ dataDirectory: plugins.path.join(root, 'database'),
|
|
311
|
-
socketPath: plugins.path.join(root, 'run', 'database.sock') });
|
|
312
|
-
await database.start({ mode: 'closed_restore' });
|
|
313
|
-
const metaIterator = metaSpool.values()[Symbol.asyncIterator]();
|
|
314
|
-
try {
|
|
315
|
-
const first = await metaIterator.next();
|
|
316
|
-
const second = await metaIterator.next();
|
|
317
|
-
if (first.done || !second.done)
|
|
318
|
-
throw new Error('Rehearsal metadata staging is not singular.');
|
|
319
|
-
assertStoredAuthorityMeta(first.value.value);
|
|
320
|
-
const meta = first.value.value;
|
|
321
|
-
await database.restoreClosedMeta(meta);
|
|
322
|
-
}
|
|
323
|
-
finally {
|
|
324
|
-
await metaIterator.return?.(undefined);
|
|
325
|
-
}
|
|
326
|
-
await transport.send({ version: 1, type: 'restore.progress', done: false }, childReceiveTimeoutMs);
|
|
327
|
-
continue;
|
|
328
|
-
}
|
|
329
|
-
const next = await recordRestoreIterator.next();
|
|
330
|
-
if (!next.done) {
|
|
331
|
-
await database.insertClosedRestoredRecord(next.value);
|
|
332
|
-
await transport.send({ version: 1, type: 'restore.progress', done: false }, childReceiveTimeoutMs);
|
|
333
|
-
continue;
|
|
334
|
-
}
|
|
335
|
-
await database.stop();
|
|
336
|
-
database = new AuthSwitchAuthorityDatabase({ dataDirectory: plugins.path.join(root, 'database'),
|
|
337
|
-
socketPath: plugins.path.join(root, 'run', 'database.sock') });
|
|
338
|
-
await database.start();
|
|
339
|
-
recordReadbackIterator = database.readStoredRecords({ pageSize: 64 })[Symbol.asyncIterator]();
|
|
340
|
-
sourceReadbackIterator = sourceSpool.values()[Symbol.asyncIterator]();
|
|
341
|
-
restored = true;
|
|
342
|
-
await transport.send({ version: 1, type: 'restore.progress', done: true }, childReceiveTimeoutMs);
|
|
343
|
-
continue;
|
|
344
|
-
}
|
|
345
|
-
if (message.type === 'record.next') {
|
|
346
|
-
phase = 'readback';
|
|
347
|
-
if (!restored || recordsReadbackDone)
|
|
348
|
-
throw new Error('Rehearsal record readback state is invalid.');
|
|
349
|
-
const next = await recordReadbackIterator.next();
|
|
350
|
-
if (next.done)
|
|
351
|
-
recordsReadbackDone = true;
|
|
352
|
-
await transport.send({ version: 1, type: 'record', record: next.done ? null : next.value }, childReceiveTimeoutMs);
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
if (message.type === 'source.next') {
|
|
356
|
-
phase = 'readback';
|
|
357
|
-
if (!restored || !recordsReadbackDone)
|
|
358
|
-
throw new Error('Rehearsal source readback state is invalid.');
|
|
359
|
-
const next = await sourceReadbackIterator.next();
|
|
360
|
-
await transport.send({ version: 1, type: 'source', source: next.done ? null : next.value }, childReceiveTimeoutMs);
|
|
361
|
-
continue;
|
|
362
|
-
}
|
|
363
|
-
if (message.type === 'close') {
|
|
364
|
-
phase = 'cleanup';
|
|
365
|
-
await cleanup();
|
|
366
|
-
await transport.send({ version: 1, type: 'closed' }, childReceiveTimeoutMs);
|
|
367
|
-
await transport.finishWritable();
|
|
368
|
-
cleanClose = true;
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
throw new Error('Unknown rehearsal request.');
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
catch {
|
|
375
|
-
try {
|
|
376
|
-
await cleanup();
|
|
377
|
-
}
|
|
378
|
-
catch {
|
|
379
|
-
phase = 'cleanup';
|
|
380
|
-
}
|
|
381
|
-
try {
|
|
382
|
-
await transport.send({ version: 1, type: 'failed', phase }, childReceiveTimeoutMs);
|
|
383
|
-
await transport.finishWritable();
|
|
384
|
-
}
|
|
385
|
-
catch { /* The parent may already have closed the control channel. */ }
|
|
386
|
-
process.exitCode = 1;
|
|
387
|
-
}
|
|
388
|
-
finally {
|
|
389
|
-
if (!cleanClose)
|
|
390
|
-
key.fill(0);
|
|
391
|
-
transport.destroy();
|
|
392
|
-
}
|
|
393
|
-
};
|
|
394
|
-
await run().catch(() => { process.exitCode = 1; });
|
|
395
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aG9yaXR5cmVoZWFyc2FsLmNoaWxkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvYXV0aG9yaXR5cmVoZWFyc2FsLmNoaWxkLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSwyQkFBMkIsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQzdFLE9BQU8sRUFBRSx5QkFBeUIsRUFBNkIsTUFBTSw4QkFBOEIsQ0FBQztBQUVwRyxPQUFPLEVBQ0wsaUNBQWlDLEVBQ2pDLG1DQUFtQyxFQUNuQyxtQ0FBbUMsRUFDbkMsb0NBQW9DLEVBQ3BDLGdDQUFnQyxHQUtqQyxNQUFNLGlDQUFpQyxDQUFDO0FBRXpDLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLENBQUM7QUFDckMsTUFBTSxxQkFBcUIsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUM7QUFFbEQsTUFBTSxRQUFRLEdBQUcsS0FBSyxFQUFFLElBQW9DLEVBQUUsS0FBaUIsRUFBaUIsRUFBRTtJQUNoRyxJQUFJLE1BQU0sR0FBRyxDQUFDLENBQUM7SUFDZixPQUFPLE1BQU0sR0FBRyxLQUFLLENBQUMsVUFBVSxFQUFFLENBQUM7UUFDakMsTUFBTSxNQUFNLEdBQUcsTUFBTSxJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssRUFBRSxNQUFNLEVBQUUsS0FBSyxDQUFDLFVBQVUsR0FBRyxNQUFNLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFDaEYsSUFBSSxNQUFNLENBQUMsWUFBWSxHQUFHLENBQUM7WUFBRSxNQUFNLElBQUksS0FBSyxDQUFDLHNEQUFzRCxDQUFDLENBQUM7UUFDckcsTUFBTSxJQUFJLE1BQU0sQ0FBQyxZQUFZLENBQUM7SUFDaEMsQ0FBQztBQUNILENBQUMsQ0FBQztBQUVGLE1BQU0sU0FBUyxHQUFHLEtBQUssRUFBRSxJQUFvQyxF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
|