@serve.zone/dcrouter 17.2.4 → 17.4.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/deno.json +1 -1
- package/dist_serve/bundle.js +2024 -1758
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.d.ts +11 -1
- package/dist_ts/classes.dcrouter.js +63 -3
- package/dist_ts/config/classes.api-token-manager.d.ts +20 -2
- package/dist_ts/config/classes.api-token-manager.js +201 -6
- package/dist_ts/config/classes.gateway-client-manager.d.ts +36 -2
- package/dist_ts/config/classes.gateway-client-manager.js +391 -27
- package/dist_ts/db/documents/classes.api-token.doc.d.ts +2 -1
- package/dist_ts/db/documents/classes.api-token.doc.js +8 -2
- package/dist_ts/db/documents/classes.cached.email.d.ts +10 -1
- package/dist_ts/db/documents/classes.cached.email.js +27 -2
- package/dist_ts/db/documents/classes.gateway-client.doc.d.ts +25 -0
- package/dist_ts/db/documents/classes.gateway-client.doc.js +73 -2
- package/dist_ts/db/documents/classes.ops-config-event.doc.d.ts +55 -0
- package/dist_ts/db/documents/classes.ops-config-event.doc.js +296 -0
- package/dist_ts/db/documents/index.d.ts +1 -0
- package/dist_ts/db/documents/index.js +2 -1
- package/dist_ts/dns/classes.gateway-route-dns-reconciler.d.ts +52 -0
- package/dist_ts/dns/classes.gateway-route-dns-reconciler.js +609 -0
- package/dist_ts/dns/index.d.ts +1 -0
- package/dist_ts/dns/index.js +2 -1
- package/dist_ts/dns/manager.dns.d.ts +19 -0
- package/dist_ts/dns/manager.dns.js +25 -1
- package/dist_ts/email/classes.accepted-email-spool.d.ts +8 -1
- package/dist_ts/email/classes.accepted-email-spool.js +93 -1
- package/dist_ts/email/classes.smartmta-storage-manager.d.ts +7 -1
- package/dist_ts/email/classes.smartmta-storage-manager.js +46 -1
- package/dist_ts/email/smartmta-storage-policy.js +3 -1
- package/dist_ts/monitoring/classes.ops-event-manager.d.ts +32 -0
- package/dist_ts/monitoring/classes.ops-event-manager.js +71 -0
- package/dist_ts/monitoring/index.d.ts +1 -0
- package/dist_ts/monitoring/index.js +2 -1
- package/dist_ts/opsserver/classes.opsserver.d.ts +1 -0
- package/dist_ts/opsserver/classes.opsserver.js +3 -1
- package/dist_ts/opsserver/handlers/certificate.handler.d.ts +1 -0
- package/dist_ts/opsserver/handlers/certificate.handler.js +35 -5
- package/dist_ts/opsserver/handlers/config-event.handler.d.ts +12 -0
- package/dist_ts/opsserver/handlers/config-event.handler.js +47 -0
- package/dist_ts/opsserver/handlers/dns-record.handler.d.ts +1 -0
- package/dist_ts/opsserver/handlers/dns-record.handler.js +82 -3
- package/dist_ts/opsserver/handlers/email-ops.handler.d.ts +6 -0
- package/dist_ts/opsserver/handlers/email-ops.handler.js +40 -9
- package/dist_ts/opsserver/handlers/gatewayclient.handler.d.ts +9 -1
- package/dist_ts/opsserver/handlers/gatewayclient.handler.js +714 -162
- package/dist_ts/opsserver/handlers/index.d.ts +1 -0
- package/dist_ts/opsserver/handlers/index.js +2 -1
- package/dist_ts/opsserver/helpers/gateway-client-auth.d.ts +25 -0
- package/dist_ts/opsserver/helpers/gateway-client-auth.js +168 -0
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/dist_ts_apiclient/classes.gatewayclient.d.ts +13 -1
- package/dist_ts_apiclient/classes.gatewayclient.js +18 -2
- package/dist_ts_interfaces/data/config-events.d.ts +34 -0
- package/dist_ts_interfaces/data/config-events.js +8 -0
- package/dist_ts_interfaces/data/index.d.ts +1 -0
- package/dist_ts_interfaces/data/index.js +2 -1
- package/dist_ts_interfaces/data/route-management.d.ts +28 -1
- package/dist_ts_interfaces/data/route-management.js +3 -1
- package/dist_ts_interfaces/data/workhoster.d.ts +1 -0
- package/dist_ts_interfaces/requests/config-events.d.ts +35 -0
- package/dist_ts_interfaces/requests/config-events.js +4 -0
- package/dist_ts_interfaces/requests/index.d.ts +1 -0
- package/dist_ts_interfaces/requests/index.js +2 -1
- package/dist_ts_interfaces/requests/realtime.d.ts +1 -1
- package/dist_ts_interfaces/requests/realtime.js +2 -1
- package/dist_ts_migrations/index.js +148 -9
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate/config-events.d.ts +19 -0
- package/dist_ts_web/appstate/config-events.js +68 -0
- package/dist_ts_web/appstate/runtime.js +7 -1
- package/dist_ts_web/appstate.d.ts +1 -0
- package/dist_ts_web/appstate.js +2 -1
- package/dist_ts_web/elements/ops-platform-events.d.ts +21 -0
- package/dist_ts_web/elements/ops-platform-events.js +361 -0
- package/dist_ts_web/elements/ops-view-logs.d.ts +4 -0
- package/dist_ts_web/elements/ops-view-logs.js +96 -9
- package/package.json +4 -4
- package/readme.plan.md +22 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +72 -2
- package/ts/config/classes.api-token-manager.ts +237 -4
- package/ts/config/classes.gateway-client-manager.ts +463 -19
- package/ts/db/documents/classes.api-token.doc.ts +8 -1
- package/ts/db/documents/classes.cached.email.ts +21 -1
- package/ts/db/documents/classes.gateway-client.doc.ts +86 -0
- package/ts/db/documents/classes.ops-config-event.doc.ts +257 -0
- package/ts/db/documents/index.ts +1 -0
- package/ts/dns/classes.gateway-route-dns-reconciler.ts +801 -0
- package/ts/dns/index.ts +1 -0
- package/ts/dns/manager.dns.ts +36 -0
- package/ts/email/classes.accepted-email-spool.ts +111 -0
- package/ts/email/classes.smartmta-storage-manager.ts +57 -1
- package/ts/email/smartmta-storage-policy.ts +2 -0
- package/ts/monitoring/classes.ops-event-manager.ts +99 -0
- package/ts/monitoring/index.ts +2 -1
- package/ts/opsserver/classes.opsserver.ts +2 -0
- package/ts/opsserver/handlers/certificate.handler.ts +51 -4
- package/ts/opsserver/handlers/config-event.handler.ts +62 -0
- package/ts/opsserver/handlers/dns-record.handler.ts +101 -2
- package/ts/opsserver/handlers/email-ops.handler.ts +42 -8
- package/ts/opsserver/handlers/gatewayclient.handler.ts +804 -136
- package/ts/opsserver/handlers/index.ts +1 -0
- package/ts/opsserver/helpers/gateway-client-auth.ts +198 -0
- package/ts/plugins.ts +2 -0
- package/ts_apiclient/classes.gatewayclient.ts +45 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate/config-events.ts +98 -0
- package/ts_web/appstate/runtime.ts +8 -1
- package/ts_web/appstate.ts +1 -0
- package/ts_web/elements/ops-platform-events.ts +315 -0
- package/ts_web/elements/ops-view-logs.ts +84 -7
|
@@ -3,6 +3,7 @@ import { logger } from '../logger.js';
|
|
|
3
3
|
import { ApiTokenDoc } from '../db/index.js';
|
|
4
4
|
import type {
|
|
5
5
|
IApiTokenPolicy,
|
|
6
|
+
IGatewayCredentialLifecycle,
|
|
6
7
|
IStoredApiToken,
|
|
7
8
|
IApiTokenInfo,
|
|
8
9
|
TApiTokenScope,
|
|
@@ -11,9 +12,11 @@ import type {
|
|
|
11
12
|
const TOKEN_PREFIX_STR = 'dcr_';
|
|
12
13
|
const ENV_ADMIN_TOKEN_ID = 'env-admin-token';
|
|
13
14
|
const ENV_ADMIN_TOKEN_CREATED_BY = 'dcrouter-env';
|
|
15
|
+
const LAST_USED_PERSIST_INTERVAL_MS = 60_000;
|
|
14
16
|
|
|
15
17
|
export class ApiTokenManager {
|
|
16
18
|
private tokens = new Map<string, IStoredApiToken>();
|
|
19
|
+
private lastUsedPersistedAt = new Map<string, number>();
|
|
17
20
|
|
|
18
21
|
constructor() {}
|
|
19
22
|
|
|
@@ -38,6 +41,7 @@ export class ApiTokenManager {
|
|
|
38
41
|
expiresInDays: number | null,
|
|
39
42
|
createdBy: string,
|
|
40
43
|
policy?: IApiTokenPolicy,
|
|
44
|
+
gatewayCredentialLifecycle: IGatewayCredentialLifecycle | null = null,
|
|
41
45
|
): Promise<{ id: string; rawToken: string }> {
|
|
42
46
|
const id = plugins.uuid.v4();
|
|
43
47
|
const randomBytes = plugins.crypto.randomBytes(32);
|
|
@@ -53,6 +57,7 @@ export class ApiTokenManager {
|
|
|
53
57
|
tokenHash,
|
|
54
58
|
scopes,
|
|
55
59
|
policy,
|
|
60
|
+
gatewayCredentialLifecycle: this.normalizeGatewayCredentialLifecycle(gatewayCredentialLifecycle, id),
|
|
56
61
|
createdAt: now,
|
|
57
62
|
expiresAt: expiresInDays != null ? now + expiresInDays * 86400000 : null,
|
|
58
63
|
lastUsedAt: null,
|
|
@@ -66,6 +71,33 @@ export class ApiTokenManager {
|
|
|
66
71
|
return { id, rawToken };
|
|
67
72
|
}
|
|
68
73
|
|
|
74
|
+
public async createProvisionedGatewayCredentialCandidate(
|
|
75
|
+
name: string,
|
|
76
|
+
scopes: TApiTokenScope[],
|
|
77
|
+
expiresInDays: number | null,
|
|
78
|
+
createdBy: string,
|
|
79
|
+
policy: IApiTokenPolicy,
|
|
80
|
+
credentialSequence: number,
|
|
81
|
+
policyDigest: string,
|
|
82
|
+
): Promise<{ id: string; rawToken: string }> {
|
|
83
|
+
if (policy.role !== 'gatewayClient' || !policy.gatewayClient?.id
|
|
84
|
+
|| !Number.isSafeInteger(policy.gatewayClient.policyGeneration)
|
|
85
|
+
|| !Number.isSafeInteger(credentialSequence) || credentialSequence < 1
|
|
86
|
+
|| !/^[a-f0-9]{64}$/.test(policyDigest)) {
|
|
87
|
+
throw new Error('Provisioned gateway credentials require a bound gateway-client policy');
|
|
88
|
+
}
|
|
89
|
+
return await this.createToken(name, scopes, expiresInDays, createdBy, policy, {
|
|
90
|
+
source: 'provisioned',
|
|
91
|
+
state: 'candidate',
|
|
92
|
+
sequence: credentialSequence,
|
|
93
|
+
policyGeneration: policy.gatewayClient.policyGeneration,
|
|
94
|
+
policyDigest,
|
|
95
|
+
finalizedAt: null,
|
|
96
|
+
cleanupCompletedAt: null,
|
|
97
|
+
revokedCredentialCount: null,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
69
101
|
/**
|
|
70
102
|
* Validate a raw token string. Returns the stored token if valid, null otherwise.
|
|
71
103
|
* Also updates lastUsedAt.
|
|
@@ -80,9 +112,18 @@ export class ApiTokenManager {
|
|
|
80
112
|
if (!stored.enabled) return null;
|
|
81
113
|
if (stored.expiresAt !== null && stored.expiresAt < Date.now()) return null;
|
|
82
114
|
|
|
83
|
-
|
|
84
|
-
stored.lastUsedAt =
|
|
85
|
-
this.
|
|
115
|
+
const now = Date.now();
|
|
116
|
+
stored.lastUsedAt = now;
|
|
117
|
+
const persistedAt = this.lastUsedPersistedAt.get(stored.id) || 0;
|
|
118
|
+
if (now - persistedAt >= LAST_USED_PERSIST_INTERVAL_MS) {
|
|
119
|
+
this.lastUsedPersistedAt.set(stored.id, now);
|
|
120
|
+
try {
|
|
121
|
+
await this.persistToken(stored);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
this.lastUsedPersistedAt.set(stored.id, persistedAt);
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
86
127
|
return stored;
|
|
87
128
|
}
|
|
88
129
|
}
|
|
@@ -146,13 +187,148 @@ export class ApiTokenManager {
|
|
|
146
187
|
public async revokeToken(id: string): Promise<boolean> {
|
|
147
188
|
if (!this.tokens.has(id)) return false;
|
|
148
189
|
const token = this.tokens.get(id)!;
|
|
149
|
-
|
|
190
|
+
// Disable durably before deletion so a failed delete cannot resurrect the
|
|
191
|
+
// credential after a restart.
|
|
192
|
+
token.enabled = false;
|
|
193
|
+
await this.persistToken(token);
|
|
150
194
|
const doc = await ApiTokenDoc.findById(id);
|
|
151
195
|
if (doc) await doc.delete();
|
|
196
|
+
this.tokens.delete(id);
|
|
197
|
+
this.lastUsedPersistedAt.delete(id);
|
|
152
198
|
logger.log('info', `API token '${token.name}' revoked (id: ${id})`);
|
|
153
199
|
return true;
|
|
154
200
|
}
|
|
155
201
|
|
|
202
|
+
/** Revoke only ordinary gateway-client credentials bound to one live client. */
|
|
203
|
+
public async revokeGatewayClientCredentials(
|
|
204
|
+
gatewayClientId: string,
|
|
205
|
+
excludingTokenId?: string,
|
|
206
|
+
): Promise<number> {
|
|
207
|
+
const ids = Array.from(this.tokens.values())
|
|
208
|
+
.filter((token) => token.id !== excludingTokenId)
|
|
209
|
+
.filter((token) => token.id !== ENV_ADMIN_TOKEN_ID)
|
|
210
|
+
.filter((token) => token.policy?.role === 'gatewayClient')
|
|
211
|
+
.filter((token) => token.policy?.gatewayClient?.id === gatewayClientId)
|
|
212
|
+
.map((token) => token.id);
|
|
213
|
+
let revoked = 0;
|
|
214
|
+
for (const id of ids) {
|
|
215
|
+
if (await this.revokeToken(id)) revoked++;
|
|
216
|
+
}
|
|
217
|
+
return revoked;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Revoke only older provisioned credentials during a finalized handover. */
|
|
221
|
+
public async revokeOlderProvisionedGatewayCredentials(
|
|
222
|
+
gatewayClientId: string,
|
|
223
|
+
finalizedSequence: number,
|
|
224
|
+
excludingTokenId: string,
|
|
225
|
+
): Promise<number> {
|
|
226
|
+
if (!Number.isSafeInteger(finalizedSequence) || finalizedSequence < 1) {
|
|
227
|
+
throw new Error('Gateway credential finalization sequence is invalid');
|
|
228
|
+
}
|
|
229
|
+
const ids = Array.from(this.tokens.values())
|
|
230
|
+
.filter((token) => token.id !== excludingTokenId)
|
|
231
|
+
.filter((token) => token.policy?.role === 'gatewayClient')
|
|
232
|
+
.filter((token) => token.policy?.gatewayClient?.id === gatewayClientId)
|
|
233
|
+
.filter((token) => token.gatewayCredentialLifecycle?.source === 'provisioned')
|
|
234
|
+
.filter((token) => Number(token.gatewayCredentialLifecycle?.sequence) < finalizedSequence)
|
|
235
|
+
.map((token) => token.id);
|
|
236
|
+
let revoked = 0;
|
|
237
|
+
for (const id of ids) {
|
|
238
|
+
if (await this.revokeToken(id)) revoked++;
|
|
239
|
+
}
|
|
240
|
+
return revoked;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Durably activate a provisioned candidate while preserving retry identity. */
|
|
244
|
+
public async activateProvisionedGatewayCredential(
|
|
245
|
+
tokenId: string,
|
|
246
|
+
gatewayClientId: string,
|
|
247
|
+
): Promise<{
|
|
248
|
+
finalizedAt: number;
|
|
249
|
+
wasAlreadyActive: boolean;
|
|
250
|
+
lifecycle: IGatewayCredentialLifecycle;
|
|
251
|
+
}> {
|
|
252
|
+
const token = this.tokens.get(tokenId);
|
|
253
|
+
if (!token || token.policy?.role !== 'gatewayClient'
|
|
254
|
+
|| token.policy.gatewayClient?.id !== gatewayClientId) {
|
|
255
|
+
throw new Error('Provisioned gateway credential is missing or has the wrong owner');
|
|
256
|
+
}
|
|
257
|
+
const lifecycle = this.normalizeGatewayCredentialLifecycle(
|
|
258
|
+
token.gatewayCredentialLifecycle,
|
|
259
|
+
token.id,
|
|
260
|
+
);
|
|
261
|
+
if (!lifecycle) {
|
|
262
|
+
throw new Error('Manual gateway credentials cannot be finalized');
|
|
263
|
+
}
|
|
264
|
+
if (lifecycle.state === 'active') {
|
|
265
|
+
return {
|
|
266
|
+
finalizedAt: lifecycle.finalizedAt!,
|
|
267
|
+
wasAlreadyActive: true,
|
|
268
|
+
lifecycle,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const previousLifecycle = lifecycle;
|
|
273
|
+
const finalizedAt = Date.now();
|
|
274
|
+
token.gatewayCredentialLifecycle = {
|
|
275
|
+
...lifecycle,
|
|
276
|
+
state: 'active',
|
|
277
|
+
finalizedAt,
|
|
278
|
+
cleanupCompletedAt: null,
|
|
279
|
+
revokedCredentialCount: null,
|
|
280
|
+
};
|
|
281
|
+
try {
|
|
282
|
+
await this.persistToken(token);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
token.gatewayCredentialLifecycle = previousLifecycle;
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
finalizedAt,
|
|
289
|
+
wasAlreadyActive: false,
|
|
290
|
+
lifecycle: token.gatewayCredentialLifecycle,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Mark sibling cleanup complete so later retries never touch newer tokens. */
|
|
295
|
+
public async completeProvisionedGatewayCredentialHandover(
|
|
296
|
+
tokenId: string,
|
|
297
|
+
gatewayClientId: string,
|
|
298
|
+
revokedCredentialCount: number,
|
|
299
|
+
): Promise<IGatewayCredentialLifecycle> {
|
|
300
|
+
const token = this.tokens.get(tokenId);
|
|
301
|
+
if (!token || token.policy?.role !== 'gatewayClient'
|
|
302
|
+
|| token.policy.gatewayClient?.id !== gatewayClientId) {
|
|
303
|
+
throw new Error('Provisioned gateway credential is missing or has the wrong owner');
|
|
304
|
+
}
|
|
305
|
+
const lifecycle = this.normalizeGatewayCredentialLifecycle(
|
|
306
|
+
token.gatewayCredentialLifecycle,
|
|
307
|
+
token.id,
|
|
308
|
+
);
|
|
309
|
+
if (!lifecycle || lifecycle.state !== 'active') {
|
|
310
|
+
throw new Error('Provisioned gateway credential is not active');
|
|
311
|
+
}
|
|
312
|
+
if (lifecycle.cleanupCompletedAt !== null) return lifecycle;
|
|
313
|
+
if (!Number.isSafeInteger(revokedCredentialCount) || revokedCredentialCount < 0) {
|
|
314
|
+
throw new Error('Revoked credential count is invalid');
|
|
315
|
+
}
|
|
316
|
+
const previousLifecycle = lifecycle;
|
|
317
|
+
token.gatewayCredentialLifecycle = {
|
|
318
|
+
...lifecycle,
|
|
319
|
+
cleanupCompletedAt: Date.now(),
|
|
320
|
+
revokedCredentialCount,
|
|
321
|
+
};
|
|
322
|
+
try {
|
|
323
|
+
await this.persistToken(token);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
token.gatewayCredentialLifecycle = previousLifecycle;
|
|
326
|
+
throw error;
|
|
327
|
+
}
|
|
328
|
+
return token.gatewayCredentialLifecycle;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
|
|
156
332
|
/**
|
|
157
333
|
* Roll (regenerate) a token's secret while keeping its identity.
|
|
158
334
|
* Returns the new raw token value (shown once).
|
|
@@ -197,12 +373,14 @@ export class ApiTokenManager {
|
|
|
197
373
|
tokenHash: doc.tokenHash,
|
|
198
374
|
scopes: doc.scopes,
|
|
199
375
|
policy: doc.policy,
|
|
376
|
+
gatewayCredentialLifecycle: this.normalizeGatewayCredentialLifecycle(doc.gatewayCredentialLifecycle, doc.id),
|
|
200
377
|
createdAt: doc.createdAt,
|
|
201
378
|
expiresAt: doc.expiresAt,
|
|
202
379
|
lastUsedAt: doc.lastUsedAt,
|
|
203
380
|
createdBy: doc.createdBy,
|
|
204
381
|
enabled: doc.enabled,
|
|
205
382
|
});
|
|
383
|
+
this.lastUsedPersistedAt.set(doc.id, doc.lastUsedAt || 0);
|
|
206
384
|
}
|
|
207
385
|
}
|
|
208
386
|
}
|
|
@@ -226,6 +404,7 @@ export class ApiTokenManager {
|
|
|
226
404
|
tokenHash: this.hashToken(rawToken),
|
|
227
405
|
scopes: ['*'],
|
|
228
406
|
policy: { role: 'admin' },
|
|
407
|
+
gatewayCredentialLifecycle: null,
|
|
229
408
|
createdAt: existing?.createdAt || now,
|
|
230
409
|
expiresAt: null,
|
|
231
410
|
lastUsedAt: existing?.lastUsedAt || null,
|
|
@@ -238,6 +417,58 @@ export class ApiTokenManager {
|
|
|
238
417
|
logger.log('info', `Environment admin API token ensured (id: ${stored.id})`);
|
|
239
418
|
}
|
|
240
419
|
|
|
420
|
+
private normalizeGatewayCredentialLifecycle(
|
|
421
|
+
valueArg: unknown,
|
|
422
|
+
tokenIdArg: string,
|
|
423
|
+
): IGatewayCredentialLifecycle | null {
|
|
424
|
+
if (valueArg == null) return null;
|
|
425
|
+
if (typeof valueArg !== 'object' || Array.isArray(valueArg)) {
|
|
426
|
+
throw new Error(`API token '${tokenIdArg}' has malformed gateway credential lifecycle`);
|
|
427
|
+
}
|
|
428
|
+
const value = valueArg as Partial<IGatewayCredentialLifecycle>;
|
|
429
|
+
if (value.source !== 'provisioned') {
|
|
430
|
+
throw new Error(`API token '${tokenIdArg}' has malformed gateway credential lifecycle`);
|
|
431
|
+
}
|
|
432
|
+
if (!Number.isSafeInteger(value.sequence) || Number(value.sequence) < 1
|
|
433
|
+
|| !Number.isSafeInteger(value.policyGeneration) || Number(value.policyGeneration) < 1
|
|
434
|
+
|| typeof value.policyDigest !== 'string' || !/^[a-f0-9]{64}$/.test(value.policyDigest)) {
|
|
435
|
+
throw new Error(`API token '${tokenIdArg}' has malformed gateway credential lifecycle`);
|
|
436
|
+
}
|
|
437
|
+
const base = {
|
|
438
|
+
source: 'provisioned' as const,
|
|
439
|
+
sequence: Number(value.sequence),
|
|
440
|
+
policyGeneration: Number(value.policyGeneration),
|
|
441
|
+
policyDigest: value.policyDigest,
|
|
442
|
+
};
|
|
443
|
+
if (value.state === 'candidate' && value.finalizedAt === null
|
|
444
|
+
&& value.cleanupCompletedAt === null && value.revokedCredentialCount === null) {
|
|
445
|
+
return {
|
|
446
|
+
...base,
|
|
447
|
+
state: 'candidate',
|
|
448
|
+
finalizedAt: null,
|
|
449
|
+
cleanupCompletedAt: null,
|
|
450
|
+
revokedCredentialCount: null,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
if (value.state === 'active' && Number.isSafeInteger(value.finalizedAt) && Number(value.finalizedAt) > 0) {
|
|
454
|
+
const cleanupPending = value.cleanupCompletedAt === null && value.revokedCredentialCount === null;
|
|
455
|
+
const cleanupComplete = Number.isSafeInteger(value.cleanupCompletedAt)
|
|
456
|
+
&& Number(value.cleanupCompletedAt) > 0
|
|
457
|
+
&& Number.isSafeInteger(value.revokedCredentialCount)
|
|
458
|
+
&& Number(value.revokedCredentialCount) >= 0;
|
|
459
|
+
if (cleanupPending || cleanupComplete) {
|
|
460
|
+
return {
|
|
461
|
+
...base,
|
|
462
|
+
state: 'active',
|
|
463
|
+
finalizedAt: Number(value.finalizedAt),
|
|
464
|
+
cleanupCompletedAt: cleanupComplete ? Number(value.cleanupCompletedAt) : null,
|
|
465
|
+
revokedCredentialCount: cleanupComplete ? Number(value.revokedCredentialCount) : null,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
throw new Error(`API token '${tokenIdArg}' has malformed gateway credential lifecycle`);
|
|
470
|
+
}
|
|
471
|
+
|
|
241
472
|
private hashToken(rawToken: string): string {
|
|
242
473
|
return plugins.crypto.createHash('sha256').update(rawToken).digest('hex');
|
|
243
474
|
}
|
|
@@ -249,6 +480,7 @@ export class ApiTokenManager {
|
|
|
249
480
|
existing.tokenHash = stored.tokenHash;
|
|
250
481
|
existing.scopes = stored.scopes;
|
|
251
482
|
existing.policy = stored.policy;
|
|
483
|
+
existing.gatewayCredentialLifecycle = stored.gatewayCredentialLifecycle ?? null;
|
|
252
484
|
existing.createdAt = stored.createdAt;
|
|
253
485
|
existing.expiresAt = stored.expiresAt;
|
|
254
486
|
existing.lastUsedAt = stored.lastUsedAt;
|
|
@@ -262,6 +494,7 @@ export class ApiTokenManager {
|
|
|
262
494
|
doc.tokenHash = stored.tokenHash;
|
|
263
495
|
doc.scopes = stored.scopes;
|
|
264
496
|
doc.policy = stored.policy;
|
|
497
|
+
doc.gatewayCredentialLifecycle = stored.gatewayCredentialLifecycle ?? null;
|
|
265
498
|
doc.createdAt = stored.createdAt;
|
|
266
499
|
doc.expiresAt = stored.expiresAt;
|
|
267
500
|
doc.lastUsedAt = stored.lastUsedAt;
|