@cortexkit/anthropic-auth-core 1.22.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/accounts.d.ts +52 -8
- package/dist/cachekeep.d.ts +27 -1
- package/dist/cch.d.ts +11 -1
- package/dist/claude-code.d.ts +23 -1
- package/dist/claustrum-enrollment.d.ts +72 -0
- package/dist/claustrum-scoped-roster.d.ts +27 -0
- package/dist/claustrum-scoped-runtime.d.ts +31 -0
- package/dist/claustrum-scoped.d.ts +53 -0
- package/dist/claustrum.d.ts +16 -82
- package/dist/commands/account.d.ts +38 -11
- package/dist/constants.d.ts +3 -2
- package/dist/custom-headers.d.ts +3 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +29700 -31
- package/dist/internal-types/LICENSE +21 -0
- package/dist/internal-types/commands/builtins.d.ts +71 -0
- package/dist/internal-types/commands/index.d.ts +10 -0
- package/dist/internal-types/commands/menu.d.ts +61 -0
- package/dist/internal-types/commands/model.d.ts +238 -0
- package/dist/internal-types/commands/pi.d.ts +23 -0
- package/dist/internal-types/commands/seam.d.ts +72 -0
- package/dist/internal-types/fs/refresh-file-lock.d.ts +33 -0
- package/dist/internal-types/logger/capture-sink.d.ts +14 -0
- package/dist/internal-types/logger/engine.d.ts +100 -0
- package/dist/internal-types/logger/index.d.ts +6 -0
- package/dist/internal-types/logger/redact.d.ts +11 -0
- package/dist/internal-types/pi-slot/index.d.ts +3 -0
- package/dist/internal-types/pi-slot/slot.d.ts +49 -0
- package/dist/internal-types/quota/codec.d.ts +11 -0
- package/dist/internal-types/quota/format.d.ts +46 -0
- package/dist/internal-types/quota/index.d.ts +10 -0
- package/dist/internal-types/quota/map.d.ts +63 -0
- package/dist/internal-types/quota/merge.d.ts +48 -0
- package/dist/internal-types/quota/projection.d.ts +64 -0
- package/dist/internal-types/store/attribution.d.ts +28 -0
- package/dist/internal-types/store/errors.d.ts +55 -0
- package/dist/internal-types/store/hooks.d.ts +12 -0
- package/dist/internal-types/store/identity.d.ts +39 -0
- package/dist/internal-types/store/index.d.ts +21 -0
- package/dist/internal-types/store/mutate.d.ts +189 -0
- package/dist/internal-types/store/pool.d.ts +196 -0
- package/dist/internal-types/store/provider-state.d.ts +131 -0
- package/dist/internal-types/store/publication.d.ts +47 -0
- package/dist/internal-types/store/pull.d.ts +36 -0
- package/dist/internal-types/store/refresh-lock.d.ts +63 -0
- package/dist/internal-types/store/refresh.d.ts +72 -0
- package/dist/internal-types/store/rows.d.ts +313 -0
- package/dist/internal-types/store/runtime.d.ts +36 -0
- package/dist/internal-types/store/schema.d.ts +443 -0
- package/dist/internal-types/store/settings.d.ts +63 -0
- package/dist/internal-types/store/torn.d.ts +78 -0
- package/dist/migration-source.d.ts +33 -0
- package/dist/model-remap.d.ts +27 -0
- package/dist/models.d.ts +60 -0
- package/dist/native-account-runtime.d.ts +121 -0
- package/dist/native-account-view.d.ts +126 -0
- package/dist/native-command-effects.d.ts +59 -0
- package/dist/native-credential-validation.d.ts +42 -0
- package/dist/native-custody-activation.d.ts +45 -0
- package/dist/native-custody.d.ts +114 -0
- package/dist/native-enrollment-authority.d.ts +2 -0
- package/dist/native-host-auth-read.d.ts +3 -0
- package/dist/native-host-auth-write.d.ts +51 -0
- package/dist/native-host-auth.d.ts +12 -0
- package/dist/native-local-credential-service.d.ts +76 -0
- package/dist/native-local-runtime-readers.d.ts +22 -0
- package/dist/native-lock-observer.d.ts +12 -0
- package/dist/native-menu-executor.d.ts +243 -0
- package/dist/native-menu-model.d.ts +142 -0
- package/dist/native-migration-proof.d.ts +28 -0
- package/dist/native-migration-source.d.ts +47 -0
- package/dist/native-migration.d.ts +66 -0
- package/dist/native-quota-codec.d.ts +32 -0
- package/dist/native-refresh-coordinator.d.ts +240 -0
- package/dist/native-roster-seed.d.ts +115 -0
- package/dist/native-runtime-registry.d.ts +19 -0
- package/dist/native-runtime.d.ts +204 -0
- package/dist/native-transient-account.d.ts +38 -0
- package/dist/native-ui.d.ts +45 -0
- package/dist/native-usage-publication.d.ts +11 -0
- package/dist/native-vault-admission.d.ts +8 -0
- package/dist/native-vault-connection.d.ts +33 -0
- package/dist/native-vault-paths.d.ts +13 -0
- package/dist/native-vault-runtime.d.ts +102 -0
- package/dist/native-vault-seed.d.ts +16 -0
- package/dist/oauth-profile.d.ts +2 -1
- package/dist/pool-authority.d.ts +159 -0
- package/dist/pool-binding.d.ts +32 -0
- package/dist/pool-paths.d.ts +21 -0
- package/dist/pool-store.d.ts +32 -0
- package/dist/prime.d.ts +11 -3
- package/dist/provider-error.d.ts +139 -0
- package/dist/quota-header-feed.d.ts +13 -0
- package/dist/quota-manager.d.ts +30 -5
- package/dist/relay.d.ts +22 -1
- package/dist/request-history.d.ts +34 -0
- package/dist/sse.d.ts +81 -0
- package/dist/sticky-routing.d.ts +29 -0
- package/package.json +9 -3
- package/dist/accounts.js +0 -3330
- package/dist/auth.js +0 -182
- package/dist/cache1h.js +0 -97
- package/dist/cachekeep-registry.js +0 -124
- package/dist/cachekeep.js +0 -578
- package/dist/cch.js +0 -127
- package/dist/claude-code.js +0 -388
- package/dist/claustrum.js +0 -529
- package/dist/commands/account.js +0 -239
- package/dist/constants.js +0 -82
- package/dist/dump.js +0 -616
- package/dist/fast.js +0 -66
- package/dist/json.js +0 -10
- package/dist/killswitch.js +0 -132
- package/dist/logger.js +0 -286
- package/dist/logging.js +0 -59
- package/dist/mid-conversation-output-config.js +0 -89
- package/dist/models.js +0 -141
- package/dist/network-errors.js +0 -27
- package/dist/oauth-profile.js +0 -54
- package/dist/pkce.js +0 -17
- package/dist/prime.js +0 -823
- package/dist/provider.js +0 -0
- package/dist/quota-header-feed.js +0 -279
- package/dist/quota-headers.js +0 -121
- package/dist/quota-manager.js +0 -805
- package/dist/quotas.js +0 -141
- package/dist/relay.js +0 -1243
- package/dist/routing.js +0 -94
- package/dist/start.js +0 -29
- package/dist/sticky-routing.js +0 -523
- package/dist/thinking-binding.js +0 -54
- package/dist/token-fingerprint.js +0 -8
package/dist/claustrum.js
DELETED
|
@@ -1,529 +0,0 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import { userInfo } from 'node:os';
|
|
3
|
-
import { SubcCallError, SubcClient, } from '@cortexkit/subc-client';
|
|
4
|
-
import { logger } from './logger';
|
|
5
|
-
export function getDefaultClaustrumConnectionPath() {
|
|
6
|
-
const uid = process.getuid?.() ?? userInfo().uid;
|
|
7
|
-
return `/run/user/${uid}/subc-connection.json`;
|
|
8
|
-
}
|
|
9
|
-
function isRecord(value) {
|
|
10
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
11
|
-
}
|
|
12
|
-
function isEndpoint(value) {
|
|
13
|
-
return (isRecord(value) &&
|
|
14
|
-
typeof value.host === 'string' &&
|
|
15
|
-
value.host.trim().length > 0 &&
|
|
16
|
-
typeof value.port === 'number' &&
|
|
17
|
-
Number.isInteger(value.port) &&
|
|
18
|
-
value.port > 0 &&
|
|
19
|
-
value.port <= 65_535);
|
|
20
|
-
}
|
|
21
|
-
export async function detectClaustrumConnection(path = getDefaultClaustrumConnectionPath()) {
|
|
22
|
-
let raw;
|
|
23
|
-
try {
|
|
24
|
-
raw = await readFile(path, 'utf8');
|
|
25
|
-
}
|
|
26
|
-
catch (error) {
|
|
27
|
-
const code = error.code;
|
|
28
|
-
if (code === 'ENOENT')
|
|
29
|
-
return { status: 'absent', path };
|
|
30
|
-
return {
|
|
31
|
-
status: 'malformed',
|
|
32
|
-
path,
|
|
33
|
-
reason: `unreadable (${code ?? 'unknown'})`,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
let value;
|
|
37
|
-
try {
|
|
38
|
-
value = JSON.parse(raw);
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
return {
|
|
42
|
-
status: 'malformed',
|
|
43
|
-
path,
|
|
44
|
-
reason: 'invalid JSON',
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
if (!isRecord(value) ||
|
|
48
|
-
typeof value.schema !== 'number' ||
|
|
49
|
-
!Number.isFinite(value.schema) ||
|
|
50
|
-
typeof value.wire_version !== 'number' ||
|
|
51
|
-
!Number.isFinite(value.wire_version) ||
|
|
52
|
-
!Array.isArray(value.endpoints) ||
|
|
53
|
-
value.endpoints.length === 0 ||
|
|
54
|
-
!value.endpoints.every(isEndpoint)) {
|
|
55
|
-
return {
|
|
56
|
-
status: 'malformed',
|
|
57
|
-
path,
|
|
58
|
-
reason: 'connection file has an invalid schema, wire_version, or endpoints',
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
return {
|
|
62
|
-
status: 'available',
|
|
63
|
-
schema: value.schema,
|
|
64
|
-
wireVersion: value.wire_version,
|
|
65
|
-
endpoints: value.endpoints.map((endpoint) => ({
|
|
66
|
-
host: endpoint.host,
|
|
67
|
-
port: endpoint.port,
|
|
68
|
-
})),
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
export const CUSTODY_TOMBSTONE_PREFIX = 'claustrum-tombstone:v1:';
|
|
72
|
-
export function custodyTombstoneKey(provider) {
|
|
73
|
-
return `${CUSTODY_TOMBSTONE_PREFIX}${provider}`;
|
|
74
|
-
}
|
|
75
|
-
export function isCustodyTombstoneValue(value) {
|
|
76
|
-
return typeof value === 'string' && value.startsWith(CUSTODY_TOMBSTONE_PREFIX);
|
|
77
|
-
}
|
|
78
|
-
export function isCustodyTombstoneOAuth(auth, provider) {
|
|
79
|
-
if (!isRecord(auth) || auth.type !== 'oauth')
|
|
80
|
-
return false;
|
|
81
|
-
const key = custodyTombstoneKey(provider);
|
|
82
|
-
return auth.refresh === key && auth.access === key;
|
|
83
|
-
}
|
|
84
|
-
export class CustodyTombstoneRefreshError extends Error {
|
|
85
|
-
provider;
|
|
86
|
-
code = 'custody_tombstone_refresh';
|
|
87
|
-
constructor(provider) {
|
|
88
|
-
super(`${provider} main slot is vault-custodied; local refresh is forbidden — the vault-served main path is not yet implemented`);
|
|
89
|
-
this.provider = provider;
|
|
90
|
-
this.name = 'CustodyTombstoneRefreshError';
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
export function assertNotCustodyTombstone(refreshToken, provider) {
|
|
94
|
-
if (isCustodyTombstoneValue(refreshToken)) {
|
|
95
|
-
throw new CustodyTombstoneRefreshError(provider);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
const CUSTODY_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
99
|
-
// The suffix is base64url for 256 CSPRNG bits; its charset is not fixture-derived.
|
|
100
|
-
const CUSTODY_HANDLE_PATTERN = /^ckh_[A-Za-z0-9_-]{43}$/;
|
|
101
|
-
const RESERVED_CUSTODY_IDS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
102
|
-
function isCustodyId(value) {
|
|
103
|
-
return (typeof value === 'string' &&
|
|
104
|
-
CUSTODY_ID_PATTERN.test(value) &&
|
|
105
|
-
!RESERVED_CUSTODY_IDS.has(value));
|
|
106
|
-
}
|
|
107
|
-
export function isValidCustodyHandle(value) {
|
|
108
|
-
return typeof value === 'string' && CUSTODY_HANDLE_PATTERN.test(value);
|
|
109
|
-
}
|
|
110
|
-
export function readCustodyHandles(json, provider) {
|
|
111
|
-
if (!isCustodyId(provider)) {
|
|
112
|
-
throw new Error(`Invalid custody provider id ${provider}`);
|
|
113
|
-
}
|
|
114
|
-
const providers = isRecord(json) && Array.isArray(json.providers) ? json.providers : [];
|
|
115
|
-
const source = providers.find((entry) => isRecord(entry) &&
|
|
116
|
-
Object.hasOwn(entry, 'provider') &&
|
|
117
|
-
entry.provider === provider);
|
|
118
|
-
if (!source || !Array.isArray(source.accounts)) {
|
|
119
|
-
throw new Error(`Missing custody handles for provider ${provider}`);
|
|
120
|
-
}
|
|
121
|
-
return {
|
|
122
|
-
provider: String(source.provider),
|
|
123
|
-
serve: String(source.serve),
|
|
124
|
-
shape: String(source.shape),
|
|
125
|
-
accounts: source.accounts.flatMap((entry) => {
|
|
126
|
-
if (!isRecord(entry))
|
|
127
|
-
return [];
|
|
128
|
-
if (!Object.hasOwn(entry, 'label') ||
|
|
129
|
-
!Object.hasOwn(entry, 'handle') ||
|
|
130
|
-
!Object.hasOwn(entry, 'credential_id') ||
|
|
131
|
-
typeof entry.label !== 'string' ||
|
|
132
|
-
typeof entry.handle !== 'string' ||
|
|
133
|
-
!isCustodyId(entry.label) ||
|
|
134
|
-
!isValidCustodyHandle(entry.handle) ||
|
|
135
|
-
typeof entry.credential_id !== 'string')
|
|
136
|
-
return [];
|
|
137
|
-
return [
|
|
138
|
-
{
|
|
139
|
-
label: entry.label,
|
|
140
|
-
handle: entry.handle,
|
|
141
|
-
credentialId: entry.credential_id,
|
|
142
|
-
},
|
|
143
|
-
];
|
|
144
|
-
}),
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
export class ClaustrumClient {
|
|
148
|
-
#client;
|
|
149
|
-
#connector;
|
|
150
|
-
#connectionFile;
|
|
151
|
-
#handshakeTimeoutMs;
|
|
152
|
-
#reconnecting = null;
|
|
153
|
-
#nextReconnectAt = 0;
|
|
154
|
-
#closed = false;
|
|
155
|
-
constructor(client, connector, connectionFile, handshakeTimeoutMs) {
|
|
156
|
-
this.#client = client;
|
|
157
|
-
this.#connector = connector;
|
|
158
|
-
this.#connectionFile = connectionFile;
|
|
159
|
-
this.#handshakeTimeoutMs = handshakeTimeoutMs;
|
|
160
|
-
}
|
|
161
|
-
static async connect(options = {}) {
|
|
162
|
-
const connectionFile = options.connectionFile ?? getDefaultClaustrumConnectionPath();
|
|
163
|
-
const connector = options.connector ??
|
|
164
|
-
((connectOptions) => SubcClient.connect(connectOptions));
|
|
165
|
-
const client = await connector({
|
|
166
|
-
connectionFile,
|
|
167
|
-
handshakeTimeoutMs: options.handshakeTimeoutMs,
|
|
168
|
-
});
|
|
169
|
-
return new ClaustrumClient(client, connector, connectionFile, options.handshakeTimeoutMs);
|
|
170
|
-
}
|
|
171
|
-
async catalogList(moduleId) {
|
|
172
|
-
return this.#client.catalogList(moduleId);
|
|
173
|
-
}
|
|
174
|
-
async routeOpen(target, identity, options = {}) {
|
|
175
|
-
// The host process inherits aft's supervised-spawn environment; null is the
|
|
176
|
-
// library contract that omits that identity instead of impersonating aft.
|
|
177
|
-
return this.#client.routeOpen(target, identity, {
|
|
178
|
-
...options,
|
|
179
|
-
consumerIdentity: null,
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
async request(handle, body, options = {}) {
|
|
183
|
-
return this.#client.request(handle, body, options);
|
|
184
|
-
}
|
|
185
|
-
async call(moduleId, method, params, options = {}) {
|
|
186
|
-
try {
|
|
187
|
-
return await this.#client.call(moduleId, method, params, {
|
|
188
|
-
...options,
|
|
189
|
-
consumerIdentity: null,
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
catch (error) {
|
|
193
|
-
if (!this.#shouldReconnect(error))
|
|
194
|
-
throw error;
|
|
195
|
-
await this.#reconnect();
|
|
196
|
-
return this.#client.call(moduleId, method, params, {
|
|
197
|
-
...options,
|
|
198
|
-
consumerIdentity: null,
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
subscribe(handle, body, onEvent, options = {}) {
|
|
203
|
-
return this.#client.subscribe(handle, body, onEvent, options);
|
|
204
|
-
}
|
|
205
|
-
async closeRoute(handle, options = {}) {
|
|
206
|
-
return this.#client.closeRoute(handle, options);
|
|
207
|
-
}
|
|
208
|
-
async closeManagedRoute(target, identity, options = {}) {
|
|
209
|
-
return this.#client.closeManagedRoute(target, identity, {
|
|
210
|
-
...options,
|
|
211
|
-
consumerIdentity: null,
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
close() {
|
|
215
|
-
this.#closed = true;
|
|
216
|
-
this.#client.close();
|
|
217
|
-
}
|
|
218
|
-
#shouldReconnect(error) {
|
|
219
|
-
return (!this.#closed &&
|
|
220
|
-
error instanceof SubcCallError &&
|
|
221
|
-
error.kind === 'terminal' &&
|
|
222
|
-
error.code !== 'missing_identity' &&
|
|
223
|
-
error.code !== 'invalid_control_body');
|
|
224
|
-
}
|
|
225
|
-
async #reconnect() {
|
|
226
|
-
if (this.#closed)
|
|
227
|
-
throw new Error('Claustrum client is closed');
|
|
228
|
-
if (this.#reconnecting) {
|
|
229
|
-
await this.#reconnecting;
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
const now = Date.now();
|
|
233
|
-
if (now < this.#nextReconnectAt) {
|
|
234
|
-
throw new Error('Claustrum client reconnect is backed off');
|
|
235
|
-
}
|
|
236
|
-
this.#nextReconnectAt = now + CLAUSTRUM_CREDENTIAL_REFRESH_BACKOFF_MS;
|
|
237
|
-
this.#reconnecting = this.#connector({
|
|
238
|
-
connectionFile: this.#connectionFile,
|
|
239
|
-
handshakeTimeoutMs: this.#handshakeTimeoutMs,
|
|
240
|
-
})
|
|
241
|
-
.then((client) => {
|
|
242
|
-
if (this.#closed) {
|
|
243
|
-
client.close();
|
|
244
|
-
throw new Error('Claustrum client is closed');
|
|
245
|
-
}
|
|
246
|
-
const previous = this.#client;
|
|
247
|
-
this.#client = client;
|
|
248
|
-
previous.close();
|
|
249
|
-
})
|
|
250
|
-
.finally(() => {
|
|
251
|
-
this.#reconnecting = null;
|
|
252
|
-
});
|
|
253
|
-
await this.#reconnecting;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
export function connectClaustrumClient(options = {}) {
|
|
257
|
-
return ClaustrumClient.connect(options);
|
|
258
|
-
}
|
|
259
|
-
export const CLAUSTRUM_MODULE_ID = 'claustrum';
|
|
260
|
-
export const DEFAULT_CLAUSTRUM_CREDENTIAL_MIN_TTL_MS = 120_000;
|
|
261
|
-
const CLAUSTRUM_CREDENTIAL_REFRESH_BACKOFF_MS = 60_000;
|
|
262
|
-
export const ERROR_CLASS_WIRE_SET = [
|
|
263
|
-
'transient',
|
|
264
|
-
'permanent',
|
|
265
|
-
'auth_required',
|
|
266
|
-
'context_overflow',
|
|
267
|
-
];
|
|
268
|
-
export class ClaustrumCredentialError extends Error {
|
|
269
|
-
code;
|
|
270
|
-
action;
|
|
271
|
-
errorClass;
|
|
272
|
-
constructor(message, code, errorClass, action) {
|
|
273
|
-
super(message);
|
|
274
|
-
this.code = code;
|
|
275
|
-
this.action = action;
|
|
276
|
-
this.name = 'ClaustrumCredentialError';
|
|
277
|
-
this.errorClass = errorClass;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
function credentialErrorAction(errorClass) {
|
|
281
|
-
switch (errorClass) {
|
|
282
|
-
case 'permanent':
|
|
283
|
-
return 'gone';
|
|
284
|
-
case 'auth_required':
|
|
285
|
-
return 'reauth';
|
|
286
|
-
case 'context_overflow':
|
|
287
|
-
return 'reduce_and_retry';
|
|
288
|
-
case 'transient':
|
|
289
|
-
return 'retry';
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
function asCredentialError(response, fallbackCode = 'invalid_response') {
|
|
293
|
-
const result = isRecord(response) && isRecord(response.result)
|
|
294
|
-
? response.result
|
|
295
|
-
: undefined;
|
|
296
|
-
const error = result && isRecord(result.error) ? result.error : undefined;
|
|
297
|
-
const rawErrorClass = error?.class;
|
|
298
|
-
const errorClass = typeof rawErrorClass === 'string' &&
|
|
299
|
-
ERROR_CLASS_WIRE_SET.includes(rawErrorClass)
|
|
300
|
-
? rawErrorClass
|
|
301
|
-
: 'transient';
|
|
302
|
-
if (typeof rawErrorClass !== 'string' ||
|
|
303
|
-
!ERROR_CLASS_WIRE_SET.includes(rawErrorClass)) {
|
|
304
|
-
logger.warn('claustrum', 'unrecognised credential error class', {
|
|
305
|
-
errorClass: rawErrorClass ?? null,
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
const code = error && typeof error.code === 'string' ? error.code : fallbackCode;
|
|
309
|
-
return new ClaustrumCredentialError(`Claustrum credential request failed: ${code}`, code, errorClass, credentialErrorAction(errorClass));
|
|
310
|
-
}
|
|
311
|
-
function asCredentialCallError(error) {
|
|
312
|
-
if (error instanceof ClaustrumCredentialError) {
|
|
313
|
-
return error;
|
|
314
|
-
}
|
|
315
|
-
if (error instanceof SubcCallError && error.kind === 'terminal') {
|
|
316
|
-
logger.warn('claustrum', 'terminal credential call failed', {
|
|
317
|
-
code: error.code ?? null,
|
|
318
|
-
message: error.message,
|
|
319
|
-
});
|
|
320
|
-
return new ClaustrumCredentialError(`Claustrum credential request failed: ${error.message}`, error.code ?? 'terminal_error', 'transient', 'retry');
|
|
321
|
-
}
|
|
322
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
323
|
-
const code = isRecord(error) && typeof error.code === 'string'
|
|
324
|
-
? error.code
|
|
325
|
-
: 'transport_error';
|
|
326
|
-
return new ClaustrumCredentialError(`Claustrum credential request failed: ${message}`, code, 'transient', 'retry');
|
|
327
|
-
}
|
|
328
|
-
function decodeCredentialGetResponse(response) {
|
|
329
|
-
const result = isRecord(response) && isRecord(response.result)
|
|
330
|
-
? response.result
|
|
331
|
-
: undefined;
|
|
332
|
-
if (result && isRecord(result.error)) {
|
|
333
|
-
throw asCredentialError(response);
|
|
334
|
-
}
|
|
335
|
-
const payload = result?.payload;
|
|
336
|
-
if (!Array.isArray(payload) ||
|
|
337
|
-
payload.length === 0 ||
|
|
338
|
-
!payload.every((value) => typeof value === 'number' &&
|
|
339
|
-
Number.isInteger(value) &&
|
|
340
|
-
value >= 0 &&
|
|
341
|
-
value <= 255)) {
|
|
342
|
-
throw asCredentialError(response);
|
|
343
|
-
}
|
|
344
|
-
const recordVersion = result?.record_version;
|
|
345
|
-
if (typeof recordVersion !== 'number' ||
|
|
346
|
-
!Number.isSafeInteger(recordVersion) ||
|
|
347
|
-
recordVersion < 0) {
|
|
348
|
-
throw asCredentialError(response, 'invalid_record_version');
|
|
349
|
-
}
|
|
350
|
-
const rawExpiresAtMs = result?.expires_at_ms;
|
|
351
|
-
const expiresAtMs = rawExpiresAtMs === null || rawExpiresAtMs === undefined
|
|
352
|
-
? null
|
|
353
|
-
: typeof rawExpiresAtMs === 'number' && Number.isFinite(rawExpiresAtMs)
|
|
354
|
-
? rawExpiresAtMs
|
|
355
|
-
: undefined;
|
|
356
|
-
if (expiresAtMs === undefined) {
|
|
357
|
-
throw asCredentialError(response, 'invalid_expiry');
|
|
358
|
-
}
|
|
359
|
-
const decoded = new TextDecoder().decode(Uint8Array.from(payload));
|
|
360
|
-
return {
|
|
361
|
-
payload: decoded,
|
|
362
|
-
expiresAtMs,
|
|
363
|
-
recordVersion,
|
|
364
|
-
...(typeof result?.project_id === 'string' && {
|
|
365
|
-
projectId: result.project_id,
|
|
366
|
-
}),
|
|
367
|
-
...(typeof result?.account_id === 'string' && {
|
|
368
|
-
accountId: result.account_id,
|
|
369
|
-
}),
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
export class ClaustrumCredentialCache {
|
|
373
|
-
#cache = new Map();
|
|
374
|
-
#inFlight = new Map();
|
|
375
|
-
#client;
|
|
376
|
-
#identity;
|
|
377
|
-
#now;
|
|
378
|
-
#refreshBackoffUntil = new Map();
|
|
379
|
-
#minTtlMs;
|
|
380
|
-
constructor(client, options = {}) {
|
|
381
|
-
this.#client = client;
|
|
382
|
-
this.#identity = options.identity;
|
|
383
|
-
this.#now = options.now ?? Date.now;
|
|
384
|
-
this.#minTtlMs = options.minTtlMs ?? DEFAULT_CLAUSTRUM_CREDENTIAL_MIN_TTL_MS;
|
|
385
|
-
if (!Number.isSafeInteger(this.#minTtlMs) || this.#minTtlMs < 0) {
|
|
386
|
-
throw new RangeError('minTtlMs must be a non-negative safe integer');
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
async get(handle, minTtlMs = this.#minTtlMs) {
|
|
390
|
-
if (!Number.isSafeInteger(minTtlMs) || minTtlMs < 0) {
|
|
391
|
-
throw new RangeError('minTtlMs must be a non-negative safe integer');
|
|
392
|
-
}
|
|
393
|
-
const now = this.#now();
|
|
394
|
-
const cached = this.#cache.get(handle);
|
|
395
|
-
if (cached && cached.expiresAtMs !== null && cached.expiresAtMs > now) {
|
|
396
|
-
if (cached.expiresAtMs - now <= minTtlMs) {
|
|
397
|
-
this.#refreshIfApproachingExpiry(handle, now, minTtlMs);
|
|
398
|
-
}
|
|
399
|
-
return cached;
|
|
400
|
-
}
|
|
401
|
-
if (cached) {
|
|
402
|
-
this.#cache.delete(handle);
|
|
403
|
-
this.#refreshBackoffUntil.delete(handle);
|
|
404
|
-
}
|
|
405
|
-
const pending = this.#inFlight.get(handle);
|
|
406
|
-
if (pending)
|
|
407
|
-
return pending;
|
|
408
|
-
const load = this.#load(handle, minTtlMs);
|
|
409
|
-
this.#inFlight.set(handle, load);
|
|
410
|
-
try {
|
|
411
|
-
return await load;
|
|
412
|
-
}
|
|
413
|
-
finally {
|
|
414
|
-
if (this.#inFlight.get(handle) === load)
|
|
415
|
-
this.#inFlight.delete(handle);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
peek(handle) {
|
|
419
|
-
return this.#cache.get(handle);
|
|
420
|
-
}
|
|
421
|
-
seedForTest(handle, credential) {
|
|
422
|
-
this.#cache.set(handle, credential);
|
|
423
|
-
}
|
|
424
|
-
reduceMinTtlMs() {
|
|
425
|
-
this.#minTtlMs = Math.floor(this.#minTtlMs / 2);
|
|
426
|
-
return this.#minTtlMs;
|
|
427
|
-
}
|
|
428
|
-
async reportAuthFailure(handle, providerStatusOrServedCredential, servedCredentialOrStatus, reporterSource) {
|
|
429
|
-
const providerStatus = typeof providerStatusOrServedCredential === 'number'
|
|
430
|
-
? providerStatusOrServedCredential
|
|
431
|
-
: typeof servedCredentialOrStatus === 'number'
|
|
432
|
-
? servedCredentialOrStatus
|
|
433
|
-
: 401;
|
|
434
|
-
const servedCredential = typeof providerStatusOrServedCredential === 'number'
|
|
435
|
-
? typeof servedCredentialOrStatus === 'object' &&
|
|
436
|
-
servedCredentialOrStatus !== null
|
|
437
|
-
? servedCredentialOrStatus
|
|
438
|
-
: undefined
|
|
439
|
-
: providerStatusOrServedCredential;
|
|
440
|
-
const recordVersion = servedCredential?.recordVersion;
|
|
441
|
-
if (typeof recordVersion !== 'number' ||
|
|
442
|
-
!Number.isSafeInteger(recordVersion) ||
|
|
443
|
-
recordVersion < 0) {
|
|
444
|
-
throw new TypeError('record_version is required from the credential served to the provider');
|
|
445
|
-
}
|
|
446
|
-
try {
|
|
447
|
-
let response;
|
|
448
|
-
try {
|
|
449
|
-
response = await this.#client.call(CLAUSTRUM_MODULE_ID, 'credential.report_auth_failure', {
|
|
450
|
-
handle,
|
|
451
|
-
provider_status: providerStatus,
|
|
452
|
-
record_version: recordVersion,
|
|
453
|
-
...(reporterSource ? { reporter_source: reporterSource } : {}),
|
|
454
|
-
}, { identity: this.#identity });
|
|
455
|
-
}
|
|
456
|
-
catch (error) {
|
|
457
|
-
throw asCredentialCallError(error);
|
|
458
|
-
}
|
|
459
|
-
const result = isRecord(response) && isRecord(response.result)
|
|
460
|
-
? response.result
|
|
461
|
-
: undefined;
|
|
462
|
-
if (result && isRecord(result.error)) {
|
|
463
|
-
throw asCredentialError(response);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
finally {
|
|
467
|
-
this.invalidate(handle, recordVersion);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
invalidate(handle, recordVersion) {
|
|
471
|
-
const cached = this.#cache.get(handle);
|
|
472
|
-
if (cached &&
|
|
473
|
-
(recordVersion === undefined || cached.recordVersion === recordVersion)) {
|
|
474
|
-
this.#cache.delete(handle);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
close() {
|
|
478
|
-
this.#client.close();
|
|
479
|
-
}
|
|
480
|
-
#refreshIfApproachingExpiry(handle, now, minTtlMs) {
|
|
481
|
-
if (this.#inFlight.has(handle))
|
|
482
|
-
return;
|
|
483
|
-
const retryAt = this.#refreshBackoffUntil.get(handle);
|
|
484
|
-
if (retryAt !== undefined && retryAt > now)
|
|
485
|
-
return;
|
|
486
|
-
this.#refreshBackoffUntil.set(handle, now + CLAUSTRUM_CREDENTIAL_REFRESH_BACKOFF_MS);
|
|
487
|
-
const load = this.#load(handle, minTtlMs);
|
|
488
|
-
this.#inFlight.set(handle, load);
|
|
489
|
-
void load
|
|
490
|
-
.catch(() => { })
|
|
491
|
-
.finally(() => {
|
|
492
|
-
if (this.#inFlight.get(handle) === load)
|
|
493
|
-
this.#inFlight.delete(handle);
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
async #load(handle, minTtlMs) {
|
|
497
|
-
let response;
|
|
498
|
-
try {
|
|
499
|
-
response = await this.#client.call(CLAUSTRUM_MODULE_ID, 'credential.get', {
|
|
500
|
-
handle,
|
|
501
|
-
force_refresh: false,
|
|
502
|
-
min_ttl_ms: minTtlMs,
|
|
503
|
-
}, { identity: this.#identity });
|
|
504
|
-
}
|
|
505
|
-
catch (error) {
|
|
506
|
-
throw asCredentialCallError(error);
|
|
507
|
-
}
|
|
508
|
-
const result = decodeCredentialGetResponse(response);
|
|
509
|
-
const credential = {
|
|
510
|
-
payload: result.payload,
|
|
511
|
-
expiresAtMs: result.expiresAtMs,
|
|
512
|
-
recordVersion: result.recordVersion,
|
|
513
|
-
...(result.projectId !== undefined && { projectId: result.projectId }),
|
|
514
|
-
...(result.accountId !== undefined && { accountId: result.accountId }),
|
|
515
|
-
};
|
|
516
|
-
if (credential.expiresAtMs !== null &&
|
|
517
|
-
credential.expiresAtMs > this.#now()) {
|
|
518
|
-
this.#cache.set(handle, credential);
|
|
519
|
-
}
|
|
520
|
-
return credential;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
export async function connectClaustrumCredentialCache(options = {}) {
|
|
524
|
-
if (options.enabled !== true)
|
|
525
|
-
return null;
|
|
526
|
-
const { enabled: _enabled, identity, now, minTtlMs, ...clientOptions } = options;
|
|
527
|
-
const client = await connectClaustrumClient(clientOptions);
|
|
528
|
-
return new ClaustrumCredentialCache(client, { identity, now, minTtlMs });
|
|
529
|
-
}
|