@byline/admin 6.7.2 → 6.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/admin-account/components/change-password.js +1 -1
- package/dist/modules/admin-account/components/container.js +18 -3
- package/dist/modules/admin-account/components/container.module.js +2 -0
- package/dist/modules/admin-account/components/container_module.css +7 -0
- package/dist/modules/admin-account/components/preferences.js +1 -1
- package/dist/modules/admin-account/components/update.js +1 -1
- package/dist/modules/admin-roles/components/create.js +1 -1
- package/dist/modules/admin-roles/components/permissions.js +1 -1
- package/dist/modules/admin-roles/components/update.js +1 -1
- package/dist/modules/admin-users/abilities.d.ts +1 -0
- package/dist/modules/admin-users/abilities.js +8 -1
- package/dist/modules/admin-users/components/create.js +1 -1
- package/dist/modules/admin-users/components/roles.js +1 -1
- package/dist/modules/admin-users/components/set-password.js +1 -1
- package/dist/modules/admin-users/components/update.js +1 -1
- package/dist/modules/auth/auth-maintenance-repository.d.ts +31 -0
- package/dist/modules/auth/auth-maintenance-repository.js +1 -0
- package/dist/modules/auth/auth-maintenance.d.ts +11 -0
- package/dist/modules/auth/auth-maintenance.js +39 -0
- package/dist/modules/auth/auth-maintenance.test.node.d.ts +1 -0
- package/dist/modules/auth/components/passkey-browser.d.ts +14 -0
- package/dist/modules/auth/components/passkey-browser.js +118 -0
- package/dist/modules/auth/components/passkey-browser.test.node.d.ts +1 -0
- package/dist/modules/auth/components/passkey-manager.d.ts +26 -0
- package/dist/modules/auth/components/passkey-manager.js +406 -0
- package/dist/modules/auth/components/passkey-manager.module.js +22 -0
- package/dist/modules/auth/components/passkey-manager_module.css +104 -0
- package/dist/modules/auth/components/sign-in-form.js +62 -3
- package/dist/modules/auth/components/sign-in-form.module.js +5 -1
- package/dist/modules/auth/components/sign-in-form_module.css +40 -0
- package/dist/modules/auth/index.d.ts +5 -0
- package/dist/modules/auth/index.js +3 -0
- package/dist/modules/auth/jwt-session-provider.d.ts +12 -4
- package/dist/modules/auth/jwt-session-provider.js +135 -94
- package/dist/modules/auth/native-passkey-provider.d.ts +72 -0
- package/dist/modules/auth/native-passkey-provider.js +618 -0
- package/dist/modules/auth/passkey-rate-limiter.d.ts +14 -0
- package/dist/modules/auth/passkey-rate-limiter.js +60 -0
- package/dist/modules/auth/passkey-rate-limiter.test.node.d.ts +8 -0
- package/dist/modules/auth/passkey-storage-validation.d.ts +14 -0
- package/dist/modules/auth/passkey-storage-validation.js +59 -0
- package/dist/modules/auth/passkeys-repository.d.ts +96 -0
- package/dist/modules/auth/passkeys-repository.js +1 -0
- package/dist/modules/auth/refresh-tokens-repository.d.ts +1 -1
- package/dist/modules/auth/sign-in-rate-limiter.d.ts +2 -0
- package/dist/modules/auth/sign-in-rate-limiter.js +1 -1
- package/dist/services/admin-services-context.d.ts +1 -1
- package/dist/services/admin-services-types.d.ts +60 -0
- package/dist/store.d.ts +4 -0
- package/package.json +16 -9
- package/src/modules/admin-account/components/change-password.tsx +1 -1
- package/src/modules/admin-account/components/container.module.css +9 -0
- package/src/modules/admin-account/components/container.tsx +12 -3
- package/src/modules/admin-account/components/preferences.tsx +1 -1
- package/src/modules/admin-account/components/update.tsx +1 -1
- package/src/modules/admin-roles/components/create.tsx +1 -1
- package/src/modules/admin-roles/components/permissions.tsx +1 -1
- package/src/modules/admin-roles/components/update.tsx +1 -1
- package/src/modules/admin-users/abilities.ts +7 -0
- package/src/modules/admin-users/components/create.tsx +1 -1
- package/src/modules/admin-users/components/roles.tsx +1 -1
- package/src/modules/admin-users/components/set-password.tsx +1 -1
- package/src/modules/admin-users/components/update.tsx +1 -1
- package/src/modules/auth/auth-maintenance-repository.ts +26 -0
- package/src/modules/auth/auth-maintenance.test.node.ts +110 -0
- package/src/modules/auth/auth-maintenance.ts +62 -0
- package/src/modules/auth/components/passkey-browser.test.node.ts +188 -0
- package/src/modules/auth/components/passkey-browser.ts +139 -0
- package/src/modules/auth/components/passkey-manager.module.css +100 -0
- package/src/modules/auth/components/passkey-manager.test.tsx +189 -0
- package/src/modules/auth/components/passkey-manager.tsx +386 -0
- package/src/modules/auth/components/sign-in-form.module.css +37 -0
- package/src/modules/auth/components/sign-in-form.test.tsx +92 -0
- package/src/modules/auth/components/sign-in-form.tsx +65 -4
- package/src/modules/auth/index.ts +15 -0
- package/src/modules/auth/jwt-session-provider.ts +116 -44
- package/src/modules/auth/native-passkey-provider.ts +710 -0
- package/src/modules/auth/passkey-rate-limiter.test.node.ts +83 -0
- package/src/modules/auth/passkey-rate-limiter.ts +76 -0
- package/src/modules/auth/passkey-storage-validation.ts +108 -0
- package/src/modules/auth/passkeys-repository.ts +97 -0
- package/src/modules/auth/refresh-tokens-repository.ts +1 -1
- package/src/modules/auth/sign-in-rate-limiter.ts +1 -1
- package/src/services/admin-services-context.tsx +2 -0
- package/src/services/admin-services-types.ts +60 -0
- package/src/store.ts +4 -0
|
@@ -182,7 +182,7 @@ function ChangeAccountPassword({ account, onClose, onSuccess }) {
|
|
|
182
182
|
children: [
|
|
183
183
|
/*#__PURE__*/ jsx(Button, {
|
|
184
184
|
type: "button",
|
|
185
|
-
intent: "
|
|
185
|
+
intent: "noeffect",
|
|
186
186
|
size: "sm",
|
|
187
187
|
onClick: onClose,
|
|
188
188
|
className: clsx('byline-account-change-password-action', change_password_module.action),
|
|
@@ -5,6 +5,7 @@ import { LocalDateTime } from "@byline/admin/react";
|
|
|
5
5
|
import { useTranslation } from "@byline/i18n/react";
|
|
6
6
|
import { CloseIcon, Drawer, EditIcon, IconButton } from "@byline/ui/react";
|
|
7
7
|
import clsx from "clsx";
|
|
8
|
+
import { PasskeyManager } from "../../auth/components/passkey-manager.js";
|
|
8
9
|
import { ChangeAccountPassword } from "./change-password.js";
|
|
9
10
|
import container_module from "./container.module.js";
|
|
10
11
|
import { Preferences } from "./preferences.js";
|
|
@@ -208,6 +209,19 @@ function AccountSelfContainer({ account }) {
|
|
|
208
209
|
/*#__PURE__*/ jsx("p", {
|
|
209
210
|
className: clsx('muted', 'byline-account-status-help', container_module["status-help"]),
|
|
210
211
|
children: t('account.preferences.help')
|
|
212
|
+
}),
|
|
213
|
+
/*#__PURE__*/ jsxs("div", {
|
|
214
|
+
className: clsx('byline-account-preferences-theme', container_module["preferences-theme"]),
|
|
215
|
+
children: [
|
|
216
|
+
/*#__PURE__*/ jsx("p", {
|
|
217
|
+
className: clsx('byline-account-line', container_module.line),
|
|
218
|
+
children: /*#__PURE__*/ jsx("span", {
|
|
219
|
+
className: "muted",
|
|
220
|
+
children: t('account.appearance.colorTheme')
|
|
221
|
+
})
|
|
222
|
+
}),
|
|
223
|
+
/*#__PURE__*/ jsx(ThemeSwitch, {})
|
|
224
|
+
]
|
|
211
225
|
})
|
|
212
226
|
]
|
|
213
227
|
})
|
|
@@ -225,9 +239,10 @@ function AccountSelfContainer({ account }) {
|
|
|
225
239
|
children: t('account.password.intro')
|
|
226
240
|
})
|
|
227
241
|
}),
|
|
228
|
-
/*#__PURE__*/ jsx(
|
|
229
|
-
|
|
230
|
-
|
|
242
|
+
/*#__PURE__*/ jsx(PasskeyManager, {
|
|
243
|
+
targetUserId: currentAccount.id,
|
|
244
|
+
targetIsSuperAdmin: currentAccount.is_super_admin,
|
|
245
|
+
section: ContainerSection
|
|
231
246
|
}),
|
|
232
247
|
/*#__PURE__*/ jsxs(ContainerSection, {
|
|
233
248
|
title: t('account.sections.status'),
|
|
@@ -15,6 +15,8 @@ const container_module = {
|
|
|
15
15
|
statusOff: "status-off-WJAcBy",
|
|
16
16
|
"status-help": "status-help-GVOlMV",
|
|
17
17
|
statusHelp: "status-help-GVOlMV",
|
|
18
|
+
"preferences-theme": "preferences-theme-SHTZmr",
|
|
19
|
+
preferencesTheme: "preferences-theme-SHTZmr",
|
|
18
20
|
"not-set": "not-set-Ek2yXe",
|
|
19
21
|
notSet: "not-set-Ek2yXe",
|
|
20
22
|
drawer: "drawer-dDlpic",
|
|
@@ -60,6 +60,13 @@
|
|
|
60
60
|
margin-bottom: 0;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
:is(.preferences-theme-SHTZmr, .byline-account-preferences-theme) {
|
|
64
|
+
gap: var(--spacing-8);
|
|
65
|
+
margin-top: var(--spacing-20);
|
|
66
|
+
flex-direction: column;
|
|
67
|
+
display: flex;
|
|
68
|
+
}
|
|
69
|
+
|
|
63
70
|
:is(.not-set-Ek2yXe, .byline-account-not-set) {
|
|
64
71
|
font-style: italic;
|
|
65
72
|
}
|
|
@@ -124,7 +124,7 @@ function Preferences({ account, onClose, onSuccess }) {
|
|
|
124
124
|
children: [
|
|
125
125
|
/*#__PURE__*/ jsx(Button, {
|
|
126
126
|
type: "button",
|
|
127
|
-
intent: "
|
|
127
|
+
intent: "noeffect",
|
|
128
128
|
size: "sm",
|
|
129
129
|
onClick: onClose,
|
|
130
130
|
className: clsx('byline-account-preferences-action', preferences_module.action),
|
|
@@ -183,7 +183,7 @@ function UpdateAccount({ account, onClose, onSuccess }) {
|
|
|
183
183
|
children: [
|
|
184
184
|
/*#__PURE__*/ jsx(Button, {
|
|
185
185
|
type: "button",
|
|
186
|
-
intent: "
|
|
186
|
+
intent: "noeffect",
|
|
187
187
|
size: "sm",
|
|
188
188
|
onClick: onClose,
|
|
189
189
|
className: clsx('byline-account-update-action', update_module.action),
|
|
@@ -144,7 +144,7 @@ function CreateAdminRole({ onClose, onSuccess }) {
|
|
|
144
144
|
children: [
|
|
145
145
|
/*#__PURE__*/ jsx(Button, {
|
|
146
146
|
type: "button",
|
|
147
|
-
intent: "
|
|
147
|
+
intent: "noeffect",
|
|
148
148
|
size: "sm",
|
|
149
149
|
onClick: onClose,
|
|
150
150
|
className: clsx('byline-role-create-action', create_module.action),
|
|
@@ -207,7 +207,7 @@ function RolePermissions({ role, registered, initialAbilities, onSaved }) {
|
|
|
207
207
|
children: [
|
|
208
208
|
/*#__PURE__*/ jsx(Button, {
|
|
209
209
|
type: "button",
|
|
210
|
-
intent: "
|
|
210
|
+
intent: "noeffect",
|
|
211
211
|
size: "xs",
|
|
212
212
|
onClick: handleCancel,
|
|
213
213
|
disabled: saving,
|
|
@@ -134,7 +134,7 @@ function UpdateRole({ role, onClose, onSuccess }) {
|
|
|
134
134
|
children: [
|
|
135
135
|
/*#__PURE__*/ jsx(Button, {
|
|
136
136
|
type: "button",
|
|
137
|
-
intent: "
|
|
137
|
+
intent: "noeffect",
|
|
138
138
|
size: "sm",
|
|
139
139
|
onClick: onClose,
|
|
140
140
|
className: clsx('byline-role-update-action', update_module.action),
|
|
@@ -30,6 +30,7 @@ export declare const ADMIN_USERS_ABILITIES: {
|
|
|
30
30
|
readonly update: 'admin.users.update';
|
|
31
31
|
readonly delete: 'admin.users.delete';
|
|
32
32
|
readonly changePassword: 'admin.users.changePassword';
|
|
33
|
+
readonly managePasskeys: 'admin.users.managePasskeys';
|
|
33
34
|
};
|
|
34
35
|
export type AdminUsersAbilityKey = (typeof ADMIN_USERS_ABILITIES)[keyof typeof ADMIN_USERS_ABILITIES];
|
|
35
36
|
/**
|
|
@@ -3,9 +3,16 @@ const ADMIN_USERS_ABILITIES = {
|
|
|
3
3
|
create: 'admin.users.create',
|
|
4
4
|
update: 'admin.users.update',
|
|
5
5
|
delete: 'admin.users.delete',
|
|
6
|
-
changePassword: 'admin.users.changePassword'
|
|
6
|
+
changePassword: 'admin.users.changePassword',
|
|
7
|
+
managePasskeys: 'admin.users.managePasskeys'
|
|
7
8
|
};
|
|
8
9
|
function registerAdminUsersAbilities(registry) {
|
|
10
|
+
registry.register({
|
|
11
|
+
key: ADMIN_USERS_ABILITIES.managePasskeys,
|
|
12
|
+
label: 'Manage admin user passkeys',
|
|
13
|
+
group: 'admin.users',
|
|
14
|
+
source: 'admin'
|
|
15
|
+
});
|
|
9
16
|
registry.register({
|
|
10
17
|
key: ADMIN_USERS_ABILITIES.read,
|
|
11
18
|
label: 'Read admin users',
|
|
@@ -239,7 +239,7 @@ function CreateAdminUser({ onClose, onSuccess }) {
|
|
|
239
239
|
children: [
|
|
240
240
|
/*#__PURE__*/ jsx(Button, {
|
|
241
241
|
type: "button",
|
|
242
|
-
intent: "
|
|
242
|
+
intent: "noeffect",
|
|
243
243
|
size: "sm",
|
|
244
244
|
onClick: onClose,
|
|
245
245
|
className: clsx('byline-user-create-action', create_module.action),
|
|
@@ -130,7 +130,7 @@ function SetPassword({ user, onClose, onSuccess }) {
|
|
|
130
130
|
children: [
|
|
131
131
|
/*#__PURE__*/ jsx(Button, {
|
|
132
132
|
type: "button",
|
|
133
|
-
intent: "
|
|
133
|
+
intent: "noeffect",
|
|
134
134
|
size: "sm",
|
|
135
135
|
onClick: onClose,
|
|
136
136
|
className: clsx('byline-user-set-password-action', set_password_module.action),
|
|
@@ -230,7 +230,7 @@ function UpdateUser({ user, onClose, onSuccess }) {
|
|
|
230
230
|
children: [
|
|
231
231
|
/*#__PURE__*/ jsx(Button, {
|
|
232
232
|
type: "button",
|
|
233
|
-
intent: "
|
|
233
|
+
intent: "noeffect",
|
|
234
234
|
size: "sm",
|
|
235
235
|
onClick: onClose,
|
|
236
236
|
className: clsx('byline-user-update-action', update_module.action),
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
/** Bounded housekeeping; expiry and revocation remain independent authorization checks. */
|
|
9
|
+
export interface AuthMaintenanceRepository {
|
|
10
|
+
/** Remove at most 100 attempts expired or consumed on/before the supplied grace cutoff. */
|
|
11
|
+
purgeAttempts(before: Date): Promise<number>;
|
|
12
|
+
/** Candidate only: eligibility must be checked again under its owning account lock. */
|
|
13
|
+
findTerminalLogin(before: Date): Promise<{
|
|
14
|
+
id: string;
|
|
15
|
+
adminUserId: string;
|
|
16
|
+
} | null>;
|
|
17
|
+
/**
|
|
18
|
+
* Requires the owning account lock. Re-read eligibility, delete at most 100 refresh
|
|
19
|
+
* children, then delete the terminal parent only if no children remain. Never cascade.
|
|
20
|
+
*/
|
|
21
|
+
pruneTerminalLogin(input: {
|
|
22
|
+
id: string;
|
|
23
|
+
adminUserId: string;
|
|
24
|
+
before: Date;
|
|
25
|
+
}): Promise<{
|
|
26
|
+
refreshDeleted: number;
|
|
27
|
+
loginDeleted: boolean;
|
|
28
|
+
}>;
|
|
29
|
+
/** Remove at most 100 legacy null-sid rows expired on/before the retention cutoff. */
|
|
30
|
+
purgeLegacyRefreshTokens(before: Date): Promise<number>;
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
import type { RecurringTaskDefinition } from '@byline/core';
|
|
9
|
+
import type { AdminStore } from '../../store.js';
|
|
10
|
+
/** Register for native sessions even on password-only sites; configuration never runs cleanup. */
|
|
11
|
+
export declare function createAuthMaintenanceTasks(store: AdminStore, now?: () => Date): RecurringTaskDefinition[];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const DAY = 86400000;
|
|
2
|
+
function createAuthMaintenanceTasks(store, now = ()=>new Date()) {
|
|
3
|
+
const task = (name, retention, batch)=>({
|
|
4
|
+
name: `auth.maintenance.${name}`,
|
|
5
|
+
intervalMs: 60000,
|
|
6
|
+
leaseMs: 60000,
|
|
7
|
+
async run (context) {
|
|
8
|
+
const started = performance.now();
|
|
9
|
+
const cutoff = new Date(now().getTime() - retention);
|
|
10
|
+
for(let n = 0; n < 32 && performance.now() - started < 30000; n++){
|
|
11
|
+
context.signal.throwIfAborted();
|
|
12
|
+
await context.heartbeat();
|
|
13
|
+
context.signal.throwIfAborted();
|
|
14
|
+
if (!await batch(cutoff)) return {
|
|
15
|
+
workRemaining: false
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
workRemaining: true
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
return [
|
|
24
|
+
task('attempts', 300000, async (cutoff)=>await store.authMaintenance.purgeAttempts(cutoff) === 100),
|
|
25
|
+
task('logins', 30 * DAY, async (cutoff)=>{
|
|
26
|
+
const candidate = await store.authMaintenance.findTerminalLogin(cutoff);
|
|
27
|
+
if (!candidate) return false;
|
|
28
|
+
await store.withSessionLock(candidate.adminUserId, async (scoped)=>{
|
|
29
|
+
await scoped.authMaintenance.pruneTerminalLogin({
|
|
30
|
+
...candidate,
|
|
31
|
+
before: cutoff
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
return true;
|
|
35
|
+
}),
|
|
36
|
+
task('legacy-refresh', 30 * DAY, async (cutoff)=>await store.authMaintenance.purgeLegacyRefreshTokens(cutoff) === 100)
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
export { createAuthMaintenanceTasks };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
import type { PasskeyManagementAction } from '@byline/auth';
|
|
9
|
+
import type { PasskeyAdminServices, PasskeyPageBinding } from '../../../services/admin-services-types.js';
|
|
10
|
+
export declare function signInWithPasskey(services: PasskeyAdminServices, isActive?: () => boolean): Promise<unknown>;
|
|
11
|
+
export declare function provePasskeyAction(services: PasskeyAdminServices, binding: PasskeyPageBinding, action: PasskeyManagementAction, password?: string): Promise<import("@byline/auth").PasskeyManagementGrant>;
|
|
12
|
+
export declare function enrollPasskey(services: PasskeyAdminServices, binding: PasskeyPageBinding, name: string, password?: string): Promise<import("@byline/auth").PasskeyCredentialSummary>;
|
|
13
|
+
/** Render fixed translated messages, never provider payloads or authenticator identifiers. */
|
|
14
|
+
export declare function passkeyErrorKey(error: unknown): string;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { browserSupportsWebAuthn, startAuthentication, startRegistration } from "@simplewebauthn/browser";
|
|
2
|
+
function ready(binding, expiresAt) {
|
|
3
|
+
binding.assertCurrent();
|
|
4
|
+
if (expiresAt && Date.parse(expiresAt) <= Date.now()) throw Object.assign(new Error('Expired ceremony'), {
|
|
5
|
+
code: 'ERR_INVALID_TOKEN'
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
function supported() {
|
|
9
|
+
if (!browserSupportsWebAuthn()) throw Object.assign(new Error('WebAuthn unavailable'), {
|
|
10
|
+
code: 'PASSKEY_UNSUPPORTED'
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
async function signInWithPasskey(services, isActive = ()=>true) {
|
|
14
|
+
supported();
|
|
15
|
+
const captured = services.captureLogin(true);
|
|
16
|
+
const binding = {
|
|
17
|
+
...captured,
|
|
18
|
+
assertCurrent: ()=>{
|
|
19
|
+
if (!isActive()) throw new Error('Sign-in page closed');
|
|
20
|
+
captured.assertCurrent();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const ceremony = await services.signInOptions({
|
|
24
|
+
data: {}
|
|
25
|
+
});
|
|
26
|
+
ready(binding, ceremony.expiresAt);
|
|
27
|
+
const response = await startAuthentication({
|
|
28
|
+
optionsJSON: ceremony.options
|
|
29
|
+
});
|
|
30
|
+
ready(binding, ceremony.expiresAt);
|
|
31
|
+
return services.signIn({
|
|
32
|
+
data: {
|
|
33
|
+
ceremonyId: ceremony.ceremonyId,
|
|
34
|
+
response
|
|
35
|
+
}
|
|
36
|
+
}, binding);
|
|
37
|
+
}
|
|
38
|
+
async function provePasskeyAction(services, binding, action, password) {
|
|
39
|
+
ready(binding);
|
|
40
|
+
const expectedSessionId = binding.expectedSessionId;
|
|
41
|
+
if (!expectedSessionId) throw Object.assign(new Error('Login changed'), {
|
|
42
|
+
code: 'ERR_SESSION_CHANGED'
|
|
43
|
+
});
|
|
44
|
+
if (void 0 !== password) {
|
|
45
|
+
const grant = await services.passwordProof({
|
|
46
|
+
data: {
|
|
47
|
+
expectedSessionId,
|
|
48
|
+
action,
|
|
49
|
+
password
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
ready(binding, grant.expiresAt);
|
|
53
|
+
return grant;
|
|
54
|
+
}
|
|
55
|
+
supported();
|
|
56
|
+
const ceremony = await services.proofOptions({
|
|
57
|
+
data: {
|
|
58
|
+
expectedSessionId,
|
|
59
|
+
action
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
ready(binding, ceremony.expiresAt);
|
|
63
|
+
const response = await startAuthentication({
|
|
64
|
+
optionsJSON: ceremony.options
|
|
65
|
+
});
|
|
66
|
+
ready(binding, ceremony.expiresAt);
|
|
67
|
+
const grant = await services.proof({
|
|
68
|
+
data: {
|
|
69
|
+
expectedSessionId,
|
|
70
|
+
ceremonyId: ceremony.ceremonyId,
|
|
71
|
+
response
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
ready(binding, grant.expiresAt);
|
|
75
|
+
return grant;
|
|
76
|
+
}
|
|
77
|
+
async function enrollPasskey(services, binding, name, password) {
|
|
78
|
+
supported();
|
|
79
|
+
const grant = await provePasskeyAction(services, binding, {
|
|
80
|
+
purpose: 'enroll'
|
|
81
|
+
}, password);
|
|
82
|
+
const expectedSessionId = binding.expectedSessionId;
|
|
83
|
+
const ceremony = await services.enrollmentOptions({
|
|
84
|
+
data: {
|
|
85
|
+
expectedSessionId,
|
|
86
|
+
grantId: grant.grantId
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
ready(binding, ceremony.expiresAt);
|
|
90
|
+
const response = await startRegistration({
|
|
91
|
+
optionsJSON: ceremony.options
|
|
92
|
+
});
|
|
93
|
+
ready(binding, ceremony.expiresAt);
|
|
94
|
+
return services.enrollment({
|
|
95
|
+
data: {
|
|
96
|
+
expectedSessionId,
|
|
97
|
+
ceremonyId: ceremony.ceremonyId,
|
|
98
|
+
response,
|
|
99
|
+
name
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function passkeyErrorKey(error) {
|
|
104
|
+
const value = error;
|
|
105
|
+
const name = value?.cause?.name ?? value?.name;
|
|
106
|
+
if ('AbortError' === name || 'NotAllowedError' === name || value?.code === 'ERROR_CEREMONY_ABORTED') return 'passkeys.errors.cancelled';
|
|
107
|
+
if ('ConstraintError' === name || 'NotSupportedError' === name || value?.code === 'PASSKEY_UNSUPPORTED' || value?.code?.startsWith('ERROR_AUTHENTICATOR_MISSING_') || value?.code === 'ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG') return 'passkeys.errors.unsupported';
|
|
108
|
+
if ('UnknownError' === name || value?.code === 'ERROR_AUTHENTICATOR_GENERAL_ERROR') return 'passkeys.errors.device';
|
|
109
|
+
if (value?.code === 'ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED') return 'passkeys.errors.duplicate';
|
|
110
|
+
if (value?.code === 'ERR_INVALID_TOKEN') return 'passkeys.errors.expired';
|
|
111
|
+
if (value?.code === 'ERR_SESSION_CHANGED') return 'passkeys.errors.sessionChanged';
|
|
112
|
+
if (value?.code === 'ERR_PASSKEY_LIMIT') return 'passkeys.errors.limit';
|
|
113
|
+
if (value?.code === 'ERR_FORBIDDEN') return 'passkeys.errors.forbidden';
|
|
114
|
+
if (value?.code === 'ERR_INVALID_CREDENTIALS') return 'passkeys.errors.proof';
|
|
115
|
+
if (value?.status === 429) return 'passkeys.errors.rateLimited';
|
|
116
|
+
return 'passkeys.errors.unavailable';
|
|
117
|
+
}
|
|
118
|
+
export { enrollPasskey, passkeyErrorKey, provePasskeyAction, signInWithPasskey };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
import type React from 'react';
|
|
9
|
+
/** A host page's card component, so the passkey panel matches the cards around it. */
|
|
10
|
+
type PasskeySectionComponent = React.ComponentType<{
|
|
11
|
+
title: string;
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function PasskeyManager({ targetUserId, targetIsSuperAdmin, section: Section, ownAccountNotice, }: {
|
|
15
|
+
targetUserId: string;
|
|
16
|
+
targetIsSuperAdmin?: boolean;
|
|
17
|
+
/** Renders the panel inside the page's own card; defaults to a standalone card. */
|
|
18
|
+
section?: PasskeySectionComponent;
|
|
19
|
+
/**
|
|
20
|
+
* Admin screens pass this to keep the admin (recovery) view even when the
|
|
21
|
+
* target is the signed-in user; it replaces the description in that case,
|
|
22
|
+
* typically pointing the user to their own account page.
|
|
23
|
+
*/
|
|
24
|
+
ownAccountNotice?: React.ReactNode;
|
|
25
|
+
}): React.JSX.Element | null;
|
|
26
|
+
export {};
|