@antelopejs/dms 0.8.3 → 0.9.1
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/CHANGELOG.md +36 -0
- package/data/module-store/catalog.json +71 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -1
- package/dist/implementations/dms/frontend-backend-proxies.d.ts +3 -0
- package/dist/implementations/dms/frontend-backend-proxies.js +39 -0
- package/dist/implementations/dms/frontend-backend-proxies.js.map +1 -0
- package/dist/implementations/dms/frontend-bootstrap.d.ts +1 -0
- package/dist/implementations/dms/frontend-bootstrap.js +13 -0
- package/dist/implementations/dms/frontend-bootstrap.js.map +1 -1
- package/dist/implementations/dms/frontend-modules.d.ts +6 -2
- package/dist/implementations/dms/frontend-modules.js +37 -8
- package/dist/implementations/dms/frontend-modules.js.map +1 -1
- package/dist/implementations/dms/frontend-ready.d.ts +8 -0
- package/dist/implementations/dms/frontend-ready.js +34 -0
- package/dist/implementations/dms/frontend-ready.js.map +1 -0
- package/dist/implementations/dms/manifest.d.ts +7 -0
- package/dist/implementations/dms/manifest.js +13 -0
- package/dist/implementations/dms/manifest.js.map +1 -1
- package/dist/implementations/dms/menu-categories.d.ts +16 -0
- package/dist/implementations/dms/menu-categories.js +30 -0
- package/dist/implementations/dms/menu-categories.js.map +1 -0
- package/dist/implementations/dms/nav-badges.d.ts +0 -8
- package/dist/implementations/dms/nav-badges.js +0 -16
- package/dist/implementations/dms/nav-badges.js.map +1 -1
- package/dist/implementations/dms/page.d.ts +13 -5
- package/dist/implementations/dms/page.js +72 -41
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/implementations/dms-html-render/index.js +5 -2
- package/dist/implementations/dms-html-render/index.js.map +1 -1
- package/dist/implementations/dms-notifications/index.d.ts +4 -3
- package/dist/implementations/dms-notifications/index.js +14 -6
- package/dist/implementations/dms-notifications/index.js.map +1 -1
- package/dist/pages/settings/settings-index.js +3 -2
- package/dist/pages/settings/settings-index.js.map +1 -1
- package/dist/pages/settings/users/invites.js +2 -2
- package/dist/pages/settings/users/invites.js.map +1 -1
- package/dist/pages/settings/users/member-change-notifications.js +17 -6
- package/dist/pages/settings/users/member-change-notifications.js.map +1 -1
- package/dist/pages/settings/users/member-invite-form.d.ts +3 -2
- package/dist/pages/settings/users/member-invite-form.js +7 -4
- package/dist/pages/settings/users/member-invite-form.js.map +1 -1
- package/dist/pages/settings/users/member-roles-field.js +2 -1
- package/dist/pages/settings/users/member-roles-field.js.map +1 -1
- package/dist/pages/settings/users/members.js +4 -1
- package/dist/pages/settings/users/members.js.map +1 -1
- package/dist/pages/settings/users/notifications.d.ts +6 -0
- package/dist/pages/settings/users/notifications.js +23 -15
- package/dist/pages/settings/users/notifications.js.map +1 -1
- package/dist/pages/settings/users/profile.js +1 -1
- package/dist/pages/settings/users/profile.js.map +1 -1
- package/dist/pages/settings/users/region-form.d.ts +1 -1
- package/dist/pages/settings/users/region-form.js +2 -1
- package/dist/pages/settings/users/region-form.js.map +1 -1
- package/dist/pages/settings/users/role-editor-store.d.ts +3 -1
- package/dist/pages/settings/users/role-editor-store.js +17 -4
- package/dist/pages/settings/users/role-editor-store.js.map +1 -1
- package/dist/pages/settings/users/roles.js +1 -0
- package/dist/pages/settings/users/roles.js.map +1 -1
- package/dist/realtime/registry.d.ts +1 -0
- package/dist/realtime/registry.js +5 -0
- package/dist/realtime/registry.js.map +1 -1
- package/dist/routes/index.d.ts +1 -0
- package/dist/routes/index.js +1 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/realtime.d.ts +5 -1
- package/dist/routes/realtime.js +16 -9
- package/dist/routes/realtime.js.map +1 -1
- package/dist/routes/store.d.ts +21 -0
- package/dist/routes/store.js +130 -0
- package/dist/routes/store.js.map +1 -0
- package/dist/store/activity.d.ts +2 -0
- package/dist/store/activity.js +12 -0
- package/dist/store/activity.js.map +1 -0
- package/dist/store/cache.d.ts +8 -0
- package/dist/store/cache.js +29 -0
- package/dist/store/cache.js.map +1 -0
- package/dist/store/catalog.d.ts +4 -0
- package/dist/store/catalog.js +69 -0
- package/dist/store/catalog.js.map +1 -0
- package/dist/store/compatibility.d.ts +3 -0
- package/dist/store/compatibility.js +45 -0
- package/dist/store/compatibility.js.map +1 -0
- package/dist/store/declarations.d.ts +18 -0
- package/dist/store/declarations.js +116 -0
- package/dist/store/declarations.js.map +1 -0
- package/dist/store/frontend-compatibility.d.ts +18 -0
- package/dist/store/frontend-compatibility.js +93 -0
- package/dist/store/frontend-compatibility.js.map +1 -0
- package/dist/store/interfaces.d.ts +6 -0
- package/dist/store/interfaces.js +126 -0
- package/dist/store/interfaces.js.map +1 -0
- package/dist/store/listing.d.ts +2 -0
- package/dist/store/listing.js +180 -0
- package/dist/store/listing.js.map +1 -0
- package/dist/store/operations.d.ts +8 -0
- package/dist/store/operations.js +254 -0
- package/dist/store/operations.js.map +1 -0
- package/dist/store/registry.d.ts +4 -0
- package/dist/store/registry.js +61 -0
- package/dist/store/registry.js.map +1 -0
- package/dist/store/requirements.d.ts +14 -0
- package/dist/store/requirements.js +81 -0
- package/dist/store/requirements.js.map +1 -0
- package/dist/store/runtime.d.ts +11 -0
- package/dist/store/runtime.js +78 -0
- package/dist/store/runtime.js.map +1 -0
- package/dist/store/tarball.d.ts +2 -0
- package/dist/store/tarball.js +80 -0
- package/dist/store/tarball.js.map +1 -0
- package/dist/store/types.d.ts +98 -0
- package/dist/store/types.js +3 -0
- package/dist/store/types.js.map +1 -0
- package/dist/test/integration/account-deletion.test.js +2 -2
- package/dist/test/integration/account-deletion.test.js.map +1 -1
- package/dist/test/integration/auth/auth-key-rotation.test.js +2 -1
- package/dist/test/integration/auth/auth-key-rotation.test.js.map +1 -1
- package/dist/test/integration/auth/current-password.test.js +2 -1
- package/dist/test/integration/auth/current-password.test.js.map +1 -1
- package/dist/test/integration/auth/session-revocation.test.js +2 -1
- package/dist/test/integration/auth/session-revocation.test.js.map +1 -1
- package/dist/test/integration/auth/sign-in-country.test.js +2 -1
- package/dist/test/integration/auth/sign-in-country.test.js.map +1 -1
- package/dist/test/integration/auth/two-factor-codes.test.js +2 -1
- package/dist/test/integration/auth/two-factor-codes.test.js.map +1 -1
- package/dist/test/integration/auth/two-factor-password.test.js +3 -2
- package/dist/test/integration/auth/two-factor-password.test.js.map +1 -1
- package/dist/test/integration/email-change.test.js +2 -1
- package/dist/test/integration/email-change.test.js.map +1 -1
- package/dist/test/integration/files/component-files.test.js +2 -2
- package/dist/test/integration/files/component-files.test.js.map +1 -1
- package/dist/test/integration/member-settings-access.test.js +8 -7
- package/dist/test/integration/member-settings-access.test.js.map +1 -1
- package/dist/test/integration/menu-access-events.test.d.ts +1 -0
- package/dist/test/integration/menu-access-events.test.js +148 -0
- package/dist/test/integration/menu-access-events.test.js.map +1 -0
- package/dist/test/integration/notification-inbox.test.js +4 -1
- package/dist/test/integration/notification-inbox.test.js.map +1 -1
- package/dist/test/integration/notification-locked-subjects.test.js +3 -2
- package/dist/test/integration/notification-locked-subjects.test.js.map +1 -1
- package/dist/test/integration/notification-unread-badge.test.js +51 -5
- package/dist/test/integration/notification-unread-badge.test.js.map +1 -1
- package/dist/test/integration/page-layout-route-guard.test.d.ts +1 -0
- package/dist/test/integration/page-layout-route-guard.test.js +56 -0
- package/dist/test/integration/page-layout-route-guard.test.js.map +1 -0
- package/dist/test/integration/permission-warnings.test.js +3 -2
- package/dist/test/integration/permission-warnings.test.js.map +1 -1
- package/dist/test/integration/removed-member-notifications.test.js +3 -2
- package/dist/test/integration/removed-member-notifications.test.js.map +1 -1
- package/dist/test/integration/role-delete-reassign.test.js +2 -1
- package/dist/test/integration/role-delete-reassign.test.js.map +1 -1
- package/dist/test/integration/role-permission-ancestors.test.js +2 -1
- package/dist/test/integration/role-permission-ancestors.test.js.map +1 -1
- package/dist/test/unit/frontend-bootstrap.test.js +13 -0
- package/dist/test/unit/frontend-bootstrap.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/category-slug-registry.test.js +14 -5
- package/dist/test/unit/implementations/dms/category-slug-registry.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.d.ts +1 -0
- package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.js +66 -0
- package/dist/test/unit/implementations/dms/frontend-backend-proxies.test.js.map +1 -0
- package/dist/test/unit/implementations/dms/manifest-strip.test.js +26 -0
- package/dist/test/unit/implementations/dms/manifest-strip.test.js.map +1 -1
- package/dist/test/unit/implementations/dms/nav-badges.test.js +23 -9
- package/dist/test/unit/implementations/dms/nav-badges.test.js.map +1 -1
- package/dist/test/unit/implementations/dms-html-render/generate-html.test.js +22 -4
- package/dist/test/unit/implementations/dms-html-render/generate-html.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms/page-layout-query.test.js +12 -0
- package/dist/test/unit/interfaces/dms/page-layout-query.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms/page-teardown.test.js +1 -1
- package/dist/test/unit/interfaces/dms/page-teardown.test.js.map +1 -1
- package/dist/test/unit/pages/settings/member-roles-field.test.js +1 -1
- package/dist/test/unit/pages/settings/member-roles-field.test.js.map +1 -1
- package/dist/test/unit/pages/settings/role-delete-confirm.test.js.map +1 -1
- package/dist/test/unit/realtime/menu-topics.test.d.ts +1 -0
- package/dist/test/unit/realtime/menu-topics.test.js +26 -0
- package/dist/test/unit/realtime/menu-topics.test.js.map +1 -0
- package/dist/test/unit/store/store.test.d.ts +1 -0
- package/dist/test/unit/store/store.test.js +127 -0
- package/dist/test/unit/store/store.test.js.map +1 -0
- package/dist/validation/store.schema.d.ts +39 -0
- package/dist/validation/store.schema.js +27 -0
- package/dist/validation/store.schema.js.map +1 -0
- package/frontend-vue/deferred-plugins.ts +3 -4
- package/frontend-vue/dms.email.ts +5 -1
- package/frontend-vue/dms.frontend.ts +15 -5
- package/frontend-vue/dms.server.ts +74 -0
- package/frontend-vue/layers/dms-auth/app/composables/useLogout.ts +1 -1
- package/frontend-vue/layers/dms-auth/app/composables/usePostLoginRedirect.ts +16 -3
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/2fa.vue +3 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/backup.vue +3 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/forgot-validation.vue +1 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/oauth-complete.vue +26 -3
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/recover.vue +1 -1
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/signup.vue +22 -25
- package/frontend-vue/layers/dms-auth/app/custom-pages/auth/validate.vue +1 -1
- package/frontend-vue/layers/dms-auth/app/middleware/auth.ts +5 -7
- package/frontend-vue/layers/dms-auth/app/plugins/permissions-preload.ts +17 -0
- package/frontend-vue/layers/dms-auth/server/accounts.ts +135 -0
- package/frontend-vue/layers/dms-auth/server/actions.ts +251 -0
- package/frontend-vue/layers/dms-auth/server/oauth.ts +260 -0
- package/frontend-vue/layers/dms-auth/server/session.ts +158 -0
- package/frontend-vue/layers/dms-core/app/build/utils/asyncDataScope.ts +47 -0
- package/frontend-vue/layers/dms-core/app/build/utils/permission-preview.ts +6 -2
- package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +148 -0
- package/frontend-vue/layers/dms-core/app/composables/auth/useAuthFetch.ts +7 -20
- package/frontend-vue/layers/dms-core/app/composables/auth/useCurrentUser.ts +2 -2
- package/frontend-vue/layers/dms-core/app/composables/auth/useMultiAccount.ts +18 -5
- package/frontend-vue/layers/dms-core/app/composables/auth/usePermissions.ts +35 -6
- package/frontend-vue/layers/dms-core/app/composables/auth/useSessionRecovery.ts +1 -1
- package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +20 -14
- package/frontend-vue/layers/dms-core/app/composables/period/periodClock.ts +121 -0
- package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +42 -0
- package/frontend-vue/layers/dms-core/app/composables/period/periodTimeZone.ts +31 -0
- package/frontend-vue/layers/dms-core/app/composables/period/presetResolvers.ts +48 -19
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +33 -4
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +78 -25
- package/frontend-vue/layers/dms-core/app/composables/realtime/usePageRealtime.ts +3 -0
- package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +36 -6
- package/frontend-vue/layers/dms-core/app/composables/user/useUserRegionalPreferences.ts +1 -1
- package/frontend-vue/layers/dms-core/app/plugins/async-data-scope.ts +26 -0
- package/frontend-vue/layers/dms-core/app/utils/pageRoutes.ts +22 -0
- package/frontend-vue/layers/dms-core/app/utils/regional.ts +36 -3
- package/frontend-vue/layers/dms-core/app/utils/trailingDeduper.ts +10 -1
- package/frontend-vue/layers/dms-core/shared/types/app-config.ts +48 -4
- package/frontend-vue/layers/dms-layout/app/app.config.ts +42 -23
- package/frontend-vue/layers/dms-layout/app/assets/css/main.css +72 -20
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardFrame.vue +9 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +0 -3
- package/frontend-vue/layers/dms-layout/app/build/components/notification/NotificationPopover.vue +9 -3
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreDependencyPicker.vue +136 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +98 -0
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +247 -29
- package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +19 -2
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/members/MemberRolePicker.vue +13 -12
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +17 -7
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationPreferencesForm.vue +0 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfilePersonalInfo.vue +36 -33
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/profile/ProfileSecuritySummary.vue +1 -2
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-types.ts +5 -4
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecuritySessions.vue +35 -28
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityStatus.vue +0 -1
- package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/security/SecurityTwoFactor.vue +1 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/dev-reload/useDevReloadHolder.ts +7 -6
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/sidePanelState.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/navigation/useVisitNavBadges.ts +26 -0
- package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationCatalog.ts +25 -23
- package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationPreferences.ts +44 -23
- package/frontend-vue/layers/dms-layout/app/build/composables/security/useSessionHandoff.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/preferencesSummary.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useAccountData.ts +3 -2
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfileAvatar.ts +1 -18
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/profile/useProfilePreferences.ts +19 -23
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityFormat.ts +6 -3
- package/frontend-vue/layers/dms-layout/app/build/composables/settings/security/useSecurityOverview.ts +13 -18
- package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +186 -120
- package/frontend-vue/layers/dms-layout/app/build/utils/module-store-install.ts +146 -0
- package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +8 -6
- package/frontend-vue/layers/dms-layout/app/build/utils/publicErrorMessage.ts +19 -0
- package/frontend-vue/layers/dms-layout/app/components/navigation/CollapsedMenuItem.vue +0 -11
- package/frontend-vue/layers/dms-layout/app/components/navigation/NavigationMenu.vue +3 -13
- package/frontend-vue/layers/dms-layout/app/composables/general/useSystemState.ts +7 -11
- package/frontend-vue/layers/dms-layout/app/composables/notification/useNotifications.ts +103 -22
- package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +109 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/useModulesListing.ts +25 -102
- package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +0 -10
- package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +177 -96
- package/frontend-vue/layers/dms-layout/app/composables/profile/useUserLanguage.ts +3 -2
- package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +341 -62
- package/frontend-vue/layers/dms-layout/app/emails/components/EmailLayout.vue +31 -11
- package/frontend-vue/layers/dms-layout/app/error.vue +5 -2
- package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +1 -1
- package/frontend-vue/layers/dms-layout/app/middleware/page-leave-guard.global.ts +2 -2
- package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +12 -9
- package/frontend-vue/layers/dms-layout/app/plugins/dms-menu-sync.client.ts +31 -3
- package/frontend-vue/layers/dms-layout/app/plugins/favicon.ts +58 -0
- package/frontend-vue/layers/dms-layout/app/plugins/notification-stream.client.ts +15 -7
- package/frontend-vue/layers/dms-layout/app/plugins/profile-sync.client.ts +5 -1
- package/frontend-vue/layers/dms-layout/app/plugins/regional-preferences.ts +10 -4
- package/frontend-vue/layers/dms-layout/app/plugins/time-zone-cookie.client.ts +12 -0
- package/frontend-vue/layers/dms-layout/app/types/module-store.ts +134 -0
- package/frontend-vue/layers/dms-layout/app/types/page.ts +13 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +107 -9
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +107 -9
- package/frontend-vue/layers/dms-layout/server/html-render.ts +58 -0
- package/frontend-vue/layers/dms-layout/server/render-token.ts +95 -0
- package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/useOnboarding.ts +25 -21
- package/frontend-vue/layers/dms-onboarding/app/middleware/onboarding.global.ts +1 -1
- package/frontend-vue/layers/dms-onboarding/app/plugins/onboarding-preload.ts +9 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +4 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/files/useFileMetadata.ts +162 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +23 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageDataRefresh.ts +101 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/navigation/useNavBadges.ts +33 -6
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +19 -16
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodQuery.ts +40 -20
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +6 -8
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableListData.ts +42 -14
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +19 -8
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +5 -2
- package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +9 -9
- package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +20 -18
- package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/File.vue +26 -132
- package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +40 -105
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +2 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +4 -4
- package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +9 -13
- package/frontend-vue/layers/dms-ui/app/components/table-view/CascaderPath.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/table-view/FilePreview.vue +12 -24
- package/frontend-vue/layers/dms-ui/app/components/table-view/ImagePreview.vue +17 -29
- package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +7 -3
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +20 -8
- package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +8 -11
- package/frontend-vue/layers/dms-ui/app/composables/chart/useChartFetch.ts +146 -182
- package/frontend-vue/layers/dms-ui/app/composables/live/usePageLiveRefresh.ts +8 -2
- package/frontend-vue/layers/dms-ui/app/composables/useFileReadUrls.ts +11 -99
- package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +32 -3
- package/frontend-vue/package.json +3 -3
- package/frontend-vue/pnpm-lock.yaml +16 -22
- package/frontend-vue/server/private-options.ts +27 -0
- package/frontend-vue/tests/async-data-scope.test.ts +121 -0
- package/frontend-vue/tests/auth-middleware-error.test.ts +3 -3
- package/frontend-vue/tests/banner-block-fetch.test.ts +3 -0
- package/frontend-vue/tests/block-fetch-options-forwarding.test.ts +3 -0
- package/frontend-vue/tests/block-items-period-realtime.test.ts +3 -0
- package/frontend-vue/tests/block-not-found.test.ts +16 -2
- package/frontend-vue/tests/block-page-refresh.test.ts +3 -0
- package/frontend-vue/tests/block-route-params.test.ts +3 -0
- package/frontend-vue/tests/branding-defaults.test.ts +34 -0
- package/frontend-vue/tests/builtin-emails.test.ts +23 -2
- package/frontend-vue/tests/chart-live-pause.test.ts +4 -1
- package/frontend-vue/tests/chart-value-propagation.test.ts +4 -1
- package/frontend-vue/tests/composed-text-blocks.test.ts +3 -0
- package/frontend-vue/tests/email-layout.test.ts +66 -4
- package/frontend-vue/tests/error-page-message.test.ts +25 -2
- package/frontend-vue/tests/favicon.test.ts +131 -0
- package/frontend-vue/tests/file-fields-ssr.test.ts +145 -0
- package/frontend-vue/tests/file-read-urls.test.ts +145 -34
- package/frontend-vue/tests/file-unavailable.test.ts +5 -1
- package/frontend-vue/tests/fixtures/dataEngine.ts +44 -0
- package/frontend-vue/tests/form-load-error.test.ts +55 -1
- package/frontend-vue/tests/helpers/api-data-runtime.ts +77 -0
- package/frontend-vue/tests/image-hydration-ids.test.ts +21 -16
- package/frontend-vue/tests/image-reference-preservation.test.ts +59 -53
- package/frontend-vue/tests/input-number-adjust.test.ts +14 -0
- package/frontend-vue/tests/key-value-list-copy.test.ts +3 -0
- package/frontend-vue/tests/member-role-picker.test.ts +4 -2
- package/frontend-vue/tests/menu-sync-reconnect.test.ts +47 -0
- package/frontend-vue/tests/metadata-retry.test.ts +5 -1
- package/frontend-vue/tests/module-routing.test.ts +2 -5
- package/frontend-vue/tests/module-store-catalog.test.ts +270 -51
- package/frontend-vue/tests/modules-catalog.test.ts +17 -4
- package/frontend-vue/tests/modules-listing-transfer.test.ts +95 -0
- package/frontend-vue/tests/multi-account-validate.test.ts +23 -1
- package/frontend-vue/tests/nav-badge-tones.test.ts +66 -3
- package/frontend-vue/tests/notification-counts-from-writes.test.ts +223 -0
- package/frontend-vue/tests/notification-counts-transfer.test.ts +88 -0
- package/frontend-vue/tests/notification-inbox-display.test.ts +57 -5
- package/frontend-vue/tests/notification-popover-errors.test.ts +21 -1
- package/frontend-vue/tests/oauth-complete-handoff.test.ts +209 -0
- package/frontend-vue/tests/onboarding-middleware.test.ts +1 -1
- package/frontend-vue/tests/onboarding-transfer.test.ts +106 -0
- package/frontend-vue/tests/page-data-refresh.test.ts +141 -0
- package/frontend-vue/tests/page-fill-height.test.ts +16 -3
- package/frontend-vue/tests/page-no-visible-blocks.test.ts +12 -2
- package/frontend-vue/tests/page-realtime-remount.test.ts +1 -1
- package/frontend-vue/tests/page-record-label.test.ts +0 -7
- package/frontend-vue/tests/page-validation-redirect.test.ts +12 -2
- package/frontend-vue/tests/period-hydration-now.test.ts +185 -0
- package/frontend-vue/tests/period-live-pause.test.ts +3 -0
- package/frontend-vue/tests/period-reader-zone.test.ts +137 -0
- package/frontend-vue/tests/period-scope-first-fetch.test.ts +17 -1
- package/frontend-vue/tests/period-ssr.test.ts +195 -0
- package/frontend-vue/tests/permission-preview-partial.test.ts +0 -2
- package/frontend-vue/tests/permissions-transfer.test.ts +194 -0
- package/frontend-vue/tests/post-login-redirect.test.ts +10 -5
- package/frontend-vue/tests/profile-personal-info-ssr.test.ts +150 -0
- package/frontend-vue/tests/profile-preferences-summary.test.ts +15 -11
- package/frontend-vue/tests/realtime-reconnect.test.ts +22 -3
- package/frontend-vue/tests/roles-name-errors.test.ts +1 -1
- package/frontend-vue/tests/row-side-panel.test.ts +1 -1
- package/frontend-vue/tests/security-sessions-load-error.test.ts +3 -1
- package/frontend-vue/tests/server-routes-vs-pages.test.ts +26 -0
- package/frontend-vue/tests/server-routes.test.ts +582 -0
- package/frontend-vue/tests/session-handoff.test.ts +1 -1
- package/frontend-vue/tests/settings-reads-ssr.test.ts +232 -0
- package/frontend-vue/tests/side-panels.test.ts +8 -1
- package/frontend-vue/tests/sign-in-exit-history.test.ts +219 -0
- package/frontend-vue/tests/signup-invitation-link.test.ts +56 -8
- package/frontend-vue/tests/site-layout-categories.test.ts +42 -0
- package/frontend-vue/tests/site-layout-hydration.test.ts +3 -3
- package/frontend-vue/tests/site-layout-rendered.test.ts +103 -0
- package/frontend-vue/tests/site-layout-resync.test.ts +87 -0
- package/frontend-vue/tests/ssr-guard-preload.test.ts +188 -0
- package/frontend-vue/tests/table-cell-tones.test.ts +36 -1
- package/frontend-vue/tests/table-data-ssr.test.ts +195 -0
- package/frontend-vue/tests/table-list-data-requests.test.ts +154 -0
- package/frontend-vue/tests/table-retry-loading.test.ts +37 -0
- package/frontend-vue/tests/table-source-navigation.test.ts +0 -1
- package/frontend-vue/tests/table-source-period-scope.test.ts +0 -1
- package/frontend-vue/tests/table-view-views.test.ts +1 -1
- package/frontend-vue/tests/top-list-card-actions.test.ts +3 -0
- package/frontend-vue/tests/top-list-card-period-badge-ssr.test.ts +212 -0
- package/frontend-vue/tests/top-list-card-period-badge.test.ts +79 -0
- package/frontend-vue/tests/top-list-card-value-suffix.test.ts +3 -0
- package/frontend-vue/tests/trailing-deduper.test.ts +22 -0
- package/frontend-vue/tests/use-api-data-update.test.ts +119 -0
- package/frontend-vue/tests/use-api-data.test.ts +410 -0
- package/frontend-vue/tests/user-language.test.ts +8 -5
- package/frontend-vue/tests/widget-label-i18n.test.ts +4 -1
- package/frontend-vue/vitest.config.ts +6 -0
- package/package.json +2 -2
- package/skills/dms/SKILL.md +2 -1
- package/skills/dms-module/SKILL.md +1 -1
- package/skills/dms-pages/SKILL.md +4 -1
- package/frontend-vue/layers/dms-core/app/build/composables/page/usePageRecordLabel.ts +0 -9
- package/frontend-vue/layers/dms-layout/app/composables/page/usePrefetch.ts +0 -21
- package/frontend-vue/layers/dms-ui/app/build/composables/form/useControlError.ts +0 -10
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useServerRenderedAsyncData.ts +0 -23
- package/frontend-vue/tests/control-error-path.test.ts +0 -11
- package/frontend-vue/tests/modules-listing-per-request.test.ts +0 -104
- package/frontend-vue/tests/page-layout-background-refresh.test.ts +0 -321
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { Component } from "vue";
|
|
2
2
|
import defaults from "./layers/dms-layout/app/app.config";
|
|
3
3
|
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Branding shared with transactional templates, without the browser module.
|
|
6
|
+
* The loader merges the layout's app config into theirs as well; `branding`
|
|
7
|
+
* holds no array that the second merge would repeat.
|
|
8
|
+
*/
|
|
5
9
|
export const appConfig = { branding: defaults.branding };
|
|
6
10
|
|
|
7
11
|
interface EmailModule {
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { defineAsyncComponent, type Component } from "vue";
|
|
2
2
|
import {
|
|
3
3
|
type DmsFrontendModule,
|
|
4
|
-
useDmsAppConfig,
|
|
5
4
|
useDmsRuntimeConfig,
|
|
6
5
|
} from "#dms/frontend-module";
|
|
7
|
-
import { defu } from "defu";
|
|
8
|
-
import appConfig from "./layers/dms-layout/app/app.config";
|
|
9
6
|
import authMiddleware from "./layers/dms-auth/app/middleware/auth";
|
|
7
|
+
import permissionsPreloadPlugin from "./layers/dms-auth/app/plugins/permissions-preload";
|
|
8
|
+
import asyncDataScopePlugin from "./layers/dms-core/app/plugins/async-data-scope";
|
|
10
9
|
import homepageRedirectMiddleware from "./layers/dms-layout/app/middleware/homepage-redirect.global";
|
|
11
10
|
import moduleRoutingMiddleware from "./layers/dms-layout/app/middleware/module-routing.global";
|
|
12
11
|
import pageLeaveGuardMiddleware from "./layers/dms-layout/app/middleware/page-leave-guard.global";
|
|
13
12
|
import accessibilityPlugin from "./layers/dms-layout/app/plugins/accessibility";
|
|
14
13
|
import colorModePlugin from "./layers/dms-layout/app/plugins/color-mode";
|
|
14
|
+
import faviconPlugin from "./layers/dms-layout/app/plugins/favicon";
|
|
15
15
|
import interfaceScalePlugin from "./layers/dms-layout/app/plugins/interface-scale";
|
|
16
16
|
import fontPreloadPlugin from "./layers/dms-layout/app/plugins/font-preload";
|
|
17
17
|
import permissionPreviewPrepaintPlugin from "./layers/dms-layout/app/plugins/permission-preview-prepaint";
|
|
@@ -19,6 +19,7 @@ import regionalPreferencesPlugin from "./layers/dms-layout/app/plugins/regional-
|
|
|
19
19
|
import seoPlugin from "./layers/dms-layout/app/plugins/seo";
|
|
20
20
|
import sidePanelHostPlugin from "./layers/dms-layout/app/plugins/side-panel-host";
|
|
21
21
|
import onboardingMiddleware from "./layers/dms-onboarding/app/middleware/onboarding.global";
|
|
22
|
+
import onboardingPreloadPlugin from "./layers/dms-onboarding/app/plugins/onboarding-preload";
|
|
22
23
|
import registerPlugin from "./layers/dms-ui/app/plugins/register";
|
|
23
24
|
import shortcutsPlugin from "./layers/dms-ui/app/plugins/shortcuts";
|
|
24
25
|
import tableViewDisplaysPlugin from "./layers/dms-ui/app/plugins/table-view-displays";
|
|
@@ -164,6 +165,13 @@ function registerPlugins(sdk: Parameters<DmsFrontendModule["setup"]>[0]): void {
|
|
|
164
165
|
await plugin.default(context);
|
|
165
166
|
}, clientOnly);
|
|
166
167
|
}
|
|
168
|
+
// Before any data is read: every key lives in the session's scope.
|
|
169
|
+
sdk.registerPlugin(asyncDataScopePlugin);
|
|
170
|
+
// First: the requests the middlewares wait on start with the app, alongside
|
|
171
|
+
// the ones the plugins below and the page make, instead of one after the
|
|
172
|
+
// other once the page has set up.
|
|
173
|
+
sdk.registerPlugin(onboardingPreloadPlugin);
|
|
174
|
+
sdk.registerPlugin(permissionsPreloadPlugin);
|
|
167
175
|
sdk.registerPlugin(colorModePlugin);
|
|
168
176
|
sdk.registerPlugin(permissionPreviewPrepaintPlugin);
|
|
169
177
|
sdk.registerPlugin(fontPreloadPlugin);
|
|
@@ -171,6 +179,7 @@ function registerPlugins(sdk: Parameters<DmsFrontendModule["setup"]>[0]): void {
|
|
|
171
179
|
sdk.registerPlugin(accessibilityPlugin);
|
|
172
180
|
sdk.registerPlugin(regionalPreferencesPlugin);
|
|
173
181
|
sdk.registerPlugin(seoPlugin);
|
|
182
|
+
sdk.registerPlugin(faviconPlugin);
|
|
174
183
|
sdk.registerPlugin(sidePanelHostPlugin);
|
|
175
184
|
sdk.registerPlugin(registerPlugin);
|
|
176
185
|
sdk.registerPlugin(shortcutsPlugin);
|
|
@@ -195,12 +204,13 @@ function registerMiddleware(
|
|
|
195
204
|
|
|
196
205
|
const frontendModule: DmsFrontendModule = {
|
|
197
206
|
componentPrefix: "Dms",
|
|
207
|
+
// The layout's app config is not merged here: the loader applies every
|
|
208
|
+
// layer's `app/app.config.ts` before any module is set up, and a second
|
|
209
|
+
// merge would repeat the arrays defu concatenates.
|
|
198
210
|
setup(sdk) {
|
|
199
211
|
const runtimeDms = (useDmsRuntimeConfig().public as DmsPublicOptions).dms;
|
|
200
212
|
if (runtimeDms && typeof window !== "undefined")
|
|
201
213
|
runtimeDms.baseURL = window.location.origin;
|
|
202
|
-
const runtimeAppConfig = useDmsAppConfig();
|
|
203
|
-
Object.assign(runtimeAppConfig, defu(runtimeAppConfig, appConfig));
|
|
204
214
|
registerPlugins(sdk);
|
|
205
215
|
registerMiddleware(sdk);
|
|
206
216
|
registerComponents(sdk);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { defineDmsServer } from "#dms/server";
|
|
2
|
+
import * as actions from "./layers/dms-auth/server/actions";
|
|
3
|
+
import * as oauth from "./layers/dms-auth/server/oauth";
|
|
4
|
+
import * as session from "./layers/dms-auth/server/session";
|
|
5
|
+
import { renderHtml } from "./layers/dms-layout/server/html-render";
|
|
6
|
+
|
|
7
|
+
// What the DMS serves from the frontend server: its session, sign-in and
|
|
8
|
+
// provider sign-in routes, the session logic the server runs outside them,
|
|
9
|
+
// the e-mail render the backend asks for, and the backend prefixes the
|
|
10
|
+
// browser reaches through it. The renderer proxies no path on its own.
|
|
11
|
+
export default defineDmsServer((server) => {
|
|
12
|
+
// The backend's API and the DMS's own routes (menu, page, page layouts,
|
|
13
|
+
// permissions, realtime), with the session's token added server-side.
|
|
14
|
+
server.registerBackendProxy("/api/**");
|
|
15
|
+
server.registerBackendProxy("/dms/**");
|
|
16
|
+
|
|
17
|
+
server.registerRoute("GET", "/api/_auth/session", session.readSessionRoute);
|
|
18
|
+
server.registerRoute(
|
|
19
|
+
"POST",
|
|
20
|
+
"/api/_auth/session",
|
|
21
|
+
session.refreshSessionRoute,
|
|
22
|
+
);
|
|
23
|
+
server.registerRoute("DELETE", "/api/_auth/session", session.logoutRoute);
|
|
24
|
+
server.registerRoute(
|
|
25
|
+
["HEAD", "PUT", "PATCH", "OPTIONS"],
|
|
26
|
+
"/api/_auth/session",
|
|
27
|
+
session.sessionMethodNotAllowed,
|
|
28
|
+
{ sameOrigin: false },
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
server.registerRoute("POST", "/auth/login", actions.login);
|
|
32
|
+
server.registerRoute("POST", "/auth/register", actions.signup);
|
|
33
|
+
server.registerRoute("POST", "/auth/verify-2fa", actions.verifyTwoFactor);
|
|
34
|
+
server.registerRoute(
|
|
35
|
+
"POST",
|
|
36
|
+
"/auth/request-2fa-email",
|
|
37
|
+
actions.requestTwoFactorEmail,
|
|
38
|
+
);
|
|
39
|
+
server.registerRoute("POST", "/auth/establish", actions.establish);
|
|
40
|
+
server.registerRoute("POST", "/auth/switch-account", actions.switchAccount);
|
|
41
|
+
server.registerRoute("POST", "/auth/switch-tenant", actions.switchTenant);
|
|
42
|
+
server.registerRoute(
|
|
43
|
+
"POST",
|
|
44
|
+
"/auth/validate-account",
|
|
45
|
+
actions.validateAccount,
|
|
46
|
+
);
|
|
47
|
+
server.registerRoute(
|
|
48
|
+
"POST",
|
|
49
|
+
"/auth/remove-account",
|
|
50
|
+
actions.removeStoredAccount,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
server.registerRoute("GET", "/auth/oauth/:provider/start", oauth.oauthStart);
|
|
54
|
+
server.registerRoute(
|
|
55
|
+
"GET",
|
|
56
|
+
"/auth/oauth/:provider/callback",
|
|
57
|
+
oauth.oauthCallback,
|
|
58
|
+
);
|
|
59
|
+
server.registerRoute("POST", "/auth/oauth/handoff", oauth.oauthHandoff);
|
|
60
|
+
|
|
61
|
+
server.registerAuth({
|
|
62
|
+
signInPath: "/auth",
|
|
63
|
+
signInExempt: ["/auth/**", "/onboarding"],
|
|
64
|
+
refreshSession: session.renewSession,
|
|
65
|
+
publicSession: session.publicSession,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// The backend authenticates with a single-use service token, not a
|
|
69
|
+
// browser session, and its default `renderEndpoint` names this path.
|
|
70
|
+
server.registerRoute("POST", "/api/html/render", renderHtml, {
|
|
71
|
+
sameOrigin: false,
|
|
72
|
+
overridesBackend: true,
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -13,7 +13,7 @@ const FALLBACK_AUTH_ROUTE = "/auth";
|
|
|
13
13
|
* Any "Preview as role" state goes too.
|
|
14
14
|
*/
|
|
15
15
|
export function useLogout() {
|
|
16
|
-
const { user, clear: clearSession } =
|
|
16
|
+
const { user, clear: clearSession } = useDmsUserSession<User>();
|
|
17
17
|
const { accounts, removeAccount } = useMultiAccount();
|
|
18
18
|
const { purge: purgePermissionPreviews } = usePermissionPreview();
|
|
19
19
|
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
type PostLoginDestination = string | (() => string | undefined);
|
|
2
2
|
|
|
3
|
+
interface PostLoginRedirectOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Open the destination in place of the current page in the history. For a
|
|
6
|
+
* page that consumed a one-time sign-in token (the OAuth completion, the
|
|
7
|
+
* second factor): back on it, the user would only meet a dead token, or a
|
|
8
|
+
* page sending them forward again.
|
|
9
|
+
*/
|
|
10
|
+
replace?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
3
13
|
/** Refresh authenticated state before resolving and opening the destination. */
|
|
4
|
-
export async function usePostLoginRedirect(
|
|
14
|
+
export async function usePostLoginRedirect(
|
|
15
|
+
redirectUrl?: PostLoginDestination,
|
|
16
|
+
options: PostLoginRedirectOptions = {},
|
|
17
|
+
) {
|
|
5
18
|
const dmsApp = useDmsApp();
|
|
6
|
-
const { fetch: refreshUser, user } =
|
|
19
|
+
const { fetch: refreshUser, user } = useDmsUserSession();
|
|
7
20
|
const { addCurrentAccount } = useMultiAccount();
|
|
8
21
|
const siteLayout = useSiteLayout();
|
|
9
22
|
const homepage = useHomepage();
|
|
@@ -24,5 +37,5 @@ export async function usePostLoginRedirect(redirectUrl?: PostLoginDestination) {
|
|
|
24
37
|
|
|
25
38
|
const destination =
|
|
26
39
|
typeof redirectUrl === "function" ? redirectUrl() : redirectUrl;
|
|
27
|
-
await navigateDms(destination || homepage);
|
|
40
|
+
await navigateDms(destination || homepage, options);
|
|
28
41
|
}
|
|
@@ -115,7 +115,9 @@ async function verify() {
|
|
|
115
115
|
},
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
// The sign-in token in this page's URL is spent: the destination takes
|
|
119
|
+
// its place in the history.
|
|
120
|
+
await usePostLoginRedirect(undefined, { replace: true });
|
|
119
121
|
} catch (error: unknown) {
|
|
120
122
|
pin.value = [];
|
|
121
123
|
await showError(error, "page.2fa.error_title", {
|
|
@@ -63,7 +63,9 @@ async function onSubmit(payload: FormSubmitEvent<Schema>) {
|
|
|
63
63
|
},
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
// The sign-in token in this page's URL is spent: the destination takes
|
|
67
|
+
// its place in the history.
|
|
68
|
+
await usePostLoginRedirect(undefined, { replace: true });
|
|
67
69
|
} catch (error: unknown) {
|
|
68
70
|
// A wrong code shows under the field; an expired sign-in above the form.
|
|
69
71
|
await showError(error, "page.backup.error_title", {
|
|
@@ -19,7 +19,7 @@ const { formError, showFormError, clearFormError, showError } =
|
|
|
19
19
|
useAuthFormError();
|
|
20
20
|
|
|
21
21
|
if (!route.query.email) {
|
|
22
|
-
throw
|
|
22
|
+
throw createDmsError({
|
|
23
23
|
statusCode: HTTP_BAD_REQUEST,
|
|
24
24
|
statusMessage: INVALID_REQUEST,
|
|
25
25
|
message: "Missing required email parameter",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { type OAuthHandoff, sanitizeRedirectPath } from "../../../shared/oauth";
|
|
3
3
|
|
|
4
4
|
const OAUTH_FAILED_KEY = "error.oauth.failed";
|
|
5
|
+
const IN_FLIGHT_COMPLETION_KEY = "__dmsInFlightOAuthCompletion";
|
|
5
6
|
|
|
6
7
|
const route = useDmsRoute();
|
|
7
8
|
const homepage = useHomepage();
|
|
@@ -39,7 +40,7 @@ const HANDOFF_ACTIONS: HandoffActions = {
|
|
|
39
40
|
},
|
|
40
41
|
REPLACE,
|
|
41
42
|
),
|
|
42
|
-
none: () => usePostLoginRedirect(redirect.value),
|
|
43
|
+
none: () => usePostLoginRedirect(redirect.value, REPLACE),
|
|
43
44
|
};
|
|
44
45
|
|
|
45
46
|
function runHandoffAction(handoff: OAuthHandoff): unknown {
|
|
@@ -56,7 +57,7 @@ function redirectToLoginWithError(): unknown {
|
|
|
56
57
|
);
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
async function completeOAuth(): Promise<void> {
|
|
60
61
|
try {
|
|
61
62
|
const handoff = await $fetch<OAuthHandoff>("/auth/oauth/handoff", {
|
|
62
63
|
method: "POST",
|
|
@@ -65,7 +66,29 @@ onMounted(async () => {
|
|
|
65
66
|
} catch {
|
|
66
67
|
await dmsApp.runWithContext(() => redirectToLoginWithError());
|
|
67
68
|
}
|
|
68
|
-
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// One completion per visit, held on the DMS app rather than by this instance.
|
|
72
|
+
// The post-login redirect refreshes the site layout for the new identity,
|
|
73
|
+
// which empties the page layout cache: the page unmounts this component and
|
|
74
|
+
// mounts a new one while its layout is fetched again. Starting over from that
|
|
75
|
+
// mount consumed the handoff once more and refreshed again, so the page
|
|
76
|
+
// remounted in a loop instead of leaving. The new instance joins the running
|
|
77
|
+
// completion; a later visit, once it has settled, starts its own.
|
|
78
|
+
function joinCompletion(): Promise<void> {
|
|
79
|
+
const holder = dmsApp as unknown as Record<PropertyKey, unknown>;
|
|
80
|
+
const running = holder[IN_FLIGHT_COMPLETION_KEY] as Promise<void> | undefined;
|
|
81
|
+
if (running) return running;
|
|
82
|
+
const completion = completeOAuth().finally(() => {
|
|
83
|
+
if (holder[IN_FLIGHT_COMPLETION_KEY] === completion) {
|
|
84
|
+
holder[IN_FLIGHT_COMPLETION_KEY] = undefined;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
holder[IN_FLIGHT_COMPLETION_KEY] = completion;
|
|
88
|
+
return completion;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
onMounted(joinCompletion);
|
|
69
92
|
</script>
|
|
70
93
|
|
|
71
94
|
<template>
|
|
@@ -23,7 +23,7 @@ const { formError, clearFormError, showError } = useAuthFormError();
|
|
|
23
23
|
const form = useTemplateRef<AuthFormHandle>("form");
|
|
24
24
|
|
|
25
25
|
if (!route.query.token || !route.query.email) {
|
|
26
|
-
throw
|
|
26
|
+
throw createDmsError({
|
|
27
27
|
statusCode: HTTP_BAD_REQUEST,
|
|
28
28
|
statusMessage: INVALID_REQUEST,
|
|
29
29
|
message: "Missing required authentication parameters",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { useTemplateRef } from "vue";
|
|
3
3
|
import * as z from "zod";
|
|
4
4
|
import type { FormSubmitEvent } from "@nuxt/ui";
|
|
5
5
|
import StageCard from "../../../../dms-layout/app/build/components/layout/StageCard.vue";
|
|
@@ -78,7 +78,6 @@ const config = useDmsRuntimeConfig();
|
|
|
78
78
|
const homepage = useHomepage();
|
|
79
79
|
const dmsApp = useDmsApp();
|
|
80
80
|
const { formError, clearFormError, showError } = useAuthFormError();
|
|
81
|
-
const { $authFetch } = useAuthFetch();
|
|
82
81
|
const form = useTemplateRef<AuthFormHandle>("form");
|
|
83
82
|
|
|
84
83
|
const route = useDmsRoute();
|
|
@@ -91,34 +90,32 @@ const hasInvitationToken = computed(
|
|
|
91
90
|
() => typeof queryToken.value === "string" && queryToken.value !== "",
|
|
92
91
|
);
|
|
93
92
|
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
// A revoked, replaced or expired link says so as the page opens, rather than
|
|
94
|
+
// after the whole form was filled: checked with the page, so the server
|
|
95
|
+
// renders the notice. The server answers with the very check the signup
|
|
96
|
+
// runs; any other failure leaves the form to the signup to settle.
|
|
97
|
+
const invitationCheck = useApiData(
|
|
98
|
+
() => (hasInvitationToken.value ? "/api/auth/validate-invite-token" : null),
|
|
99
|
+
{
|
|
100
|
+
method: "POST",
|
|
101
|
+
body: () => ({ token: queryToken.value, email: queryEmail.value }),
|
|
102
|
+
},
|
|
96
103
|
);
|
|
104
|
+
// The invitation can be retired while the form is open: the signup says so.
|
|
105
|
+
const closedOnSignup = ref<ClosedInvitation>();
|
|
106
|
+
const invitationState = computed<InvitationState>(() => {
|
|
107
|
+
if (!hasInvitationToken.value) return "invalid";
|
|
108
|
+
if (closedOnSignup.value) return closedOnSignup.value;
|
|
109
|
+
const { status, error } = invitationCheck;
|
|
110
|
+
if (status.value === "idle" || status.value === "pending") return "checking";
|
|
111
|
+
return error.value ? (closedInvitationOf(error.value) ?? "open") : "open";
|
|
112
|
+
});
|
|
97
113
|
const closedInvitation = computed(() =>
|
|
98
114
|
invitationState.value in CLOSED_INVITATION_NOTICES
|
|
99
115
|
? CLOSED_INVITATION_NOTICES[invitationState.value as ClosedInvitation]
|
|
100
116
|
: undefined,
|
|
101
117
|
);
|
|
102
118
|
|
|
103
|
-
// A revoked, replaced or expired link says so as the page opens, rather than
|
|
104
|
-
// after the whole form was filled. The server answers with the very check the
|
|
105
|
-
// signup runs; any other failure leaves the form to the signup to settle.
|
|
106
|
-
async function checkInvitation(): Promise<void> {
|
|
107
|
-
try {
|
|
108
|
-
await $authFetch("/api/auth/validate-invite-token", {
|
|
109
|
-
method: "POST",
|
|
110
|
-
body: { token: queryToken.value, email: queryEmail.value },
|
|
111
|
-
});
|
|
112
|
-
invitationState.value = "open";
|
|
113
|
-
} catch (error: unknown) {
|
|
114
|
-
invitationState.value = closedInvitationOf(error) ?? "open";
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
onMounted(() => {
|
|
119
|
-
if (invitationState.value === "checking") void checkInvitation();
|
|
120
|
-
});
|
|
121
|
-
|
|
122
119
|
// The invitation link carries the language the invitee was invited in: the
|
|
123
120
|
// page opens in it, and the footer switcher still lets them pick another one
|
|
124
121
|
// before the account is created with whichever is current.
|
|
@@ -149,7 +146,7 @@ async function onSubmit(payload: FormSubmitEvent<Schema>) {
|
|
|
149
146
|
isLoading.value = true;
|
|
150
147
|
clearFormError();
|
|
151
148
|
|
|
152
|
-
await $fetch("/auth/
|
|
149
|
+
await $fetch("/auth/register", {
|
|
153
150
|
method: "POST",
|
|
154
151
|
body: {
|
|
155
152
|
...payload.data,
|
|
@@ -167,7 +164,7 @@ async function onSubmit(payload: FormSubmitEvent<Schema>) {
|
|
|
167
164
|
// The invitation can be retired while the form is open.
|
|
168
165
|
const closed = closedInvitationOf(error);
|
|
169
166
|
if (closed) {
|
|
170
|
-
|
|
167
|
+
closedOnSignup.value = closed;
|
|
171
168
|
return;
|
|
172
169
|
}
|
|
173
170
|
// A refused value (an address already used) shows under its field;
|
|
@@ -19,7 +19,7 @@ const { formError, showFormError, clearFormError, showError } =
|
|
|
19
19
|
useAuthFormError();
|
|
20
20
|
|
|
21
21
|
if (!route.query.id) {
|
|
22
|
-
throw
|
|
22
|
+
throw createDmsError({
|
|
23
23
|
statusCode: HTTP_BAD_REQUEST,
|
|
24
24
|
statusMessage: INVALID_REQUEST,
|
|
25
25
|
message: "Missing required id parameter",
|
|
@@ -10,12 +10,10 @@ function isAuthOptions(value: MiddlewareMeta): value is AuthMiddlewareOptions {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
async function loadPermissionsIfNeeded() {
|
|
13
|
-
const {
|
|
14
|
-
|
|
15
|
-
if (isLoaded.value) return;
|
|
13
|
+
const { loadPermissions } = usePermissions();
|
|
16
14
|
|
|
17
15
|
try {
|
|
18
|
-
await
|
|
16
|
+
await loadPermissions();
|
|
19
17
|
} catch {
|
|
20
18
|
/* ignore — permissions load failure is not fatal here */
|
|
21
19
|
}
|
|
@@ -26,7 +24,7 @@ function handleAuthenticatedUser(
|
|
|
26
24
|
options: MiddlewareMeta,
|
|
27
25
|
) {
|
|
28
26
|
const authConfig = useDmsRuntimeConfig().public.dms;
|
|
29
|
-
const { user } =
|
|
27
|
+
const { user } = useDmsUserSession();
|
|
30
28
|
|
|
31
29
|
if (authConfig.mustValidateEmail && !user.value?.isValidated) {
|
|
32
30
|
if (to.path === "/auth/validate") return;
|
|
@@ -76,14 +74,14 @@ export default defineDmsMiddleware(async (to) => {
|
|
|
76
74
|
// is committed and rendered with its own status. Re-throwing it here would
|
|
77
75
|
// reject the pending navigation the SSR render awaits, turning that 404 into
|
|
78
76
|
// a 500 fallback render.
|
|
79
|
-
if (
|
|
77
|
+
if (useDmsError().value) return abortDmsNavigation();
|
|
80
78
|
|
|
81
79
|
const options = to.meta.auth as MiddlewareMeta | undefined;
|
|
82
80
|
if (!options) return;
|
|
83
81
|
|
|
84
82
|
if (await isPublicAccessRoute(to)) return;
|
|
85
83
|
|
|
86
|
-
const { loggedIn } =
|
|
84
|
+
const { loggedIn } = useDmsUserSession();
|
|
87
85
|
|
|
88
86
|
if (loggedIn.value) {
|
|
89
87
|
await loadPermissionsIfNeeded();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads the permission set as the app starts when the `auth` middleware will
|
|
3
|
+
* need it, rather than once the middlewares ahead of it are done: the request
|
|
4
|
+
* runs alongside the others the render makes, and the middleware joins it.
|
|
5
|
+
*
|
|
6
|
+
* Only then: a signed-in user opening a registered page that is not public,
|
|
7
|
+
* which is when the middleware loads the set. Anywhere else nothing is asked
|
|
8
|
+
* that was not asked before. A failure stays the middleware's to handle.
|
|
9
|
+
*/
|
|
10
|
+
export default defineDmsPlugin(() => {
|
|
11
|
+
if (!useDmsUserSession().loggedIn.value) return;
|
|
12
|
+
const page = useSiteLayout().findMatchingRoute(useDmsRoute().path);
|
|
13
|
+
if (!page || page.metadata.publicAccess === true) return;
|
|
14
|
+
usePermissions()
|
|
15
|
+
.loadPermissions()
|
|
16
|
+
.catch(() => undefined);
|
|
17
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import type { DmsServerEvent, DmsSession } from "#dms/server";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The accounts this browser is signed in to: an index of their descriptors,
|
|
6
|
+
* and one sealed cookie per account holding its refresh token. The frontend
|
|
7
|
+
* server scopes and seals the cookies; their names and contents are the DMS's,
|
|
8
|
+
* unchanged from when the renderer wrote them, so stored accounts survive the
|
|
9
|
+
* move.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const ACCOUNT_INDEX_COOKIE = "dms_account_index";
|
|
13
|
+
const ACCOUNT_COOKIE_PREFIX = "dms_account_";
|
|
14
|
+
const MAX_AGE_SECONDS = 60 * 60 * 24 * 30;
|
|
15
|
+
const UUID =
|
|
16
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
17
|
+
|
|
18
|
+
export interface AccountDescriptor {
|
|
19
|
+
accountId: string;
|
|
20
|
+
userId?: string;
|
|
21
|
+
email?: string;
|
|
22
|
+
name?: string;
|
|
23
|
+
activeTenantId?: string;
|
|
24
|
+
persistent: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface StoredAccount extends AccountDescriptor {
|
|
28
|
+
refreshToken?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type AccountIndex = Record<string, AccountDescriptor>;
|
|
32
|
+
|
|
33
|
+
interface SessionUser {
|
|
34
|
+
_id?: string;
|
|
35
|
+
id?: string;
|
|
36
|
+
email?: string;
|
|
37
|
+
name?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Whether a session, or a stored account, outlives the browser. Only a login
|
|
42
|
+
* that left "keep me signed in" unchecked says no; a cookie written before
|
|
43
|
+
* the flag existed carries none, and keeps the lifetime it was given then.
|
|
44
|
+
*/
|
|
45
|
+
export function isPersistent(session?: { persistent?: unknown }): boolean {
|
|
46
|
+
return session?.persistent !== false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function lifetime(persistent: boolean): number | undefined {
|
|
50
|
+
return persistent ? MAX_AGE_SECONDS : undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function accountCookieName(accountId: string): string {
|
|
54
|
+
if (!UUID.test(accountId)) throw new Error("Invalid account id");
|
|
55
|
+
return `${ACCOUNT_COOKIE_PREFIX}${accountId.replaceAll("-", "")}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function descriptor(session: DmsSession, accountId: string): AccountDescriptor {
|
|
59
|
+
const user = (session.user ?? {}) as SessionUser;
|
|
60
|
+
return {
|
|
61
|
+
accountId,
|
|
62
|
+
userId: user._id ?? user.id,
|
|
63
|
+
email: user.email,
|
|
64
|
+
name: user.name,
|
|
65
|
+
activeTenantId: session.activeTenantId,
|
|
66
|
+
persistent: isPersistent(session),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function readAccounts(event: DmsServerEvent): AccountIndex {
|
|
71
|
+
return event.cookies.readSealed<AccountIndex>(ACCOUNT_INDEX_COOKIE) ?? {};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function writeAccountIndex(event: DmsServerEvent, accounts: AccountIndex) {
|
|
75
|
+
const persistent = Object.values(accounts).some(isPersistent);
|
|
76
|
+
event.cookies.writeSealed(ACCOUNT_INDEX_COOKIE, accounts, {
|
|
77
|
+
maxAge: lifetime(persistent),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function readAccount(
|
|
82
|
+
event: DmsServerEvent,
|
|
83
|
+
accountId: unknown,
|
|
84
|
+
): StoredAccount | undefined {
|
|
85
|
+
if (typeof accountId !== "string" || !UUID.test(accountId)) return undefined;
|
|
86
|
+
return event.cookies.readSealed<StoredAccount>(accountCookieName(accountId));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function storeAccount(
|
|
90
|
+
event: DmsServerEvent,
|
|
91
|
+
session: DmsSession,
|
|
92
|
+
accountId?: string,
|
|
93
|
+
): AccountDescriptor {
|
|
94
|
+
const id = accountId ?? randomUUID();
|
|
95
|
+
const account = descriptor(session, id);
|
|
96
|
+
writeAccountIndex(event, { ...readAccounts(event), [id]: account });
|
|
97
|
+
event.cookies.writeSealed(
|
|
98
|
+
accountCookieName(id),
|
|
99
|
+
{ refreshToken: session.refreshToken, ...account },
|
|
100
|
+
{ maxAge: lifetime(account.persistent) },
|
|
101
|
+
);
|
|
102
|
+
return account;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function assertAccountInvariant(session: DmsSession, accountId: string) {
|
|
106
|
+
if (!accountId || session.accountId !== accountId)
|
|
107
|
+
throw new Error("Account session invariant violated");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Write `session` as the current one, and as one of the stored accounts. The
|
|
112
|
+
* frontend server stamps it with `updatedAt`, which lets the browser tell a
|
|
113
|
+
* page fetched before this write, and restored from its cache, from a
|
|
114
|
+
* current one.
|
|
115
|
+
*/
|
|
116
|
+
export function persistAccountSession(
|
|
117
|
+
event: DmsServerEvent,
|
|
118
|
+
session: DmsSession,
|
|
119
|
+
accountId?: string,
|
|
120
|
+
): AccountDescriptor {
|
|
121
|
+
const id = accountId ?? session.accountId ?? randomUUID();
|
|
122
|
+
session.accountId = id;
|
|
123
|
+
const account = storeAccount(event, session, id);
|
|
124
|
+
assertAccountInvariant(session, account.accountId);
|
|
125
|
+
event.session.write(session);
|
|
126
|
+
return account;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function removeAccount(event: DmsServerEvent, accountId: unknown) {
|
|
130
|
+
const accounts = { ...readAccounts(event) };
|
|
131
|
+
if (typeof accountId === "string") delete accounts[accountId];
|
|
132
|
+
writeAccountIndex(event, accounts);
|
|
133
|
+
if (typeof accountId === "string" && UUID.test(accountId))
|
|
134
|
+
event.cookies.clear(accountCookieName(accountId));
|
|
135
|
+
}
|