agl 1.0.17 → 21.0.2
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/.smartconfig.json +55 -0
- package/changelog.md +1026 -0
- package/cli.js +14 -0
- package/dist_serve/bundle.js +44778 -0
- package/dist_serve/index.html +13 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/classes.authmodels.d.ts +126 -0
- package/dist_ts/classes.authmodels.js +1422 -0
- package/dist_ts/classes.authstore.d.ts +160 -0
- package/dist_ts/classes.authstore.js +2483 -0
- package/dist_ts/classes.browserresourcehost.d.ts +85 -0
- package/dist_ts/classes.browserresourcehost.js +912 -0
- package/dist_ts/classes.cli.d.ts +21 -0
- package/dist_ts/classes.cli.js +1082 -0
- package/dist_ts/classes.config.d.ts +23 -0
- package/dist_ts/classes.config.js +267 -0
- package/dist_ts/classes.controller.d.ts +523 -0
- package/dist_ts/classes.controller.js +10611 -0
- package/dist_ts/classes.controllermanagement.d.ts +32 -0
- package/dist_ts/classes.controllermanagement.js +222 -0
- package/dist_ts/classes.draftmanager.d.ts +30 -0
- package/dist_ts/classes.draftmanager.js +179 -0
- package/dist_ts/classes.embeddeddb.d.ts +30 -0
- package/dist_ts/classes.embeddeddb.js +159 -0
- package/dist_ts/classes.flexframedtransport.d.ts +56 -0
- package/dist_ts/classes.flexframedtransport.js +275 -0
- package/dist_ts/classes.flexmodels.private.d.ts +231 -0
- package/dist_ts/classes.flexmodels.private.js +985 -0
- package/dist_ts/classes.flexmodels.public.d.ts +207 -0
- package/dist_ts/classes.flexmodels.public.js +885 -0
- package/dist_ts/classes.flexprojectionreader.d.ts +89 -0
- package/dist_ts/classes.flexprojectionreader.js +580 -0
- package/dist_ts/classes.flexservice.d.ts +196 -0
- package/dist_ts/classes.flexservice.js +3704 -0
- package/dist_ts/classes.flexstore.d.ts +174 -0
- package/dist_ts/classes.flexstore.js +3400 -0
- package/dist_ts/classes.flexsupervisor.d.ts +105 -0
- package/dist_ts/classes.flexsupervisor.js +1191 -0
- package/dist_ts/classes.gitreversion.d.ts +431 -0
- package/dist_ts/classes.gitreversion.js +10592 -0
- package/dist_ts/classes.harnessoperationmanager.d.ts +113 -0
- package/dist_ts/classes.harnessoperationmanager.js +602 -0
- package/dist_ts/classes.opencodeclient.d.ts +146 -0
- package/dist_ts/classes.opencodeclient.js +3003 -0
- package/dist_ts/classes.opencodesupervisor.d.ts +60 -0
- package/dist_ts/classes.opencodesupervisor.js +1003 -0
- package/dist_ts/classes.passkeymanager.d.ts +34 -0
- package/dist_ts/classes.passkeymanager.js +456 -0
- package/dist_ts/classes.processinspection.d.ts +50 -0
- package/dist_ts/classes.processinspection.js +484 -0
- package/dist_ts/classes.resourcecoordinator.d.ts +64 -0
- package/dist_ts/classes.resourcecoordinator.js +294 -0
- package/dist_ts/classes.sessionintelligencereader.d.ts +13 -0
- package/dist_ts/classes.sessionintelligencereader.js +40 -0
- package/dist_ts/classes.systemmetricssampler.d.ts +53 -0
- package/dist_ts/classes.systemmetricssampler.js +168 -0
- package/dist_ts/classes.terminalmanager.d.ts +95 -0
- package/dist_ts/classes.terminalmanager.js +981 -0
- package/dist_ts/classes.terminalresourcehost.d.ts +20 -0
- package/dist_ts/classes.terminalresourcehost.js +84 -0
- package/dist_ts/classes.upgradecoordinator.d.ts +244 -0
- package/dist_ts/classes.upgradecoordinator.js +2155 -0
- package/dist_ts/classes.upgradetransaction.d.ts +92 -0
- package/dist_ts/classes.upgradetransaction.js +2125 -0
- package/dist_ts/classes.uploadmanager.d.ts +24 -0
- package/dist_ts/classes.uploadmanager.js +210 -0
- package/dist_ts/classes.validation.d.ts +91 -0
- package/dist_ts/classes.validation.js +1087 -0
- package/dist_ts/flexharness.child.d.ts +1 -0
- package/dist_ts/flexharness.child.js +28 -0
- package/dist_ts/functions.canonicaldirectory.d.ts +21 -0
- package/dist_ts/functions.canonicaldirectory.js +144 -0
- package/dist_ts/functions.controllerdataroot.d.ts +15 -0
- package/dist_ts/functions.controllerdataroot.js +76 -0
- package/dist_ts/functions.embeddeddb.d.ts +2 -0
- package/dist_ts/functions.embeddeddb.js +21 -0
- package/dist_ts/functions.flexcredentialstore.d.ts +16 -0
- package/dist_ts/functions.flexcredentialstore.js +93 -0
- package/dist_ts/functions.opencodeauth.d.ts +3 -0
- package/dist_ts/functions.opencodeauth.js +31 -0
- package/dist_ts/functions.runtimeenvironment.d.ts +2 -0
- package/dist_ts/functions.runtimeenvironment.js +79 -0
- package/dist_ts/functions.upgradefollower.d.ts +3 -0
- package/dist_ts/functions.upgradefollower.js +95 -0
- package/dist_ts/index.d.ts +24 -0
- package/dist_ts/index.js +25 -0
- package/dist_ts/interfaces.auth.d.ts +181 -0
- package/dist_ts/interfaces.auth.js +82 -0
- package/dist_ts/interfaces.config.d.ts +34 -0
- package/dist_ts/interfaces.config.js +2 -0
- package/dist_ts/interfaces.flexipc.d.ts +939 -0
- package/dist_ts/interfaces.flexipc.js +1492 -0
- package/dist_ts/interfaces.gitreversion.d.ts +246 -0
- package/dist_ts/interfaces.gitreversion.js +2 -0
- package/dist_ts/interfaces.lifecycle.d.ts +37 -0
- package/dist_ts/interfaces.lifecycle.js +2 -0
- package/dist_ts/interfaces.opencode.d.ts +101 -0
- package/dist_ts/interfaces.opencode.js +3 -0
- package/dist_ts/interfaces.projects.d.ts +156 -0
- package/dist_ts/interfaces.projects.js +5 -0
- package/dist_ts/plugins.d.ts +36 -0
- package/dist_ts/plugins.js +40 -0
- package/dist_ts_interfaces/index.d.ts +2 -0
- package/dist_ts_interfaces/index.js +3 -0
- package/dist_ts_interfaces/interfaces.d.ts +747 -0
- package/dist_ts_interfaces/interfaces.js +47 -0
- package/dist_ts_interfaces/plugins.d.ts +4 -0
- package/dist_ts_interfaces/plugins.js +3 -0
- package/dist_ts_interfaces/requests.d.ts +806 -0
- package/dist_ts_interfaces/requests.js +2 -0
- package/dist_ts_migration/classes.documentmigrationrunner.d.ts +8 -0
- package/dist_ts_migration/classes.documentmigrationrunner.js +24 -0
- package/dist_ts_migration/classes.flexmigrationrunner.d.ts +11 -0
- package/dist_ts_migration/classes.flexmigrationrunner.js +25 -0
- package/dist_ts_migration/classes.flexpostharnessmigrationrunner.d.ts +10 -0
- package/dist_ts_migration/classes.flexpostharnessmigrationrunner.js +15 -0
- package/dist_ts_migration/classes.migrationrunner.d.ts +8 -0
- package/dist_ts_migration/classes.migrationrunner.js +34 -0
- package/dist_ts_migration/index.d.ts +12 -0
- package/dist_ts_migration/index.js +13 -0
- package/dist_ts_migration/v10_flexharnessv3migration.d.ts +11 -0
- package/dist_ts_migration/v10_flexharnessv3migration.js +23 -0
- package/dist_ts_migration/v13_flexprojectionorder.d.ts +11 -0
- package/dist_ts_migration/v13_flexprojectionorder.js +23 -0
- package/dist_ts_migration/v16_flexprovidercredentials.d.ts +22 -0
- package/dist_ts_migration/v16_flexprovidercredentials.js +149 -0
- package/dist_ts_migration/v18_resourcelayout.d.ts +22 -0
- package/dist_ts_migration/v18_resourcelayout.js +23 -0
- package/dist_ts_migration/v18_resourcelayoutmigration.d.ts +6 -0
- package/dist_ts_migration/v18_resourcelayoutmigration.js +61 -0
- package/dist_ts_migration/v19_flexprojectmanagement.d.ts +11 -0
- package/dist_ts_migration/v19_flexprojectmanagement.js +21 -0
- package/dist_ts_migration/v1_embeddeddatabaselocation.d.ts +28 -0
- package/dist_ts_migration/v1_embeddeddatabaselocation.js +415 -0
- package/dist_ts_migration/v22_projectfilesystemidentitymigration.d.ts +7 -0
- package/dist_ts_migration/v22_projectfilesystemidentitymigration.js +105 -0
- package/dist_ts_migration/v2_controllerdataroot.d.ts +130 -0
- package/dist_ts_migration/v2_controllerdataroot.js +1887 -0
- package/dist_ts_migration/v6_protocoldocumentmigration.d.ts +7 -0
- package/dist_ts_migration/v6_protocoldocumentmigration.js +100 -0
- package/dist_ts_migration/v6_protocoldocuments.d.ts +7 -0
- package/dist_ts_migration/v6_protocoldocuments.js +138 -0
- package/html/index.html +13 -0
- package/{LICENSE → license.md} +2 -2
- package/package.json +86 -48
- package/readme.md +480 -0
- package/readme.plan.md +188 -0
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/classes.authmodels.ts +1245 -0
- package/ts/classes.authstore.ts +3274 -0
- package/ts/classes.browserresourcehost.ts +1205 -0
- package/ts/classes.cli.ts +1254 -0
- package/ts/classes.config.ts +351 -0
- package/ts/classes.controller.ts +14426 -0
- package/ts/classes.controllermanagement.ts +330 -0
- package/ts/classes.draftmanager.ts +232 -0
- package/ts/classes.embeddeddb.ts +166 -0
- package/ts/classes.flexframedtransport.ts +283 -0
- package/ts/classes.flexmodels.private.ts +823 -0
- package/ts/classes.flexmodels.public.ts +732 -0
- package/ts/classes.flexprojectionreader.ts +816 -0
- package/ts/classes.flexservice.ts +4302 -0
- package/ts/classes.flexstore.ts +4565 -0
- package/ts/classes.flexsupervisor.ts +1453 -0
- package/ts/classes.gitreversion.ts +13353 -0
- package/ts/classes.harnessoperationmanager.ts +870 -0
- package/ts/classes.opencodeclient.ts +4110 -0
- package/ts/classes.opencodesupervisor.ts +1280 -0
- package/ts/classes.passkeymanager.ts +570 -0
- package/ts/classes.processinspection.ts +623 -0
- package/ts/classes.resourcecoordinator.ts +436 -0
- package/ts/classes.sessionintelligencereader.ts +52 -0
- package/ts/classes.systemmetricssampler.ts +215 -0
- package/ts/classes.terminalmanager.ts +1207 -0
- package/ts/classes.terminalresourcehost.ts +122 -0
- package/ts/classes.upgradecoordinator.ts +2675 -0
- package/ts/classes.upgradetransaction.ts +2900 -0
- package/ts/classes.uploadmanager.ts +240 -0
- package/ts/classes.validation.ts +1409 -0
- package/ts/flexharness.child.ts +35 -0
- package/ts/functions.canonicaldirectory.ts +213 -0
- package/ts/functions.controllerdataroot.ts +107 -0
- package/ts/functions.embeddeddb.ts +22 -0
- package/ts/functions.flexcredentialstore.ts +128 -0
- package/ts/functions.opencodeauth.ts +34 -0
- package/ts/functions.runtimeenvironment.ts +90 -0
- package/ts/functions.upgradefollower.ts +115 -0
- package/ts/index.ts +36 -6
- package/ts/interfaces.auth.ts +316 -0
- package/ts/interfaces.config.ts +37 -0
- package/ts/interfaces.flexipc.ts +2586 -0
- package/ts/interfaces.gitreversion.ts +336 -0
- package/ts/interfaces.lifecycle.ts +46 -0
- package/ts/interfaces.opencode.ts +153 -0
- package/ts/interfaces.projects.ts +182 -0
- package/ts/plugins.ts +64 -0
- package/ts_interfaces/index.ts +2 -0
- package/ts_interfaces/interfaces.ts +827 -0
- package/ts_interfaces/plugins.ts +15 -0
- package/ts_interfaces/requests.ts +1186 -0
- package/ts_migration/classes.documentmigrationrunner.ts +32 -0
- package/ts_migration/classes.flexmigrationrunner.ts +33 -0
- package/ts_migration/classes.flexpostharnessmigrationrunner.ts +22 -0
- package/ts_migration/classes.migrationrunner.ts +40 -0
- package/ts_migration/index.ts +12 -0
- package/ts_migration/v10_flexharnessv3migration.ts +35 -0
- package/ts_migration/v13_flexprojectionorder.ts +33 -0
- package/ts_migration/v16_flexprovidercredentials.ts +196 -0
- package/ts_migration/v18_resourcelayout.ts +55 -0
- package/ts_migration/v18_resourcelayoutmigration.ts +68 -0
- package/ts_migration/v19_flexprojectmanagement.ts +28 -0
- package/ts_migration/v1_embeddeddatabaselocation.ts +560 -0
- package/ts_migration/v22_projectfilesystemidentitymigration.ts +121 -0
- package/ts_migration/v2_controllerdataroot.ts +2349 -0
- package/ts_migration/v6_protocoldocumentmigration.ts +144 -0
- package/ts_migration/v6_protocoldocuments.ts +198 -0
- package/ts_web/00_commitinfo_data.ts +8 -0
- package/ts_web/classes.controllersocketclient.ts +517 -0
- package/ts_web/classes.sessiondraftsync.ts +390 -0
- package/ts_web/elements.harnesscontrollerapp.ts +11494 -0
- package/ts_web/index.ts +2 -0
- package/ts_web/plugins.ts +31 -0
- package/.gitlab-ci.yml +0 -71
- package/.npmignore +0 -5
- package/README.md +0 -40
- package/dist/cli.js +0 -2
- package/dist/index.d.ts +0 -0
- package/dist/index.js +0 -9
- package/dist/mod_bundle/index.d.ts +0 -1
- package/dist/mod_bundle/index.js +0 -18
- package/dist/mod_bundle/mod.plugins.d.ts +0 -4
- package/dist/mod_bundle/mod.plugins.js +0 -11
- package/dist/mod_install/index.d.ts +0 -4
- package/dist/mod_install/index.js +0 -21
- package/dist/mod_install/mod.plugins.d.ts +0 -1
- package/dist/mod_install/mod.plugins.js +0 -7
- package/dist/mod_standard/index.d.ts +0 -4
- package/dist/mod_standard/index.js +0 -21
- package/dist/mod_standard/mod.browser.d.ts +0 -6
- package/dist/mod_standard/mod.browser.js +0 -14
- package/dist/mod_standard/mod.clean.d.ts +0 -4
- package/dist/mod_standard/mod.clean.js +0 -12
- package/dist/mod_standard/mod.compile.d.ts +0 -4
- package/dist/mod_standard/mod.compile.js +0 -21
- package/dist/mod_standard/mod.plugins.d.ts +0 -8
- package/dist/mod_standard/mod.plugins.js +0 -19
- package/dist/mod_standard/mod.test.d.ts +0 -4
- package/dist/mod_standard/mod.test.js +0 -12
- package/dist/plr.cli.d.ts +0 -3
- package/dist/plr.cli.js +0 -57
- package/dist/plr.config.d.ts +0 -11
- package/dist/plr.config.js +0 -26
- package/dist/plr.mods.d.ts +0 -17
- package/dist/plr.mods.js +0 -17
- package/dist/plr.paths.d.ts +0 -19
- package/dist/plr.paths.js +0 -26
- package/dist/plr.plugins.d.ts +0 -8
- package/dist/plr.plugins.js +0 -16
- package/npmextra.json +0 -10
- package/ts/mod_bundle/index.ts +0 -20
- package/ts/mod_bundle/mod.plugins.ts +0 -9
- package/ts/mod_install/index.ts +0 -20
- package/ts/mod_install/mod.plugins.ts +0 -1
- package/ts/mod_standard/index.ts +0 -23
- package/ts/mod_standard/mod.browser.ts +0 -17
- package/ts/mod_standard/mod.clean.ts +0 -13
- package/ts/mod_standard/mod.compile.ts +0 -23
- package/ts/mod_standard/mod.plugins.ts +0 -17
- package/ts/mod_standard/mod.test.ts +0 -13
- package/ts/plr.cli.ts +0 -60
- package/ts/plr.config.ts +0 -36
- package/ts/plr.mods.ts +0 -25
- package/ts/plr.paths.ts +0 -29
- package/ts/plr.plugins.ts +0 -17
- package/tslint.json +0 -3
- package/yarn.lock +0 -3464
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import {
|
|
3
|
+
AuthError,
|
|
4
|
+
type IAuthenticationBeginOptions,
|
|
5
|
+
type IAuthenticationBeginResult,
|
|
6
|
+
type IAuthenticationFinishOptions,
|
|
7
|
+
type IAuthenticationResult,
|
|
8
|
+
type IPasskeyManagerOptions,
|
|
9
|
+
type ISetupBeginOptions,
|
|
10
|
+
type ISetupBeginResult,
|
|
11
|
+
type ISetupFinishOptions,
|
|
12
|
+
type IStoredPasskeyCredential,
|
|
13
|
+
type IWebAuthnCeremonyDocument,
|
|
14
|
+
type IWebAuthnServerImplementation,
|
|
15
|
+
type IWebAuthnSetupCeremonyDocument,
|
|
16
|
+
} from './interfaces.auth.js';
|
|
17
|
+
|
|
18
|
+
const setupCeremonyLifetimeMs = 5 * 60 * 1000;
|
|
19
|
+
const authenticationCeremonyLifetimeMs = 2 * 60 * 1000;
|
|
20
|
+
const maxAuthenticationVerificationAttemptsPerWindow = 128;
|
|
21
|
+
const base64Url32BytePattern = /^[A-Za-z0-9_-]{43}$/;
|
|
22
|
+
|
|
23
|
+
const defaultWebAuthn: IWebAuthnServerImplementation = {
|
|
24
|
+
generateRegistrationOptions: plugins.simplewebauthnServer.generateRegistrationOptions,
|
|
25
|
+
verifyRegistrationResponse: plugins.simplewebauthnServer.verifyRegistrationResponse,
|
|
26
|
+
generateAuthenticationOptions: plugins.simplewebauthnServer.generateAuthenticationOptions,
|
|
27
|
+
verifyAuthenticationResponse: plugins.simplewebauthnServer.verifyAuthenticationResponse,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const requirePositiveLimit = (
|
|
31
|
+
valueArg: number,
|
|
32
|
+
labelArg: string,
|
|
33
|
+
maximumArg = 10_000,
|
|
34
|
+
): number => {
|
|
35
|
+
if (!Number.isSafeInteger(valueArg) || valueArg < 1 || valueArg > maximumArg) {
|
|
36
|
+
throw new Error(`${labelArg} must be a positive safe integer no greater than ${maximumArg}.`);
|
|
37
|
+
}
|
|
38
|
+
return valueArg;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const requirePeerId = (peerIdArg: string): string => {
|
|
42
|
+
if (
|
|
43
|
+
typeof peerIdArg !== 'string'
|
|
44
|
+
|| peerIdArg.length === 0
|
|
45
|
+
|| peerIdArg.length > 512
|
|
46
|
+
|| peerIdArg.trim() !== peerIdArg
|
|
47
|
+
) {
|
|
48
|
+
throw new AuthError('peer_mismatch', 'The connection identity is malformed.');
|
|
49
|
+
}
|
|
50
|
+
return peerIdArg;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const requireOrigin = (originArg: string): string => {
|
|
54
|
+
if (typeof originArg !== 'string' || originArg.length === 0 || originArg.length > 2048) {
|
|
55
|
+
throw new AuthError('origin_mismatch', 'The request origin is malformed.');
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const origin = new URL(originArg);
|
|
59
|
+
if (
|
|
60
|
+
origin.origin !== originArg
|
|
61
|
+
|| (origin.protocol !== 'http:' && origin.protocol !== 'https:')
|
|
62
|
+
|| origin.username.length > 0
|
|
63
|
+
|| origin.password.length > 0
|
|
64
|
+
|| origin.pathname !== '/'
|
|
65
|
+
|| origin.search.length > 0
|
|
66
|
+
|| origin.hash.length > 0
|
|
67
|
+
) {
|
|
68
|
+
throw new Error('not an origin');
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
throw new AuthError('origin_mismatch', 'The request origin is malformed.');
|
|
72
|
+
}
|
|
73
|
+
return originArg;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const requireCeremonyId = (ceremonyIdArg: string): string => {
|
|
77
|
+
if (typeof ceremonyIdArg !== 'string' || !base64Url32BytePattern.test(ceremonyIdArg)) {
|
|
78
|
+
throw new AuthError('ceremony_invalid', 'The WebAuthn ceremony identifier is malformed.');
|
|
79
|
+
}
|
|
80
|
+
return ceremonyIdArg;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const randomBase64Url = (): string => plugins.crypto.randomBytes(32).toString('base64url');
|
|
84
|
+
|
|
85
|
+
const base64UrlValuesEqual = (leftArg: string, rightArg: string): boolean => {
|
|
86
|
+
if (!base64Url32BytePattern.test(leftArg) || !base64Url32BytePattern.test(rightArg)) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
const left = Buffer.from(leftArg, 'base64url');
|
|
90
|
+
const right = Buffer.from(rightArg, 'base64url');
|
|
91
|
+
return left.byteLength === right.byteLength && plugins.crypto.timingSafeEqual(left, right);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export class PasskeyManager {
|
|
95
|
+
private readonly store: IPasskeyManagerOptions['store'];
|
|
96
|
+
private readonly relyingPartyName: string;
|
|
97
|
+
private readonly webAuthn: IWebAuthnServerImplementation;
|
|
98
|
+
private readonly now: () => Date;
|
|
99
|
+
private readonly setupAttemptLimit: number;
|
|
100
|
+
private readonly setupAttemptWindowMs: number;
|
|
101
|
+
private readonly authenticationAttemptLimitPerPeer: number;
|
|
102
|
+
private readonly authenticationAttemptWindowMs: number;
|
|
103
|
+
private readonly maxActiveSetupCeremonies: number;
|
|
104
|
+
private readonly maxActiveAuthenticationCeremonies: number;
|
|
105
|
+
private readonly setupAttemptTimestamps: number[] = [];
|
|
106
|
+
private readonly authenticationVerificationAttemptTimestamps: number[] = [];
|
|
107
|
+
private readonly authenticationAttemptTimestampsByPeer = new Map<string, number[]>();
|
|
108
|
+
private ceremonyCreationTail: Promise<void> = Promise.resolve();
|
|
109
|
+
|
|
110
|
+
constructor(optionsArg: IPasskeyManagerOptions) {
|
|
111
|
+
this.store = optionsArg.store;
|
|
112
|
+
this.relyingPartyName = optionsArg.relyingPartyName ?? 'Agent Gateway Layer';
|
|
113
|
+
if (this.relyingPartyName.trim().length === 0 || this.relyingPartyName.length > 128) {
|
|
114
|
+
throw new Error('relyingPartyName must contain 1-128 characters.');
|
|
115
|
+
}
|
|
116
|
+
this.webAuthn = optionsArg.webAuthn ?? defaultWebAuthn;
|
|
117
|
+
this.now = optionsArg.now ?? (() => new Date());
|
|
118
|
+
this.setupAttemptLimit = requirePositiveLimit(
|
|
119
|
+
optionsArg.setupAttemptLimit ?? 10,
|
|
120
|
+
'setupAttemptLimit',
|
|
121
|
+
);
|
|
122
|
+
this.setupAttemptWindowMs = requirePositiveLimit(
|
|
123
|
+
optionsArg.setupAttemptWindowMs ?? 60_000,
|
|
124
|
+
'setupAttemptWindowMs',
|
|
125
|
+
24 * 60 * 60 * 1000,
|
|
126
|
+
);
|
|
127
|
+
this.authenticationAttemptLimitPerPeer = requirePositiveLimit(
|
|
128
|
+
optionsArg.authenticationAttemptLimitPerPeer ?? 6,
|
|
129
|
+
'authenticationAttemptLimitPerPeer',
|
|
130
|
+
);
|
|
131
|
+
this.authenticationAttemptWindowMs = requirePositiveLimit(
|
|
132
|
+
optionsArg.authenticationAttemptWindowMs ?? 60_000,
|
|
133
|
+
'authenticationAttemptWindowMs',
|
|
134
|
+
24 * 60 * 60 * 1000,
|
|
135
|
+
);
|
|
136
|
+
this.maxActiveSetupCeremonies = requirePositiveLimit(
|
|
137
|
+
optionsArg.maxActiveSetupCeremonies ?? 16,
|
|
138
|
+
'maxActiveSetupCeremonies',
|
|
139
|
+
);
|
|
140
|
+
this.maxActiveAuthenticationCeremonies = requirePositiveLimit(
|
|
141
|
+
optionsArg.maxActiveAuthenticationCeremonies ?? 64,
|
|
142
|
+
'maxActiveAuthenticationCeremonies',
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public async getAuthState(): Promise<{
|
|
147
|
+
state: 'setupRequired' | 'ready';
|
|
148
|
+
setupCodeExpired: boolean;
|
|
149
|
+
}> {
|
|
150
|
+
const state = await this.store.getState();
|
|
151
|
+
return {
|
|
152
|
+
state: state.authState,
|
|
153
|
+
setupCodeExpired: state.setup !== null
|
|
154
|
+
&& state.setup.expiresAt.getTime() <= this.currentTime().getTime(),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
public async beginSetup(optionsArg: ISetupBeginOptions): Promise<ISetupBeginResult> {
|
|
159
|
+
const peerId = requirePeerId(optionsArg.peerId);
|
|
160
|
+
const origin = requireOrigin(optionsArg.origin);
|
|
161
|
+
return this.withCeremonyCreationLock(async () => {
|
|
162
|
+
const now = this.currentTime();
|
|
163
|
+
await this.assertCeremonyCapacity('setup', now);
|
|
164
|
+
const state = await this.store.getState();
|
|
165
|
+
this.assertConfiguredOrigin(origin, state.config.publicOrigin);
|
|
166
|
+
// the attempt budget must burn BEFORE the code is checked — a limiter
|
|
167
|
+
// that only counts correct codes throttles the legitimate operator and
|
|
168
|
+
// leaves guessing unmetered
|
|
169
|
+
this.recordSetupAttempt(now);
|
|
170
|
+
await this.store.assertSetupAuthority(optionsArg.setupCode, undefined, now);
|
|
171
|
+
if (!state.setup) {
|
|
172
|
+
throw new AuthError('setup_unavailable', 'Passkey setup is no longer available.');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const registrationOptions = await this.webAuthn.generateRegistrationOptions({
|
|
176
|
+
rpName: this.relyingPartyName,
|
|
177
|
+
rpID: state.config.rpId,
|
|
178
|
+
userName: 'agl',
|
|
179
|
+
userDisplayName: 'agl',
|
|
180
|
+
userID: Buffer.from(state.webauthnUserId, 'base64url'),
|
|
181
|
+
timeout: setupCeremonyLifetimeMs,
|
|
182
|
+
attestationType: 'none',
|
|
183
|
+
excludeCredentials: [],
|
|
184
|
+
authenticatorSelection: {
|
|
185
|
+
residentKey: 'required',
|
|
186
|
+
requireResidentKey: true,
|
|
187
|
+
userVerification: 'required',
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
const ceremonyId = randomBase64Url();
|
|
191
|
+
const expiresAt = new Date(Math.min(
|
|
192
|
+
now.getTime() + setupCeremonyLifetimeMs,
|
|
193
|
+
state.setup.expiresAt.getTime(),
|
|
194
|
+
));
|
|
195
|
+
const ceremony: IWebAuthnSetupCeremonyDocument = {
|
|
196
|
+
id: ceremonyId,
|
|
197
|
+
kind: 'setup',
|
|
198
|
+
state: 'pending',
|
|
199
|
+
peerId,
|
|
200
|
+
challenge: registrationOptions.challenge,
|
|
201
|
+
origin,
|
|
202
|
+
rpId: state.config.rpId,
|
|
203
|
+
userId: state.webauthnUserId,
|
|
204
|
+
setupGeneration: state.setup.generation,
|
|
205
|
+
setupHash: state.setup.hash,
|
|
206
|
+
createdAt: now,
|
|
207
|
+
expiresAt,
|
|
208
|
+
};
|
|
209
|
+
await this.store.createCeremony(ceremony);
|
|
210
|
+
return { ceremonyId, options: registrationOptions };
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
public async finishSetup(optionsArg: ISetupFinishOptions): Promise<IAuthenticationResult> {
|
|
215
|
+
const peerId = requirePeerId(optionsArg.peerId);
|
|
216
|
+
const origin = requireOrigin(optionsArg.origin);
|
|
217
|
+
const ceremonyId = requireCeremonyId(optionsArg.ceremonyId);
|
|
218
|
+
const now = this.currentTime();
|
|
219
|
+
const stateBefore = await this.store.getState();
|
|
220
|
+
this.assertConfiguredOrigin(origin, stateBefore.config.publicOrigin);
|
|
221
|
+
// burn the attempt budget BEFORE consuming the ceremony: an exhausted
|
|
222
|
+
// budget must reject without destroying a still-valid ceremony, otherwise
|
|
223
|
+
// the user's authenticator keeps a credential the store never accepted
|
|
224
|
+
this.recordSetupAttempt(now);
|
|
225
|
+
const ceremony = await this.store.consumeCeremony({
|
|
226
|
+
ceremonyId,
|
|
227
|
+
kind: 'setup',
|
|
228
|
+
peerId,
|
|
229
|
+
origin,
|
|
230
|
+
rpId: stateBefore.config.rpId,
|
|
231
|
+
userId: stateBefore.webauthnUserId,
|
|
232
|
+
now,
|
|
233
|
+
});
|
|
234
|
+
if (
|
|
235
|
+
ceremony.kind !== 'setup'
|
|
236
|
+
|| typeof ceremony.setupGeneration !== 'string'
|
|
237
|
+
|| typeof ceremony.setupHash !== 'string'
|
|
238
|
+
) {
|
|
239
|
+
throw new AuthError('ceremony_invalid', 'The WebAuthn ceremony has the wrong kind.');
|
|
240
|
+
}
|
|
241
|
+
const setupCeremony: IWebAuthnSetupCeremonyDocument = {
|
|
242
|
+
...ceremony,
|
|
243
|
+
kind: 'setup',
|
|
244
|
+
setupGeneration: ceremony.setupGeneration,
|
|
245
|
+
setupHash: ceremony.setupHash,
|
|
246
|
+
};
|
|
247
|
+
await this.store.assertSetupAuthority(optionsArg.setupCode, setupCeremony, now);
|
|
248
|
+
|
|
249
|
+
let verification: Awaited<ReturnType<
|
|
250
|
+
IWebAuthnServerImplementation['verifyRegistrationResponse']
|
|
251
|
+
>>;
|
|
252
|
+
try {
|
|
253
|
+
verification = await this.webAuthn.verifyRegistrationResponse({
|
|
254
|
+
response: optionsArg.response,
|
|
255
|
+
expectedChallenge: ceremony.challenge,
|
|
256
|
+
expectedOrigin: ceremony.origin,
|
|
257
|
+
expectedRPID: ceremony.rpId,
|
|
258
|
+
requireUserPresence: true,
|
|
259
|
+
requireUserVerification: true,
|
|
260
|
+
});
|
|
261
|
+
} catch (errorArg) {
|
|
262
|
+
throw new AuthError('verification_failed', 'Passkey registration verification failed.', {
|
|
263
|
+
cause: errorArg,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
if (
|
|
267
|
+
!verification.verified
|
|
268
|
+
|| !verification.registrationInfo.userVerified
|
|
269
|
+
|| verification.registrationInfo.credential.id !== optionsArg.response.id
|
|
270
|
+
) {
|
|
271
|
+
throw new AuthError('verification_failed', 'Passkey registration verification failed.');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const verifiedCredential = verification.registrationInfo.credential;
|
|
275
|
+
const storedCredential: IStoredPasskeyCredential = {
|
|
276
|
+
id: verifiedCredential.id,
|
|
277
|
+
publicKey: Buffer.from(verifiedCredential.publicKey).toString('base64url'),
|
|
278
|
+
counter: verifiedCredential.counter,
|
|
279
|
+
transports: [...(verifiedCredential.transports ?? optionsArg.response.response.transports ?? [])],
|
|
280
|
+
deviceType: verification.registrationInfo.credentialDeviceType,
|
|
281
|
+
backedUp: verification.registrationInfo.credentialBackedUp,
|
|
282
|
+
createdAt: now,
|
|
283
|
+
counterUpdateId: randomBase64Url(),
|
|
284
|
+
};
|
|
285
|
+
await this.store.commitFirstCredential(setupCeremony, storedCredential);
|
|
286
|
+
return { authenticated: true, credentialId: storedCredential.id };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
public async preflightSetupFinish(optionsArg: ISetupFinishOptions): Promise<void> {
|
|
290
|
+
const peerId = requirePeerId(optionsArg.peerId);
|
|
291
|
+
const origin = requireOrigin(optionsArg.origin);
|
|
292
|
+
const ceremonyId = requireCeremonyId(optionsArg.ceremonyId);
|
|
293
|
+
const now = this.currentTime();
|
|
294
|
+
const state = await this.store.getState();
|
|
295
|
+
this.assertConfiguredOrigin(origin, state.config.publicOrigin);
|
|
296
|
+
const ceremony = await this.store.assertPendingCeremony({
|
|
297
|
+
ceremonyId,
|
|
298
|
+
kind: 'setup',
|
|
299
|
+
peerId,
|
|
300
|
+
origin,
|
|
301
|
+
rpId: state.config.rpId,
|
|
302
|
+
userId: state.webauthnUserId,
|
|
303
|
+
now,
|
|
304
|
+
});
|
|
305
|
+
if (
|
|
306
|
+
ceremony.kind !== 'setup'
|
|
307
|
+
|| typeof ceremony.setupGeneration !== 'string'
|
|
308
|
+
|| typeof ceremony.setupHash !== 'string'
|
|
309
|
+
) {
|
|
310
|
+
throw new AuthError('ceremony_invalid', 'The WebAuthn ceremony has the wrong kind.');
|
|
311
|
+
}
|
|
312
|
+
// preflight is the first place a wrong code arrives in the controller
|
|
313
|
+
// flow, so it must burn the attempt budget too
|
|
314
|
+
this.recordSetupAttempt(now);
|
|
315
|
+
await this.store.assertSetupAuthority(optionsArg.setupCode, {
|
|
316
|
+
...ceremony,
|
|
317
|
+
kind: 'setup',
|
|
318
|
+
setupGeneration: ceremony.setupGeneration,
|
|
319
|
+
setupHash: ceremony.setupHash,
|
|
320
|
+
}, now);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
public async beginAuthentication(
|
|
324
|
+
optionsArg: IAuthenticationBeginOptions,
|
|
325
|
+
): Promise<IAuthenticationBeginResult> {
|
|
326
|
+
const peerId = requirePeerId(optionsArg.peerId);
|
|
327
|
+
const origin = requireOrigin(optionsArg.origin);
|
|
328
|
+
return this.withCeremonyCreationLock(async () => {
|
|
329
|
+
const now = this.currentTime();
|
|
330
|
+
await this.assertCeremonyCapacity('authentication', now);
|
|
331
|
+
const state = await this.store.getState();
|
|
332
|
+
this.assertConfiguredOrigin(origin, state.config.publicOrigin);
|
|
333
|
+
if (state.authState !== 'ready' || state.credentials.length === 0) {
|
|
334
|
+
throw new AuthError('setup_unavailable', 'Passkey authentication is not configured.');
|
|
335
|
+
}
|
|
336
|
+
this.recordAuthenticationAttempt(now, peerId, false);
|
|
337
|
+
const authenticationOptions = await this.webAuthn.generateAuthenticationOptions({
|
|
338
|
+
rpID: state.config.rpId,
|
|
339
|
+
timeout: authenticationCeremonyLifetimeMs,
|
|
340
|
+
userVerification: 'required',
|
|
341
|
+
});
|
|
342
|
+
const ceremonyId = randomBase64Url();
|
|
343
|
+
const ceremony: IWebAuthnCeremonyDocument = {
|
|
344
|
+
id: ceremonyId,
|
|
345
|
+
kind: 'authentication',
|
|
346
|
+
state: 'pending',
|
|
347
|
+
peerId,
|
|
348
|
+
challenge: authenticationOptions.challenge,
|
|
349
|
+
origin,
|
|
350
|
+
rpId: state.config.rpId,
|
|
351
|
+
userId: state.webauthnUserId,
|
|
352
|
+
createdAt: now,
|
|
353
|
+
expiresAt: new Date(now.getTime() + authenticationCeremonyLifetimeMs),
|
|
354
|
+
};
|
|
355
|
+
await this.store.createCeremony(ceremony);
|
|
356
|
+
return { ceremonyId, options: authenticationOptions };
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
public async finishAuthentication(
|
|
361
|
+
optionsArg: IAuthenticationFinishOptions,
|
|
362
|
+
): Promise<IAuthenticationResult> {
|
|
363
|
+
const peerId = requirePeerId(optionsArg.peerId);
|
|
364
|
+
const origin = requireOrigin(optionsArg.origin);
|
|
365
|
+
const ceremonyId = requireCeremonyId(optionsArg.ceremonyId);
|
|
366
|
+
const now = this.currentTime();
|
|
367
|
+
const state = await this.store.getState();
|
|
368
|
+
this.assertConfiguredOrigin(origin, state.config.publicOrigin);
|
|
369
|
+
const ceremony = await this.store.consumeCeremony({
|
|
370
|
+
ceremonyId,
|
|
371
|
+
kind: 'authentication',
|
|
372
|
+
peerId,
|
|
373
|
+
origin,
|
|
374
|
+
rpId: state.config.rpId,
|
|
375
|
+
userId: state.webauthnUserId,
|
|
376
|
+
now,
|
|
377
|
+
});
|
|
378
|
+
if (state.authState !== 'ready' || state.credentials.length === 0) {
|
|
379
|
+
throw new AuthError('setup_unavailable', 'Passkey authentication is not configured.');
|
|
380
|
+
}
|
|
381
|
+
const userHandle = optionsArg.response.response.userHandle;
|
|
382
|
+
if (
|
|
383
|
+
typeof userHandle !== 'string'
|
|
384
|
+
|| !base64UrlValuesEqual(userHandle, state.webauthnUserId)
|
|
385
|
+
) {
|
|
386
|
+
throw new AuthError('verification_failed', 'The passkey user handle is invalid.');
|
|
387
|
+
}
|
|
388
|
+
const credential = state.credentials.find(
|
|
389
|
+
(entry) => entry.id === optionsArg.response.id,
|
|
390
|
+
);
|
|
391
|
+
if (!credential) {
|
|
392
|
+
throw new AuthError('credential_not_found', 'The passkey credential is unknown.');
|
|
393
|
+
}
|
|
394
|
+
this.recordAuthenticationAttempt(now, peerId, true);
|
|
395
|
+
|
|
396
|
+
let verification: Awaited<ReturnType<
|
|
397
|
+
IWebAuthnServerImplementation['verifyAuthenticationResponse']
|
|
398
|
+
>>;
|
|
399
|
+
try {
|
|
400
|
+
verification = await this.webAuthn.verifyAuthenticationResponse({
|
|
401
|
+
response: optionsArg.response,
|
|
402
|
+
expectedChallenge: ceremony.challenge,
|
|
403
|
+
expectedOrigin: ceremony.origin,
|
|
404
|
+
expectedRPID: ceremony.rpId,
|
|
405
|
+
credential: {
|
|
406
|
+
id: credential.id,
|
|
407
|
+
publicKey: Buffer.from(credential.publicKey, 'base64url'),
|
|
408
|
+
counter: credential.counter,
|
|
409
|
+
transports: [...credential.transports],
|
|
410
|
+
},
|
|
411
|
+
requireUserVerification: true,
|
|
412
|
+
advancedFIDOConfig: {
|
|
413
|
+
userVerification: 'required',
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
} catch (errorArg) {
|
|
417
|
+
throw new AuthError('verification_failed', 'Passkey authentication verification failed.', {
|
|
418
|
+
cause: errorArg,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
if (
|
|
422
|
+
!verification.verified
|
|
423
|
+
|| !verification.authenticationInfo.userVerified
|
|
424
|
+
|| verification.authenticationInfo.credentialID !== credential.id
|
|
425
|
+
|| verification.authenticationInfo.origin !== ceremony.origin
|
|
426
|
+
|| verification.authenticationInfo.rpID !== ceremony.rpId
|
|
427
|
+
) {
|
|
428
|
+
throw new AuthError('verification_failed', 'Passkey authentication verification failed.');
|
|
429
|
+
}
|
|
430
|
+
await this.store.updateCounter(
|
|
431
|
+
credential.id,
|
|
432
|
+
credential.counter,
|
|
433
|
+
verification.authenticationInfo.newCounter,
|
|
434
|
+
);
|
|
435
|
+
return { authenticated: true, credentialId: credential.id };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
public async preflightAuthenticationFinish(
|
|
439
|
+
optionsArg: IAuthenticationFinishOptions,
|
|
440
|
+
): Promise<void> {
|
|
441
|
+
const peerId = requirePeerId(optionsArg.peerId);
|
|
442
|
+
const origin = requireOrigin(optionsArg.origin);
|
|
443
|
+
const ceremonyId = requireCeremonyId(optionsArg.ceremonyId);
|
|
444
|
+
const now = this.currentTime();
|
|
445
|
+
const state = await this.store.getState();
|
|
446
|
+
this.assertConfiguredOrigin(origin, state.config.publicOrigin);
|
|
447
|
+
if (state.authState !== 'ready' || state.credentials.length === 0) {
|
|
448
|
+
throw new AuthError('setup_unavailable', 'Passkey authentication is not configured.');
|
|
449
|
+
}
|
|
450
|
+
const userHandle = optionsArg.response.response.userHandle;
|
|
451
|
+
if (
|
|
452
|
+
typeof userHandle !== 'string'
|
|
453
|
+
|| !base64UrlValuesEqual(userHandle, state.webauthnUserId)
|
|
454
|
+
) {
|
|
455
|
+
throw new AuthError('verification_failed', 'The passkey user handle is invalid.');
|
|
456
|
+
}
|
|
457
|
+
if (!state.credentials.some((entry) => entry.id === optionsArg.response.id)) {
|
|
458
|
+
throw new AuthError('credential_not_found', 'The passkey credential is unknown.');
|
|
459
|
+
}
|
|
460
|
+
await this.store.assertPendingCeremony({
|
|
461
|
+
ceremonyId,
|
|
462
|
+
kind: 'authentication',
|
|
463
|
+
peerId,
|
|
464
|
+
origin,
|
|
465
|
+
rpId: state.config.rpId,
|
|
466
|
+
userId: state.webauthnUserId,
|
|
467
|
+
now,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
private currentTime(): Date {
|
|
472
|
+
const now = this.now();
|
|
473
|
+
if (!(now instanceof Date) || !Number.isFinite(now.getTime())) {
|
|
474
|
+
throw new Error('The PasskeyManager clock returned an invalid Date.');
|
|
475
|
+
}
|
|
476
|
+
return new Date(now);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
private recordSetupAttempt(nowArg: Date): void {
|
|
480
|
+
const cutoff = nowArg.getTime() - this.setupAttemptWindowMs;
|
|
481
|
+
while (
|
|
482
|
+
this.setupAttemptTimestamps.length > 0
|
|
483
|
+
&& this.setupAttemptTimestamps[0] <= cutoff
|
|
484
|
+
) {
|
|
485
|
+
this.setupAttemptTimestamps.shift();
|
|
486
|
+
}
|
|
487
|
+
if (this.setupAttemptTimestamps.length >= this.setupAttemptLimit) {
|
|
488
|
+
throw new AuthError('rate_limited', 'Too many setup attempts. Try again later.');
|
|
489
|
+
}
|
|
490
|
+
this.setupAttemptTimestamps.push(nowArg.getTime());
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
private recordAuthenticationAttempt(
|
|
494
|
+
nowArg: Date,
|
|
495
|
+
peerIdArg: string,
|
|
496
|
+
includeGlobalVerificationLimitArg: boolean,
|
|
497
|
+
): void {
|
|
498
|
+
const now = nowArg.getTime();
|
|
499
|
+
const cutoff = now - this.authenticationAttemptWindowMs;
|
|
500
|
+
while (
|
|
501
|
+
this.authenticationVerificationAttemptTimestamps.length > 0
|
|
502
|
+
&& this.authenticationVerificationAttemptTimestamps[0] <= cutoff
|
|
503
|
+
) {
|
|
504
|
+
this.authenticationVerificationAttemptTimestamps.shift();
|
|
505
|
+
}
|
|
506
|
+
for (const [peerId, timestamps] of this.authenticationAttemptTimestampsByPeer) {
|
|
507
|
+
while (timestamps.length > 0 && timestamps[0] <= cutoff) timestamps.shift();
|
|
508
|
+
if (timestamps.length === 0) this.authenticationAttemptTimestampsByPeer.delete(peerId);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const peerTimestamps = this.authenticationAttemptTimestampsByPeer.get(peerIdArg) ?? [];
|
|
512
|
+
if (peerTimestamps.length >= this.authenticationAttemptLimitPerPeer) {
|
|
513
|
+
throw new AuthError('rate_limited', 'Too many authentication attempts. Try again later.');
|
|
514
|
+
}
|
|
515
|
+
if (
|
|
516
|
+
includeGlobalVerificationLimitArg
|
|
517
|
+
&& this.authenticationVerificationAttemptTimestamps.length
|
|
518
|
+
>= maxAuthenticationVerificationAttemptsPerWindow
|
|
519
|
+
) {
|
|
520
|
+
throw new AuthError('rate_limited', 'Too many passkey verification attempts. Try again later.');
|
|
521
|
+
}
|
|
522
|
+
if (
|
|
523
|
+
peerTimestamps.length === 0
|
|
524
|
+
&& this.authenticationAttemptTimestampsByPeer.size
|
|
525
|
+
>= this.maxActiveAuthenticationCeremonies * 2
|
|
526
|
+
) {
|
|
527
|
+
throw new AuthError('rate_limited', 'Too many authentication peers. Try again later.');
|
|
528
|
+
}
|
|
529
|
+
if (includeGlobalVerificationLimitArg) {
|
|
530
|
+
this.authenticationVerificationAttemptTimestamps.push(now);
|
|
531
|
+
}
|
|
532
|
+
peerTimestamps.push(now);
|
|
533
|
+
this.authenticationAttemptTimestampsByPeer.set(peerIdArg, peerTimestamps);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
private async assertCeremonyCapacity(
|
|
537
|
+
kindArg: 'setup' | 'authentication',
|
|
538
|
+
nowArg: Date,
|
|
539
|
+
): Promise<void> {
|
|
540
|
+
const maximum = kindArg === 'setup'
|
|
541
|
+
? this.maxActiveSetupCeremonies
|
|
542
|
+
: this.maxActiveAuthenticationCeremonies;
|
|
543
|
+
const active = await this.store.countActiveCeremonies(kindArg, nowArg);
|
|
544
|
+
if (active >= maximum) {
|
|
545
|
+
throw new AuthError('ceremony_limit', `Too many active ${kindArg} ceremonies.`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
private assertConfiguredOrigin(actualArg: string, expectedArg: string): void {
|
|
550
|
+
if (actualArg !== expectedArg) {
|
|
551
|
+
throw new AuthError('origin_mismatch', 'The request origin is not trusted.');
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
private async withCeremonyCreationLock<TResult>(
|
|
556
|
+
functionArg: () => Promise<TResult>,
|
|
557
|
+
): Promise<TResult> {
|
|
558
|
+
const previous = this.ceremonyCreationTail;
|
|
559
|
+
let release!: () => void;
|
|
560
|
+
this.ceremonyCreationTail = new Promise<void>((resolve) => {
|
|
561
|
+
release = resolve;
|
|
562
|
+
});
|
|
563
|
+
await previous;
|
|
564
|
+
try {
|
|
565
|
+
return await functionArg();
|
|
566
|
+
} finally {
|
|
567
|
+
release();
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|