@byline/host-tanstack-start 4.19.0 → 5.1.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/admin-shell/chrome/admin-app-bar.d.ts +7 -0
- package/dist/admin-shell/chrome/admin-app-bar.js +10 -2
- package/dist/admin-shell/chrome/session-change-boundary.d.ts +14 -0
- package/dist/admin-shell/chrome/session-change-boundary.js +87 -0
- package/dist/admin-shell/chrome/session-change-boundary.test.d.ts +8 -0
- package/dist/admin-shell/chrome/sign-in-page.d.ts +2 -1
- package/dist/admin-shell/chrome/sign-in-page.js +60 -3
- package/dist/admin-shell/collections/edit.d.ts +1 -1
- package/dist/admin-shell/collections/edit.js +151 -31
- package/dist/admin-shell/collections/history.js +4 -2
- package/dist/admin-shell/collections/list.js +30 -6
- package/dist/admin-shell/collections/restore-version-modal.d.ts +3 -1
- package/dist/admin-shell/collections/restore-version-modal.js +9 -4
- package/dist/admin-shell/collections/tree-list.js +16 -6
- package/dist/admin-shell/collections/version-history.d.ts +2 -1
- package/dist/admin-shell/collections/version-history.js +11 -2
- package/dist/admin-shell/document-mutation-notice.d.ts +6 -0
- package/dist/admin-shell/document-mutation-notice.js +53 -0
- package/dist/admin-shell/document-mutation-state.d.ts +22 -0
- package/dist/admin-shell/document-mutation-state.js +71 -0
- package/dist/admin-shell/document-mutation-state.test.d.ts +1 -0
- package/dist/admin-shell/scheduled-publications/list.js +15 -5
- package/dist/admin-shell/singletons/history.js +4 -2
- package/dist/admin-shell/singletons/view.d.ts +1 -1
- package/dist/admin-shell/singletons/view.js +98 -19
- package/dist/integrations/byline-admin-services.js +7 -1
- package/dist/integrations/byline-field-services.js +2 -9
- package/dist/integrations/client-ip.d.ts +19 -0
- package/dist/integrations/client-ip.js +34 -0
- package/dist/integrations/session-coordination.d.ts +24 -0
- package/dist/integrations/session-coordination.js +85 -0
- package/dist/integrations/session-middleware.d.ts +11 -0
- package/dist/integrations/session-middleware.js +94 -0
- package/dist/integrations/session-request-kind.d.ts +9 -0
- package/dist/integrations/session-request-kind.js +10 -0
- package/dist/integrations/session-request-middleware.d.ts +9 -0
- package/dist/integrations/session-request-middleware.js +7 -0
- package/dist/integrations/sign-in-body.d.ts +12 -0
- package/dist/integrations/sign-in-body.js +52 -0
- package/dist/integrations/sign-in-middleware.d.ts +9 -0
- package/dist/integrations/sign-in-middleware.js +10 -0
- package/dist/routes/create-admin-layout-route.js +46 -31
- package/dist/routes/create-collection-edit-route.js +1 -1
- package/dist/routes/create-collection-list-route.d.ts +0 -7
- package/dist/routes/create-collection-list-route.js +7 -9
- package/dist/routes/create-sign-in-route.js +4 -2
- package/dist/routes/create-singleton-route.d.ts +2 -0
- package/dist/routes/create-singleton-route.js +4 -3
- package/dist/server-fns/admin-account/change-password.d.ts +3 -1
- package/dist/server-fns/admin-account/change-password.js +9 -3
- package/dist/server-fns/admin-account/get.d.ts +3 -1
- package/dist/server-fns/admin-account/get.js +4 -1
- package/dist/server-fns/admin-account/index.d.ts +5 -5
- package/dist/server-fns/admin-account/update.d.ts +3 -1
- package/dist/server-fns/admin-account/update.js +4 -1
- package/dist/server-fns/admin-activity/get.d.ts +3 -1
- package/dist/server-fns/admin-activity/get.js +4 -1
- package/dist/server-fns/admin-permissions/get-role-abilities.d.ts +3 -1
- package/dist/server-fns/admin-permissions/get-role-abilities.js +4 -1
- package/dist/server-fns/admin-permissions/index.d.ts +4 -4
- package/dist/server-fns/admin-permissions/list-registered.d.ts +3 -1
- package/dist/server-fns/admin-permissions/list-registered.js +4 -1
- package/dist/server-fns/admin-permissions/set-role-abilities.d.ts +3 -1
- package/dist/server-fns/admin-permissions/set-role-abilities.js +4 -1
- package/dist/server-fns/admin-permissions/who-has.d.ts +3 -1
- package/dist/server-fns/admin-permissions/who-has.js +4 -1
- package/dist/server-fns/admin-roles/create.d.ts +3 -1
- package/dist/server-fns/admin-roles/create.js +4 -1
- package/dist/server-fns/admin-roles/delete.d.ts +3 -1
- package/dist/server-fns/admin-roles/delete.js +4 -1
- package/dist/server-fns/admin-roles/get.d.ts +3 -1
- package/dist/server-fns/admin-roles/get.js +4 -1
- package/dist/server-fns/admin-roles/index.d.ts +6 -6
- package/dist/server-fns/admin-roles/list.d.ts +3 -1
- package/dist/server-fns/admin-roles/list.js +4 -1
- package/dist/server-fns/admin-roles/reorder.d.ts +3 -1
- package/dist/server-fns/admin-roles/reorder.js +4 -1
- package/dist/server-fns/admin-roles/update.d.ts +3 -1
- package/dist/server-fns/admin-roles/update.js +4 -1
- package/dist/server-fns/admin-users/create.d.ts +3 -1
- package/dist/server-fns/admin-users/create.js +4 -1
- package/dist/server-fns/admin-users/delete.d.ts +3 -1
- package/dist/server-fns/admin-users/delete.js +4 -1
- package/dist/server-fns/admin-users/disable.d.ts +3 -1
- package/dist/server-fns/admin-users/disable.js +4 -1
- package/dist/server-fns/admin-users/enable.d.ts +3 -1
- package/dist/server-fns/admin-users/enable.js +4 -1
- package/dist/server-fns/admin-users/get-user-roles.d.ts +3 -1
- package/dist/server-fns/admin-users/get-user-roles.js +4 -1
- package/dist/server-fns/admin-users/get.d.ts +3 -1
- package/dist/server-fns/admin-users/get.js +4 -1
- package/dist/server-fns/admin-users/index.d.ts +10 -10
- package/dist/server-fns/admin-users/list.d.ts +3 -1
- package/dist/server-fns/admin-users/list.js +4 -1
- package/dist/server-fns/admin-users/set-password.d.ts +3 -1
- package/dist/server-fns/admin-users/set-password.js +4 -1
- package/dist/server-fns/admin-users/set-user-roles.d.ts +3 -1
- package/dist/server-fns/admin-users/set-user-roles.js +4 -1
- package/dist/server-fns/admin-users/update.d.ts +3 -1
- package/dist/server-fns/admin-users/update.js +4 -1
- package/dist/server-fns/ai/execute.d.ts +3 -1
- package/dist/server-fns/ai/execute.js +4 -1
- package/dist/server-fns/ai/index.d.ts +1 -1
- package/dist/server-fns/analytics/maintenance.d.ts +6 -2
- package/dist/server-fns/analytics/maintenance.js +7 -2
- package/dist/server-fns/analytics/queries.d.ts +18 -6
- package/dist/server-fns/analytics/queries.js +19 -6
- package/dist/server-fns/auth/current-user.d.ts +1 -0
- package/dist/server-fns/auth/current-user.js +5 -2
- package/dist/server-fns/auth/renew.d.ts +13 -0
- package/dist/server-fns/auth/renew.js +45 -0
- package/dist/server-fns/auth/sign-in.d.ts +2 -1
- package/dist/server-fns/auth/sign-in.js +88 -17
- package/dist/server-fns/auth/sign-out.d.ts +3 -1
- package/dist/server-fns/auth/sign-out.js +38 -6
- package/dist/server-fns/collections/audit.d.ts +3 -1
- package/dist/server-fns/collections/audit.js +4 -1
- package/dist/server-fns/collections/copy-to-locale.d.ts +5 -1
- package/dist/server-fns/collections/copy-to-locale.js +7 -2
- package/dist/server-fns/collections/create.d.ts +4 -1
- package/dist/server-fns/collections/create.js +8 -3
- package/dist/server-fns/collections/delete-locale.d.ts +5 -1
- package/dist/server-fns/collections/delete-locale.js +7 -2
- package/dist/server-fns/collections/delete-outcome.d.ts +7 -3
- package/dist/server-fns/collections/delete-outcome.js +4 -0
- package/dist/server-fns/collections/delete.d.ts +5 -1
- package/dist/server-fns/collections/delete.js +8 -3
- package/dist/server-fns/collections/duplicate.d.ts +5 -1
- package/dist/server-fns/collections/duplicate.js +8 -3
- package/dist/server-fns/collections/get.d.ts +1 -0
- package/dist/server-fns/collections/get.js +12 -6
- package/dist/server-fns/collections/history.d.ts +3 -1
- package/dist/server-fns/collections/history.js +4 -1
- package/dist/server-fns/collections/index.d.ts +21 -21
- package/dist/server-fns/collections/list.d.ts +3 -1
- package/dist/server-fns/collections/list.js +15 -4
- package/dist/server-fns/collections/reindex.d.ts +3 -1
- package/dist/server-fns/collections/reindex.js +4 -1
- package/dist/server-fns/collections/reorder.d.ts +10 -2
- package/dist/server-fns/collections/reorder.js +23 -76
- package/dist/server-fns/collections/restore-version.d.ts +6 -1
- package/dist/server-fns/collections/restore-version.js +8 -2
- package/dist/server-fns/collections/save-outcome.d.ts +1 -0
- package/dist/server-fns/collections/save-outcome.js +1 -0
- package/dist/server-fns/collections/scheduled-publication.d.ts +64 -7
- package/dist/server-fns/collections/scheduled-publication.js +42 -21
- package/dist/server-fns/collections/set-list-view-preference.d.ts +3 -1
- package/dist/server-fns/collections/set-list-view-preference.js +4 -1
- package/dist/server-fns/collections/stats.js +4 -1
- package/dist/server-fns/collections/status.d.ts +14 -2
- package/dist/server-fns/collections/status.js +18 -5
- package/dist/server-fns/collections/tree-mutation.d.ts +7 -6
- package/dist/server-fns/collections/tree-mutation.js +17 -5
- package/dist/server-fns/collections/tree.d.ts +29 -6
- package/dist/server-fns/collections/tree.js +32 -17
- package/dist/server-fns/collections/update.d.ts +18 -6
- package/dist/server-fns/collections/update.js +39 -20
- package/dist/server-fns/collections/upload.d.ts +11 -1
- package/dist/server-fns/collections/upload.js +6 -2
- package/dist/server-fns/document-mutation-errors.d.ts +5 -0
- package/dist/server-fns/document-mutation-errors.js +50 -0
- package/dist/server-fns/editable-revision.d.ts +9 -0
- package/dist/server-fns/editable-revision.js +11 -0
- package/dist/server-fns/i18n/set-locale.d.ts +3 -1
- package/dist/server-fns/i18n/set-locale.js +18 -3
- package/dist/server-fns/preview/disable.d.ts +3 -1
- package/dist/server-fns/preview/disable.js +4 -1
- package/dist/server-fns/preview/enable.d.ts +3 -1
- package/dist/server-fns/preview/enable.js +4 -1
- package/dist/server-fns/singleton-document-read.d.ts +1 -1
- package/dist/server-fns/singleton-document-read.js +7 -3
- package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +9 -2
- package/dist/server-fns/singletons/cancel-scheduled-publish.js +7 -1
- package/dist/server-fns/singletons/change-status.d.ts +5 -1
- package/dist/server-fns/singletons/change-status.js +7 -1
- package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +8 -2
- package/dist/server-fns/singletons/confirm-scheduled-publish.js +7 -2
- package/dist/server-fns/singletons/copy-to-locale.d.ts +5 -1
- package/dist/server-fns/singletons/copy-to-locale.js +7 -2
- package/dist/server-fns/singletons/find-by-version.d.ts +3 -1
- package/dist/server-fns/singletons/find-by-version.js +4 -1
- package/dist/server-fns/singletons/get-scheduled-publish.d.ts +3 -1
- package/dist/server-fns/singletons/get-scheduled-publish.js +4 -1
- package/dist/server-fns/singletons/get.d.ts +6 -4
- package/dist/server-fns/singletons/get.js +4 -1
- package/dist/server-fns/singletons/history.d.ts +3 -1
- package/dist/server-fns/singletons/history.js +4 -1
- package/dist/server-fns/singletons/restore-version.d.ts +5 -1
- package/dist/server-fns/singletons/restore-version.js +9 -3
- package/dist/server-fns/singletons/schedule-publish.d.ts +8 -2
- package/dist/server-fns/singletons/schedule-publish.js +7 -2
- package/dist/server-fns/singletons/unpublish.d.ts +5 -1
- package/dist/server-fns/singletons/unpublish.js +7 -1
- package/dist/server-fns/singletons/update.d.ts +6 -4
- package/dist/server-fns/singletons/update.js +8 -4
- package/dist/server-fns/structural-receipt.d.ts +4 -0
- package/dist/server-fns/structural-receipt.js +31 -0
- package/package.json +20 -20
- package/src/admin-shell/chrome/admin-app-bar.tsx +11 -6
- package/src/admin-shell/chrome/session-change-boundary.test.tsx +78 -0
- package/src/admin-shell/chrome/session-change-boundary.tsx +94 -0
- package/src/admin-shell/chrome/sign-in-page.tsx +68 -3
- package/src/admin-shell/collections/edit.tsx +173 -54
- package/src/admin-shell/collections/history.tsx +11 -2
- package/src/admin-shell/collections/list.tsx +22 -6
- package/src/admin-shell/collections/restore-version-modal.tsx +12 -3
- package/src/admin-shell/collections/save-navigation.test.tsx +156 -8
- package/src/admin-shell/collections/tree-list.tsx +16 -6
- package/src/admin-shell/collections/version-history.tsx +13 -3
- package/src/admin-shell/document-mutation-notice.tsx +45 -0
- package/src/admin-shell/document-mutation-state.test.tsx +146 -0
- package/src/admin-shell/document-mutation-state.ts +95 -0
- package/src/admin-shell/scheduled-publications/list.tsx +16 -6
- package/src/admin-shell/singletons/history.test.tsx +3 -1
- package/src/admin-shell/singletons/history.tsx +10 -2
- package/src/admin-shell/singletons/view.test.tsx +59 -18
- package/src/admin-shell/singletons/view.tsx +110 -29
- package/src/integrations/byline-admin-services.ts +7 -1
- package/src/integrations/byline-field-services.ts +2 -3
- package/src/integrations/client-ip.test.node.ts +45 -0
- package/src/integrations/client-ip.ts +54 -0
- package/src/integrations/framework-fetch-shape.test.node.ts +56 -0
- package/src/integrations/session-client.test.node.ts +121 -0
- package/src/integrations/session-coordination.test.node.ts +77 -0
- package/src/integrations/session-coordination.ts +96 -0
- package/src/integrations/session-middleware.test.node.ts +87 -0
- package/src/integrations/session-middleware.ts +107 -0
- package/src/integrations/session-request-kind.ts +18 -0
- package/src/integrations/session-request-middleware.ts +17 -0
- package/src/integrations/sign-in-body.test.node.ts +59 -0
- package/src/integrations/sign-in-body.ts +50 -0
- package/src/integrations/sign-in-middleware.ts +24 -0
- package/src/routes/create-admin-layout-route.tsx +48 -30
- package/src/routes/create-collection-edit-route.tsx +1 -0
- package/src/routes/create-collection-list-route.tsx +13 -2
- package/src/routes/create-sign-in-route.tsx +10 -2
- package/src/routes/create-singleton-route.test.node.ts +10 -2
- package/src/routes/create-singleton-route.tsx +6 -4
- package/src/server-fns/admin-account/change-password.test.node.ts +69 -0
- package/src/server-fns/admin-account/change-password.ts +13 -2
- package/src/server-fns/admin-account/get.ts +2 -0
- package/src/server-fns/admin-account/update.ts +2 -0
- package/src/server-fns/admin-activity/get.ts +2 -0
- package/src/server-fns/admin-permissions/get-role-abilities.ts +2 -0
- package/src/server-fns/admin-permissions/list-registered.ts +5 -4
- package/src/server-fns/admin-permissions/set-role-abilities.ts +2 -0
- package/src/server-fns/admin-permissions/who-has.ts +2 -0
- package/src/server-fns/admin-roles/create.ts +2 -0
- package/src/server-fns/admin-roles/delete.ts +2 -0
- package/src/server-fns/admin-roles/get.ts +2 -0
- package/src/server-fns/admin-roles/list.ts +5 -4
- package/src/server-fns/admin-roles/reorder.ts +2 -0
- package/src/server-fns/admin-roles/update.ts +2 -0
- package/src/server-fns/admin-users/create.ts +2 -0
- package/src/server-fns/admin-users/delete.ts +2 -0
- package/src/server-fns/admin-users/disable.ts +2 -0
- package/src/server-fns/admin-users/enable.ts +2 -0
- package/src/server-fns/admin-users/get-user-roles.ts +2 -0
- package/src/server-fns/admin-users/get.ts +2 -0
- package/src/server-fns/admin-users/list.ts +2 -0
- package/src/server-fns/admin-users/set-password.ts +2 -0
- package/src/server-fns/admin-users/set-user-roles.ts +2 -0
- package/src/server-fns/admin-users/update.ts +2 -0
- package/src/server-fns/ai/execute.ts +3 -0
- package/src/server-fns/analytics/maintenance.ts +4 -0
- package/src/server-fns/analytics/queries.ts +14 -8
- package/src/server-fns/auth/current-user.ts +7 -3
- package/src/server-fns/auth/renew.test.node.ts +85 -0
- package/src/server-fns/auth/renew.ts +54 -0
- package/src/server-fns/auth/sign-in.test.node.ts +114 -0
- package/src/server-fns/auth/sign-in.ts +80 -27
- package/src/server-fns/auth/sign-out.test.node.ts +85 -0
- package/src/server-fns/auth/sign-out.ts +44 -19
- package/src/server-fns/collections/audit.ts +2 -0
- package/src/server-fns/collections/copy-to-locale.ts +43 -36
- package/src/server-fns/collections/create.ts +56 -50
- package/src/server-fns/collections/delete-locale.ts +43 -32
- package/src/server-fns/collections/delete-outcome.test.node.ts +16 -0
- package/src/server-fns/collections/delete-outcome.ts +27 -16
- package/src/server-fns/collections/delete.ts +46 -38
- package/src/server-fns/collections/duplicate.ts +40 -32
- package/src/server-fns/collections/get.ts +11 -4
- package/src/server-fns/collections/history.ts +2 -0
- package/src/server-fns/collections/list.ts +10 -3
- package/src/server-fns/collections/reindex.ts +2 -0
- package/src/server-fns/collections/reorder.ts +34 -124
- package/src/server-fns/collections/restore-version.ts +49 -39
- package/src/server-fns/collections/revision-input.test.node.ts +122 -0
- package/src/server-fns/collections/save-outcome.test.node.ts +3 -0
- package/src/server-fns/collections/save-outcome.ts +2 -0
- package/src/server-fns/collections/scheduled-publication.ts +149 -115
- package/src/server-fns/collections/set-list-view-preference.ts +2 -0
- package/src/server-fns/collections/stats.ts +2 -0
- package/src/server-fns/collections/status.ts +89 -64
- package/src/server-fns/collections/tree-mutation.test.node.ts +87 -29
- package/src/server-fns/collections/tree-mutation.ts +28 -11
- package/src/server-fns/collections/tree.ts +156 -122
- package/src/server-fns/collections/update.ts +113 -86
- package/src/server-fns/collections/upload-singleton.test.node.ts +10 -7
- package/src/server-fns/collections/upload.ts +91 -77
- package/src/server-fns/document-mutation-errors.ts +67 -0
- package/src/server-fns/document-mutation-transport.test.node.ts +211 -0
- package/src/server-fns/editable-revision.test.node.ts +23 -0
- package/src/server-fns/editable-revision.ts +18 -0
- package/src/server-fns/i18n/set-locale.ts +19 -3
- package/src/server-fns/preview/disable.ts +8 -4
- package/src/server-fns/preview/enable.ts +9 -5
- package/src/server-fns/singleton-document-read.ts +7 -3
- package/src/server-fns/singletons/cancel-scheduled-publish.ts +12 -3
- package/src/server-fns/singletons/change-status.ts +12 -3
- package/src/server-fns/singletons/confirm-scheduled-publish.ts +14 -6
- package/src/server-fns/singletons/copy-to-locale.ts +22 -15
- package/src/server-fns/singletons/find-by-version.ts +2 -0
- package/src/server-fns/singletons/get-scheduled-publish.ts +2 -0
- package/src/server-fns/singletons/get.ts +5 -3
- package/src/server-fns/singletons/history.ts +2 -0
- package/src/server-fns/singletons/restore-version.ts +19 -12
- package/src/server-fns/singletons/schedule-publish.ts +20 -7
- package/src/server-fns/singletons/singletons.test.node.ts +69 -25
- package/src/server-fns/singletons/unpublish.ts +12 -3
- package/src/server-fns/singletons/update.ts +23 -16
- package/src/server-fns/structural-receipt.ts +41 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useRouter } from "@tanstack/react-router";
|
|
4
4
|
import { FormRenderer } from "@byline/admin/react";
|
|
5
|
-
import {
|
|
5
|
+
import { getDefaultStatus, getWorkflow, getWorkflowStatuses, validateStatusTransition } from "@byline/core";
|
|
6
6
|
import { useTranslation } from "@byline/i18n/react";
|
|
7
7
|
import { Container, Section, useToastManager } from "@byline/ui/react";
|
|
8
8
|
import { getAdminRoutePath } from "../../routes/admin-path.js";
|
|
@@ -10,11 +10,11 @@ import { hasCommittedDocumentHookFailure } from "../../server-fns/collections/in
|
|
|
10
10
|
import { cancelSingletonScheduledPublish, changeSingletonStatus, confirmSingletonScheduledPublish, copySingletonToLocale, scheduleSingletonPublish, unpublishSingleton, updateSingleton } from "../../server-fns/singletons/index.js";
|
|
11
11
|
import { useNavigate } from "../chrome/loose-router.js";
|
|
12
12
|
import { useTanStackNavigationGuard } from "../collections/tanstack-navigation-guard.js";
|
|
13
|
+
import { useDocumentMutationState } from "../document-mutation-state.js";
|
|
13
14
|
import { SingletonViewMenu } from "./view-menu.js";
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
function SingletonView({ singletonDefinition, adminConfig, document, initialData, locale, contentLocales, defaultContentLocale }) {
|
|
15
|
+
function SingletonView({ singletonDefinition, adminConfig, document: loadedDocument, initialData, locale, contentLocales, defaultContentLocale }) {
|
|
16
|
+
const mutation = useDocumentMutationState(loadedDocument);
|
|
17
|
+
const document = mutation.document ?? null;
|
|
18
18
|
const router = useRouter();
|
|
19
19
|
const navigate = useNavigate();
|
|
20
20
|
const toastManager = useToastManager();
|
|
@@ -55,14 +55,20 @@ function SingletonView({ singletonDefinition, adminConfig, document, initialData
|
|
|
55
55
|
};
|
|
56
56
|
const handleSubmit = async ({ data })=>{
|
|
57
57
|
try {
|
|
58
|
+
mutation.assertWritable();
|
|
58
59
|
const result = await updateSingleton({
|
|
59
60
|
data: {
|
|
60
61
|
singleton: path,
|
|
61
62
|
data,
|
|
62
63
|
locale: locale ?? defaultContentLocale,
|
|
63
|
-
|
|
64
|
+
...null == document ? {
|
|
65
|
+
expectedState: 'empty'
|
|
66
|
+
} : {
|
|
67
|
+
expectedRevision: mutation.expectedRevision()
|
|
68
|
+
}
|
|
64
69
|
}
|
|
65
70
|
});
|
|
71
|
+
mutation.adopt(result);
|
|
66
72
|
if (null != document) notifyScheduleSuspended();
|
|
67
73
|
if (hasCommittedDocumentHookFailure(result)) notifyCommittedHookFailure();
|
|
68
74
|
else toast(t('singletons.edit.updateTitle', {
|
|
@@ -72,8 +78,14 @@ function SingletonView({ singletonDefinition, adminConfig, document, initialData
|
|
|
72
78
|
}), 'success');
|
|
73
79
|
await reload();
|
|
74
80
|
} catch (err) {
|
|
75
|
-
const
|
|
76
|
-
|
|
81
|
+
const handled = mutation.report(err);
|
|
82
|
+
if ('blocked' === handled) throw err;
|
|
83
|
+
if ('committed' === handled) {
|
|
84
|
+
notifyCommittedHookFailure();
|
|
85
|
+
await reload();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const description = t("singletons.edit.updateFailedDescription", {
|
|
77
89
|
label: label.toLowerCase()
|
|
78
90
|
});
|
|
79
91
|
toast(t('singletons.edit.updateTitle', {
|
|
@@ -84,12 +96,15 @@ function SingletonView({ singletonDefinition, adminConfig, document, initialData
|
|
|
84
96
|
};
|
|
85
97
|
const handleStatusChange = async (status)=>{
|
|
86
98
|
try {
|
|
87
|
-
|
|
99
|
+
mutation.assertWritable();
|
|
100
|
+
const result = await changeSingletonStatus({
|
|
88
101
|
data: {
|
|
102
|
+
expectedRevision: mutation.expectedRevision(),
|
|
89
103
|
singleton: path,
|
|
90
104
|
status
|
|
91
105
|
}
|
|
92
106
|
});
|
|
107
|
+
mutation.adopt(result);
|
|
93
108
|
toast(t('collections.edit.statusUpdateTitle', {
|
|
94
109
|
label
|
|
95
110
|
}), t("collections.edit.statusChangedDescription", {
|
|
@@ -97,44 +112,64 @@ function SingletonView({ singletonDefinition, adminConfig, document, initialData
|
|
|
97
112
|
}), 'success');
|
|
98
113
|
await reload();
|
|
99
114
|
} catch (err) {
|
|
115
|
+
const handled = mutation.report(err);
|
|
116
|
+
if ('blocked' === handled) throw err;
|
|
117
|
+
if ('committed' === handled) {
|
|
118
|
+
notifyCommittedHookFailure();
|
|
119
|
+
await reload();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
100
122
|
toast(t('collections.edit.statusUpdateTitle', {
|
|
101
123
|
label
|
|
102
124
|
}), t("collections.edit.statusChangeFailedDescription", {
|
|
103
|
-
message:
|
|
125
|
+
message: t('documentConcurrency.failed')
|
|
104
126
|
}), 'danger');
|
|
105
127
|
throw err;
|
|
106
128
|
}
|
|
107
129
|
};
|
|
108
130
|
const handleUnpublish = async ()=>{
|
|
109
131
|
try {
|
|
110
|
-
|
|
132
|
+
mutation.assertWritable();
|
|
133
|
+
const result = await unpublishSingleton({
|
|
111
134
|
data: {
|
|
135
|
+
expectedRevision: mutation.expectedRevision(),
|
|
112
136
|
singleton: path
|
|
113
137
|
}
|
|
114
138
|
});
|
|
139
|
+
mutation.adopt(result);
|
|
115
140
|
toast(t('collections.edit.unpublishTitle', {
|
|
116
141
|
label
|
|
117
142
|
}), t("collections.edit.unpublishedDescription"), 'success');
|
|
118
143
|
await reload();
|
|
119
144
|
} catch (err) {
|
|
145
|
+
const handled = mutation.report(err);
|
|
146
|
+
if ('blocked' === handled) throw err;
|
|
147
|
+
if ('committed' === handled) {
|
|
148
|
+
notifyCommittedHookFailure();
|
|
149
|
+
await reload();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
120
152
|
toast(t('collections.edit.unpublishTitle', {
|
|
121
153
|
label
|
|
122
154
|
}), t("collections.edit.unpublishFailedDescription", {
|
|
123
|
-
message:
|
|
155
|
+
message: t('documentConcurrency.failed')
|
|
124
156
|
}), 'danger');
|
|
125
157
|
throw err;
|
|
126
158
|
}
|
|
127
159
|
};
|
|
128
160
|
const handleCopyToLocale = async ({ targetLocale, overwrite })=>{
|
|
129
161
|
try {
|
|
162
|
+
mutation.assertWritable();
|
|
130
163
|
const result = await copySingletonToLocale({
|
|
131
164
|
data: {
|
|
165
|
+
expectedRevision: mutation.expectedRevision(),
|
|
132
166
|
singleton: path,
|
|
133
167
|
sourceLocale: locale ?? defaultContentLocale,
|
|
134
168
|
targetLocale,
|
|
135
169
|
overwrite
|
|
136
170
|
}
|
|
137
171
|
});
|
|
172
|
+
mutation.adopt(result);
|
|
138
173
|
const sourceLocale = locale ?? defaultContentLocale;
|
|
139
174
|
const sourceLabel = contentLocales.find((entry)=>entry.code === sourceLocale)?.label ?? sourceLocale;
|
|
140
175
|
const targetLabel = contentLocales.find((entry)=>entry.code === targetLocale)?.label ?? targetLocale;
|
|
@@ -156,62 +191,106 @@ function SingletonView({ singletonDefinition, adminConfig, document, initialData
|
|
|
156
191
|
}
|
|
157
192
|
});
|
|
158
193
|
} catch (err) {
|
|
194
|
+
const handled = mutation.report(err);
|
|
195
|
+
if ('blocked' === handled) throw err;
|
|
196
|
+
if ('committed' === handled) {
|
|
197
|
+
notifyCommittedHookFailure();
|
|
198
|
+
await reload();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
159
201
|
toast(t('collections.edit.copyToLocaleTitle', {
|
|
160
202
|
label
|
|
161
203
|
}), t("collections.edit.copyFailedDescription", {
|
|
162
|
-
message:
|
|
204
|
+
message: t('documentConcurrency.failed')
|
|
163
205
|
}), 'danger');
|
|
164
206
|
throw err;
|
|
165
207
|
}
|
|
166
208
|
};
|
|
167
209
|
const handleSchedulePublication = async ({ publishAt })=>{
|
|
168
210
|
try {
|
|
169
|
-
|
|
211
|
+
mutation.assertWritable();
|
|
212
|
+
const result = await scheduleSingletonPublish({
|
|
170
213
|
data: {
|
|
214
|
+
expectedRevision: mutation.expectedRevision(),
|
|
171
215
|
singleton: path,
|
|
172
216
|
publishAt,
|
|
173
217
|
expectedVersionId: String(document?.versionId)
|
|
174
218
|
}
|
|
175
219
|
});
|
|
220
|
+
mutation.adopt(result);
|
|
176
221
|
toast(t('scheduledPublication.toast.scheduledTitle'), t("scheduledPublication.toast.scheduledDescription"), 'success');
|
|
177
222
|
await reload();
|
|
178
223
|
} catch (err) {
|
|
179
|
-
|
|
224
|
+
const handled = mutation.report(err);
|
|
225
|
+
if ('blocked' === handled) throw err;
|
|
226
|
+
if ('committed' === handled) {
|
|
227
|
+
notifyCommittedHookFailure();
|
|
228
|
+
await reload();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
toast(t('scheduledPublication.toast.failedTitle'), t('documentConcurrency.failed'), 'danger');
|
|
180
232
|
throw err;
|
|
181
233
|
}
|
|
182
234
|
};
|
|
183
235
|
const handleConfirmScheduledPublication = async ()=>{
|
|
184
236
|
try {
|
|
185
|
-
|
|
237
|
+
mutation.assertWritable();
|
|
238
|
+
const result = await confirmSingletonScheduledPublish({
|
|
186
239
|
data: {
|
|
240
|
+
expectedRevision: mutation.expectedRevision(),
|
|
187
241
|
singleton: path,
|
|
188
242
|
expectedVersionId: String(document?.versionId)
|
|
189
243
|
}
|
|
190
244
|
});
|
|
245
|
+
mutation.adopt(result);
|
|
191
246
|
toast(t('scheduledPublication.toast.confirmedTitle'), t("scheduledPublication.toast.confirmedDescription"), 'success');
|
|
192
247
|
await reload();
|
|
193
248
|
} catch (err) {
|
|
194
|
-
|
|
249
|
+
const handled = mutation.report(err);
|
|
250
|
+
if ('blocked' === handled) throw err;
|
|
251
|
+
if ('committed' === handled) {
|
|
252
|
+
notifyCommittedHookFailure();
|
|
253
|
+
await reload();
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
toast(t('scheduledPublication.toast.failedTitle'), t('documentConcurrency.failed'), 'danger');
|
|
195
257
|
throw err;
|
|
196
258
|
}
|
|
197
259
|
};
|
|
198
260
|
const handleCancelScheduledPublication = async ()=>{
|
|
199
261
|
try {
|
|
200
|
-
|
|
262
|
+
mutation.assertWritable();
|
|
263
|
+
const result = await cancelSingletonScheduledPublish({
|
|
201
264
|
data: {
|
|
265
|
+
expectedRevision: mutation.expectedRevision(),
|
|
202
266
|
singleton: path
|
|
203
267
|
}
|
|
204
268
|
});
|
|
269
|
+
mutation.adopt({
|
|
270
|
+
...result,
|
|
271
|
+
documentId: String(document?.id)
|
|
272
|
+
});
|
|
205
273
|
toast(t('scheduledPublication.toast.cancelledTitle'), t("scheduledPublication.toast.cancelledDescription"), 'success');
|
|
206
274
|
await reload();
|
|
207
275
|
} catch (err) {
|
|
208
|
-
|
|
276
|
+
const handled = mutation.report(err);
|
|
277
|
+
if ('blocked' === handled) throw err;
|
|
278
|
+
if ('committed' === handled) {
|
|
279
|
+
notifyCommittedHookFailure();
|
|
280
|
+
await reload();
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
toast(t('scheduledPublication.toast.failedTitle'), t('documentConcurrency.failed'), 'danger');
|
|
209
284
|
throw err;
|
|
210
285
|
}
|
|
211
286
|
};
|
|
212
287
|
return /*#__PURE__*/ jsx(Section, {
|
|
213
288
|
children: /*#__PURE__*/ jsx(Container, {
|
|
214
289
|
children: /*#__PURE__*/ jsx(FormRenderer, {
|
|
290
|
+
mutationIssue: mutation.issue,
|
|
291
|
+
mutationsBlocked: mutation.blocked,
|
|
292
|
+
observedRevision: mutation.revision,
|
|
293
|
+
onMutationError: mutation.report,
|
|
215
294
|
mode: null == document ? 'create' : 'edit',
|
|
216
295
|
fields: fields,
|
|
217
296
|
onSubmit: handleSubmit,
|
|
@@ -5,13 +5,19 @@ import { createAdminUser, setAdminUserPassword, setUserRoles, updateAdminUser }
|
|
|
5
5
|
import { adminSignIn } from "../server-fns/auth/index.js";
|
|
6
6
|
import { getCollectionDocumentVersion } from "../server-fns/collections/get.js";
|
|
7
7
|
import { setAdminLocaleFn } from "../server-fns/i18n/index.js";
|
|
8
|
+
import { acceptSession, coordinateAuthAction, notifySessionAction } from "./session-coordination.js";
|
|
8
9
|
const byline_admin_services_getCollectionDocumentVersion = async (collection, documentId, versionId, locale)=>{
|
|
9
10
|
const result = await getCollectionDocumentVersion(collection, documentId, versionId, locale);
|
|
10
11
|
if (null == result) throw new Error(`Document version not found: collection=${collection} document=${documentId} version=${versionId}`);
|
|
11
12
|
return result;
|
|
12
13
|
};
|
|
13
14
|
const bylineAdminServices = {
|
|
14
|
-
adminSignIn:
|
|
15
|
+
adminSignIn: async (args)=>{
|
|
16
|
+
const result = await coordinateAuthAction(()=>adminSignIn(args));
|
|
17
|
+
acceptSession(result.sessionId);
|
|
18
|
+
notifySessionAction();
|
|
19
|
+
return result;
|
|
20
|
+
},
|
|
15
21
|
updateAccount: updateAccount,
|
|
16
22
|
changeAccountPassword: changeAccountPassword,
|
|
17
23
|
setAdminLocale: setAdminLocaleFn,
|
|
@@ -8,19 +8,12 @@ const byline_field_services_getCollectionDocuments = ({ collection, params })=>g
|
|
|
8
8
|
}
|
|
9
9
|
});
|
|
10
10
|
const byline_field_services_uploadField = (collection, formData, createDocument)=>uploadField(collection, formData, createDocument);
|
|
11
|
-
const byline_field_services_placeTreeNode = async (input)=>{
|
|
12
|
-
const { orderKey } = await placeTreeNode({
|
|
11
|
+
const byline_field_services_placeTreeNode = async (input)=>placeTreeNode({
|
|
13
12
|
data: input
|
|
14
13
|
});
|
|
15
|
-
|
|
16
|
-
orderKey
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
const byline_field_services_removeFromTree = async (input)=>{
|
|
20
|
-
await removeFromTree({
|
|
14
|
+
const byline_field_services_removeFromTree = async (input)=>removeFromTree({
|
|
21
15
|
data: input
|
|
22
16
|
});
|
|
23
|
-
};
|
|
24
17
|
const byline_field_services_getTreeAncestors = (input)=>getTreeAncestors({
|
|
25
18
|
data: input
|
|
26
19
|
});
|
|
@@ -0,0 +1,19 @@
|
|
|
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 { ClientIpResolver } from '@byline/auth';
|
|
9
|
+
/** Normalize equivalent IPv6 spellings and IPv4-mapped IPv6 before choosing a rate-limit key. */
|
|
10
|
+
export declare function normalizeClientIp(value: string | null | undefined): string | null;
|
|
11
|
+
/**
|
|
12
|
+
* Configure either a direct peer resolver or an explicitly trusted proxy header.
|
|
13
|
+
* Header mode requires the deployment to overwrite that header and block proxy bypass.
|
|
14
|
+
* Never falls back from a missing/invalid trusted header to another client-supplied header.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createClientIpResolver(options: {
|
|
17
|
+
trustedProxyHeader?: string;
|
|
18
|
+
resolvePeerIp: ClientIpResolver;
|
|
19
|
+
}): ClientIpResolver;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { isIP } from "node:net";
|
|
2
|
+
function normalizeClientIp(value) {
|
|
3
|
+
if (!value || value.length > 64) return null;
|
|
4
|
+
const ip = value.trim();
|
|
5
|
+
if (4 === isIP(ip)) return ip;
|
|
6
|
+
if (6 !== isIP(ip) || ip.includes('%')) return null;
|
|
7
|
+
const normalized = new URL(`http://[${ip}]/`).hostname.slice(1, -1);
|
|
8
|
+
const mapped = /^::ffff:([0-9a-f]+):([0-9a-f]+)$/.exec(normalized);
|
|
9
|
+
if (mapped) {
|
|
10
|
+
const high = Number.parseInt(mapped[1], 16);
|
|
11
|
+
const low = Number.parseInt(mapped[2], 16);
|
|
12
|
+
return [
|
|
13
|
+
high >>> 8,
|
|
14
|
+
255 & high,
|
|
15
|
+
low >>> 8,
|
|
16
|
+
255 & low
|
|
17
|
+
].join('.');
|
|
18
|
+
}
|
|
19
|
+
return normalized;
|
|
20
|
+
}
|
|
21
|
+
function createClientIpResolver(options) {
|
|
22
|
+
const header = options.trustedProxyHeader;
|
|
23
|
+
if (void 0 !== header && !/^[a-z0-9-]+$/i.test(header)) throw new Error('Invalid trusted IP header');
|
|
24
|
+
let lastDiagnostic = -1 / 0;
|
|
25
|
+
return async (request)=>{
|
|
26
|
+
const ip = normalizeClientIp(void 0 === header ? await options.resolvePeerIp(request) : request.headers.get(header));
|
|
27
|
+
if (!ip && performance.now() - lastDiagnostic >= 60000) {
|
|
28
|
+
lastDiagnostic = performance.now();
|
|
29
|
+
console.warn('[byline:client-ip] No valid single client IP; check the configured peer resolver or trusted proxy header.');
|
|
30
|
+
}
|
|
31
|
+
return ip;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export { createClientIpResolver, normalizeClientIp };
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
export declare const sessionChangedError: () => Error;
|
|
9
|
+
export declare function expectedSession(): string | null;
|
|
10
|
+
export declare function sessionSnapshot(): {
|
|
11
|
+
sessionId: string | null;
|
|
12
|
+
epoch: number;
|
|
13
|
+
};
|
|
14
|
+
export declare function requireUnchanged(snapshot: ReturnType<typeof sessionSnapshot>): void;
|
|
15
|
+
export declare function flagSessionChanged(): void;
|
|
16
|
+
export declare function acceptSession(sessionId: string): void;
|
|
17
|
+
export declare function observeSession(sessionId: string): void;
|
|
18
|
+
export declare function sessionEpoch(): number;
|
|
19
|
+
export declare function sessionIsChanged(): boolean;
|
|
20
|
+
export declare function subscribeSessionChange(listener: () => void): () => void;
|
|
21
|
+
export declare function renewSingleFlight(work: () => Promise<void>): Promise<void>;
|
|
22
|
+
export declare function coordinateAuthAction<T>(work: () => Promise<T>): Promise<T>;
|
|
23
|
+
export declare function startSessionNotifications(): void;
|
|
24
|
+
export declare function notifySessionAction(): void;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const storageKey = 'byline.expected-login';
|
|
2
|
+
let expected = null;
|
|
3
|
+
let epoch = 0;
|
|
4
|
+
let changed = false;
|
|
5
|
+
let renewal;
|
|
6
|
+
const listeners = new Set();
|
|
7
|
+
const sessionChangedError = ()=>new Error('Session changed. Acknowledge the active account before continuing.');
|
|
8
|
+
function expectedSession() {
|
|
9
|
+
if ("u" < typeof window) return null;
|
|
10
|
+
if (!expected) expected = window.sessionStorage.getItem(storageKey);
|
|
11
|
+
return expected;
|
|
12
|
+
}
|
|
13
|
+
function sessionSnapshot() {
|
|
14
|
+
return {
|
|
15
|
+
sessionId: expectedSession(),
|
|
16
|
+
epoch: epoch
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function requireUnchanged(snapshot) {
|
|
20
|
+
if (changed || snapshot.epoch !== epoch || snapshot.sessionId !== expectedSession()) throw sessionChangedError();
|
|
21
|
+
}
|
|
22
|
+
function flagSessionChanged() {
|
|
23
|
+
changed = true;
|
|
24
|
+
epoch++;
|
|
25
|
+
for (const listener of listeners)listener();
|
|
26
|
+
}
|
|
27
|
+
function acceptSession(sessionId) {
|
|
28
|
+
if ("u" < typeof window) return;
|
|
29
|
+
if ('string' != typeof sessionId || !sessionId) throw new Error('Provider omitted login identity');
|
|
30
|
+
window.sessionStorage.setItem(storageKey, sessionId);
|
|
31
|
+
expected = sessionId;
|
|
32
|
+
changed = false;
|
|
33
|
+
epoch++;
|
|
34
|
+
for (const listener of listeners)listener();
|
|
35
|
+
}
|
|
36
|
+
function observeSession(sessionId) {
|
|
37
|
+
const old = expectedSession();
|
|
38
|
+
if (old && old !== sessionId) {
|
|
39
|
+
flagSessionChanged();
|
|
40
|
+
throw sessionChangedError();
|
|
41
|
+
}
|
|
42
|
+
if (!old) acceptSession(sessionId);
|
|
43
|
+
}
|
|
44
|
+
function sessionEpoch() {
|
|
45
|
+
return epoch;
|
|
46
|
+
}
|
|
47
|
+
function sessionIsChanged() {
|
|
48
|
+
return changed;
|
|
49
|
+
}
|
|
50
|
+
function subscribeSessionChange(listener) {
|
|
51
|
+
listeners.add(listener);
|
|
52
|
+
return ()=>{
|
|
53
|
+
listeners.delete(listener);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function renewSingleFlight(work) {
|
|
57
|
+
if (!renewal) renewal = work().finally(()=>{
|
|
58
|
+
renewal = void 0;
|
|
59
|
+
});
|
|
60
|
+
return renewal;
|
|
61
|
+
}
|
|
62
|
+
let authQueue = Promise.resolve();
|
|
63
|
+
function coordinateAuthAction(work) {
|
|
64
|
+
const run = authQueue.catch(()=>{}).then(async ()=>{
|
|
65
|
+
if ("u" > typeof navigator && navigator.locks) return navigator.locks.request('byline.auth', work);
|
|
66
|
+
return work();
|
|
67
|
+
});
|
|
68
|
+
authQueue = run;
|
|
69
|
+
return run;
|
|
70
|
+
}
|
|
71
|
+
let channel;
|
|
72
|
+
function startSessionNotifications() {
|
|
73
|
+
if ("u" < typeof BroadcastChannel || channel) return;
|
|
74
|
+
channel = new BroadcastChannel('byline.session');
|
|
75
|
+
channel.onmessage = ()=>{
|
|
76
|
+
flagSessionChanged();
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function notifySessionAction() {
|
|
80
|
+
startSessionNotifications();
|
|
81
|
+
channel?.postMessage({
|
|
82
|
+
type: 'session-changed'
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
export { acceptSession, coordinateAuthAction, expectedSession, flagSessionChanged, notifySessionAction, observeSession, renewSingleFlight, requireUnchanged, sessionChangedError, sessionEpoch, sessionIsChanged, sessionSnapshot, startSessionNotifications, subscribeSessionChange };
|
|
@@ -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
|
+
/** The boundary owns the retry signal; handler errors cannot turn into retryable outcomes. */
|
|
9
|
+
export declare const adminSessionMiddleware: import("@tanstack/react-start").FunctionMiddlewareAfterServer<{}, unknown, undefined, undefined, {
|
|
10
|
+
bylineExpectedSessionId: string | null;
|
|
11
|
+
}, undefined, undefined>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createMiddleware } from "@tanstack/react-start";
|
|
2
|
+
import { getRequest, setResponseHeader } from "@tanstack/react-start/server";
|
|
3
|
+
import { AuthError, ERR_ACCESS_EXPIRED, ERR_SESSION_CHANGED, ERR_UNAUTHENTICATED } from "@byline/auth";
|
|
4
|
+
import { getAdminRequestContext } from "@byline/client/server";
|
|
5
|
+
import { renewAdminSession } from "../server-fns/auth/renew.js";
|
|
6
|
+
import { coordinateAuthAction, flagSessionChanged, observeSession, renewSingleFlight, requireUnchanged, sessionSnapshot } from "./session-coordination.js";
|
|
7
|
+
import { sessionRequestKind } from "./session-request-kind.js";
|
|
8
|
+
const outcomeHeader = 'x-byline-auth-boundary';
|
|
9
|
+
const adminSessionMiddleware = createMiddleware({
|
|
10
|
+
type: 'function'
|
|
11
|
+
}).client(async ({ next })=>{
|
|
12
|
+
if ("u" < typeof window) return next({
|
|
13
|
+
sendContext: {
|
|
14
|
+
bylineExpectedSessionId: null
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
const snapshot = sessionSnapshot();
|
|
18
|
+
requireUnchanged(snapshot);
|
|
19
|
+
return next({
|
|
20
|
+
sendContext: {
|
|
21
|
+
bylineExpectedSessionId: snapshot.sessionId
|
|
22
|
+
},
|
|
23
|
+
fetch: async (input, init)=>{
|
|
24
|
+
const reusable = !(input instanceof Request) && !(init?.body instanceof ReadableStream);
|
|
25
|
+
let response = await fetch(input, init);
|
|
26
|
+
if ('changed' === response.headers.get(outcomeHeader)) {
|
|
27
|
+
flagSessionChanged();
|
|
28
|
+
throw ERR_SESSION_CHANGED({
|
|
29
|
+
message: 'session changed'
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
if ('renew' === response.headers.get(outcomeHeader)) {
|
|
33
|
+
requireUnchanged(snapshot);
|
|
34
|
+
await renewSingleFlight(()=>coordinateAuthAction(async ()=>{
|
|
35
|
+
const result = await renewAdminSession({
|
|
36
|
+
data: {
|
|
37
|
+
expectedSessionId: snapshot.sessionId ?? void 0
|
|
38
|
+
}
|
|
39
|
+
}).catch((error)=>{
|
|
40
|
+
if (error?.code === 'ERR_SESSION_CHANGED') flagSessionChanged();
|
|
41
|
+
throw error;
|
|
42
|
+
});
|
|
43
|
+
if (snapshot.sessionId && result.sessionId !== snapshot.sessionId) {
|
|
44
|
+
flagSessionChanged();
|
|
45
|
+
throw ERR_SESSION_CHANGED({
|
|
46
|
+
message: 'session changed during renewal'
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
observeSession(result.sessionId);
|
|
50
|
+
}));
|
|
51
|
+
requireUnchanged(snapshot);
|
|
52
|
+
if (!reusable) throw ERR_ACCESS_EXPIRED({
|
|
53
|
+
message: 'Session renewed. Please retry the operation.'
|
|
54
|
+
});
|
|
55
|
+
response = await fetch(input, init);
|
|
56
|
+
if ('changed' === response.headers.get(outcomeHeader)) flagSessionChanged();
|
|
57
|
+
}
|
|
58
|
+
requireUnchanged(snapshot);
|
|
59
|
+
return response;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}).server(async ({ next, context, method })=>{
|
|
63
|
+
const kind = sessionRequestKind(getRequest());
|
|
64
|
+
if (!kind) throw new Error('sessionRequestMiddleware is not configured');
|
|
65
|
+
let auth;
|
|
66
|
+
try {
|
|
67
|
+
auth = await getAdminRequestContext();
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (error instanceof AuthError && 'ERR_ACCESS_EXPIRED' === error.code) setResponseHeader(outcomeHeader, 'renew');
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
const expected = context.bylineExpectedSessionId;
|
|
73
|
+
if (('serverFn' === kind || 'GET' !== method) && (!expected || expected !== auth.sessionId)) {
|
|
74
|
+
setResponseHeader(outcomeHeader, 'changed');
|
|
75
|
+
throw ERR_SESSION_CHANGED({
|
|
76
|
+
message: 'session changed before operation'
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (!auth.sessionId) throw ERR_UNAUTHENTICATED({
|
|
80
|
+
message: 'provider omitted login identity'
|
|
81
|
+
});
|
|
82
|
+
try {
|
|
83
|
+
const result = await next();
|
|
84
|
+
const response = result.result;
|
|
85
|
+
if (response instanceof Response) response.headers.delete(outcomeHeader);
|
|
86
|
+
return result;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (error instanceof Response) error.headers.delete(outcomeHeader);
|
|
89
|
+
throw error;
|
|
90
|
+
} finally{
|
|
91
|
+
setResponseHeader(outcomeHeader, 'handled');
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
export { adminSessionMiddleware };
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
export declare function recordSessionRequestKind(request: object, kind: string): void;
|
|
9
|
+
export declare function sessionRequestKind(request: object): string | undefined;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const key = Symbol.for('byline.sessionRequestKinds');
|
|
2
|
+
const kinds = globalThis[key] ?? new WeakMap();
|
|
3
|
+
globalThis[key] = kinds;
|
|
4
|
+
function recordSessionRequestKind(request, kind) {
|
|
5
|
+
kinds.set(request, kind);
|
|
6
|
+
}
|
|
7
|
+
function sessionRequestKind(request) {
|
|
8
|
+
return kinds.get(request);
|
|
9
|
+
}
|
|
10
|
+
export { recordSessionRequestKind, sessionRequestKind };
|
|
@@ -0,0 +1,9 @@
|
|
|
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
|
+
/** Register after CSRF. Classifies SSR separately from remotely invoked business functions. */
|
|
9
|
+
export declare const sessionRequestMiddleware: import("@tanstack/react-start").RequestMiddlewareAfterServer<{}, undefined, undefined>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createMiddleware } from "@tanstack/react-start";
|
|
2
|
+
import { recordSessionRequestKind } from "./session-request-kind.js";
|
|
3
|
+
const sessionRequestMiddleware = createMiddleware().server(async (ctx)=>{
|
|
4
|
+
recordSessionRequestKind(ctx.request, ctx.handlerType);
|
|
5
|
+
return ctx.next();
|
|
6
|
+
});
|
|
7
|
+
export { sessionRequestMiddleware };
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
/** Limit the serialized RPC envelope, not just its eventual credential fields. */
|
|
9
|
+
export declare const SIGN_IN_MAX_BODY_BYTES: number;
|
|
10
|
+
export declare function wasSignInBodyChecked(request: Request): boolean;
|
|
11
|
+
/** Read a bounded clone before Start parses the original. Never await tee cancellation. */
|
|
12
|
+
export declare function checkSignInBody(request: Request): Promise<Response | undefined>;
|