@byline/admin 6.7.2 → 6.9.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/fields/blocks/blocks-field.js +33 -3
- package/dist/fields/blocks/blocks-field.module.js +1 -0
- package/dist/fields/blocks/blocks-field_module.css +19 -0
- package/dist/forms/form-page-chrome.d.ts +3 -2
- package/dist/forms/form-page-chrome.js +13 -1
- package/dist/forms/form-renderer.d.ts +2 -1
- package/dist/forms/form-renderer.js +23 -17
- package/dist/forms/unavailable-blocks.d.ts +15 -0
- package/dist/forms/unavailable-blocks.js +38 -0
- 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/fields/blocks/blocks-field.module.css +23 -0
- package/src/fields/blocks/blocks-field.tsx +26 -2
- package/src/forms/form-page-chrome.tsx +19 -1
- package/src/forms/form-renderer-capabilities.test.tsx +6 -6
- package/src/forms/form-renderer.tsx +24 -15
- package/src/forms/unavailable-blocks.test.tsx +152 -0
- package/src/forms/unavailable-blocks.tsx +58 -0
- 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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { Fragment, useEffect, useMemo, useState } from "react";
|
|
3
3
|
import { getAdminConfig } from "@byline/core";
|
|
4
4
|
import { useTranslation } from "@byline/i18n/react";
|
|
5
|
-
import { Card, CloseIcon, DraggableSortable, ErrorText, IconButton, Modal, PlusIcon, moveItem } from "@byline/ui/react";
|
|
5
|
+
import { Card, CloseIcon, DraggableSortable, ErrorText, IconButton, Modal, PlusIcon, WarningIcon, moveItem } from "@byline/ui/react";
|
|
6
6
|
import clsx from "clsx";
|
|
7
7
|
import { defaultScalarForField } from "../field-helpers.js";
|
|
8
8
|
import { GroupField } from "../group/group-field.js";
|
|
@@ -11,10 +11,12 @@ import { useFieldError, useFormContext } from "../../forms/form-context.js";
|
|
|
11
11
|
import { useScopedDomId } from "../../forms/form-dom-scope.js";
|
|
12
12
|
import { hasExistingIdTargets } from "../../forms/nested-path.js";
|
|
13
13
|
import { moveRepeatingItems, repeatingItemId, repeatingItemPath } from "../../forms/repeating-items.js";
|
|
14
|
+
import { useUnavailableBlocks } from "../../forms/unavailable-blocks.js";
|
|
14
15
|
import blocks_field_module from "./blocks-field.module.js";
|
|
15
16
|
const BlocksField = ({ field, defaultValue, path, contentLocale })=>{
|
|
16
17
|
const { appendPatch, getFieldValue, getFieldValues, removePendingUploadsUnder, setFieldStore } = useFormContext();
|
|
17
18
|
const { t } = useTranslation('byline-admin');
|
|
19
|
+
const unavailableBlocks = useUnavailableBlocks(path);
|
|
18
20
|
const fieldError = useFieldError(path);
|
|
19
21
|
const htmlId = useScopedDomId(path);
|
|
20
22
|
const [items, setItems] = useState([]);
|
|
@@ -170,7 +172,35 @@ const BlocksField = ({ field, defaultValue, path, contentLocale })=>{
|
|
|
170
172
|
onDragEnd: handleDragEnd,
|
|
171
173
|
className: clsx('byline-field-blocks-stack', blocks_field_module.stack),
|
|
172
174
|
children: [
|
|
173
|
-
|
|
175
|
+
Array.from({
|
|
176
|
+
length: items.length + 1
|
|
177
|
+
}, (_, index)=>/*#__PURE__*/ jsxs(Fragment, {
|
|
178
|
+
children: [
|
|
179
|
+
unavailableBlocks.map((issue, removedBefore)=>Math.min(issue.storedIndex - removedBefore, items.length) === index ? /*#__PURE__*/ jsxs("div", {
|
|
180
|
+
role: "note",
|
|
181
|
+
className: clsx('byline-field-blocks-unavailable', blocks_field_module.unavailable),
|
|
182
|
+
children: [
|
|
183
|
+
/*#__PURE__*/ jsx(WarningIcon, {
|
|
184
|
+
width: "24px",
|
|
185
|
+
height: "24px"
|
|
186
|
+
}),
|
|
187
|
+
/*#__PURE__*/ jsxs("div", {
|
|
188
|
+
children: [
|
|
189
|
+
/*#__PURE__*/ jsx("strong", {
|
|
190
|
+
children: t('fields.blocks.unavailableTitle', {
|
|
191
|
+
blockType: issue.blockType
|
|
192
|
+
})
|
|
193
|
+
}),
|
|
194
|
+
/*#__PURE__*/ jsx("p", {
|
|
195
|
+
children: t('fields.blocks.unavailableBody')
|
|
196
|
+
})
|
|
197
|
+
]
|
|
198
|
+
})
|
|
199
|
+
]
|
|
200
|
+
}, `${issue.storedIndex}-${issue.blockType}`) : null),
|
|
201
|
+
index < items.length && renderItem(items[index], index)
|
|
202
|
+
]
|
|
203
|
+
}, items[index]?.id ?? 'end')),
|
|
174
204
|
/*#__PURE__*/ jsxs("div", {
|
|
175
205
|
className: clsx('byline-field-blocks-add-row', blocks_field_module["add-row"]),
|
|
176
206
|
children: [
|
|
@@ -10,6 +10,25 @@
|
|
|
10
10
|
display: flex;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
:is(.unavailable-B9h_EU, .byline-field-blocks-unavailable) {
|
|
14
|
+
align-items: flex-start;
|
|
15
|
+
gap: var(--spacing-8);
|
|
16
|
+
padding: var(--spacing-16);
|
|
17
|
+
border: 1px solid var(--stroke-warning);
|
|
18
|
+
border-radius: var(--border-radius-md);
|
|
19
|
+
background-color: oklch(from var(--fill-warning-weak) l c h / calc(alpha * .5));
|
|
20
|
+
color: var(--foreground);
|
|
21
|
+
display: flex;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
:is(.unavailable-B9h_EU > :first-child, .byline-field-blocks-unavailable > :first-child) {
|
|
25
|
+
flex-shrink: 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
:is(.unavailable-B9h_EU p, .byline-field-blocks-unavailable p) {
|
|
29
|
+
margin: var(--spacing-4) 0 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
13
32
|
:is(.add-row-gEqjM_, .byline-field-blocks-add-row) {
|
|
14
33
|
color: var(--muted);
|
|
15
34
|
align-items: center;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* visible at its call site.
|
|
15
15
|
*/
|
|
16
16
|
import type { ReactNode, RefObject } from 'react';
|
|
17
|
-
import type { SlugifierFn, StructuralMutationReceipt, WorkflowStatus } from '@byline/core';
|
|
17
|
+
import type { SlugifierFn, StructuralMutationReceipt, UnavailableBlock, WorkflowStatus } from '@byline/core';
|
|
18
18
|
import { type DocumentActionsLocaleOption } from './document-actions.js';
|
|
19
19
|
import { type UseScheduledPublicationReturn } from './scheduled-publication-control.js';
|
|
20
20
|
import type { PublishedVersionInfo } from './form-renderer.js';
|
|
@@ -78,8 +78,9 @@ export interface FormConcurrencyNoticesProps {
|
|
|
78
78
|
onDiscardRequested: () => void;
|
|
79
79
|
scheduling: UseScheduledPublicationReturn;
|
|
80
80
|
restoreWarnings?: string[];
|
|
81
|
+
unavailableBlocks?: UnavailableBlock[];
|
|
81
82
|
}
|
|
82
|
-
export declare const FormConcurrencyNotices: ({ disabled, mutationIssue, scheduledPublicationsNeedReconfirmation, scheduledPublicationsHref, warningRef, discarding, reloadFailed, onDiscardRequested, scheduling, restoreWarnings, }: FormConcurrencyNoticesProps) => ReactNode;
|
|
83
|
+
export declare const FormConcurrencyNotices: ({ disabled, mutationIssue, scheduledPublicationsNeedReconfirmation, scheduledPublicationsHref, warningRef, discarding, reloadFailed, onDiscardRequested, scheduling, restoreWarnings, unavailableBlocks, }: FormConcurrencyNoticesProps) => ReactNode;
|
|
83
84
|
export interface FormSidebarWidgetsProps {
|
|
84
85
|
/** Mutations blocked or a discard in flight. */
|
|
85
86
|
disabled: boolean;
|
|
@@ -132,7 +132,7 @@ const FormStatusBar = ({ disabled, initialData, workflowStatuses, publishedVersi
|
|
|
132
132
|
]
|
|
133
133
|
});
|
|
134
134
|
};
|
|
135
|
-
const FormConcurrencyNotices = ({ disabled, mutationIssue, scheduledPublicationsNeedReconfirmation, scheduledPublicationsHref, warningRef, discarding, reloadFailed, onDiscardRequested, scheduling, restoreWarnings })=>{
|
|
135
|
+
const FormConcurrencyNotices = ({ disabled, mutationIssue, scheduledPublicationsNeedReconfirmation, scheduledPublicationsHref, warningRef, discarding, reloadFailed, onDiscardRequested, scheduling, restoreWarnings, unavailableBlocks })=>{
|
|
136
136
|
const { t } = useTranslation('byline-admin');
|
|
137
137
|
return /*#__PURE__*/ jsxs(Fragment, {
|
|
138
138
|
children: [
|
|
@@ -189,6 +189,18 @@ const FormConcurrencyNotices = ({ disabled, mutationIssue, scheduledPublications
|
|
|
189
189
|
onCancel: scheduling.cancel
|
|
190
190
|
}),
|
|
191
191
|
scheduling.modal,
|
|
192
|
+
unavailableBlocks && unavailableBlocks.length > 0 && /*#__PURE__*/ jsx(Alert, {
|
|
193
|
+
className: "m-0 mt-4",
|
|
194
|
+
intent: "warning",
|
|
195
|
+
icon: true,
|
|
196
|
+
close: false,
|
|
197
|
+
title: t('forms.unavailableBlocks.title'),
|
|
198
|
+
children: /*#__PURE__*/ jsx("p", {
|
|
199
|
+
children: t('forms.unavailableBlocks.body', {
|
|
200
|
+
count: unavailableBlocks.length
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
}),
|
|
192
204
|
restoreWarnings && restoreWarnings.length > 0 && /*#__PURE__*/ jsxs(Alert, {
|
|
193
205
|
className: "m-0 mt-4",
|
|
194
206
|
intent: "warning",
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
8
|
import { type ReactNode } from 'react';
|
|
9
|
-
import type { AdminResourceConfig, Field, WorkflowStatus } from '@byline/core';
|
|
9
|
+
import type { AdminResourceConfig, Field, UnavailableBlock, WorkflowStatus } from '@byline/core';
|
|
10
10
|
import { type ScheduledPublicationInfo, type SchedulePublicationInput } from './scheduled-publication-control.js';
|
|
11
11
|
import { type SystemFieldsSubmitPayload } from './use-form-submission.js';
|
|
12
12
|
import type { DocumentActionsLocaleOption } from './document-actions.js';
|
|
@@ -142,6 +142,7 @@ export interface FormRendererProps {
|
|
|
142
142
|
* them with the new shape.
|
|
143
143
|
*/
|
|
144
144
|
restoreWarnings?: string[];
|
|
145
|
+
unavailableBlocks?: UnavailableBlock[];
|
|
145
146
|
/**
|
|
146
147
|
* Default content locale used when no `initialLocale` is supplied and as the
|
|
147
148
|
* fallback inside `PathWidget`. Hosts typically pass their app-wide
|
|
@@ -13,9 +13,10 @@ import form_renderer_module from "./form-renderer.module.js";
|
|
|
13
13
|
import { useNavigationGuardAdapter } from "./navigation-guard.js";
|
|
14
14
|
import { useScheduledPublication } from "./scheduled-publication-control.js";
|
|
15
15
|
import { computeStatusTransitions } from "./status-transitions.js";
|
|
16
|
+
import { UnavailableBlocksProvider } from "./unavailable-blocks.js";
|
|
16
17
|
import { useDocumentReloadRecovery } from "./use-document-reload-recovery.js";
|
|
17
18
|
import { useFormSubmission } from "./use-form-submission.js";
|
|
18
|
-
const FormContent = ({ mode, mutationIssue, mutationsBlocked = false, observedRevision, onMutationError, onTreeMutationCommitted, scheduledPublicationsNeedReconfirmation = false, scheduledPublicationsHref, onReloadDocument, fields, onSubmit, onCancel, onStatusChange, onUnpublish, scheduledPublication, onSchedulePublication, onConfirmScheduledPublication, onCancelScheduledPublication, onDelete, onDuplicate, onCopyToLocale, onDeleteLocale, contentLocales, nextStatus, workflowStatuses, publishedVersion, initialData, adminConfig, useAsTitle, useAsPath, showPath = true, heading, advertiseLocales, tree, headingLabel, headerSlot, collectionPath, initialLocale, onLocaleChange, defaultLocale = 'en', useNavigationGuard: useNavigationGuardProp, restoreWarnings, _activeTabBySet, _onTabChange })=>{
|
|
19
|
+
const FormContent = ({ mode, mutationIssue, mutationsBlocked = false, observedRevision, onMutationError, onTreeMutationCommitted, scheduledPublicationsNeedReconfirmation = false, scheduledPublicationsHref, onReloadDocument, fields, onSubmit, onCancel, onStatusChange, onUnpublish, scheduledPublication, onSchedulePublication, onConfirmScheduledPublication, onCancelScheduledPublication, onDelete, onDuplicate, onCopyToLocale, onDeleteLocale, contentLocales, nextStatus, workflowStatuses, publishedVersion, initialData, adminConfig, useAsTitle, useAsPath, showPath = true, heading, advertiseLocales, tree, headingLabel, headerSlot, collectionPath, initialLocale, onLocaleChange, defaultLocale = 'en', useNavigationGuard: useNavigationGuardProp, restoreWarnings, unavailableBlocks, _activeTabBySet, _onTabChange })=>{
|
|
19
20
|
const { hasChanges: hasChangesFn, subscribeMeta } = useFormContext();
|
|
20
21
|
const { t } = useTranslation('byline-admin');
|
|
21
22
|
const [hasChanges, setHasChanges] = useState(hasChangesFn());
|
|
@@ -177,7 +178,8 @@ const FormContent = ({ mode, mutationIssue, mutationsBlocked = false, observedRe
|
|
|
177
178
|
reloadFailed: reloadFailed,
|
|
178
179
|
onDiscardRequested: requestDiscard,
|
|
179
180
|
scheduling: scheduling,
|
|
180
|
-
restoreWarnings: restoreWarnings
|
|
181
|
+
restoreWarnings: restoreWarnings,
|
|
182
|
+
unavailableBlocks: unavailableBlocks
|
|
181
183
|
}),
|
|
182
184
|
/*#__PURE__*/ jsx(FormLayout, {
|
|
183
185
|
fields: fields,
|
|
@@ -233,20 +235,24 @@ const FormContent = ({ mode, mutationIssue, mutationsBlocked = false, observedRe
|
|
|
233
235
|
const FormRenderer = (props)=>{
|
|
234
236
|
const { mode, initialData, initialLocale, collectionPath } = props;
|
|
235
237
|
const savedTabsRef = useRef({});
|
|
236
|
-
return /*#__PURE__*/ jsx(
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
238
|
+
return /*#__PURE__*/ jsx(UnavailableBlocksProvider, {
|
|
239
|
+
issues: props.unavailableBlocks,
|
|
240
|
+
fields: initialData?.fields ?? initialData ?? {},
|
|
241
|
+
children: /*#__PURE__*/ jsx(FormProvider, {
|
|
242
|
+
initialData: initialData,
|
|
243
|
+
documentId: 'edit' === mode && 'string' == typeof initialData?.id ? initialData.id : null,
|
|
244
|
+
collectionPath: collectionPath ?? null,
|
|
245
|
+
children: /*#__PURE__*/ jsx(FormContent, {
|
|
246
|
+
...props,
|
|
247
|
+
_activeTabBySet: savedTabsRef.current,
|
|
248
|
+
_onTabChange: (tabSetName, tabName)=>{
|
|
249
|
+
savedTabsRef.current = {
|
|
250
|
+
...savedTabsRef.current,
|
|
251
|
+
[tabSetName]: tabName
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
}, `${initialLocale ?? 'default'}-${initialData?.versionId ?? ''}`)
|
|
256
|
+
});
|
|
251
257
|
};
|
|
252
258
|
export { FormRenderer };
|
|
@@ -0,0 +1,15 @@
|
|
|
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 { ReactNode } from 'react';
|
|
9
|
+
import { type UnavailableBlock } from '@byline/core';
|
|
10
|
+
export declare function UnavailableBlocksProvider({ issues, fields, children, }: {
|
|
11
|
+
issues?: readonly UnavailableBlock[];
|
|
12
|
+
fields: Record<string, any>;
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
}): import("react").JSX.Element;
|
|
15
|
+
export declare function useUnavailableBlocks(path: string): UnavailableBlock[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useMemo } from "react";
|
|
3
|
+
import { parseInstancePath } from "@byline/core";
|
|
4
|
+
import { repeatingItemPath } from "./repeating-items.js";
|
|
5
|
+
const UnavailableBlocksContext = /*#__PURE__*/ createContext([]);
|
|
6
|
+
function bindUnavailableBlocks(issues, fields) {
|
|
7
|
+
return issues.map((issue)=>{
|
|
8
|
+
const parsed = parseInstancePath(issue.fieldPath);
|
|
9
|
+
if (!parsed.ok) return issue;
|
|
10
|
+
let value = fields;
|
|
11
|
+
let path = '';
|
|
12
|
+
for (const segment of parsed.segments)if ('field' === segment.kind) {
|
|
13
|
+
path = path ? `${path}.${segment.name}` : segment.name;
|
|
14
|
+
value = value?.[segment.name];
|
|
15
|
+
} else if ('index' === segment.kind) {
|
|
16
|
+
value = value?.[segment.index];
|
|
17
|
+
path = repeatingItemPath(path, value, segment.index);
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
...issue,
|
|
21
|
+
fieldPath: path
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function UnavailableBlocksProvider({ issues, fields, children }) {
|
|
26
|
+
const bound = useMemo(()=>bindUnavailableBlocks(issues ?? [], fields), [
|
|
27
|
+
issues,
|
|
28
|
+
fields
|
|
29
|
+
]);
|
|
30
|
+
return /*#__PURE__*/ jsx(UnavailableBlocksContext.Provider, {
|
|
31
|
+
value: bound,
|
|
32
|
+
children: children
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function useUnavailableBlocks(path) {
|
|
36
|
+
return useContext(UnavailableBlocksContext).filter((issue)=>issue.fieldPath === path);
|
|
37
|
+
}
|
|
38
|
+
export { UnavailableBlocksProvider, useUnavailableBlocks };
|
|
@@ -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;
|