@cosmicdrift/kumiko-samples 0.392.2 → 0.394.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/package.json +1 -1
- package/packages/bundled-features/package.json +15 -5
- package/packages/bundled-features/src/account-security/README.md +1 -1
- package/packages/bundled-features/src/account-security/changes.json +6 -0
- package/packages/bundled-features/src/account-security/feature.ts +9 -1
- package/packages/bundled-features/src/audit/event-classification.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/changes.json +6 -0
- package/packages/bundled-features/src/auth-email-password/web/auth-client.ts +10 -52
- package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +4 -4
- package/packages/bundled-features/src/auth-email-password/web/auth-redirect.ts +3 -12
- package/packages/bundled-features/src/auth-email-password/web/index.ts +2 -0
- package/packages/bundled-features/src/auth-email-password/web/login-failure.ts +54 -0
- package/packages/bundled-features/src/auth-email-password/web/login-url.ts +19 -0
- package/packages/bundled-features/src/auth-email-password/web/static-login.ts +183 -0
- package/packages/bundled-features/src/auth-mfa/web/mfa-client.ts +7 -63
- package/packages/bundled-features/src/billing-foundation/changes.json +30 -0
- package/packages/bundled-features/src/billing-foundation/checkout-core.ts +8 -16
- package/packages/bundled-features/src/billing-foundation/events.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/feature.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +9 -2
- package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/index.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/one-off-checkout-gate.ts +44 -0
- package/packages/bundled-features/src/billing-foundation/one-off-customer.ts +17 -0
- package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +1 -0
- package/packages/bundled-features/src/billing-foundation/types.ts +26 -0
- package/packages/bundled-features/src/billing-foundation/web/billing-plans-panel.tsx +1 -0
- package/packages/bundled-features/src/billing-foundation/web/plan-switch-confirm-dialog.tsx +8 -2
- package/packages/bundled-features/src/billing-foundation/web/plan-switch-recurring.ts +23 -0
- package/packages/bundled-features/src/billing-foundation/webhook-handler.ts +1 -0
- package/packages/bundled-features/src/cap-overview/changes.json +6 -0
- package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +3 -3
- package/packages/bundled-features/src/cap-overview/types.ts +2 -0
- package/packages/bundled-features/src/compliance-profiles/web/compliance-profile-catalog.tsx +10 -1
- package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +12 -3
- package/packages/bundled-features/src/files/changes.json +18 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-health.ts +146 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-poll-schedule.ts +74 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-poll.ts +238 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-record.ts +2 -0
- package/packages/bundled-features/src/inbound-mail-foundation/account-wake.ts +21 -0
- package/packages/bundled-features/src/inbound-mail-foundation/changes.json +155 -0
- package/packages/bundled-features/src/inbound-mail-foundation/connect-callback-verify.ts +65 -0
- package/packages/bundled-features/src/inbound-mail-foundation/connect-route-responses.ts +26 -0
- package/packages/bundled-features/src/inbound-mail-foundation/connect-routes.ts +93 -62
- package/packages/bundled-features/src/inbound-mail-foundation/constants.ts +66 -3
- package/packages/bundled-features/src/inbound-mail-foundation/content-refetch.ts +137 -0
- package/packages/bundled-features/src/inbound-mail-foundation/describe-error.ts +5 -0
- package/packages/bundled-features/src/inbound-mail-foundation/entities.ts +45 -2
- package/packages/bundled-features/src/inbound-mail-foundation/events.ts +69 -2
- package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +133 -118
- package/packages/bundled-features/src/inbound-mail-foundation/file-storage-failure.ts +23 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-address.ts +79 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/acknowledge-quarantine.write.ts +55 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/attach-message-content.write.ts +184 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +137 -84
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +65 -45
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message-prepare.ts +177 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message-schema.ts +42 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +79 -160
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +93 -30
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +46 -30
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-quarantine.query.ts +106 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/mark-credential-stored.write.ts +47 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/message-content.query.ts +99 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/message-content.ts +229 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/message-detail.query.ts +34 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/reactivate-account.write.ts +129 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/retry-quarantined-message.write.ts +49 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +48 -9
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/set-trusted-authserv-id.write.ts +66 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/thread-rollup.ts +136 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/trusted-authserv-id.ts +4 -0
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +82 -60
- package/packages/bundled-features/src/inbound-mail-foundation/i18n.ts +80 -1
- package/packages/bundled-features/src/inbound-mail-foundation/inbound-oauth-flow.ts +48 -0
- package/packages/bundled-features/src/inbound-mail-foundation/index.ts +94 -15
- package/packages/bundled-features/src/inbound-mail-foundation/ingest-failures.ts +137 -0
- package/packages/bundled-features/src/inbound-mail-foundation/ingest-payload.ts +46 -0
- package/packages/bundled-features/src/inbound-mail-foundation/mail-roles.ts +132 -0
- package/packages/bundled-features/src/inbound-mail-foundation/normalize-inbound-message.ts +119 -0
- package/packages/bundled-features/src/inbound-mail-foundation/oauth-access-token.ts +26 -8
- package/packages/bundled-features/src/inbound-mail-foundation/outbound-attachment-access.ts +28 -0
- package/packages/bundled-features/src/inbound-mail-foundation/outbound-attachments.ts +111 -0
- package/packages/bundled-features/src/inbound-mail-foundation/outbound-mail.ts +19 -1
- package/packages/bundled-features/src/inbound-mail-foundation/probe.ts +2 -1
- package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +33 -2
- package/packages/bundled-features/src/inbound-mail-foundation/provider-errors.ts +74 -0
- package/packages/bundled-features/src/inbound-mail-foundation/quarantine-entities.ts +56 -0
- package/packages/bundled-features/src/inbound-mail-foundation/quarantine-header-import.ts +63 -0
- package/packages/bundled-features/src/inbound-mail-foundation/quarantine-retry.ts +137 -0
- package/packages/bundled-features/src/inbound-mail-foundation/quarantine-screens.ts +66 -0
- package/packages/bundled-features/src/inbound-mail-foundation/quarantine.ts +72 -0
- package/packages/bundled-features/src/inbound-mail-foundation/retention-job.ts +78 -0
- package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +43 -8
- package/packages/bundled-features/src/inbound-mail-foundation/screens.ts +239 -79
- package/packages/bundled-features/src/inbound-mail-foundation/send-from-account.ts +24 -5
- package/packages/bundled-features/src/inbound-mail-foundation/sender-auth.ts +215 -0
- package/packages/bundled-features/src/inbound-mail-foundation/supervisor-ingest.ts +311 -0
- package/packages/bundled-features/src/inbound-mail-foundation/sync-account-now.ts +61 -0
- package/packages/bundled-features/src/inbound-mail-foundation/sync-cursor-store.ts +79 -0
- package/packages/bundled-features/src/inbound-mail-foundation/syncable-accounts.ts +123 -0
- package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +5 -1
- package/packages/bundled-features/src/inbound-mail-foundation/types.ts +76 -105
- package/packages/bundled-features/src/inbound-mail-foundation/wake-consumer.ts +33 -0
- package/packages/bundled-features/src/inbound-mail-foundation/watch-catch-up.ts +68 -0
- package/packages/bundled-features/src/inbound-mail-foundation/watch-lease.ts +124 -0
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor-types.ts +87 -0
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +143 -433
- package/packages/bundled-features/src/inbound-mail-foundation/web/client-plugin.ts +73 -0
- package/packages/bundled-features/src/inbound-mail-foundation/web/constants.ts +7 -0
- package/packages/bundled-features/src/inbound-mail-foundation/web/index.ts +5 -0
- package/packages/bundled-features/src/inbound-provider-imap/changes.json +42 -0
- package/packages/bundled-features/src/inbound-provider-imap/connect-mailbox.ts +155 -63
- package/packages/bundled-features/src/inbound-provider-imap/connect-my-mailbox.ts +45 -0
- package/packages/bundled-features/src/inbound-provider-imap/constants.ts +14 -1
- package/packages/bundled-features/src/inbound-provider-imap/credential-document.ts +2 -1
- package/packages/bundled-features/src/inbound-provider-imap/download-batch.ts +38 -0
- package/packages/bundled-features/src/inbound-provider-imap/feature.ts +75 -44
- package/packages/bundled-features/src/inbound-provider-imap/fetch-content.ts +84 -0
- package/packages/bundled-features/src/inbound-provider-imap/i18n.ts +28 -2
- package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +35 -84
- package/packages/bundled-features/src/inbound-provider-imap/index.ts +1 -2
- package/packages/bundled-features/src/inbound-provider-imap/mime-content.ts +85 -0
- package/packages/bundled-features/src/inbound-provider-imap/parse-message.ts +149 -0
- package/packages/bundled-features/src/inbound-provider-imap/reconnect-account.ts +45 -0
- package/packages/bundled-features/src/inbound-provider-imap/reconnect-mailbox.ts +103 -0
- package/packages/bundled-features/src/inbound-provider-imap/reconnect-prefill.query.ts +49 -0
- package/packages/bundled-features/src/inbound-provider-imap/screens.ts +209 -42
- package/packages/bundled-features/src/inbound-provider-imap/send-mail.ts +11 -2
- package/packages/bundled-features/src/inbound-provider-imap/sender-auth-headers.ts +36 -0
- package/packages/bundled-features/src/inbound-provider-inmemory/feature.ts +75 -3
- package/packages/bundled-features/src/inbound-provider-inmemory/index.ts +6 -0
- package/packages/bundled-features/src/jobs/changes.json +18 -0
- package/packages/bundled-features/src/managed-pages/web/i18n.ts +1 -0
- package/packages/bundled-features/src/subscription-paddle/changes.json +15 -0
- package/packages/bundled-features/src/subscription-paddle/checkout-config.query.ts +17 -0
- package/packages/bundled-features/src/subscription-paddle/checkout-methods.ts +107 -0
- package/packages/bundled-features/src/subscription-paddle/checkout-page.ts +154 -0
- package/packages/bundled-features/src/subscription-paddle/client.ts +78 -0
- package/packages/bundled-features/src/subscription-paddle/constants.ts +32 -0
- package/packages/bundled-features/src/subscription-paddle/feature.ts +265 -0
- package/packages/bundled-features/src/subscription-paddle/index.ts +17 -0
- package/packages/bundled-features/src/subscription-paddle/plan-switch-methods.ts +135 -0
- package/packages/bundled-features/src/subscription-paddle/price-methods.ts +148 -0
- package/packages/bundled-features/src/subscription-paddle/runtime.ts +203 -0
- package/packages/bundled-features/src/subscription-paddle/subscription-state.ts +73 -0
- package/packages/bundled-features/src/subscription-paddle/tenant-binding.ts +34 -0
- package/packages/bundled-features/src/subscription-paddle/verify-webhook.ts +61 -0
- package/packages/bundled-features/src/subscription-paddle/webhook-events.ts +172 -0
- package/packages/bundled-features/src/template-resolver/changes.json +12 -0
- package/packages/bundled-features/src/template-resolver/handlers/collection-item.query.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/collection-list.query.ts +6 -2
- package/packages/bundled-features/src/template-resolver/handlers/collection-shared.ts +25 -0
- package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +218 -117
- package/packages/bundled-features/src/template-resolver/web/i18n.ts +1 -2
- package/packages/bundled-features/src/user-data-rights-defaults/hooks/file-ref.userdata-hook.ts +2 -1
- package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-constants.ts +4 -0
- package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +11 -5
- package/samples/apps/use-all-bundled/e2e-billing-gallery/server.ts +2 -2
- package/samples/apps/use-all-bundled/feature-manifest.json +89 -39
- package/samples/apps/use-all-bundled/kumiko/migrations/0056_inbound-mail-quarantine-retry.rebuild.json +4 -0
- package/samples/apps/use-all-bundled/package.json +1 -0
- package/samples/apps/use-all-bundled/playwright.billing-docs.config.ts +15 -0
- package/samples/apps/use-all-bundled/src/app/cap-overview-caps.ts +16 -2
- package/samples/apps/use-all-bundled/src/app/cap-overview-labels-feature.ts +2 -1
- package/samples/apps/use-all-bundled/src/app/client.tsx +2 -0
- package/samples/apps/use-all-bundled/src/app/screens-feature.ts +14 -2
- package/samples/apps/use-all-bundled/src/app/seed.ts +4 -1
- package/samples/apps/use-all-bundled/src/run-config.ts +7 -5
- package/samples/recipes/cross-feature-events/README.md +10 -0
- package/samples/recipes/cross-feature-events/src/feature.ts +14 -0
- package/samples/recipes/dashboard-stats/README.md +8 -0
- package/samples/recipes/dashboard-stats/src/feature.ts +91 -1
- package/samples/recipes/env-composition/README.md +2 -2
- package/samples/recipes/env-composition/src/env.ts +2 -2
- package/samples/recipes/inbound-mail-triage/README.md +12 -0
- package/samples/recipes/pdf-audit-export/src/feature.ts +71 -48
- package/packages/bundled-features/src/subscription-dodo/changes.json +0 -14
- package/packages/bundled-features/src/subscription-dodo/client.ts +0 -45
- package/packages/bundled-features/src/subscription-dodo/constants.ts +0 -24
- package/packages/bundled-features/src/subscription-dodo/feature.ts +0 -207
- package/packages/bundled-features/src/subscription-dodo/index.ts +0 -16
- package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +0 -345
- package/packages/bundled-features/src/subscription-dodo/runtime.ts +0 -126
- package/packages/bundled-features/src/subscription-dodo/verify-webhook.ts +0 -262
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-samples",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.394.0",
|
|
4
4
|
"description": "Source trees of the Kumiko sample recipes, sample apps and bundled features in repo layout, for tooling such as few-shot corpus builds.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-bundled-features",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.394.0",
|
|
4
4
|
"description": "Built-in features — tenant, user, auth, delivery. The stuff you'd rewrite anyway, already typed.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -65,12 +65,13 @@
|
|
|
65
65
|
"./billing-foundation": "./src/billing-foundation/index.ts",
|
|
66
66
|
"./billing-foundation/web": "./src/billing-foundation/web/index.ts",
|
|
67
67
|
"./subscription-stripe": "./src/subscription-stripe/index.ts",
|
|
68
|
-
"./subscription-
|
|
68
|
+
"./subscription-paddle": "./src/subscription-paddle/index.ts",
|
|
69
69
|
"./subscription-mollie": "./src/subscription-mollie/index.ts",
|
|
70
70
|
"./foundation-shared": "./src/foundation-shared/index.ts",
|
|
71
71
|
"./auth-foundation": "./src/auth-foundation/index.ts",
|
|
72
72
|
"./mail-foundation": "./src/mail-foundation/index.ts",
|
|
73
73
|
"./inbound-mail-foundation": "./src/inbound-mail-foundation/index.ts",
|
|
74
|
+
"./inbound-mail-foundation/web": "./src/inbound-mail-foundation/web/index.ts",
|
|
74
75
|
"./inbound-provider-inmemory": "./src/inbound-provider-inmemory/index.ts",
|
|
75
76
|
"./inbound-provider-imap": "./src/inbound-provider-imap/index.ts",
|
|
76
77
|
"./mail-transport-smtp": "./src/mail-transport-smtp/index.ts",
|
|
@@ -104,6 +105,7 @@
|
|
|
104
105
|
"./auth-email-password": "./src/auth-email-password/index.ts",
|
|
105
106
|
"./auth-email-password/constants": "./src/auth-email-password/web/constants.ts",
|
|
106
107
|
"./auth-email-password/return-path": "./src/auth-email-password/web/return-path.ts",
|
|
108
|
+
"./auth-email-password/static-login": "./src/auth-email-password/web/static-login.ts",
|
|
107
109
|
"./auth-email-password/email-templates": "./src/auth-email-password/email-templates.ts",
|
|
108
110
|
"./auth-email-password/seeding": "./src/auth-email-password/seeding.ts",
|
|
109
111
|
"./auth-email-password/testing": "./src/auth-email-password/testing.ts",
|
|
@@ -364,9 +366,9 @@
|
|
|
364
366
|
"types": "./dist/subscription-stripe/index.d.ts",
|
|
365
367
|
"default": "./dist/subscription-stripe/index.js"
|
|
366
368
|
},
|
|
367
|
-
"./subscription-
|
|
368
|
-
"types": "./dist/subscription-
|
|
369
|
-
"default": "./dist/subscription-
|
|
369
|
+
"./subscription-paddle": {
|
|
370
|
+
"types": "./dist/subscription-paddle/index.d.ts",
|
|
371
|
+
"default": "./dist/subscription-paddle/index.js"
|
|
370
372
|
},
|
|
371
373
|
"./subscription-mollie": {
|
|
372
374
|
"types": "./dist/subscription-mollie/index.d.ts",
|
|
@@ -388,6 +390,10 @@
|
|
|
388
390
|
"types": "./dist/inbound-mail-foundation/index.d.ts",
|
|
389
391
|
"default": "./dist/inbound-mail-foundation/index.js"
|
|
390
392
|
},
|
|
393
|
+
"./inbound-mail-foundation/web": {
|
|
394
|
+
"types": "./dist/inbound-mail-foundation/web/index.d.ts",
|
|
395
|
+
"default": "./dist/inbound-mail-foundation/web/index.js"
|
|
396
|
+
},
|
|
391
397
|
"./inbound-provider-inmemory": {
|
|
392
398
|
"types": "./dist/inbound-provider-inmemory/index.d.ts",
|
|
393
399
|
"default": "./dist/inbound-provider-inmemory/index.js"
|
|
@@ -524,6 +530,10 @@
|
|
|
524
530
|
"types": "./dist/auth-email-password/web/return-path.d.ts",
|
|
525
531
|
"default": "./dist/auth-email-password/web/return-path.js"
|
|
526
532
|
},
|
|
533
|
+
"./auth-email-password/static-login": {
|
|
534
|
+
"types": "./dist/auth-email-password/web/static-login.d.ts",
|
|
535
|
+
"default": "./dist/auth-email-password/web/static-login.js"
|
|
536
|
+
},
|
|
527
537
|
"./auth-email-password/seeding": {
|
|
528
538
|
"types": "./dist/auth-email-password/seeding.d.ts",
|
|
529
539
|
"default": "./dist/auth-email-password/seeding.js"
|
|
@@ -25,7 +25,7 @@ const features = [
|
|
|
25
25
|
| Option | Default | Effect |
|
|
26
26
|
| --- | --- | --- |
|
|
27
27
|
| `access` | `openToAll` | Who may open the dashboard, e.g. `{ roles: ["Admin", "Editor"] }` |
|
|
28
|
-
| `nav` | none | `{ order?, parent? }` adds a sidebar entry. Without it the screen is dormant and apps link it from the user menu |
|
|
28
|
+
| `nav` | none | `{ order?, parent?, icon? }` (icon default `"user"`) adds a sidebar entry. Without it the screen is dormant and apps link it from the user menu |
|
|
29
29
|
|
|
30
30
|
The MFA panels switch on `auth-mfa:query:status` (`enabled`), exported as
|
|
31
31
|
`accountSecurityMfaStatusVisibility`.
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.394.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "account-security nav has a default icon and an icon option",
|
|
6
|
+
"detail": "The nav entry shows the `user` icon by default. `nav.icon` takes any nav icon key to override it, so apps no longer need their own nav entry just for an icon."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.376.0",
|
|
4
10
|
"type": "improvement",
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
defineFeature,
|
|
4
4
|
type FeatureDefinition,
|
|
5
5
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
6
|
+
import type { NavIconKey } from "@cosmicdrift/kumiko-types/nav-icon";
|
|
6
7
|
import {
|
|
7
8
|
AuthMfaQueries,
|
|
8
9
|
MFA_DISABLE_SCREEN_ID,
|
|
@@ -21,6 +22,7 @@ import { ACCOUNT_SECURITY_I18N } from "./web/i18n.ts";
|
|
|
21
22
|
export interface AccountSecurityNavOptions {
|
|
22
23
|
readonly order?: number;
|
|
23
24
|
readonly parent?: string;
|
|
25
|
+
readonly icon?: NavIconKey;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
export interface AccountSecurityFeatureOptions {
|
|
@@ -37,6 +39,7 @@ const DEFAULT_ACCESS: AccessRule = {
|
|
|
37
39
|
};
|
|
38
40
|
|
|
39
41
|
const DEFAULT_NAV_ORDER = 90;
|
|
42
|
+
const DEFAULT_NAV_ICON: NavIconKey = "user";
|
|
40
43
|
|
|
41
44
|
export const accountSecurityMfaStatusVisibility = {
|
|
42
45
|
query: AuthMfaQueries.status,
|
|
@@ -87,10 +90,15 @@ export function createAccountSecurityFeature(
|
|
|
87
90
|
// Explicit property assignments: the feature-AST parser reads neither
|
|
88
91
|
// `??`, shorthand properties nor a conditional spread, and the boot validator treats
|
|
89
92
|
// `parent: undefined` as a top-level entry.
|
|
90
|
-
const {
|
|
93
|
+
const {
|
|
94
|
+
order: navOrder = DEFAULT_NAV_ORDER,
|
|
95
|
+
parent: navParent,
|
|
96
|
+
icon: navIcon = DEFAULT_NAV_ICON,
|
|
97
|
+
} = nav;
|
|
91
98
|
r.nav({
|
|
92
99
|
id: ACCOUNT_SECURITY_SCREEN_ID,
|
|
93
100
|
label: ACCOUNT_SECURITY_NAV_LABEL,
|
|
101
|
+
icon: navIcon,
|
|
94
102
|
screen: ACCOUNT_SECURITY_SCREEN_QN,
|
|
95
103
|
order: navOrder,
|
|
96
104
|
parent: navParent,
|
|
@@ -18,6 +18,7 @@ export const AUDIT_EVENT_STANCES: Readonly<Record<string, AuditEventStance>> = {
|
|
|
18
18
|
"cap-counter:event:rolling-incremented": TECHNICAL_EVENT,
|
|
19
19
|
"cap-counter:event:rolling-released": TECHNICAL_EVENT,
|
|
20
20
|
"inbound-mail-foundation:event:inbound-message-received": TECHNICAL_EVENT,
|
|
21
|
+
"inbound-mail-foundation:event:inbound-message-content-fetched": TECHNICAL_EVENT,
|
|
21
22
|
"inbound-mail-foundation:event:mail-thread-updated": TECHNICAL_EVENT,
|
|
22
23
|
"document-ingest-foundation:event:document-ingest-requested": TECHNICAL_EVENT,
|
|
23
24
|
"document-ingest-foundation:event:document-ingest-skipped": TECHNICAL_EVENT,
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.394.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "static-login subpath for static sites",
|
|
6
|
+
"detail": "`@cosmicdrift/kumiko-bundled-features/auth-email-password/static-login` imports neither React nor the renderer. It offers `loginWithPassword` and `verifyMfaCode` (same requests as the SPA login, one result union with success, mfa-challenge, mfa-setup-required and failure incl. network and rate limit), `appReturnPath` and `postLoginPath` for the `next` filter, `forgotPasswordHref`, and `marketingLoginUrl({ localePaths, isAppPath })`, a `LoginUrlBuilder` for the auth gate that sends visitors to the marketing login with `next`. `LoginUrlBuilder` and `failureFromErrorField` are now exported from `auth-email-password/web`."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.390.0",
|
|
4
10
|
"type": "improvement",
|
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
|
|
10
10
|
import { parseRoles } from "@cosmicdrift/kumiko-framework/utils";
|
|
11
11
|
import { CSRF_HEADER_NAME, readCsrfToken } from "@cosmicdrift/kumiko-renderer/live";
|
|
12
|
+
import {
|
|
13
|
+
failureFromErrorField,
|
|
14
|
+
type LoginFailure,
|
|
15
|
+
parseRetryAfterSeconds,
|
|
16
|
+
} from "./login-failure.ts";
|
|
17
|
+
|
|
18
|
+
export type { KnownLoginFailureReason, LoginFailure } from "./login-failure.ts";
|
|
19
|
+
export { parseRetryAfterSeconds };
|
|
12
20
|
|
|
13
21
|
export type TenantSummary = {
|
|
14
22
|
readonly tenantId: string;
|
|
@@ -36,28 +44,6 @@ export type LoginResponse = {
|
|
|
36
44
|
readonly landingPath?: string;
|
|
37
45
|
};
|
|
38
46
|
|
|
39
|
-
// Reason codes the login-handler is known to emit today (login-screen.tsx's
|
|
40
|
-
// reasonToKey maps every one of these to an i18n key). The server can also
|
|
41
|
-
// send a reason this client doesn't know about yet (rolling deploys, a new
|
|
42
|
-
// enforcement policy) — `string & {}` keeps that open while still giving
|
|
43
|
-
// autocomplete/typo-checking on the known set, instead of collapsing both
|
|
44
|
-
// to plain `string` (coding-standards: no magic strings without a union).
|
|
45
|
-
export type KnownLoginFailureReason =
|
|
46
|
-
| "invalid_credentials"
|
|
47
|
-
| "no_membership"
|
|
48
|
-
| "account_locked"
|
|
49
|
-
| "email_not_verified"
|
|
50
|
-
| "rate_limited"
|
|
51
|
-
| "invalid_body"
|
|
52
|
-
| "mfa_not_supported"
|
|
53
|
-
| "mfa_setup_required";
|
|
54
|
-
|
|
55
|
-
export type LoginFailure = {
|
|
56
|
-
readonly reason: KnownLoginFailureReason | (string & {});
|
|
57
|
-
readonly message?: string;
|
|
58
|
-
readonly retryAfterSeconds?: number;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
47
|
export function csrfHeader(): Record<string, string> {
|
|
62
48
|
const token = readCsrfToken();
|
|
63
49
|
return token !== undefined ? { [CSRF_HEADER_NAME]: token } : {};
|
|
@@ -76,13 +62,6 @@ export class AuthRequestError extends Error {
|
|
|
76
62
|
}
|
|
77
63
|
}
|
|
78
64
|
|
|
79
|
-
// Only a non-negative integer second count is usable; anything else is treated as absent.
|
|
80
|
-
export function parseRetryAfterSeconds(header: string | null): number | undefined {
|
|
81
|
-
if (header === null) return undefined;
|
|
82
|
-
if (!/^\d+$/.test(header)) return undefined;
|
|
83
|
-
return Number(header);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
65
|
// LoginResult mirrors the two-step login contract auth-routes.ts owns: a
|
|
87
66
|
// straight success, an MFA challenge (auth-mfa mints the token), an
|
|
88
67
|
// MFA-setup-required block (enforcement policy blocks an unenrolled user),
|
|
@@ -128,13 +107,7 @@ export async function login(req: LoginRequest): Promise<LoginResult> {
|
|
|
128
107
|
mfaSetupRequired?: boolean;
|
|
129
108
|
preauthSetupToken?: string;
|
|
130
109
|
landingPath?: string;
|
|
131
|
-
error?:
|
|
132
|
-
| {
|
|
133
|
-
code?: string;
|
|
134
|
-
message?: string;
|
|
135
|
-
details?: { reason?: string; retryAfterSeconds?: number };
|
|
136
|
-
}
|
|
137
|
-
| string;
|
|
110
|
+
error?: unknown;
|
|
138
111
|
};
|
|
139
112
|
if (body.isSuccess === true) {
|
|
140
113
|
if (body.mfaRequired === true && typeof body.challengeToken === "string") {
|
|
@@ -153,22 +126,7 @@ export async function login(req: LoginRequest): Promise<LoginResult> {
|
|
|
153
126
|
return { kind: "success", data: toLoginResponse(body.token, body.user, body.landingPath) };
|
|
154
127
|
}
|
|
155
128
|
}
|
|
156
|
-
|
|
157
|
-
// structured object. Extract the most descriptive reason.
|
|
158
|
-
const err = body.error;
|
|
159
|
-
if (typeof err === "string") {
|
|
160
|
-
return { kind: "failure", error: { reason: err } };
|
|
161
|
-
}
|
|
162
|
-
const reason = err?.details?.reason ?? err?.code ?? "login_failed";
|
|
163
|
-
const retry = err?.details?.retryAfterSeconds;
|
|
164
|
-
return {
|
|
165
|
-
kind: "failure",
|
|
166
|
-
error: {
|
|
167
|
-
reason,
|
|
168
|
-
...(err?.message !== undefined && { message: err.message }),
|
|
169
|
-
...(retry !== undefined && { retryAfterSeconds: retry }),
|
|
170
|
-
},
|
|
171
|
-
};
|
|
129
|
+
return { kind: "failure", error: failureFromErrorField(body.error, "login_failed") };
|
|
172
130
|
}
|
|
173
131
|
|
|
174
132
|
// POST /api/auth/logout. The server revokes the session when
|
|
@@ -16,6 +16,10 @@ import {
|
|
|
16
16
|
followNextOrLandingAfterLogin,
|
|
17
17
|
} from "./auth-redirect.ts";
|
|
18
18
|
import { LoginScreen, type LoginScreenProps } from "./login-screen.tsx";
|
|
19
|
+
import type { LoginUrlBuilder } from "./login-url.ts";
|
|
20
|
+
|
|
21
|
+
export type { LoginUrlBuilder };
|
|
22
|
+
|
|
19
23
|
import { SessionProvider, useSession } from "./session.tsx";
|
|
20
24
|
import { SessionBootstrapErrorScreen } from "./session-bootstrap-error.tsx";
|
|
21
25
|
|
|
@@ -159,10 +163,6 @@ export function createLoginRoute(
|
|
|
159
163
|
return LoginRoute;
|
|
160
164
|
}
|
|
161
165
|
|
|
162
|
-
/** Builds the login page URL. `returnPath` is the current path + query + hash;
|
|
163
|
-
* append it as `next` yourself (use `NEXT_QUERY_PARAM`) if the login page should return there. */
|
|
164
|
-
export type LoginUrlBuilder = (locale: string, returnPath: string) => string;
|
|
165
|
-
|
|
166
166
|
export type AuthGateOptions = LoginRouteOptions & {
|
|
167
167
|
/** Login page outside the SPA (root-relative path or http(s) URL), or a function
|
|
168
168
|
* that builds it per locale. Unauthenticated visitors are sent there with
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
// string, so anything read back from it is untrusted: only same-origin
|
|
3
3
|
// root-relative paths pass (no "//host", no "/\host", no scheme).
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { appendReturnTo, NEXT_QUERY_PARAM } from "./login-url.ts";
|
|
6
|
+
import { isSafeReturnPath, readReturnToFromSearch } from "./return-path.ts";
|
|
6
7
|
|
|
7
|
-
export
|
|
8
|
+
export { appendReturnTo, NEXT_QUERY_PARAM };
|
|
8
9
|
|
|
9
10
|
export const isSafeNextPath = isSafeReturnPath;
|
|
10
11
|
|
|
@@ -18,16 +19,6 @@ export function readReturnToFromWindow(): string | null {
|
|
|
18
19
|
return typeof window === "undefined" ? null : readReturnToFromSearch(window.location.search);
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
// Adds returnTo to an href of a sibling auth page; a missing value leaves the href untouched.
|
|
22
|
-
export function appendReturnTo(href: string, returnTo: string | null): string {
|
|
23
|
-
if (returnTo === null) return href;
|
|
24
|
-
const hashAt = href.indexOf("#");
|
|
25
|
-
const base = hashAt === -1 ? href : href.slice(0, hashAt);
|
|
26
|
-
const hash = hashAt === -1 ? "" : href.slice(hashAt);
|
|
27
|
-
const separator = base.includes("?") ? "&" : "?";
|
|
28
|
-
return `${base}${separator}${RETURN_TO_QUERY_PARAM}=${encodeURIComponent(returnTo)}${hash}`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
22
|
// The safe `next` of this page's URL; null on native, where there is no window.
|
|
32
23
|
export function readNextFromWindow(): string | null {
|
|
33
24
|
return typeof window === "undefined" ? null : readNextFromSearch(window.location.search);
|
|
@@ -32,6 +32,7 @@ export { AuthCard, AuthShellProvider, useAuthShell } from "./auth-form-primitive
|
|
|
32
32
|
export type {
|
|
33
33
|
AuthGateOptions,
|
|
34
34
|
LoginRouteOptions,
|
|
35
|
+
LoginUrlBuilder,
|
|
35
36
|
MfaSetupComponentProps,
|
|
36
37
|
MfaVerifyComponentProps,
|
|
37
38
|
SessionAuthGateOptions,
|
|
@@ -57,6 +58,7 @@ export { ForgotPasswordScreen } from "./forgot-password-screen.tsx";
|
|
|
57
58
|
export { defaultTranslations, mergeTranslations } from "./i18n.ts";
|
|
58
59
|
export type { InviteAcceptScreenProps } from "./invite-accept-screen.tsx";
|
|
59
60
|
export { InviteAcceptScreen } from "./invite-accept-screen.tsx";
|
|
61
|
+
export { failureFromErrorField } from "./login-failure.ts";
|
|
60
62
|
export type { AuthLegalLink, LoginScreenProps } from "./login-screen.tsx";
|
|
61
63
|
export { LoginScreen } from "./login-screen.tsx";
|
|
62
64
|
export type { RequestAccountUnlockScreenProps } from "./request-account-unlock-screen.tsx";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Pure (no DOM, no renderer): shared by the SPA clients and the static login, which must stay
|
|
2
|
+
// importable from a plain script.
|
|
3
|
+
|
|
4
|
+
// Reason codes the login handler is known to emit (login-screen.tsx's reasonToKey maps every one
|
|
5
|
+
// of these to an i18n key). The server can also send a reason this client does not know yet
|
|
6
|
+
// (rolling deploys, a new enforcement policy), so consumers type `KnownLoginFailureReason |
|
|
7
|
+
// (string & {})`: autocomplete on the known set, no collapse to plain `string`.
|
|
8
|
+
export type KnownLoginFailureReason =
|
|
9
|
+
| "invalid_credentials"
|
|
10
|
+
| "no_membership"
|
|
11
|
+
| "account_locked"
|
|
12
|
+
| "email_not_verified"
|
|
13
|
+
| "rate_limited"
|
|
14
|
+
| "invalid_body"
|
|
15
|
+
| "mfa_not_supported"
|
|
16
|
+
| "mfa_setup_required";
|
|
17
|
+
|
|
18
|
+
export type LoginFailure = {
|
|
19
|
+
readonly reason: KnownLoginFailureReason | (string & {});
|
|
20
|
+
readonly message?: string;
|
|
21
|
+
readonly retryAfterSeconds?: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Only a non-negative integer second count is usable; anything else is treated as absent.
|
|
25
|
+
export function parseRetryAfterSeconds(header: string | null): number | undefined {
|
|
26
|
+
if (header === null) return undefined;
|
|
27
|
+
if (!/^\d+$/.test(header)) return undefined;
|
|
28
|
+
return Number(header);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
32
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function stringOrUndefined(value: unknown): string | undefined {
|
|
36
|
+
return typeof value === "string" ? value : undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// The server sends `error` either as a string ("invalid_body") or as a structured object; this
|
|
40
|
+
// extracts the most descriptive reason, falling back to the endpoint's generic one.
|
|
41
|
+
export function failureFromErrorField(error: unknown, fallbackReason: string): LoginFailure {
|
|
42
|
+
if (typeof error === "string") return { reason: error };
|
|
43
|
+
const fields = isRecord(error) ? error : {};
|
|
44
|
+
const details = isRecord(fields["details"]) ? fields["details"] : {};
|
|
45
|
+
const reason =
|
|
46
|
+
stringOrUndefined(details["reason"]) ?? stringOrUndefined(fields["code"]) ?? fallbackReason;
|
|
47
|
+
const message = stringOrUndefined(fields["message"]);
|
|
48
|
+
const retry = details["retryAfterSeconds"];
|
|
49
|
+
return {
|
|
50
|
+
reason,
|
|
51
|
+
...(message !== undefined && { message }),
|
|
52
|
+
...(typeof retry === "number" && { retryAfterSeconds: retry }),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Pure (no DOM, no `new URL`): the redirect vocabulary shared by the SPA gate and the static login.
|
|
2
|
+
|
|
3
|
+
import { RETURN_TO_QUERY_PARAM } from "./return-path.ts";
|
|
4
|
+
|
|
5
|
+
export const NEXT_QUERY_PARAM = "next";
|
|
6
|
+
|
|
7
|
+
/** Builds the login page URL. `returnPath` is the current path + query + hash;
|
|
8
|
+
* append it as `next` yourself (use `NEXT_QUERY_PARAM`) if the login page should return there. */
|
|
9
|
+
export type LoginUrlBuilder = (locale: string, returnPath: string) => string;
|
|
10
|
+
|
|
11
|
+
// Adds returnTo to an href of a sibling auth page; a missing value leaves the href untouched.
|
|
12
|
+
export function appendReturnTo(href: string, returnTo: string | null): string {
|
|
13
|
+
if (returnTo === null) return href;
|
|
14
|
+
const hashAt = href.indexOf("#");
|
|
15
|
+
const base = hashAt === -1 ? href : href.slice(0, hashAt);
|
|
16
|
+
const hash = hashAt === -1 ? "" : href.slice(hashAt);
|
|
17
|
+
const separator = base.includes("?") ? "&" : "?";
|
|
18
|
+
return `${base}${separator}${RETURN_TO_QUERY_PARAM}=${encodeURIComponent(returnTo)}${hash}`;
|
|
19
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// DOM-free login for static sites (plain Astro scripts): fetch plus pure string logic only. It
|
|
2
|
+
// must not import auth-client.ts or auth-gate.tsx, which pull in the renderer, React and window
|
|
3
|
+
// access at import time. Only the pure modules below are allowed.
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
failureFromErrorField,
|
|
7
|
+
isRecord,
|
|
8
|
+
type KnownLoginFailureReason,
|
|
9
|
+
parseRetryAfterSeconds,
|
|
10
|
+
} from "./login-failure.ts";
|
|
11
|
+
import { appendReturnTo, type LoginUrlBuilder, NEXT_QUERY_PARAM } from "./login-url.ts";
|
|
12
|
+
import { isSafeReturnPath } from "./return-path.ts";
|
|
13
|
+
|
|
14
|
+
export { NEXT_QUERY_PARAM };
|
|
15
|
+
|
|
16
|
+
export const STATIC_LOGIN_FAILED_REASON = "login_failed";
|
|
17
|
+
export const STATIC_MFA_VERIFY_FAILED_REASON = "mfa_verify_failed";
|
|
18
|
+
export const STATIC_NETWORK_FAILURE_REASON = "network";
|
|
19
|
+
export const STATIC_RATE_LIMITED_REASON = "rate_limited" satisfies KnownLoginFailureReason;
|
|
20
|
+
export const STATIC_MFA_SETUP_REQUIRED_REASON =
|
|
21
|
+
"mfa_setup_required" satisfies KnownLoginFailureReason;
|
|
22
|
+
|
|
23
|
+
export type StaticAuthResult =
|
|
24
|
+
| { readonly kind: "success"; readonly landingPath?: string }
|
|
25
|
+
| { readonly kind: "mfa-challenge"; readonly challengeToken: string }
|
|
26
|
+
| { readonly kind: "mfa-setup-required" }
|
|
27
|
+
| {
|
|
28
|
+
readonly kind: "failure";
|
|
29
|
+
readonly reason: KnownLoginFailureReason | (string & {});
|
|
30
|
+
readonly retryAfterSeconds?: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function failure(
|
|
34
|
+
reason: string,
|
|
35
|
+
retryAfterSeconds?: number,
|
|
36
|
+
): Extract<StaticAuthResult, { kind: "failure" }> {
|
|
37
|
+
return {
|
|
38
|
+
kind: "failure",
|
|
39
|
+
reason,
|
|
40
|
+
...(retryAfterSeconds !== undefined && { retryAfterSeconds }),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function interpretBody(body: Record<string, unknown>, fallbackReason: string): StaticAuthResult {
|
|
45
|
+
if (body["isSuccess"] === true) {
|
|
46
|
+
const challengeToken = body["challengeToken"];
|
|
47
|
+
if (body["mfaRequired"] === true && typeof challengeToken === "string") {
|
|
48
|
+
return { kind: "mfa-challenge", challengeToken };
|
|
49
|
+
}
|
|
50
|
+
if (body["mfaSetupRequired"] === true) return { kind: "mfa-setup-required" };
|
|
51
|
+
if (body["token"] !== undefined && body["user"] !== undefined) {
|
|
52
|
+
const landingPath = body["landingPath"];
|
|
53
|
+
return typeof landingPath === "string"
|
|
54
|
+
? { kind: "success", landingPath }
|
|
55
|
+
: { kind: "success" };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const { reason, retryAfterSeconds } = failureFromErrorField(body["error"], fallbackReason);
|
|
59
|
+
if (reason === STATIC_MFA_SETUP_REQUIRED_REASON) return { kind: "mfa-setup-required" };
|
|
60
|
+
return failure(reason, retryAfterSeconds);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Same request shape as auth-client's login() and mfa-client's verifyMfaChallenge(): no CSRF
|
|
64
|
+
// header because no session exists yet.
|
|
65
|
+
async function postAuth(
|
|
66
|
+
path: string,
|
|
67
|
+
payload: Record<string, string>,
|
|
68
|
+
fallbackReason: string,
|
|
69
|
+
): Promise<StaticAuthResult> {
|
|
70
|
+
let res: Response;
|
|
71
|
+
try {
|
|
72
|
+
res = await fetch(path, {
|
|
73
|
+
method: "POST",
|
|
74
|
+
credentials: "same-origin",
|
|
75
|
+
headers: { "Content-Type": "application/json" },
|
|
76
|
+
body: JSON.stringify(payload),
|
|
77
|
+
});
|
|
78
|
+
} catch {
|
|
79
|
+
return failure(STATIC_NETWORK_FAILURE_REASON);
|
|
80
|
+
}
|
|
81
|
+
if (res.status === 429) {
|
|
82
|
+
return failure(
|
|
83
|
+
STATIC_RATE_LIMITED_REASON,
|
|
84
|
+
parseRetryAfterSeconds(res.headers.get("Retry-After")),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
const parsed: unknown = await res.json().catch(() => ({}));
|
|
88
|
+
return interpretBody(isRecord(parsed) ? parsed : {}, fallbackReason);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function loginWithPassword(email: string, password: string): Promise<StaticAuthResult> {
|
|
92
|
+
return postAuth("/api/auth/login", { email, password }, STATIC_LOGIN_FAILED_REASON);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function verifyMfaCode(challengeToken: string, code: string): Promise<StaticAuthResult> {
|
|
96
|
+
return postAuth(
|
|
97
|
+
"/api/auth/mfa/verify",
|
|
98
|
+
{ challengeToken, code },
|
|
99
|
+
STATIC_MFA_VERIFY_FAILED_REASON,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type IsAppPath = (pathname: string) => boolean;
|
|
104
|
+
|
|
105
|
+
function pathnameOf(pathAndQuery: string): string {
|
|
106
|
+
const end = pathAndQuery.search(/[?#]/);
|
|
107
|
+
return end === -1 ? pathAndQuery : pathAndQuery.slice(0, end);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// `next` is read from a URL, so beyond the shared same-origin check it must also point into the
|
|
111
|
+
// app; string logic only, because React Native's URL getters throw.
|
|
112
|
+
export function appReturnPath(candidate: unknown, isAppPath: IsAppPath): string | null {
|
|
113
|
+
if (!isSafeReturnPath(candidate)) return null;
|
|
114
|
+
return isAppPath(pathnameOf(candidate)) ? candidate : null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function readAppNext(search: string, isAppPath: IsAppPath): string | null {
|
|
118
|
+
return appReturnPath(new URLSearchParams(search).get(NEXT_QUERY_PARAM), isAppPath);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function postLoginPath(options: {
|
|
122
|
+
readonly search: string;
|
|
123
|
+
readonly landingPath?: string | undefined;
|
|
124
|
+
readonly isAppPath: IsAppPath;
|
|
125
|
+
readonly fallbackPath: string;
|
|
126
|
+
}): string {
|
|
127
|
+
const next = readAppNext(options.search, options.isAppPath);
|
|
128
|
+
if (next !== null) return next;
|
|
129
|
+
return appReturnPath(options.landingPath, options.isAppPath) ?? options.fallbackPath;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function forgotPasswordHref(
|
|
133
|
+
forgotPasswordPath: string,
|
|
134
|
+
search: string,
|
|
135
|
+
isAppPath: IsAppPath,
|
|
136
|
+
): string {
|
|
137
|
+
return appendReturnTo(forgotPasswordPath, readAppNext(search, isAppPath));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export type MarketingLoginPaths = {
|
|
141
|
+
readonly default: string;
|
|
142
|
+
/** Lowercase locale prefix ("de") to its login path. */
|
|
143
|
+
readonly byLocale?: Readonly<Record<string, string>>;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
function trimTrailingSlashes(pathname: string): string {
|
|
147
|
+
return pathname.replace(/\/+$/, "");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function isMarketingLoginPath(pathname: string, localePaths: MarketingLoginPaths): boolean {
|
|
151
|
+
const target = trimTrailingSlashes(pathname);
|
|
152
|
+
const loginPaths = [localePaths.default, ...Object.values(localePaths.byLocale ?? {})];
|
|
153
|
+
return loginPaths.some((loginPath) => trimTrailingSlashes(loginPath) === target);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function loginPathFor(locale: string, localePaths: MarketingLoginPaths): string {
|
|
157
|
+
const prefix = (locale.toLowerCase().split(/[-_]/)[0] ?? "").trim();
|
|
158
|
+
const byLocale = localePaths.byLocale;
|
|
159
|
+
if (byLocale !== undefined && Object.hasOwn(byLocale, prefix)) {
|
|
160
|
+
return byLocale[prefix] ?? localePaths.default;
|
|
161
|
+
}
|
|
162
|
+
return localePaths.default;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Marketing site to app login: keeps the user's place as `next` when it is an app path, and never
|
|
166
|
+
// bounces from a login page back to itself.
|
|
167
|
+
export function marketingLoginUrl(options: {
|
|
168
|
+
readonly localePaths: MarketingLoginPaths;
|
|
169
|
+
readonly isAppPath: IsAppPath;
|
|
170
|
+
}): LoginUrlBuilder {
|
|
171
|
+
const { localePaths, isAppPath } = options;
|
|
172
|
+
return (locale, returnPath) => {
|
|
173
|
+
const hashAt = returnPath.indexOf("#");
|
|
174
|
+
const pathAndQuery = hashAt === -1 ? returnPath : returnPath.slice(0, hashAt);
|
|
175
|
+
const pathname = pathnameOf(pathAndQuery);
|
|
176
|
+
if (isMarketingLoginPath(pathname, localePaths)) return pathname;
|
|
177
|
+
const loginPath = loginPathFor(locale, localePaths);
|
|
178
|
+
const next = appReturnPath(pathAndQuery, isAppPath);
|
|
179
|
+
return next === null
|
|
180
|
+
? loginPath
|
|
181
|
+
: `${loginPath}?${NEXT_QUERY_PARAM}=${encodeURIComponent(next)}`;
|
|
182
|
+
};
|
|
183
|
+
}
|