@finueva/auth 0.10.1 → 0.11.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 +52 -16
- package/THIRD_PARTY_NOTICES.md +26 -0
- package/dist/device.d.ts +1 -1
- package/dist/device.js +3 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +863 -8
- package/dist/server.d.ts +1 -1
- package/dist/server.js +3 -0
- package/dist/{types-Byw9JQwL.d.ts → types-BqpgLkQB.d.ts} +40 -3
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Use `@finueva/auth` to add Finueva sign-in, organization access, invitations, and Drive credentials to a browser, server, CLI, or native application.
|
|
4
4
|
|
|
5
|
-
The current published version is `0.10.
|
|
5
|
+
The current published version is `0.10.1`. The package is ESM-only. Node.js consumers require Node.js 22 or later. Other server runtimes must provide compatible Fetch, Web Crypto, URL, and Web Streams APIs. Its interface is Experimental until version `1.0.0`.
|
|
6
|
+
|
|
7
|
+
The passkey and recent-authentication methods below describe this repository's next package candidate. Published version `0.10.1` does not include them.
|
|
6
8
|
|
|
7
9
|
## Install
|
|
8
10
|
|
|
@@ -76,21 +78,23 @@ The browser owns the session cookie. The SDK does not expose or persist it.
|
|
|
76
78
|
|
|
77
79
|
## Common Tasks
|
|
78
80
|
|
|
79
|
-
| Task
|
|
80
|
-
|
|
|
81
|
-
| Create an account
|
|
82
|
-
| Read the current session
|
|
83
|
-
| Observe session state
|
|
84
|
-
| Update a profile
|
|
85
|
-
| Change a password
|
|
86
|
-
| Reset a password
|
|
87
|
-
| Verify an email
|
|
88
|
-
| List organizations
|
|
89
|
-
| Create an ordinary team
|
|
90
|
-
| Invite an ordinary member
|
|
91
|
-
| Accept an invitation
|
|
92
|
-
|
|
|
93
|
-
|
|
|
81
|
+
| Task | Browser | Server | Device |
|
|
82
|
+
| ----------------------------- | ------------------------------------------- | -------------------------------------- | -------------------------------------- |
|
|
83
|
+
| Create an account | `signUp()` | Not available | `signUpWithEmail()` |
|
|
84
|
+
| Read the current session | `getSession()` | `getSession()` | `getSession()` |
|
|
85
|
+
| Observe session state | `onAuthStateChange()` | Not available | Not available |
|
|
86
|
+
| Update a profile | `updateProfile()` | Not available | `updateProfile()` |
|
|
87
|
+
| Change a password | `changePassword()` | Not available | `changePassword()` |
|
|
88
|
+
| Reset a password | `requestPasswordReset()`, `resetPassword()` | Not available | Not available |
|
|
89
|
+
| Verify an email | `sendVerificationEmail()` | Not available | Not available |
|
|
90
|
+
| List organizations | `listOrganizations()` | `listOrganizations()` | `listOrganizations()` |
|
|
91
|
+
| Create an ordinary team | `createOrganizationTeam()` | `createOrganizationTeam()` | `createOrganizationTeam()` |
|
|
92
|
+
| Invite an ordinary member | `createOrganizationMemberInvitation()` | `createOrganizationMemberInvitation()` | `createOrganizationMemberInvitation()` |
|
|
93
|
+
| Accept an invitation | `acceptInvitation()` | `acceptInvitation()` | Not available |
|
|
94
|
+
| Enroll a passkey | `addPasskey()` | Not available | Not available |
|
|
95
|
+
| Perform recent authentication | Three `*RecentAuthentication*()` methods | Not available | Not available |
|
|
96
|
+
| Request Drive access | `getDriveCredential()` | `getDriveCredential()` | `getDriveCredential()` |
|
|
97
|
+
| End the current session | `signOut()` | `signOut()` | `revokeCurrentDevice()` |
|
|
94
98
|
|
|
95
99
|
## Create An Organization Invitation
|
|
96
100
|
|
|
@@ -108,6 +112,38 @@ console.log(invitation.id, invitation.expiresAt);
|
|
|
108
112
|
|
|
109
113
|
If the response is lost, repeat the same request with the same key. The SDK does not retry automatically. The result confirms invitation creation. It does not confirm email delivery.
|
|
110
114
|
|
|
115
|
+
## Perform Recent Authentication
|
|
116
|
+
|
|
117
|
+
The experimental passkey methods run only when the browser page uses the exact Auth Origin. To enroll a passkey, first create a new password-authenticated session on that origin:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
await auth.signIn({ email, password });
|
|
121
|
+
await auth.addPasskey();
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The enrollment authorization expires after five minutes. It is single-use. Signup, social sign-in, session refresh, bearer use, and generic session creation do not authorize enrollment. A User without a fresh password sign-in cannot use this path.
|
|
125
|
+
|
|
126
|
+
Start one reviewed platform-role action before invoking passkey authentication:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
await auth.startPlatformRoleChangeRecentAuthentication({
|
|
130
|
+
targetUserId: "user_01J8R7M6N5P4Q3S2T1",
|
|
131
|
+
requestedRoles: ["administrator", "investigator"],
|
|
132
|
+
expectedRevision: 4,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
await auth.verifyRecentAuthenticationWithPasskey();
|
|
136
|
+
|
|
137
|
+
const continuation = await auth.getRecentAuthenticationStatus();
|
|
138
|
+
if (continuation.status === "ready") {
|
|
139
|
+
showReviewedActionIsReady();
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The maintained passkey client performs the WebAuthn ceremony for the pending action when a compatible authenticator is present. Auth binds the exact generated challenge to that action. The SDK returns only `pending` or `ready`. It does not expose the challenge, WebAuthn response, session, cookies, proof, or hashes. `ready` does not grant a platform role or reusable administration mode. The tracer has no proof-consumption API. A future exact mutation must add atomic one-use consumption with its state change. No platform mutation is currently implemented.
|
|
144
|
+
|
|
145
|
+
If a start result is unavailable, call `startPlatformRoleChangeRecentAuthentication()` again with the same normalized input. The client reuses the current request nonce. A different start or observed account transition removes this local retry state. Only one start request can be active. An overlapping call returns `state_conflict` before network I/O. The SDK does not retry automatically.
|
|
146
|
+
|
|
111
147
|
## Request A Drive Credential
|
|
112
148
|
|
|
113
149
|
Request one short-lived credential for one workspace:
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -47,6 +47,32 @@ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
|
47
47
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
48
48
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
49
49
|
|
|
50
|
+
## @simplewebauthn/browser 13.3.0
|
|
51
|
+
|
|
52
|
+
Source: https://github.com/MasterKale/SimpleWebAuthn
|
|
53
|
+
|
|
54
|
+
MIT License
|
|
55
|
+
|
|
56
|
+
Copyright (c) 2020 Matthew Miller
|
|
57
|
+
|
|
58
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
59
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
60
|
+
the Software without restriction, including without limitation the rights to
|
|
61
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
62
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
63
|
+
subject to the following conditions:
|
|
64
|
+
|
|
65
|
+
The above copyright notice and this permission notice shall be included in all
|
|
66
|
+
copies or substantial portions of the Software.
|
|
67
|
+
|
|
68
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
69
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
70
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
71
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
72
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
73
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
74
|
+
SOFTWARE.
|
|
75
|
+
|
|
50
76
|
## ofetch 1.5.1
|
|
51
77
|
|
|
52
78
|
Source: https://github.com/unjs/ofetch
|
package/dist/device.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { B as SocialIdTokenInput, C as OrganizationTeam, E as OrganizationTeamPage, F as ReceivedOrganizationInvitation, H as UpdateOrganizationDisplayInput, I as ReceivedOrganizationInvitationPage, J as AuthErrorStatus, K as AuthError, S as OrganizationPageInput, T as OrganizationTeamMemberPage, U as UpdateOrganizationTeamDisplayInput, V as SocialIdTokenProvider, W as UpdateProfileInput, _ as OrganizationInvitation, b as OrganizationMemberPage, d as CreatedOrganizationTeam, f as DriveCredential, g as OrganizationDisplay, l as CreateOrganizationMemberInvitationInput, o as AuthSession, p as DriveCredentialInput, q as AuthErrorCode, r as AuthOrganization, u as CreatedOrganizationMemberInvitation, v as OrganizationInvitationPage, w as OrganizationTeamDisplay, x as OrganizationPage, y as OrganizationMember } from "./types-BqpgLkQB.js";
|
|
2
2
|
//#region src/device.d.ts
|
|
3
3
|
type DeviceAuthorizationErrorCode = "device_access_denied" | "device_authorization_cancelled" | "device_code_expired" | "invalid_device_authorization" | "identity_authority_unavailable";
|
|
4
4
|
declare class DeviceAuthorizationError extends Error {
|
package/dist/device.js
CHANGED
|
@@ -455,6 +455,9 @@ const messages = {
|
|
|
455
455
|
auth_request_rate_limited: "Authentication requests are temporarily limited.",
|
|
456
456
|
state_conflict: "The authentication state conflicts with the request.",
|
|
457
457
|
access_denied: "Access is denied.",
|
|
458
|
+
platform_target_unavailable: "The platform administration target is unavailable.",
|
|
459
|
+
platform_quorum_unavailable: "Platform administration quorum is unavailable.",
|
|
460
|
+
platform_recovery_required: "Platform administration requires separate recovery.",
|
|
458
461
|
identity_authority_unavailable: "Authentication is temporarily unavailable."
|
|
459
462
|
};
|
|
460
463
|
var AuthError = class extends Error {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as PlatformRole, B as SocialIdTokenInput, C as OrganizationTeam, D as OrganizationWorkspaceSelection, E as OrganizationTeamPage, F as ReceivedOrganizationInvitation, G as WorkspaceSelection, H as UpdateOrganizationDisplayInput, I as ReceivedOrganizationInvitationPage, J as AuthErrorStatus, K as AuthError, L as RecentAuthenticationStatus, M as PlatformRoleApproval, N as PlatformRoleChangeRecentAuthenticationInput, O as PersonalWorkspaceSelection, P as PlatformRoleFirstApproval, R as SendVerificationEmailInput, S as OrganizationPageInput, T as OrganizationTeamMemberPage, U as UpdateOrganizationTeamDisplayInput, V as SocialIdTokenProvider, W as UpdateProfileInput, _ as OrganizationInvitation, a as AuthPersistenceDriver, b as OrganizationMemberPage, c as AuthUser, d as CreatedOrganizationTeam, f as DriveCredential, g as OrganizationDisplay, h as InvitationAcceptance, i as AuthPersistence, j as PlatformRoleApplied, k as PlatformBootstrapRoleChangeInput, l as CreateOrganizationMemberInvitationInput, m as FinuevaAuthClient, n as AuthClientOptions, o as AuthSession, p as DriveCredentialInput, q as AuthErrorCode, r as AuthOrganization, s as AuthState, t as AcceptInvitationInput, u as CreatedOrganizationMemberInvitation, v as OrganizationInvitationPage, w as OrganizationTeamDisplay, x as OrganizationPage, y as OrganizationMember, z as SignInInput } from "./types-BqpgLkQB.js";
|
|
2
2
|
//#region src/client.d.ts
|
|
3
3
|
declare function createAuthClient(options: AuthClientOptions): FinuevaAuthClient;
|
|
4
4
|
//#endregion
|
|
5
|
-
export { type AcceptInvitationInput, type AuthClientOptions, AuthError, type AuthErrorCode, type AuthErrorStatus, type AuthOrganization, type AuthPersistence, type AuthPersistenceDriver, type AuthSession, type AuthState, type AuthUser, type CreateOrganizationMemberInvitationInput, type CreatedOrganizationMemberInvitation, type CreatedOrganizationTeam, type DriveCredential, type DriveCredentialInput, type FinuevaAuthClient, type InvitationAcceptance, type OrganizationDisplay, type OrganizationInvitation, type OrganizationInvitationPage, type OrganizationMember, type OrganizationMemberPage, type OrganizationPage, type OrganizationPageInput, type OrganizationTeam, type OrganizationTeamDisplay, type OrganizationTeamMemberPage, type OrganizationTeamPage, type OrganizationWorkspaceSelection, type PersonalWorkspaceSelection, type ReceivedOrganizationInvitation, type ReceivedOrganizationInvitationPage, type SendVerificationEmailInput, type SignInInput, type SocialIdTokenInput, type SocialIdTokenProvider, type UpdateOrganizationDisplayInput, type UpdateOrganizationTeamDisplayInput, type UpdateProfileInput, type WorkspaceSelection, createAuthClient };
|
|
5
|
+
export { type AcceptInvitationInput, type AuthClientOptions, AuthError, type AuthErrorCode, type AuthErrorStatus, type AuthOrganization, type AuthPersistence, type AuthPersistenceDriver, type AuthSession, type AuthState, type AuthUser, type CreateOrganizationMemberInvitationInput, type CreatedOrganizationMemberInvitation, type CreatedOrganizationTeam, type DriveCredential, type DriveCredentialInput, type FinuevaAuthClient, type InvitationAcceptance, type OrganizationDisplay, type OrganizationInvitation, type OrganizationInvitationPage, type OrganizationMember, type OrganizationMemberPage, type OrganizationPage, type OrganizationPageInput, type OrganizationTeam, type OrganizationTeamDisplay, type OrganizationTeamMemberPage, type OrganizationTeamPage, type OrganizationWorkspaceSelection, type PersonalWorkspaceSelection, type PlatformBootstrapRoleChangeInput, type PlatformRole, type PlatformRoleApplied, type PlatformRoleApproval, type PlatformRoleChangeRecentAuthenticationInput, type PlatformRoleFirstApproval, type ReceivedOrganizationInvitation, type ReceivedOrganizationInvitationPage, type RecentAuthenticationStatus, type SendVerificationEmailInput, type SignInInput, type SocialIdTokenInput, type SocialIdTokenProvider, type UpdateOrganizationDisplayInput, type UpdateOrganizationTeamDisplayInput, type UpdateProfileInput, type WorkspaceSelection, createAuthClient };
|
package/dist/index.js
CHANGED
|
@@ -383,6 +383,109 @@ function parseJSON(value, options = { strict: true }) {
|
|
|
383
383
|
return betterJSONParse(value, options);
|
|
384
384
|
}
|
|
385
385
|
//#endregion
|
|
386
|
+
//#region ../../node_modules/.pnpm/better-auth@1.6.22_drizzle-kit@0.31.10_drizzle-orm@0.45.2_@libsql+client@0.17.4_kysely@_fc032d4b6d0d0a5be5267474da10b212/node_modules/better-auth/dist/client/query.mjs
|
|
387
|
+
const isServer$1 = () => typeof window === "undefined";
|
|
388
|
+
function isAuthQueryStateEqual(a, b) {
|
|
389
|
+
return isJsonEqual(a.data, b.data) && a.error === b.error && a.isPending === b.isPending && a.isRefetching === b.isRefetching && a.refetch === b.refetch;
|
|
390
|
+
}
|
|
391
|
+
const useAuthQuery = (initializedAtom, path, $fetch, options) => {
|
|
392
|
+
const value = atom({
|
|
393
|
+
data: null,
|
|
394
|
+
error: null,
|
|
395
|
+
isPending: true,
|
|
396
|
+
isRefetching: false,
|
|
397
|
+
refetch: (queryParams) => fn(queryParams)
|
|
398
|
+
});
|
|
399
|
+
withEquality(value, isAuthQueryStateEqual);
|
|
400
|
+
const fn = async (queryParams) => {
|
|
401
|
+
return new Promise((resolve) => {
|
|
402
|
+
const opts = typeof options === "function" ? options({
|
|
403
|
+
data: value.get().data,
|
|
404
|
+
error: value.get().error,
|
|
405
|
+
isPending: value.get().isPending
|
|
406
|
+
}) : options;
|
|
407
|
+
$fetch(path, {
|
|
408
|
+
...opts,
|
|
409
|
+
query: {
|
|
410
|
+
...opts?.query,
|
|
411
|
+
...queryParams?.query
|
|
412
|
+
},
|
|
413
|
+
async onSuccess(context) {
|
|
414
|
+
const current = value.get();
|
|
415
|
+
const stableData = current.data != null && context.data != null && isJsonEqual(current.data, context.data) ? current.data : context.data;
|
|
416
|
+
value.set({
|
|
417
|
+
data: stableData,
|
|
418
|
+
error: null,
|
|
419
|
+
isPending: false,
|
|
420
|
+
isRefetching: false,
|
|
421
|
+
refetch: value.value.refetch
|
|
422
|
+
});
|
|
423
|
+
await opts?.onSuccess?.(context);
|
|
424
|
+
},
|
|
425
|
+
async onError(context) {
|
|
426
|
+
const { request } = context;
|
|
427
|
+
const retryAttempts = typeof request.retry === "number" ? request.retry : request.retry?.attempts;
|
|
428
|
+
const retryAttempt = request.retryAttempt || 0;
|
|
429
|
+
if (retryAttempts && retryAttempt < retryAttempts) return;
|
|
430
|
+
const isUnauthorized = context.error.status === 401;
|
|
431
|
+
value.set({
|
|
432
|
+
error: context.error,
|
|
433
|
+
data: isUnauthorized ? null : value.get().data,
|
|
434
|
+
isPending: false,
|
|
435
|
+
isRefetching: false,
|
|
436
|
+
refetch: value.value.refetch
|
|
437
|
+
});
|
|
438
|
+
await opts?.onError?.(context);
|
|
439
|
+
},
|
|
440
|
+
async onRequest(context) {
|
|
441
|
+
const currentValue = value.get();
|
|
442
|
+
value.set({
|
|
443
|
+
isPending: currentValue.data === null,
|
|
444
|
+
data: currentValue.data,
|
|
445
|
+
error: null,
|
|
446
|
+
isRefetching: true,
|
|
447
|
+
refetch: value.value.refetch
|
|
448
|
+
});
|
|
449
|
+
await opts?.onRequest?.(context);
|
|
450
|
+
}
|
|
451
|
+
}).catch((error) => {
|
|
452
|
+
value.set({
|
|
453
|
+
error,
|
|
454
|
+
data: value.get().data,
|
|
455
|
+
isPending: false,
|
|
456
|
+
isRefetching: false,
|
|
457
|
+
refetch: value.value.refetch
|
|
458
|
+
});
|
|
459
|
+
}).finally(() => {
|
|
460
|
+
resolve(void 0);
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
};
|
|
464
|
+
initializedAtom = Array.isArray(initializedAtom) ? initializedAtom : [initializedAtom];
|
|
465
|
+
let isInitialized = false;
|
|
466
|
+
const cleanups = [];
|
|
467
|
+
for (const initAtom of initializedAtom) {
|
|
468
|
+
const unbind = initAtom.subscribe(async () => {
|
|
469
|
+
if (isServer$1()) return;
|
|
470
|
+
if (isInitialized) await fn();
|
|
471
|
+
else onMount(value, () => {
|
|
472
|
+
const timeoutId = setTimeout(async () => {
|
|
473
|
+
if (!isInitialized) {
|
|
474
|
+
isInitialized = true;
|
|
475
|
+
await fn();
|
|
476
|
+
}
|
|
477
|
+
}, 0);
|
|
478
|
+
return () => {
|
|
479
|
+
for (const u of cleanups) u();
|
|
480
|
+
clearTimeout(timeoutId);
|
|
481
|
+
};
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
cleanups.push(unbind);
|
|
485
|
+
}
|
|
486
|
+
return value;
|
|
487
|
+
};
|
|
488
|
+
//#endregion
|
|
386
489
|
//#region ../../node_modules/.pnpm/better-auth@1.6.22_drizzle-kit@0.31.10_drizzle-orm@0.45.2_@libsql+client@0.17.4_kysely@_fc032d4b6d0d0a5be5267474da10b212/node_modules/better-auth/dist/client/session-refresh.mjs
|
|
387
490
|
const now = () => Math.floor(Date.now() / 1e3);
|
|
388
491
|
const FOCUS_REFETCH_RATE_LIMIT_SECONDS = 5;
|
|
@@ -564,6 +667,15 @@ Object.freeze({
|
|
|
564
667
|
}
|
|
565
668
|
});
|
|
566
669
|
//#endregion
|
|
670
|
+
//#region ../../node_modules/.pnpm/@better-auth+core@1.6.22_@better-auth+utils@0.4.2_@better-fetch+fetch@1.3.1_better-call_17975d57218030f5a353914665f207d6/node_modules/@better-auth/core/dist/utils/error-codes.mjs
|
|
671
|
+
function defineErrorCodes(codes) {
|
|
672
|
+
return Object.fromEntries(Object.entries(codes).map(([key, value]) => [key, {
|
|
673
|
+
code: key,
|
|
674
|
+
message: value,
|
|
675
|
+
toString: () => key
|
|
676
|
+
}]));
|
|
677
|
+
}
|
|
678
|
+
//#endregion
|
|
567
679
|
//#region ../../node_modules/.pnpm/better-call@1.3.7_zod@4.4.3/node_modules/better-call/dist/error.mjs
|
|
568
680
|
function isErrorStackTraceLimitWritable() {
|
|
569
681
|
const desc = Object.getOwnPropertyDescriptor(Error, "stackTraceLimit");
|
|
@@ -1635,7 +1747,551 @@ function createAuthClient$1(options) {
|
|
|
1635
1747
|
}, $fetch, pluginPathMethods, pluginsAtoms, atomListeners);
|
|
1636
1748
|
}
|
|
1637
1749
|
//#endregion
|
|
1638
|
-
//#region ../../node_modules/.pnpm/@
|
|
1750
|
+
//#region ../../node_modules/.pnpm/@better-auth+passkey@1.6.22_@better-auth+core@1.6.22_@better-auth+utils@0.4.2_@better-f_9dcb641568b0468b4b3f2fd37291240f/node_modules/@better-auth/passkey/dist/version-uW-pDlTC.mjs
|
|
1751
|
+
const PASSKEY_ERROR_CODES = defineErrorCodes({
|
|
1752
|
+
CHALLENGE_NOT_FOUND: "Challenge not found",
|
|
1753
|
+
YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY: "You are not allowed to register this passkey",
|
|
1754
|
+
FAILED_TO_VERIFY_REGISTRATION: "Failed to verify registration",
|
|
1755
|
+
PASSKEY_NOT_FOUND: "Passkey not found",
|
|
1756
|
+
AUTHENTICATION_FAILED: "Authentication failed",
|
|
1757
|
+
UNABLE_TO_CREATE_SESSION: "Unable to create session",
|
|
1758
|
+
FAILED_TO_UPDATE_PASSKEY: "Failed to update passkey",
|
|
1759
|
+
PREVIOUSLY_REGISTERED: "Previously registered",
|
|
1760
|
+
REGISTRATION_CANCELLED: "Registration cancelled",
|
|
1761
|
+
AUTH_CANCELLED: "Auth cancelled",
|
|
1762
|
+
UNKNOWN_ERROR: "Unknown error",
|
|
1763
|
+
SESSION_REQUIRED: "Passkey registration requires an authenticated session",
|
|
1764
|
+
RESOLVE_USER_REQUIRED: "Passkey registration requires either an authenticated session or a resolveUser callback when requireSession is false",
|
|
1765
|
+
RESOLVED_USER_INVALID: "Resolved user is invalid"
|
|
1766
|
+
});
|
|
1767
|
+
const PACKAGE_VERSION = "1.6.22";
|
|
1768
|
+
//#endregion
|
|
1769
|
+
//#region ../../node_modules/.pnpm/@simplewebauthn+browser@13.3.0/node_modules/@simplewebauthn/browser/esm/helpers/bufferToBase64URLString.js
|
|
1770
|
+
function bufferToBase64URLString(buffer) {
|
|
1771
|
+
const bytes = new Uint8Array(buffer);
|
|
1772
|
+
let str = "";
|
|
1773
|
+
for (const charCode of bytes) str += String.fromCharCode(charCode);
|
|
1774
|
+
return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
1775
|
+
}
|
|
1776
|
+
//#endregion
|
|
1777
|
+
//#region ../../node_modules/.pnpm/@simplewebauthn+browser@13.3.0/node_modules/@simplewebauthn/browser/esm/helpers/base64URLStringToBuffer.js
|
|
1778
|
+
function base64URLStringToBuffer(base64URLString) {
|
|
1779
|
+
const base64 = base64URLString.replace(/-/g, "+").replace(/_/g, "/");
|
|
1780
|
+
const padLength = (4 - base64.length % 4) % 4;
|
|
1781
|
+
const padded = base64.padEnd(base64.length + padLength, "=");
|
|
1782
|
+
const binary = atob(padded);
|
|
1783
|
+
const buffer = new ArrayBuffer(binary.length);
|
|
1784
|
+
const bytes = new Uint8Array(buffer);
|
|
1785
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
1786
|
+
return buffer;
|
|
1787
|
+
}
|
|
1788
|
+
//#endregion
|
|
1789
|
+
//#region ../../node_modules/.pnpm/@simplewebauthn+browser@13.3.0/node_modules/@simplewebauthn/browser/esm/helpers/browserSupportsWebAuthn.js
|
|
1790
|
+
function browserSupportsWebAuthn() {
|
|
1791
|
+
return _browserSupportsWebAuthnInternals.stubThis(globalThis?.PublicKeyCredential !== void 0 && typeof globalThis.PublicKeyCredential === "function");
|
|
1792
|
+
}
|
|
1793
|
+
const _browserSupportsWebAuthnInternals = { stubThis: (value) => value };
|
|
1794
|
+
//#endregion
|
|
1795
|
+
//#region ../../node_modules/.pnpm/@simplewebauthn+browser@13.3.0/node_modules/@simplewebauthn/browser/esm/helpers/toPublicKeyCredentialDescriptor.js
|
|
1796
|
+
function toPublicKeyCredentialDescriptor(descriptor) {
|
|
1797
|
+
const { id } = descriptor;
|
|
1798
|
+
return {
|
|
1799
|
+
...descriptor,
|
|
1800
|
+
id: base64URLStringToBuffer(id),
|
|
1801
|
+
transports: descriptor.transports
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
//#endregion
|
|
1805
|
+
//#region ../../node_modules/.pnpm/@simplewebauthn+browser@13.3.0/node_modules/@simplewebauthn/browser/esm/helpers/isValidDomain.js
|
|
1806
|
+
function isValidDomain(hostname) {
|
|
1807
|
+
return hostname === "localhost" || /^((xn--[a-z0-9-]+|[a-z0-9]+(-[a-z0-9]+)*)\.)+([a-z]{2,}|xn--[a-z0-9-]+)$/i.test(hostname);
|
|
1808
|
+
}
|
|
1809
|
+
//#endregion
|
|
1810
|
+
//#region ../../node_modules/.pnpm/@simplewebauthn+browser@13.3.0/node_modules/@simplewebauthn/browser/esm/helpers/webAuthnError.js
|
|
1811
|
+
var WebAuthnError = class extends Error {
|
|
1812
|
+
constructor({ message, code, cause, name }) {
|
|
1813
|
+
super(message, { cause });
|
|
1814
|
+
Object.defineProperty(this, "code", {
|
|
1815
|
+
enumerable: true,
|
|
1816
|
+
configurable: true,
|
|
1817
|
+
writable: true,
|
|
1818
|
+
value: void 0
|
|
1819
|
+
});
|
|
1820
|
+
this.name = name ?? cause.name;
|
|
1821
|
+
this.code = code;
|
|
1822
|
+
}
|
|
1823
|
+
};
|
|
1824
|
+
//#endregion
|
|
1825
|
+
//#region ../../node_modules/.pnpm/@simplewebauthn+browser@13.3.0/node_modules/@simplewebauthn/browser/esm/helpers/identifyRegistrationError.js
|
|
1826
|
+
function identifyRegistrationError({ error, options }) {
|
|
1827
|
+
const { publicKey } = options;
|
|
1828
|
+
if (!publicKey) throw Error("options was missing required publicKey property");
|
|
1829
|
+
if (error.name === "AbortError") {
|
|
1830
|
+
if (options.signal instanceof AbortSignal) return new WebAuthnError({
|
|
1831
|
+
message: "Registration ceremony was sent an abort signal",
|
|
1832
|
+
code: "ERROR_CEREMONY_ABORTED",
|
|
1833
|
+
cause: error
|
|
1834
|
+
});
|
|
1835
|
+
} else if (error.name === "ConstraintError") {
|
|
1836
|
+
if (publicKey.authenticatorSelection?.requireResidentKey === true) return new WebAuthnError({
|
|
1837
|
+
message: "Discoverable credentials were required but no available authenticator supported it",
|
|
1838
|
+
code: "ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT",
|
|
1839
|
+
cause: error
|
|
1840
|
+
});
|
|
1841
|
+
else if (options.mediation === "conditional" && publicKey.authenticatorSelection?.userVerification === "required") return new WebAuthnError({
|
|
1842
|
+
message: "User verification was required during automatic registration but it could not be performed",
|
|
1843
|
+
code: "ERROR_AUTO_REGISTER_USER_VERIFICATION_FAILURE",
|
|
1844
|
+
cause: error
|
|
1845
|
+
});
|
|
1846
|
+
else if (publicKey.authenticatorSelection?.userVerification === "required") return new WebAuthnError({
|
|
1847
|
+
message: "User verification was required but no available authenticator supported it",
|
|
1848
|
+
code: "ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT",
|
|
1849
|
+
cause: error
|
|
1850
|
+
});
|
|
1851
|
+
} else if (error.name === "InvalidStateError") return new WebAuthnError({
|
|
1852
|
+
message: "The authenticator was previously registered",
|
|
1853
|
+
code: "ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED",
|
|
1854
|
+
cause: error
|
|
1855
|
+
});
|
|
1856
|
+
else if (error.name === "NotAllowedError") return new WebAuthnError({
|
|
1857
|
+
message: error.message,
|
|
1858
|
+
code: "ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",
|
|
1859
|
+
cause: error
|
|
1860
|
+
});
|
|
1861
|
+
else if (error.name === "NotSupportedError") {
|
|
1862
|
+
if (publicKey.pubKeyCredParams.filter((param) => param.type === "public-key").length === 0) return new WebAuthnError({
|
|
1863
|
+
message: "No entry in pubKeyCredParams was of type \"public-key\"",
|
|
1864
|
+
code: "ERROR_MALFORMED_PUBKEYCREDPARAMS",
|
|
1865
|
+
cause: error
|
|
1866
|
+
});
|
|
1867
|
+
return new WebAuthnError({
|
|
1868
|
+
message: "No available authenticator supported any of the specified pubKeyCredParams algorithms",
|
|
1869
|
+
code: "ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG",
|
|
1870
|
+
cause: error
|
|
1871
|
+
});
|
|
1872
|
+
} else if (error.name === "SecurityError") {
|
|
1873
|
+
const effectiveDomain = globalThis.location.hostname;
|
|
1874
|
+
if (!isValidDomain(effectiveDomain)) return new WebAuthnError({
|
|
1875
|
+
message: `${globalThis.location.hostname} is an invalid domain`,
|
|
1876
|
+
code: "ERROR_INVALID_DOMAIN",
|
|
1877
|
+
cause: error
|
|
1878
|
+
});
|
|
1879
|
+
else if (publicKey.rp.id !== effectiveDomain) return new WebAuthnError({
|
|
1880
|
+
message: `The RP ID "${publicKey.rp.id}" is invalid for this domain`,
|
|
1881
|
+
code: "ERROR_INVALID_RP_ID",
|
|
1882
|
+
cause: error
|
|
1883
|
+
});
|
|
1884
|
+
} else if (error.name === "TypeError") {
|
|
1885
|
+
if (publicKey.user.id.byteLength < 1 || publicKey.user.id.byteLength > 64) return new WebAuthnError({
|
|
1886
|
+
message: "User ID was not between 1 and 64 characters",
|
|
1887
|
+
code: "ERROR_INVALID_USER_ID_LENGTH",
|
|
1888
|
+
cause: error
|
|
1889
|
+
});
|
|
1890
|
+
} else if (error.name === "UnknownError") return new WebAuthnError({
|
|
1891
|
+
message: "The authenticator was unable to process the specified options, or could not create a new credential",
|
|
1892
|
+
code: "ERROR_AUTHENTICATOR_GENERAL_ERROR",
|
|
1893
|
+
cause: error
|
|
1894
|
+
});
|
|
1895
|
+
return error;
|
|
1896
|
+
}
|
|
1897
|
+
//#endregion
|
|
1898
|
+
//#region ../../node_modules/.pnpm/@simplewebauthn+browser@13.3.0/node_modules/@simplewebauthn/browser/esm/helpers/webAuthnAbortService.js
|
|
1899
|
+
var BaseWebAuthnAbortService = class {
|
|
1900
|
+
constructor() {
|
|
1901
|
+
Object.defineProperty(this, "controller", {
|
|
1902
|
+
enumerable: true,
|
|
1903
|
+
configurable: true,
|
|
1904
|
+
writable: true,
|
|
1905
|
+
value: void 0
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
createNewAbortSignal() {
|
|
1909
|
+
if (this.controller) {
|
|
1910
|
+
const abortError = new Error("Cancelling existing WebAuthn API call for new one");
|
|
1911
|
+
abortError.name = "AbortError";
|
|
1912
|
+
this.controller.abort(abortError);
|
|
1913
|
+
}
|
|
1914
|
+
const newController = new AbortController();
|
|
1915
|
+
this.controller = newController;
|
|
1916
|
+
return newController.signal;
|
|
1917
|
+
}
|
|
1918
|
+
cancelCeremony() {
|
|
1919
|
+
if (this.controller) {
|
|
1920
|
+
const abortError = new Error("Manually cancelling existing WebAuthn API call");
|
|
1921
|
+
abortError.name = "AbortError";
|
|
1922
|
+
this.controller.abort(abortError);
|
|
1923
|
+
this.controller = void 0;
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
};
|
|
1927
|
+
const WebAuthnAbortService = new BaseWebAuthnAbortService();
|
|
1928
|
+
//#endregion
|
|
1929
|
+
//#region ../../node_modules/.pnpm/@simplewebauthn+browser@13.3.0/node_modules/@simplewebauthn/browser/esm/helpers/toAuthenticatorAttachment.js
|
|
1930
|
+
const attachments = ["cross-platform", "platform"];
|
|
1931
|
+
function toAuthenticatorAttachment(attachment) {
|
|
1932
|
+
if (!attachment) return;
|
|
1933
|
+
if (attachments.indexOf(attachment) < 0) return;
|
|
1934
|
+
return attachment;
|
|
1935
|
+
}
|
|
1936
|
+
//#endregion
|
|
1937
|
+
//#region ../../node_modules/.pnpm/@simplewebauthn+browser@13.3.0/node_modules/@simplewebauthn/browser/esm/methods/startRegistration.js
|
|
1938
|
+
async function startRegistration(options) {
|
|
1939
|
+
if (!options.optionsJSON && options.challenge) {
|
|
1940
|
+
console.warn("startRegistration() was not called correctly. It will try to continue with the provided options, but this call should be refactored to use the expected call structure instead. See https://simplewebauthn.dev/docs/packages/browser#typeerror-cannot-read-properties-of-undefined-reading-challenge for more information.");
|
|
1941
|
+
options = { optionsJSON: options };
|
|
1942
|
+
}
|
|
1943
|
+
const { optionsJSON, useAutoRegister = false } = options;
|
|
1944
|
+
if (!browserSupportsWebAuthn()) throw new Error("WebAuthn is not supported in this browser");
|
|
1945
|
+
const publicKey = {
|
|
1946
|
+
...optionsJSON,
|
|
1947
|
+
challenge: base64URLStringToBuffer(optionsJSON.challenge),
|
|
1948
|
+
user: {
|
|
1949
|
+
...optionsJSON.user,
|
|
1950
|
+
id: base64URLStringToBuffer(optionsJSON.user.id)
|
|
1951
|
+
},
|
|
1952
|
+
excludeCredentials: optionsJSON.excludeCredentials?.map(toPublicKeyCredentialDescriptor)
|
|
1953
|
+
};
|
|
1954
|
+
const createOptions = {};
|
|
1955
|
+
if (useAutoRegister) createOptions.mediation = "conditional";
|
|
1956
|
+
createOptions.publicKey = publicKey;
|
|
1957
|
+
createOptions.signal = WebAuthnAbortService.createNewAbortSignal();
|
|
1958
|
+
let credential;
|
|
1959
|
+
try {
|
|
1960
|
+
credential = await navigator.credentials.create(createOptions);
|
|
1961
|
+
} catch (err) {
|
|
1962
|
+
throw identifyRegistrationError({
|
|
1963
|
+
error: err,
|
|
1964
|
+
options: createOptions
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
if (!credential) throw new Error("Registration was not completed");
|
|
1968
|
+
const { id, rawId, response, type } = credential;
|
|
1969
|
+
let transports = void 0;
|
|
1970
|
+
if (typeof response.getTransports === "function") transports = response.getTransports();
|
|
1971
|
+
let responsePublicKeyAlgorithm = void 0;
|
|
1972
|
+
if (typeof response.getPublicKeyAlgorithm === "function") try {
|
|
1973
|
+
responsePublicKeyAlgorithm = response.getPublicKeyAlgorithm();
|
|
1974
|
+
} catch (error) {
|
|
1975
|
+
warnOnBrokenImplementation("getPublicKeyAlgorithm()", error);
|
|
1976
|
+
}
|
|
1977
|
+
let responsePublicKey = void 0;
|
|
1978
|
+
if (typeof response.getPublicKey === "function") try {
|
|
1979
|
+
const _publicKey = response.getPublicKey();
|
|
1980
|
+
if (_publicKey !== null) responsePublicKey = bufferToBase64URLString(_publicKey);
|
|
1981
|
+
} catch (error) {
|
|
1982
|
+
warnOnBrokenImplementation("getPublicKey()", error);
|
|
1983
|
+
}
|
|
1984
|
+
let responseAuthenticatorData;
|
|
1985
|
+
if (typeof response.getAuthenticatorData === "function") try {
|
|
1986
|
+
responseAuthenticatorData = bufferToBase64URLString(response.getAuthenticatorData());
|
|
1987
|
+
} catch (error) {
|
|
1988
|
+
warnOnBrokenImplementation("getAuthenticatorData()", error);
|
|
1989
|
+
}
|
|
1990
|
+
return {
|
|
1991
|
+
id,
|
|
1992
|
+
rawId: bufferToBase64URLString(rawId),
|
|
1993
|
+
response: {
|
|
1994
|
+
attestationObject: bufferToBase64URLString(response.attestationObject),
|
|
1995
|
+
clientDataJSON: bufferToBase64URLString(response.clientDataJSON),
|
|
1996
|
+
transports,
|
|
1997
|
+
publicKeyAlgorithm: responsePublicKeyAlgorithm,
|
|
1998
|
+
publicKey: responsePublicKey,
|
|
1999
|
+
authenticatorData: responseAuthenticatorData
|
|
2000
|
+
},
|
|
2001
|
+
type,
|
|
2002
|
+
clientExtensionResults: credential.getClientExtensionResults(),
|
|
2003
|
+
authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment)
|
|
2004
|
+
};
|
|
2005
|
+
}
|
|
2006
|
+
function warnOnBrokenImplementation(methodName, cause) {
|
|
2007
|
+
console.warn(`The browser extension that intercepted this WebAuthn API call incorrectly implemented ${methodName}. You should report this error to them.\n`, cause);
|
|
2008
|
+
}
|
|
2009
|
+
//#endregion
|
|
2010
|
+
//#region ../../node_modules/.pnpm/@simplewebauthn+browser@13.3.0/node_modules/@simplewebauthn/browser/esm/helpers/browserSupportsWebAuthnAutofill.js
|
|
2011
|
+
function browserSupportsWebAuthnAutofill() {
|
|
2012
|
+
if (!browserSupportsWebAuthn()) return _browserSupportsWebAuthnAutofillInternals.stubThis(new Promise((resolve) => resolve(false)));
|
|
2013
|
+
const globalPublicKeyCredential = globalThis.PublicKeyCredential;
|
|
2014
|
+
if (globalPublicKeyCredential?.isConditionalMediationAvailable === void 0) return _browserSupportsWebAuthnAutofillInternals.stubThis(new Promise((resolve) => resolve(false)));
|
|
2015
|
+
return _browserSupportsWebAuthnAutofillInternals.stubThis(globalPublicKeyCredential.isConditionalMediationAvailable());
|
|
2016
|
+
}
|
|
2017
|
+
const _browserSupportsWebAuthnAutofillInternals = { stubThis: (value) => value };
|
|
2018
|
+
//#endregion
|
|
2019
|
+
//#region ../../node_modules/.pnpm/@simplewebauthn+browser@13.3.0/node_modules/@simplewebauthn/browser/esm/helpers/identifyAuthenticationError.js
|
|
2020
|
+
function identifyAuthenticationError({ error, options }) {
|
|
2021
|
+
const { publicKey } = options;
|
|
2022
|
+
if (!publicKey) throw Error("options was missing required publicKey property");
|
|
2023
|
+
if (error.name === "AbortError") {
|
|
2024
|
+
if (options.signal instanceof AbortSignal) return new WebAuthnError({
|
|
2025
|
+
message: "Authentication ceremony was sent an abort signal",
|
|
2026
|
+
code: "ERROR_CEREMONY_ABORTED",
|
|
2027
|
+
cause: error
|
|
2028
|
+
});
|
|
2029
|
+
} else if (error.name === "NotAllowedError") return new WebAuthnError({
|
|
2030
|
+
message: error.message,
|
|
2031
|
+
code: "ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",
|
|
2032
|
+
cause: error
|
|
2033
|
+
});
|
|
2034
|
+
else if (error.name === "SecurityError") {
|
|
2035
|
+
const effectiveDomain = globalThis.location.hostname;
|
|
2036
|
+
if (!isValidDomain(effectiveDomain)) return new WebAuthnError({
|
|
2037
|
+
message: `${globalThis.location.hostname} is an invalid domain`,
|
|
2038
|
+
code: "ERROR_INVALID_DOMAIN",
|
|
2039
|
+
cause: error
|
|
2040
|
+
});
|
|
2041
|
+
else if (publicKey.rpId !== effectiveDomain) return new WebAuthnError({
|
|
2042
|
+
message: `The RP ID "${publicKey.rpId}" is invalid for this domain`,
|
|
2043
|
+
code: "ERROR_INVALID_RP_ID",
|
|
2044
|
+
cause: error
|
|
2045
|
+
});
|
|
2046
|
+
} else if (error.name === "UnknownError") return new WebAuthnError({
|
|
2047
|
+
message: "The authenticator was unable to process the specified options, or could not create a new assertion signature",
|
|
2048
|
+
code: "ERROR_AUTHENTICATOR_GENERAL_ERROR",
|
|
2049
|
+
cause: error
|
|
2050
|
+
});
|
|
2051
|
+
return error;
|
|
2052
|
+
}
|
|
2053
|
+
//#endregion
|
|
2054
|
+
//#region ../../node_modules/.pnpm/@simplewebauthn+browser@13.3.0/node_modules/@simplewebauthn/browser/esm/methods/startAuthentication.js
|
|
2055
|
+
async function startAuthentication(options) {
|
|
2056
|
+
if (!options.optionsJSON && options.challenge) {
|
|
2057
|
+
console.warn("startAuthentication() was not called correctly. It will try to continue with the provided options, but this call should be refactored to use the expected call structure instead. See https://simplewebauthn.dev/docs/packages/browser#typeerror-cannot-read-properties-of-undefined-reading-challenge for more information.");
|
|
2058
|
+
options = { optionsJSON: options };
|
|
2059
|
+
}
|
|
2060
|
+
const { optionsJSON, useBrowserAutofill = false, verifyBrowserAutofillInput = true } = options;
|
|
2061
|
+
if (!browserSupportsWebAuthn()) throw new Error("WebAuthn is not supported in this browser");
|
|
2062
|
+
let allowCredentials;
|
|
2063
|
+
if (optionsJSON.allowCredentials?.length !== 0) allowCredentials = optionsJSON.allowCredentials?.map(toPublicKeyCredentialDescriptor);
|
|
2064
|
+
const publicKey = {
|
|
2065
|
+
...optionsJSON,
|
|
2066
|
+
challenge: base64URLStringToBuffer(optionsJSON.challenge),
|
|
2067
|
+
allowCredentials
|
|
2068
|
+
};
|
|
2069
|
+
const getOptions = {};
|
|
2070
|
+
if (useBrowserAutofill) {
|
|
2071
|
+
if (!await browserSupportsWebAuthnAutofill()) throw Error("Browser does not support WebAuthn autofill");
|
|
2072
|
+
if (document.querySelectorAll("input[autocomplete$='webauthn']").length < 1 && verifyBrowserAutofillInput) throw Error("No <input> with \"webauthn\" as the only or last value in its `autocomplete` attribute was detected");
|
|
2073
|
+
getOptions.mediation = "conditional";
|
|
2074
|
+
publicKey.allowCredentials = [];
|
|
2075
|
+
}
|
|
2076
|
+
getOptions.publicKey = publicKey;
|
|
2077
|
+
getOptions.signal = WebAuthnAbortService.createNewAbortSignal();
|
|
2078
|
+
let credential;
|
|
2079
|
+
try {
|
|
2080
|
+
credential = await navigator.credentials.get(getOptions);
|
|
2081
|
+
} catch (err) {
|
|
2082
|
+
throw identifyAuthenticationError({
|
|
2083
|
+
error: err,
|
|
2084
|
+
options: getOptions
|
|
2085
|
+
});
|
|
2086
|
+
}
|
|
2087
|
+
if (!credential) throw new Error("Authentication was not completed");
|
|
2088
|
+
const { id, rawId, response, type } = credential;
|
|
2089
|
+
let userHandle = void 0;
|
|
2090
|
+
if (response.userHandle) userHandle = bufferToBase64URLString(response.userHandle);
|
|
2091
|
+
return {
|
|
2092
|
+
id,
|
|
2093
|
+
rawId: bufferToBase64URLString(rawId),
|
|
2094
|
+
response: {
|
|
2095
|
+
authenticatorData: bufferToBase64URLString(response.authenticatorData),
|
|
2096
|
+
clientDataJSON: bufferToBase64URLString(response.clientDataJSON),
|
|
2097
|
+
signature: bufferToBase64URLString(response.signature),
|
|
2098
|
+
userHandle
|
|
2099
|
+
},
|
|
2100
|
+
type,
|
|
2101
|
+
clientExtensionResults: credential.getClientExtensionResults(),
|
|
2102
|
+
authenticatorAttachment: toAuthenticatorAttachment(credential.authenticatorAttachment)
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
//#endregion
|
|
2106
|
+
//#region ../../node_modules/.pnpm/@better-auth+passkey@1.6.22_@better-auth+core@1.6.22_@better-auth+utils@0.4.2_@better-f_9dcb641568b0468b4b3f2fd37291240f/node_modules/@better-auth/passkey/dist/client.mjs
|
|
2107
|
+
const getPasskeyActions = ($fetch, { $listPasskeys, $store }) => {
|
|
2108
|
+
const signInPasskey = async (opts, options) => {
|
|
2109
|
+
const response = await $fetch("/passkey/generate-authenticate-options", {
|
|
2110
|
+
method: "GET",
|
|
2111
|
+
throw: false
|
|
2112
|
+
});
|
|
2113
|
+
if (!response.data) return response;
|
|
2114
|
+
const mergedExtensions = response.data.extensions || opts?.extensions ? {
|
|
2115
|
+
...response.data.extensions || {},
|
|
2116
|
+
...opts?.extensions || {}
|
|
2117
|
+
} : void 0;
|
|
2118
|
+
let res;
|
|
2119
|
+
try {
|
|
2120
|
+
res = await startAuthentication({
|
|
2121
|
+
optionsJSON: {
|
|
2122
|
+
...response.data,
|
|
2123
|
+
extensions: mergedExtensions
|
|
2124
|
+
},
|
|
2125
|
+
useBrowserAutofill: opts?.autoFill
|
|
2126
|
+
});
|
|
2127
|
+
} catch (err) {
|
|
2128
|
+
return {
|
|
2129
|
+
data: null,
|
|
2130
|
+
error: {
|
|
2131
|
+
code: err instanceof WebAuthnError ? err.code : "AUTH_CANCELLED",
|
|
2132
|
+
message: PASSKEY_ERROR_CODES.AUTH_CANCELLED.message,
|
|
2133
|
+
status: 400,
|
|
2134
|
+
statusText: "BAD_REQUEST"
|
|
2135
|
+
}
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
try {
|
|
2139
|
+
const { clientExtensionResults, ...responseBody } = res;
|
|
2140
|
+
const verified = await $fetch("/passkey/verify-authentication", {
|
|
2141
|
+
body: { response: responseBody },
|
|
2142
|
+
...opts?.fetchOptions,
|
|
2143
|
+
...options,
|
|
2144
|
+
method: "POST",
|
|
2145
|
+
throw: false
|
|
2146
|
+
});
|
|
2147
|
+
$listPasskeys.set(Math.random());
|
|
2148
|
+
$store.notify("$sessionSignal");
|
|
2149
|
+
if (opts?.returnWebAuthnResponse) return {
|
|
2150
|
+
...verified,
|
|
2151
|
+
webauthn: {
|
|
2152
|
+
response: res,
|
|
2153
|
+
clientExtensionResults
|
|
2154
|
+
}
|
|
2155
|
+
};
|
|
2156
|
+
return verified;
|
|
2157
|
+
} catch (err) {
|
|
2158
|
+
console.error(`[Better Auth] Error verifying passkey`, err);
|
|
2159
|
+
return {
|
|
2160
|
+
data: null,
|
|
2161
|
+
error: {
|
|
2162
|
+
code: "AUTH_CANCELLED",
|
|
2163
|
+
message: PASSKEY_ERROR_CODES.AUTH_CANCELLED.message,
|
|
2164
|
+
status: 400,
|
|
2165
|
+
statusText: "BAD_REQUEST"
|
|
2166
|
+
}
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
2169
|
+
};
|
|
2170
|
+
const registerPasskey = async (opts, fetchOpts) => {
|
|
2171
|
+
const options = await $fetch("/passkey/generate-register-options", {
|
|
2172
|
+
method: "GET",
|
|
2173
|
+
query: {
|
|
2174
|
+
...opts?.authenticatorAttachment && { authenticatorAttachment: opts.authenticatorAttachment },
|
|
2175
|
+
...opts?.name && { name: opts.name },
|
|
2176
|
+
...opts?.context && { context: opts.context }
|
|
2177
|
+
},
|
|
2178
|
+
throw: false
|
|
2179
|
+
});
|
|
2180
|
+
if (!options.data) return options;
|
|
2181
|
+
try {
|
|
2182
|
+
const mergedExtensions = options.data.extensions || opts?.extensions ? {
|
|
2183
|
+
...options.data.extensions || {},
|
|
2184
|
+
...opts?.extensions || {}
|
|
2185
|
+
} : void 0;
|
|
2186
|
+
const res = await startRegistration({
|
|
2187
|
+
optionsJSON: {
|
|
2188
|
+
...options.data,
|
|
2189
|
+
extensions: mergedExtensions
|
|
2190
|
+
},
|
|
2191
|
+
useAutoRegister: opts?.useAutoRegister
|
|
2192
|
+
});
|
|
2193
|
+
const { clientExtensionResults, ...responseBody } = res;
|
|
2194
|
+
const verified = await $fetch("/passkey/verify-registration", {
|
|
2195
|
+
...opts?.fetchOptions,
|
|
2196
|
+
...fetchOpts,
|
|
2197
|
+
body: {
|
|
2198
|
+
response: responseBody,
|
|
2199
|
+
name: opts?.name
|
|
2200
|
+
},
|
|
2201
|
+
method: "POST",
|
|
2202
|
+
throw: false
|
|
2203
|
+
});
|
|
2204
|
+
if (!verified.data) return verified;
|
|
2205
|
+
$listPasskeys.set(Math.random());
|
|
2206
|
+
if (opts?.returnWebAuthnResponse) return {
|
|
2207
|
+
...verified,
|
|
2208
|
+
webauthn: {
|
|
2209
|
+
response: res,
|
|
2210
|
+
clientExtensionResults
|
|
2211
|
+
}
|
|
2212
|
+
};
|
|
2213
|
+
return verified;
|
|
2214
|
+
} catch (e) {
|
|
2215
|
+
if (e instanceof WebAuthnError) {
|
|
2216
|
+
if (e.code === "ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED") return {
|
|
2217
|
+
data: null,
|
|
2218
|
+
error: {
|
|
2219
|
+
code: e.code,
|
|
2220
|
+
message: PASSKEY_ERROR_CODES.PREVIOUSLY_REGISTERED.message,
|
|
2221
|
+
status: 400,
|
|
2222
|
+
statusText: "BAD_REQUEST"
|
|
2223
|
+
}
|
|
2224
|
+
};
|
|
2225
|
+
if (e.code === "ERROR_CEREMONY_ABORTED") return {
|
|
2226
|
+
data: null,
|
|
2227
|
+
error: {
|
|
2228
|
+
code: e.code,
|
|
2229
|
+
message: PASSKEY_ERROR_CODES.REGISTRATION_CANCELLED.message,
|
|
2230
|
+
status: 400,
|
|
2231
|
+
statusText: "BAD_REQUEST"
|
|
2232
|
+
}
|
|
2233
|
+
};
|
|
2234
|
+
return {
|
|
2235
|
+
data: null,
|
|
2236
|
+
error: {
|
|
2237
|
+
code: e.code,
|
|
2238
|
+
message: e.message,
|
|
2239
|
+
status: 400,
|
|
2240
|
+
statusText: "BAD_REQUEST"
|
|
2241
|
+
}
|
|
2242
|
+
};
|
|
2243
|
+
}
|
|
2244
|
+
return {
|
|
2245
|
+
data: null,
|
|
2246
|
+
error: {
|
|
2247
|
+
code: "UNKNOWN_ERROR",
|
|
2248
|
+
message: e instanceof Error ? e.message : PASSKEY_ERROR_CODES.UNKNOWN_ERROR.message,
|
|
2249
|
+
status: 500,
|
|
2250
|
+
statusText: "INTERNAL_SERVER_ERROR"
|
|
2251
|
+
}
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
};
|
|
2255
|
+
return {
|
|
2256
|
+
signIn: { passkey: signInPasskey },
|
|
2257
|
+
passkey: { addPasskey: registerPasskey },
|
|
2258
|
+
$Infer: {}
|
|
2259
|
+
};
|
|
2260
|
+
};
|
|
2261
|
+
const passkeyClient = () => {
|
|
2262
|
+
const $listPasskeys = atom();
|
|
2263
|
+
return {
|
|
2264
|
+
id: "passkey",
|
|
2265
|
+
version: PACKAGE_VERSION,
|
|
2266
|
+
$InferServerPlugin: {},
|
|
2267
|
+
getActions: ($fetch, $store) => getPasskeyActions($fetch, {
|
|
2268
|
+
$listPasskeys,
|
|
2269
|
+
$store
|
|
2270
|
+
}),
|
|
2271
|
+
getAtoms($fetch) {
|
|
2272
|
+
return {
|
|
2273
|
+
listPasskeys: useAuthQuery($listPasskeys, "/passkey/list-user-passkeys", $fetch, { method: "GET" }),
|
|
2274
|
+
$listPasskeys
|
|
2275
|
+
};
|
|
2276
|
+
},
|
|
2277
|
+
pathMethods: {
|
|
2278
|
+
"/passkey/register": "POST",
|
|
2279
|
+
"/passkey/authenticate": "POST"
|
|
2280
|
+
},
|
|
2281
|
+
atomListeners: [{
|
|
2282
|
+
matcher(path) {
|
|
2283
|
+
return path === "/passkey/verify-registration" || path === "/passkey/delete-passkey" || path === "/passkey/update-passkey" || path === "/sign-out";
|
|
2284
|
+
},
|
|
2285
|
+
signal: "$listPasskeys"
|
|
2286
|
+
}, {
|
|
2287
|
+
matcher: (path) => path === "/passkey/verify-authentication",
|
|
2288
|
+
signal: "$sessionSignal"
|
|
2289
|
+
}],
|
|
2290
|
+
$ERROR_CODES: PASSKEY_ERROR_CODES
|
|
2291
|
+
};
|
|
2292
|
+
};
|
|
2293
|
+
//#endregion
|
|
2294
|
+
//#region ../../node_modules/.pnpm/@chiballc+utils@2.5.1_@hono+zod-openapi@1.5.1_hono@4.13.0_zod@4.4.3__hono@4.13.0_magic-_dfcdb59639d9fe73412dad0223379a88/node_modules/@chiballc/utils/dist/index.mjs
|
|
1639
2295
|
function isPromise(arg) {
|
|
1640
2296
|
return arg && typeof arg.then === "function";
|
|
1641
2297
|
}
|
|
@@ -1895,6 +2551,9 @@ const messages = {
|
|
|
1895
2551
|
auth_request_rate_limited: "Authentication requests are temporarily limited.",
|
|
1896
2552
|
state_conflict: "The authentication state conflicts with the request.",
|
|
1897
2553
|
access_denied: "Access is denied.",
|
|
2554
|
+
platform_target_unavailable: "The platform administration target is unavailable.",
|
|
2555
|
+
platform_quorum_unavailable: "Platform administration quorum is unavailable.",
|
|
2556
|
+
platform_recovery_required: "Platform administration requires separate recovery.",
|
|
1898
2557
|
identity_authority_unavailable: "Authentication is temporarily unavailable."
|
|
1899
2558
|
};
|
|
1900
2559
|
var AuthError = class extends Error {
|
|
@@ -2639,6 +3298,90 @@ function fromOrganizationMutationAuthorityError(value, status, retryAfter) {
|
|
|
2639
3298
|
if (status === 403 && code === "REQUEST_ORIGIN_DENIED") return new AuthError("request_origin_denied", 403);
|
|
2640
3299
|
return fromOrganizationReadAuthorityError(value, status);
|
|
2641
3300
|
}
|
|
3301
|
+
function normalizePlatformRoleChangeRecentAuthenticationInput(value) {
|
|
3302
|
+
if (!isObject(value) || Object.keys(value).sort().join(",") !== "changeReference,expectedGlobalRevision,expectedTargetRevision,idempotencyKey,reasonCategory,requestedRoles,targetUserId") throw new TypeError("recent authentication input is invalid");
|
|
3303
|
+
if (!isId(value.targetUserId)) throw new TypeError("targetUserId is invalid");
|
|
3304
|
+
if (!Array.isArray(value.requestedRoles) || value.requestedRoles.length < 1 || value.requestedRoles.length > 2 || value.requestedRoles.some((role) => role !== "administrator" && role !== "investigator") || new Set(value.requestedRoles).size !== value.requestedRoles.length) throw new TypeError("requestedRoles is invalid");
|
|
3305
|
+
for (const field of ["expectedTargetRevision", "expectedGlobalRevision"]) if (!Number.isSafeInteger(value[field]) || value[field] < 0 || value[field] >= Number.MAX_SAFE_INTEGER) throw new TypeError(`${field} is invalid`);
|
|
3306
|
+
if (!(typeof value.reasonCategory === "string" && [
|
|
3307
|
+
"initial_bootstrap",
|
|
3308
|
+
"personnel_change",
|
|
3309
|
+
"privilege_review",
|
|
3310
|
+
"quorum_recovery",
|
|
3311
|
+
"security_response"
|
|
3312
|
+
].includes(value.reasonCategory))) throw new TypeError("reasonCategory is invalid");
|
|
3313
|
+
for (const field of ["changeReference", "idempotencyKey"]) if (typeof value[field] !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(value[field])) throw new TypeError(`${field} is invalid`);
|
|
3314
|
+
return {
|
|
3315
|
+
action: "platform_role_change",
|
|
3316
|
+
targetUserId: value.targetUserId,
|
|
3317
|
+
requestedRoles: [...value.requestedRoles].sort(),
|
|
3318
|
+
expectedTargetRevision: value.expectedTargetRevision,
|
|
3319
|
+
expectedGlobalRevision: value.expectedGlobalRevision,
|
|
3320
|
+
reasonCategory: value.reasonCategory,
|
|
3321
|
+
changeReference: value.changeReference,
|
|
3322
|
+
idempotencyKey: value.idempotencyKey
|
|
3323
|
+
};
|
|
3324
|
+
}
|
|
3325
|
+
function normalizePlatformBootstrapRoleChangeInput(value) {
|
|
3326
|
+
const request = normalizePlatformRoleChangeRecentAuthenticationInput(value);
|
|
3327
|
+
if (request.reasonCategory !== "initial_bootstrap" || !request.requestedRoles.includes("administrator")) throw new TypeError("platform first approval input is invalid");
|
|
3328
|
+
return request;
|
|
3329
|
+
}
|
|
3330
|
+
function normalizePlatformRoleApproval(value) {
|
|
3331
|
+
exact(value, [
|
|
3332
|
+
"status",
|
|
3333
|
+
"targetUserId",
|
|
3334
|
+
"targetRevision",
|
|
3335
|
+
"globalRevision",
|
|
3336
|
+
"roles"
|
|
3337
|
+
]);
|
|
3338
|
+
if (value.status !== "awaiting_second_approval" && value.status !== "applied" || !isId(value.targetUserId)) throw authorityUnavailable();
|
|
3339
|
+
if (!Number.isSafeInteger(value.targetRevision) || value.targetRevision < 0) throw authorityUnavailable();
|
|
3340
|
+
if (!Number.isSafeInteger(value.globalRevision) || value.globalRevision < 0) throw authorityUnavailable();
|
|
3341
|
+
if (!Array.isArray(value.roles) || value.status === "awaiting_second_approval" && value.roles.length !== 0 || value.status === "applied" && !(value.roles.length === 1 && value.roles[0] === "administrator" || value.roles.length === 2 && value.roles[0] === "administrator" && value.roles[1] === "investigator")) throw authorityUnavailable();
|
|
3342
|
+
if (value.status === "applied") return {
|
|
3343
|
+
status: "applied",
|
|
3344
|
+
targetUserId: value.targetUserId,
|
|
3345
|
+
targetRevision: value.targetRevision,
|
|
3346
|
+
globalRevision: value.globalRevision,
|
|
3347
|
+
roles: value.roles
|
|
3348
|
+
};
|
|
3349
|
+
return {
|
|
3350
|
+
status: "awaiting_second_approval",
|
|
3351
|
+
targetUserId: value.targetUserId,
|
|
3352
|
+
targetRevision: value.targetRevision,
|
|
3353
|
+
globalRevision: value.globalRevision,
|
|
3354
|
+
roles: []
|
|
3355
|
+
};
|
|
3356
|
+
}
|
|
3357
|
+
function normalizeRecentAuthenticationStatus(value) {
|
|
3358
|
+
exact(value, ["status"]);
|
|
3359
|
+
if (value.status !== "pending" && value.status !== "ready") throw authorityUnavailable();
|
|
3360
|
+
return { status: value.status };
|
|
3361
|
+
}
|
|
3362
|
+
function fromRecentAuthenticationError(value, status, retryAfter) {
|
|
3363
|
+
if (status === 400 || status === 413 || status === 415) return new AuthError("invalid_request", 400);
|
|
3364
|
+
if (status === 401) return new AuthError("authentication_required", 401);
|
|
3365
|
+
if (status === 403) return new AuthError("request_origin_denied", 403);
|
|
3366
|
+
if (status === 409) return new AuthError("state_conflict", 409);
|
|
3367
|
+
if (status === 429) return new AuthError("auth_request_rate_limited", 429, parseRetryAfter(retryAfter));
|
|
3368
|
+
return authorityUnavailable();
|
|
3369
|
+
}
|
|
3370
|
+
function fromPlatformAdministrationError(value, status, retryAfter) {
|
|
3371
|
+
const code = isObject(value) && typeof value.code === "string" ? value.code : void 0;
|
|
3372
|
+
if (status === 400 && code === "BAD_REQUEST") return new AuthError("invalid_request", 400);
|
|
3373
|
+
if (status === 401 && code === "UNAUTHORIZED") return new AuthError("authentication_required", 401);
|
|
3374
|
+
if (status === 403 && code === "FORBIDDEN") return new AuthError("access_denied", 403);
|
|
3375
|
+
if (status === 404 && code === "NOT_FOUND") return new AuthError("platform_target_unavailable", 404);
|
|
3376
|
+
if (status === 409 && code === "CONFLICT") return new AuthError("state_conflict", 409);
|
|
3377
|
+
if (status === 409 && code === "QUORUM_UNAVAILABLE") return new AuthError("platform_quorum_unavailable", 409);
|
|
3378
|
+
if (status === 413 && code === "REQUEST_TOO_LONG") return new AuthError("invalid_request", 413);
|
|
3379
|
+
if (status === 415 && code === "UNSUPPORTED_MEDIA_TYPE") return new AuthError("invalid_request", 415);
|
|
3380
|
+
if (status === 422 && code === "RECOVERY_REQUIRED") return new AuthError("platform_recovery_required", 422);
|
|
3381
|
+
if (status === 429 && code === "RATE_LIMITED") return new AuthError("auth_request_rate_limited", 429, parseRetryAfter(retryAfter));
|
|
3382
|
+
if (status === 503 && code === "SERVICE_UNAVAILABLE") return authorityUnavailable();
|
|
3383
|
+
return authorityUnavailable();
|
|
3384
|
+
}
|
|
2642
3385
|
function fromInvitationAuthorityError(value, status) {
|
|
2643
3386
|
const code = isObject(value) && typeof value.code === "string" ? value.code : void 0;
|
|
2644
3387
|
if (status === 401 && code === "UNAUTHORIZED") return new AuthError("authentication_required", 401);
|
|
@@ -3580,6 +4323,8 @@ function byteLength(value) {
|
|
|
3580
4323
|
//#endregion
|
|
3581
4324
|
//#region src/client.ts
|
|
3582
4325
|
const REFRESH_WINDOW_MS = 120 * 1e3;
|
|
4326
|
+
const RECENT_AUTHENTICATION_BROWSER_HEADER = "x-finueva-recent-auth-browser";
|
|
4327
|
+
const RECENT_AUTHENTICATION_REQUEST_HEADER = "x-finueva-recent-auth-request";
|
|
3583
4328
|
function createAuthClient(options) {
|
|
3584
4329
|
const authOrigin = normalizeOrigin(options.authOrigin, "authOrigin");
|
|
3585
4330
|
const appOrigin = normalizeOrigin(options.appOrigin, "appOrigin");
|
|
@@ -3596,7 +4341,8 @@ function createAuthClient(options) {
|
|
|
3596
4341
|
credentials: "include",
|
|
3597
4342
|
retry: 0,
|
|
3598
4343
|
timeout: timeoutMs
|
|
3599
|
-
}
|
|
4344
|
+
},
|
|
4345
|
+
plugins: [passkeyClient()]
|
|
3600
4346
|
});
|
|
3601
4347
|
const persistence = createSessionPersistence(options.persistence, authOrigin, now, timeoutMs);
|
|
3602
4348
|
const authStateListeners = new Set();
|
|
@@ -3608,6 +4354,9 @@ function createAuthClient(options) {
|
|
|
3608
4354
|
let selectedWorkspaceKey;
|
|
3609
4355
|
let credentialGeneration = 0;
|
|
3610
4356
|
let authorityGeneration = 0;
|
|
4357
|
+
let recentAuthenticationBrowserToken;
|
|
4358
|
+
let recentAuthenticationStartAttempt;
|
|
4359
|
+
let recentAuthenticationStartRequest;
|
|
3611
4360
|
let sessionReadEpoch = 0;
|
|
3612
4361
|
let activeAccountMutationGeneration;
|
|
3613
4362
|
let invitationAbort;
|
|
@@ -3621,6 +4370,10 @@ function createAuthClient(options) {
|
|
|
3621
4370
|
let signalRefreshPending = false;
|
|
3622
4371
|
let signalRefreshScheduled = false;
|
|
3623
4372
|
let signalRefreshRequest;
|
|
4373
|
+
function advanceAuthorityGeneration() {
|
|
4374
|
+
recentAuthenticationStartAttempt = void 0;
|
|
4375
|
+
return ++authorityGeneration;
|
|
4376
|
+
}
|
|
3624
4377
|
function setAuthState(state) {
|
|
3625
4378
|
authState = state;
|
|
3626
4379
|
for (const listener of authStateListeners) try {
|
|
@@ -3726,7 +4479,9 @@ function createAuthClient(options) {
|
|
|
3726
4479
|
}
|
|
3727
4480
|
function updateSession(session) {
|
|
3728
4481
|
const unboundCredential = !sessionId && (credential || credentialRequests.size > 0);
|
|
4482
|
+
const changedSession = !session || sessionId !== void 0 && (session.id !== sessionId || session.user.id !== sessionUserId);
|
|
3729
4483
|
if (!session || unboundCredential || sessionId && (session.id !== sessionId || session.user.id !== sessionUserId)) clearCredential();
|
|
4484
|
+
if (changedSession || recentAuthenticationStartAttempt && (recentAuthenticationStartAttempt.sessionId !== session?.id || recentAuthenticationStartAttempt.userId !== session?.user.id)) recentAuthenticationStartAttempt = void 0;
|
|
3730
4485
|
sessionId = session?.id;
|
|
3731
4486
|
sessionUserId = session?.user.id;
|
|
3732
4487
|
return session;
|
|
@@ -3755,10 +4510,10 @@ function createAuthClient(options) {
|
|
|
3755
4510
|
}
|
|
3756
4511
|
function signOut() {
|
|
3757
4512
|
if (signOutRequest) return signOutRequest;
|
|
3758
|
-
if (activeAccountMutationGeneration !== void 0) return Promise.reject(new AuthError("state_conflict", 409));
|
|
4513
|
+
if (activeAccountMutationGeneration !== void 0 || recentAuthenticationStartRequest) return Promise.reject(new AuthError("state_conflict", 409));
|
|
3759
4514
|
const pendingInvitation = invitationRequest;
|
|
3760
4515
|
invitationAbort?.abort();
|
|
3761
|
-
const generation =
|
|
4516
|
+
const generation = advanceAuthorityGeneration();
|
|
3762
4517
|
sessionReadEpoch += 1;
|
|
3763
4518
|
clearCredential();
|
|
3764
4519
|
sessionId = void 0;
|
|
@@ -3803,10 +4558,10 @@ function createAuthClient(options) {
|
|
|
3803
4558
|
return request;
|
|
3804
4559
|
}
|
|
3805
4560
|
async function runAccountMutation(operation) {
|
|
3806
|
-
if (signOutRequest || activeAccountMutationGeneration !== void 0) throw new AuthError("state_conflict", 409);
|
|
4561
|
+
if (signOutRequest || activeAccountMutationGeneration !== void 0 || recentAuthenticationStartRequest) throw new AuthError("state_conflict", 409);
|
|
3807
4562
|
const pendingInvitation = invitationRequest;
|
|
3808
4563
|
invitationAbort?.abort();
|
|
3809
|
-
const generation =
|
|
4564
|
+
const generation = advanceAuthorityGeneration();
|
|
3810
4565
|
sessionReadEpoch += 1;
|
|
3811
4566
|
activeAccountMutationGeneration = generation;
|
|
3812
4567
|
clearCredential();
|
|
@@ -3873,8 +4628,101 @@ function createAuthClient(options) {
|
|
|
3873
4628
|
throw error;
|
|
3874
4629
|
}
|
|
3875
4630
|
}
|
|
4631
|
+
function assertRecentAuthenticationOrigin() {
|
|
4632
|
+
if (appOrigin !== authOrigin) throw new TypeError("recent authentication requires the exact Auth Origin");
|
|
4633
|
+
}
|
|
3876
4634
|
return {
|
|
3877
4635
|
_type: "browser",
|
|
4636
|
+
async startPlatformRoleChangeRecentAuthentication(input) {
|
|
4637
|
+
assertRecentAuthenticationOrigin();
|
|
4638
|
+
const body = normalizePlatformRoleChangeRecentAuthenticationInput(input);
|
|
4639
|
+
if (signOutRequest || activeAccountMutationGeneration !== void 0 || invitationRequest) throw new AuthError("state_conflict", 409);
|
|
4640
|
+
if (recentAuthenticationStartRequest) throw new AuthError("state_conflict", 409);
|
|
4641
|
+
const generation = authorityGeneration;
|
|
4642
|
+
recentAuthenticationBrowserToken ??= createOpaqueToken();
|
|
4643
|
+
const normalizedInput = JSON.stringify(body);
|
|
4644
|
+
const attempt = recentAuthenticationStartAttempt?.input === normalizedInput ? recentAuthenticationStartAttempt : {
|
|
4645
|
+
input: normalizedInput,
|
|
4646
|
+
requestToken: createOpaqueToken(),
|
|
4647
|
+
sessionId,
|
|
4648
|
+
userId: sessionUserId
|
|
4649
|
+
};
|
|
4650
|
+
recentAuthenticationStartAttempt = attempt;
|
|
4651
|
+
const request = (async () => {
|
|
4652
|
+
try {
|
|
4653
|
+
const value = await requestJson(joinURL(authOrigin, "api/v1/auth/recent-authentication"), {
|
|
4654
|
+
fetch: fetchImpl,
|
|
4655
|
+
timeoutMs,
|
|
4656
|
+
method: "POST",
|
|
4657
|
+
headers: {
|
|
4658
|
+
"content-type": "application/json",
|
|
4659
|
+
[RECENT_AUTHENTICATION_BROWSER_HEADER]: recentAuthenticationBrowserToken,
|
|
4660
|
+
[RECENT_AUTHENTICATION_REQUEST_HEADER]: attempt.requestToken
|
|
4661
|
+
},
|
|
4662
|
+
body,
|
|
4663
|
+
mapError: fromRecentAuthenticationError
|
|
4664
|
+
});
|
|
4665
|
+
if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
|
|
4666
|
+
const status = normalizeRecentAuthenticationStatus(value);
|
|
4667
|
+
if (recentAuthenticationStartAttempt === attempt) recentAuthenticationStartAttempt = void 0;
|
|
4668
|
+
return status;
|
|
4669
|
+
} catch (error) {
|
|
4670
|
+
if (recentAuthenticationStartAttempt === attempt && (!(error instanceof AuthError) || error.code !== "identity_authority_unavailable")) recentAuthenticationStartAttempt = void 0;
|
|
4671
|
+
throw error;
|
|
4672
|
+
}
|
|
4673
|
+
})();
|
|
4674
|
+
recentAuthenticationStartRequest = request;
|
|
4675
|
+
try {
|
|
4676
|
+
return await request;
|
|
4677
|
+
} finally {
|
|
4678
|
+
if (recentAuthenticationStartRequest === request) recentAuthenticationStartRequest = void 0;
|
|
4679
|
+
}
|
|
4680
|
+
},
|
|
4681
|
+
async approveBootstrapPlatformRoleChange(input) {
|
|
4682
|
+
assertRecentAuthenticationOrigin();
|
|
4683
|
+
if (signOutRequest || activeAccountMutationGeneration !== void 0 || invitationRequest) throw new AuthError("state_conflict", 409);
|
|
4684
|
+
const body = normalizePlatformBootstrapRoleChangeInput(input);
|
|
4685
|
+
const generation = authorityGeneration;
|
|
4686
|
+
const value = await requestJson(joinURL(authOrigin, "api/v1/auth/platform/role-approvals"), {
|
|
4687
|
+
fetch: fetchImpl,
|
|
4688
|
+
timeoutMs,
|
|
4689
|
+
method: "POST",
|
|
4690
|
+
headers: { "content-type": "application/json" },
|
|
4691
|
+
body,
|
|
4692
|
+
mapError: fromPlatformAdministrationError
|
|
4693
|
+
});
|
|
4694
|
+
if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
|
|
4695
|
+
return normalizePlatformRoleApproval(value);
|
|
4696
|
+
},
|
|
4697
|
+
async verifyRecentAuthenticationWithPasskey() {
|
|
4698
|
+
assertRecentAuthenticationOrigin();
|
|
4699
|
+
if (!(await runAccountMutation(async () => {
|
|
4700
|
+
const verification = await betterAuth.signIn.passkey({ autoFill: false });
|
|
4701
|
+
if (verification.error) throw fromRecentAuthenticationError(void 0, verification.error.status);
|
|
4702
|
+
})).session) throw new AuthError("authentication_required", 401);
|
|
4703
|
+
},
|
|
4704
|
+
async addPasskey() {
|
|
4705
|
+
assertRecentAuthenticationOrigin();
|
|
4706
|
+
if (!(await runAccountMutation(async () => {
|
|
4707
|
+
const registration = await betterAuth.passkey.addPasskey({ context: createOpaqueToken() });
|
|
4708
|
+
if (registration.error) throw fromAccountAuthorityError(registration.error, registration.error.status);
|
|
4709
|
+
})).session) throw new AuthError("authentication_required", 401);
|
|
4710
|
+
},
|
|
4711
|
+
async getRecentAuthenticationStatus() {
|
|
4712
|
+
assertRecentAuthenticationOrigin();
|
|
4713
|
+
if (signOutRequest || activeAccountMutationGeneration !== void 0 || invitationRequest) throw new AuthError("state_conflict", 409);
|
|
4714
|
+
const generation = authorityGeneration;
|
|
4715
|
+
const value = await requestJson(joinURL(authOrigin, "api/v1/auth/recent-authentication/status"), {
|
|
4716
|
+
fetch: fetchImpl,
|
|
4717
|
+
timeoutMs,
|
|
4718
|
+
method: "POST",
|
|
4719
|
+
headers: { "content-type": "application/json" },
|
|
4720
|
+
body: {},
|
|
4721
|
+
mapError: fromRecentAuthenticationError
|
|
4722
|
+
});
|
|
4723
|
+
if (generation !== authorityGeneration) throw new AuthError("state_conflict", 409);
|
|
4724
|
+
return normalizeRecentAuthenticationStatus(value);
|
|
4725
|
+
},
|
|
3878
4726
|
acceptInvitation(input) {
|
|
3879
4727
|
let invitation;
|
|
3880
4728
|
try {
|
|
@@ -3882,8 +4730,8 @@ function createAuthClient(options) {
|
|
|
3882
4730
|
} catch (error) {
|
|
3883
4731
|
return Promise.reject(error);
|
|
3884
4732
|
}
|
|
3885
|
-
if (signOutRequest || activeAccountMutationGeneration !== void 0 || invitationRequest) return Promise.reject(new AuthError("state_conflict", 409));
|
|
3886
|
-
const generation =
|
|
4733
|
+
if (signOutRequest || activeAccountMutationGeneration !== void 0 || invitationRequest || recentAuthenticationStartRequest) return Promise.reject(new AuthError("state_conflict", 409));
|
|
4734
|
+
const generation = advanceAuthorityGeneration();
|
|
3887
4735
|
const controller = new AbortController();
|
|
3888
4736
|
clearCredential();
|
|
3889
4737
|
const request = (async () => {
|
|
@@ -4138,5 +4986,12 @@ function createAuthClient(options) {
|
|
|
4138
4986
|
}
|
|
4139
4987
|
};
|
|
4140
4988
|
}
|
|
4989
|
+
function createOpaqueToken() {
|
|
4990
|
+
const bytes = new Uint8Array(32);
|
|
4991
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
4992
|
+
let binary = "";
|
|
4993
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
4994
|
+
return globalThis.btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
4995
|
+
}
|
|
4141
4996
|
//#endregion
|
|
4142
4997
|
export { AuthError, createAuthClient };
|
package/dist/server.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { B as SocialIdTokenInput, C as OrganizationTeam, D as OrganizationWorkspaceSelection, E as OrganizationTeamPage, F as ReceivedOrganizationInvitation, G as WorkspaceSelection, H as UpdateOrganizationDisplayInput, I as ReceivedOrganizationInvitationPage, J as AuthErrorStatus, K as AuthError, O as PersonalWorkspaceSelection, S as OrganizationPageInput, T as OrganizationTeamMemberPage, U as UpdateOrganizationTeamDisplayInput, V as SocialIdTokenProvider, _ as OrganizationInvitation, b as OrganizationMemberPage, c as AuthUser, d as CreatedOrganizationTeam, f as DriveCredential, g as OrganizationDisplay, h as InvitationAcceptance, l as CreateOrganizationMemberInvitationInput, o as AuthSession, p as DriveCredentialInput, q as AuthErrorCode, r as AuthOrganization, t as AcceptInvitationInput, u as CreatedOrganizationMemberInvitation, v as OrganizationInvitationPage, w as OrganizationTeamDisplay, x as OrganizationPage, y as OrganizationMember } from "./types-BqpgLkQB.js";
|
|
2
2
|
//#region src/server.d.ts
|
|
3
3
|
interface ServerAuthOptions {
|
|
4
4
|
authOrigin: string;
|
package/dist/server.js
CHANGED
|
@@ -192,6 +192,9 @@ const messages = {
|
|
|
192
192
|
auth_request_rate_limited: "Authentication requests are temporarily limited.",
|
|
193
193
|
state_conflict: "The authentication state conflicts with the request.",
|
|
194
194
|
access_denied: "Access is denied.",
|
|
195
|
+
platform_target_unavailable: "The platform administration target is unavailable.",
|
|
196
|
+
platform_quorum_unavailable: "Platform administration quorum is unavailable.",
|
|
197
|
+
platform_recovery_required: "Platform administration requires separate recovery.",
|
|
195
198
|
identity_authority_unavailable: "Authentication is temporarily unavailable."
|
|
196
199
|
};
|
|
197
200
|
var AuthError = class extends Error {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/errors.d.ts
|
|
2
|
-
type AuthErrorCode = "invalid_credentials" | "invalid_auth_input" | "account_conflict" | "password_reset_invalid" | "authentication_required" | "request_origin_denied" | "invalid_request" | "invalid_invitation" | "invitation_unavailable" | "invitation_recipient_mismatch" | "email_verification_required" | "invalid_workspace_selection" | "organization_context_too_large" | "credential_rate_limited" | "auth_request_rate_limited" | "state_conflict" | "access_denied" | "identity_authority_unavailable";
|
|
3
|
-
type AuthErrorStatus = 400 | 401 | 403 | 409 | 422 | 429 | 503;
|
|
2
|
+
type AuthErrorCode = "invalid_credentials" | "invalid_auth_input" | "account_conflict" | "password_reset_invalid" | "authentication_required" | "request_origin_denied" | "invalid_request" | "invalid_invitation" | "invitation_unavailable" | "invitation_recipient_mismatch" | "email_verification_required" | "invalid_workspace_selection" | "organization_context_too_large" | "credential_rate_limited" | "auth_request_rate_limited" | "state_conflict" | "access_denied" | "platform_target_unavailable" | "platform_quorum_unavailable" | "platform_recovery_required" | "identity_authority_unavailable";
|
|
3
|
+
type AuthErrorStatus = 400 | 401 | 403 | 404 | 409 | 413 | 415 | 422 | 429 | 503;
|
|
4
4
|
declare class AuthError extends Error {
|
|
5
5
|
readonly code: AuthErrorCode;
|
|
6
6
|
readonly status: AuthErrorStatus;
|
|
@@ -193,6 +193,38 @@ interface DriveCredentialInput {
|
|
|
193
193
|
workspace: WorkspaceSelection;
|
|
194
194
|
forceRefresh?: boolean;
|
|
195
195
|
}
|
|
196
|
+
type PlatformRole = "administrator" | "investigator";
|
|
197
|
+
interface PlatformRoleChangeRecentAuthenticationInput {
|
|
198
|
+
targetUserId: string;
|
|
199
|
+
requestedRoles: PlatformRole[];
|
|
200
|
+
expectedTargetRevision: number;
|
|
201
|
+
expectedGlobalRevision: number;
|
|
202
|
+
reasonCategory: "initial_bootstrap" | "personnel_change" | "privilege_review" | "quorum_recovery" | "security_response";
|
|
203
|
+
changeReference: string;
|
|
204
|
+
idempotencyKey: string;
|
|
205
|
+
}
|
|
206
|
+
interface PlatformBootstrapRoleChangeInput extends Omit<PlatformRoleChangeRecentAuthenticationInput, "reasonCategory" | "requestedRoles"> {
|
|
207
|
+
requestedRoles: ["administrator"] | ["administrator", "investigator"] | ["investigator", "administrator"];
|
|
208
|
+
reasonCategory: "initial_bootstrap";
|
|
209
|
+
}
|
|
210
|
+
interface PlatformRoleFirstApproval {
|
|
211
|
+
status: "awaiting_second_approval";
|
|
212
|
+
targetUserId: string;
|
|
213
|
+
targetRevision: number;
|
|
214
|
+
globalRevision: number;
|
|
215
|
+
roles: [];
|
|
216
|
+
}
|
|
217
|
+
interface PlatformRoleApplied {
|
|
218
|
+
status: "applied";
|
|
219
|
+
targetUserId: string;
|
|
220
|
+
targetRevision: number;
|
|
221
|
+
globalRevision: number;
|
|
222
|
+
roles: PlatformRole[];
|
|
223
|
+
}
|
|
224
|
+
type PlatformRoleApproval = PlatformRoleFirstApproval | PlatformRoleApplied;
|
|
225
|
+
interface RecentAuthenticationStatus {
|
|
226
|
+
status: "pending" | "ready";
|
|
227
|
+
}
|
|
196
228
|
interface AuthClientOptions {
|
|
197
229
|
authOrigin: string;
|
|
198
230
|
appOrigin: string;
|
|
@@ -203,6 +235,11 @@ interface AuthClientOptions {
|
|
|
203
235
|
}
|
|
204
236
|
interface FinuevaAuthClient {
|
|
205
237
|
readonly _type: "browser";
|
|
238
|
+
startPlatformRoleChangeRecentAuthentication(input: PlatformRoleChangeRecentAuthenticationInput): Promise<RecentAuthenticationStatus>;
|
|
239
|
+
approveBootstrapPlatformRoleChange(input: PlatformBootstrapRoleChangeInput): Promise<PlatformRoleApproval>;
|
|
240
|
+
addPasskey(): Promise<void>;
|
|
241
|
+
verifyRecentAuthenticationWithPasskey(): Promise<void>;
|
|
242
|
+
getRecentAuthenticationStatus(): Promise<RecentAuthenticationStatus>;
|
|
206
243
|
acceptInvitation(input: AcceptInvitationInput): Promise<InvitationAcceptance>;
|
|
207
244
|
getSession(): Promise<AuthSession | null>;
|
|
208
245
|
listOrganizations(input?: OrganizationPageInput): Promise<OrganizationPage>;
|
|
@@ -232,4 +269,4 @@ interface FinuevaAuthClient {
|
|
|
232
269
|
onSessionChange(listener: (session: AuthSession | null) => void): () => void;
|
|
233
270
|
}
|
|
234
271
|
//#endregion
|
|
235
|
-
export {
|
|
272
|
+
export { PlatformRole as A, SocialIdTokenInput as B, OrganizationTeam as C, OrganizationWorkspaceSelection as D, OrganizationTeamPage as E, ReceivedOrganizationInvitation as F, WorkspaceSelection as G, UpdateOrganizationDisplayInput as H, ReceivedOrganizationInvitationPage as I, AuthErrorStatus as J, AuthError as K, RecentAuthenticationStatus as L, PlatformRoleApproval as M, PlatformRoleChangeRecentAuthenticationInput as N, PersonalWorkspaceSelection as O, PlatformRoleFirstApproval as P, SendVerificationEmailInput as R, OrganizationPageInput as S, OrganizationTeamMemberPage as T, UpdateOrganizationTeamDisplayInput as U, SocialIdTokenProvider as V, UpdateProfileInput as W, OrganizationInvitation as _, AuthPersistenceDriver as a, OrganizationMemberPage as b, AuthUser as c, CreatedOrganizationTeam as d, DriveCredential as f, OrganizationDisplay as g, InvitationAcceptance as h, AuthPersistence as i, PlatformRoleApplied as j, PlatformBootstrapRoleChangeInput as k, CreateOrganizationMemberInvitationInput as l, FinuevaAuthClient as m, AuthClientOptions as n, AuthSession as o, DriveCredentialInput as p, AuthErrorCode as q, AuthOrganization as r, AuthState as s, AcceptInvitationInput as t, CreatedOrganizationMemberInvitation as u, OrganizationInvitationPage as v, OrganizationTeamDisplay as w, OrganizationPage as x, OrganizationMember as y, SignInInput as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finueva/auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Public browser and server SDK for Finueva Auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -49,7 +49,9 @@
|
|
|
49
49
|
"typecheck": "tsc --noEmit"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@
|
|
52
|
+
"@better-auth/core": "1.6.22",
|
|
53
|
+
"@better-auth/passkey": "1.6.22",
|
|
54
|
+
"@chiballc/utils": "2.5.1",
|
|
53
55
|
"@types/node": "24.13.3",
|
|
54
56
|
"@typescript/native": "npm:typescript@7.0.2",
|
|
55
57
|
"@vitest/coverage-v8": "4.1.10",
|