@cosmicdrift/kumiko-bundled-features 0.387.0 → 0.389.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-tools/index.d.ts +1 -1
- package/dist/agent-tools/index.js +1 -1
- package/dist/agent-tools/tool-dispatch.d.ts +12 -7
- package/dist/agent-tools/tool-dispatch.js +42 -31
- package/dist/audit/feature.js +1 -0
- package/dist/audit/handlers/audit-events.js +1 -1
- package/dist/auth-email-password/handlers/request-account-unlock.write.d.ts +1 -0
- package/dist/auth-email-password/handlers/request-account-unlock.write.js +1 -0
- package/dist/auth-email-password/handlers/request-email-verification.write.d.ts +1 -0
- package/dist/auth-email-password/handlers/request-email-verification.write.js +1 -0
- package/dist/auth-email-password/handlers/request-password-reset.write.d.ts +1 -0
- package/dist/auth-email-password/handlers/request-password-reset.write.js +1 -0
- package/dist/auth-email-password/handlers/signup-request.write.d.ts +1 -0
- package/dist/auth-email-password/handlers/signup-request.write.js +4 -0
- package/dist/auth-email-password/handlers/token-request-handler.d.ts +2 -0
- package/dist/auth-email-password/handlers/token-request-handler.js +5 -0
- package/dist/auth-email-password/magic-link-mail.d.ts +2 -0
- package/dist/auth-email-password/magic-link-mail.js +7 -3
- package/dist/auth-email-password/signup-activation.d.ts +1 -0
- package/dist/auth-email-password/signup-activation.js +1 -0
- package/dist/auth-email-password/web/auth-client.d.ts +2 -2
- package/dist/auth-email-password/web/auth-client.js +8 -4
- package/dist/auth-email-password/web/auth-redirect.d.ts +5 -1
- package/dist/auth-email-password/web/auth-redirect.js +20 -25
- package/dist/auth-email-password/web/default-auth-pages-gate.js +6 -3
- package/dist/auth-email-password/web/forgot-password-screen.d.ts +4 -1
- package/dist/auth-email-password/web/forgot-password-screen.js +7 -2
- package/dist/auth-email-password/web/index.d.ts +1 -0
- package/dist/auth-email-password/web/index.js +1 -0
- package/dist/auth-email-password/web/login-screen.js +11 -6
- package/dist/auth-email-password/web/reset-password-screen.js +7 -1
- package/dist/auth-email-password/web/return-path.d.ts +4 -0
- package/dist/auth-email-password/web/return-path.js +34 -0
- package/dist/auth-email-password/web/session.js +6 -1
- package/dist/auth-email-password/web/signup-complete-screen.js +11 -3
- package/dist/auth-email-password/web/signup-screen.d.ts +4 -1
- package/dist/auth-email-password/web/signup-screen.js +8 -3
- package/dist/auth-mfa/config.d.ts +1 -1
- package/dist/auth-mfa/config.js +3 -7
- package/dist/auth-mfa/feature-exports.d.ts +2 -2
- package/dist/auth-mfa/feature.js +8 -0
- package/dist/auth-mfa/mfa-status-checker.js +5 -3
- package/dist/auth-mfa/web/i18n.js +5 -1
- package/dist/auth-mfa/web/mfa-code-or-recovery-field.d.ts +11 -0
- package/dist/auth-mfa/web/mfa-code-or-recovery-field.js +12 -0
- package/dist/auth-mfa/web/mfa-disable-dialog.js +14 -3
- package/dist/auth-mfa/web/mfa-regenerate-recovery-dialog.js +14 -3
- package/dist/auth-mfa/web/mfa-setup-preauth-screen.js +10 -5
- package/dist/auth-mfa/web/mfa-verify-screen.js +26 -5
- package/dist/billing-foundation/checkout-core.d.ts +9 -14
- package/dist/billing-foundation/checkout-core.js +24 -39
- package/dist/billing-foundation/consumer-protection/confirmation-mail.js +8 -2
- package/dist/billing-foundation/consumer-protection/consent-gate.d.ts +1 -0
- package/dist/billing-foundation/consumer-protection/consent-gate.js +1 -0
- package/dist/billing-foundation/consumer-protection/issue-confirmation.js +9 -3
- package/dist/billing-foundation/consumer-protection/mail-notifications.js +3 -1
- package/dist/billing-foundation/consumer-protection/termination-receipt-limit.d.ts +4 -2
- package/dist/billing-foundation/consumer-protection/termination-receipt-limit.js +6 -5
- package/dist/billing-foundation/consumer-protection/termination-record.d.ts +5 -1
- package/dist/billing-foundation/consumer-protection/termination-record.js +27 -17
- package/dist/billing-foundation/consumer-protection/termination-request.js +12 -7
- package/dist/billing-foundation/consumer-protection/termination-revoke.js +12 -7
- package/dist/billing-foundation/consumer-protection/termination-texts.d.ts +1 -1
- package/dist/billing-foundation/consumer-protection/termination-texts.js +3 -0
- package/dist/billing-foundation/consumer-protection/withdrawal-period.d.ts +5 -2
- package/dist/billing-foundation/consumer-protection/withdrawal-period.js +8 -9
- package/dist/billing-foundation/events.d.ts +1 -0
- package/dist/billing-foundation/events.js +1 -0
- package/dist/billing-foundation/feature.js +3 -1
- package/dist/billing-foundation/handlers/billing-plans.query.js +35 -5
- package/dist/billing-foundation/handlers/create-checkout-session.write.js +1 -0
- package/dist/billing-foundation/handlers/plan-switch-preview.query.d.ts +13 -0
- package/dist/billing-foundation/handlers/plan-switch-preview.query.js +48 -0
- package/dist/billing-foundation/handlers/process-event.write.d.ts +6 -0
- package/dist/billing-foundation/handlers/process-event.write.js +8 -2
- package/dist/billing-foundation/handlers/start-plan-checkout.write.js +32 -5
- package/dist/billing-foundation/handlers/switch-plan.write.d.ts +2 -2
- package/dist/billing-foundation/handlers/switch-plan.write.js +56 -64
- package/dist/billing-foundation/handlers/sync-subscription.write.js +12 -1
- package/dist/billing-foundation/i18n.js +22 -0
- package/dist/billing-foundation/index.d.ts +1 -1
- package/dist/billing-foundation/plan-catalog.d.ts +21 -5
- package/dist/billing-foundation/plan-catalog.js +25 -16
- package/dist/billing-foundation/plan-switch.d.ts +12 -0
- package/dist/billing-foundation/plan-switch.js +60 -0
- package/dist/billing-foundation/types.d.ts +57 -1
- package/dist/billing-foundation/types.js +9 -0
- package/dist/billing-foundation/validate-options.js +17 -0
- package/dist/billing-foundation/web/billing-plans-panel.js +78 -32
- package/dist/billing-foundation/web/checkout-consent-dialog.d.ts +17 -3
- package/dist/billing-foundation/web/checkout-consent-dialog.js +14 -10
- package/dist/billing-foundation/web/constants.d.ts +4 -0
- package/dist/billing-foundation/web/constants.js +4 -0
- package/dist/billing-foundation/web/plan-price.d.ts +15 -0
- package/dist/billing-foundation/web/plan-price.js +32 -0
- package/dist/billing-foundation/web/plan-switch-confirm-dialog.d.ts +15 -0
- package/dist/billing-foundation/web/plan-switch-confirm-dialog.js +66 -0
- package/dist/cap-counter/cap-reservation.js +20 -7
- package/dist/cap-counter/enforce-cap.js +2 -1
- package/dist/cap-counter/feature.js +1 -0
- package/dist/cap-counter/stock-cap-guard.js +7 -1
- package/dist/cap-overview/feature.js +1 -0
- package/dist/channel-email/feature.js +1 -1
- package/dist/config/handlers/cascade.query.d.ts +5 -0
- package/dist/config/handlers/cascade.query.js +4 -3
- package/dist/config/handlers/set.write.js +12 -2
- package/dist/config/handlers/values.query.d.ts +5 -0
- package/dist/config/handlers/values.query.js +4 -3
- package/dist/config/write-helpers.d.ts +1 -0
- package/dist/config/write-helpers.js +18 -9
- package/dist/crypto-shredding/handlers/forget-subject.write.js +14 -11
- package/dist/custom-fields/lib/parse-serialized-field.js +1 -1
- package/dist/delivery/channel-context.js +6 -3
- package/dist/delivery/db/queries/preferences.js +2 -2
- package/dist/delivery/delivery-service.js +22 -7
- package/dist/delivery/feature.js +1 -0
- package/dist/delivery/handlers/preferences.query.d.ts +1 -1
- package/dist/delivery/handlers/preferences.query.js +20 -1
- package/dist/delivery/handlers/unsubscribe-page-chrome.query.js +1 -1
- package/dist/delivery/index.d.ts +1 -0
- package/dist/delivery/index.js +1 -0
- package/dist/delivery/system-channel-context.d.ts +15 -0
- package/dist/delivery/system-channel-context.js +5 -0
- package/dist/delivery/unsubscribe-link.d.ts +3 -1
- package/dist/delivery/unsubscribe-page-strings.d.ts +1 -0
- package/dist/delivery/unsubscribe-page-strings.js +1 -0
- package/dist/delivery/unsubscribe-page.d.ts +2 -0
- package/dist/delivery/unsubscribe-page.js +5 -0
- package/dist/delivery/unsubscribe.js +25 -7
- package/dist/document-ingest-foundation/append-missing-extract-event.d.ts +21 -0
- package/dist/document-ingest-foundation/append-missing-extract-event.js +42 -0
- package/dist/document-ingest-foundation/events.d.ts +11 -0
- package/dist/document-ingest-foundation/events.js +11 -0
- package/dist/document-ingest-foundation/index.d.ts +2 -1
- package/dist/document-ingest-foundation/index.js +2 -1
- package/dist/feature-toggles/feature.js +1 -0
- package/dist/file-foundation/index.d.ts +1 -1
- package/dist/file-foundation/index.js +1 -1
- package/dist/file-foundation/read-file-ref-content.d.ts +3 -0
- package/dist/file-foundation/read-file-ref-content.js +6 -1
- package/dist/file-provider-s3/env-named-feature.d.ts +0 -2
- package/dist/form-draft/db/queries/cleanup.js +2 -2
- package/dist/form-draft/decrypt-draft.js +5 -1
- package/dist/host-routing/dispatch.js +11 -20
- package/dist/host-routing/validate-options.js +3 -0
- package/dist/inbound-mail-foundation/feature.js +1 -0
- package/dist/inbound-mail-foundation/handlers/update-account.write.d.ts +2 -2
- package/dist/inbound-mail-foundation/handlers/update-account.write.js +12 -4
- package/dist/inbound-mail-foundation/i18n.js +0 -1
- package/dist/inbound-mail-foundation/screens.js +0 -3
- package/dist/inbound-mail-foundation/types.d.ts +4 -3
- package/dist/inbound-mail-foundation/watch-supervisor.js +28 -11
- package/dist/jobs/feature.d.ts +1 -0
- package/dist/jobs/feature.js +9 -4
- package/dist/jobs/handlers/tenant-failures.query.d.ts +1 -1
- package/dist/jobs/handlers/tenant-failures.query.js +45 -41
- package/dist/jobs/handlers/tenant-runs.query.d.ts +1 -1
- package/dist/jobs/handlers/tenant-runs.query.js +54 -50
- package/dist/jobs/job-run-logger.js +5 -4
- package/dist/ledger/web/index.js +1 -1
- package/dist/legal-pages/seed.d.ts +3 -1
- package/dist/legal-pages/seed.js +2 -2
- package/dist/mail-foundation/feature.d.ts +3 -3
- package/dist/mail-foundation/feature.js +3 -0
- package/dist/mail-transport-smtp/config-keys.js +1 -1
- package/dist/mail-transport-smtp/feature.js +6 -5
- package/dist/managed-pages/constants.d.ts +1 -0
- package/dist/managed-pages/constants.js +1 -0
- package/dist/managed-pages/feature.js +3 -1
- package/dist/managed-pages/index.d.ts +1 -0
- package/dist/managed-pages/index.js +1 -0
- package/dist/notes-history/handlers/add-note.write.js +1 -1
- package/dist/personal-access-tokens/feature.d.ts +1 -1
- package/dist/personal-access-tokens/feature.js +5 -0
- package/dist/renderer-pdf/render-table-pdf.d.ts +1 -1
- package/dist/renderer-pdf/render-table-pdf.js +22 -10
- package/dist/secrets/write-gate.js +8 -2
- package/dist/sessions/feature.d.ts +2 -2
- package/dist/sessions/feature.js +5 -0
- package/dist/sessions/index.d.ts +1 -1
- package/dist/sessions/index.js +1 -1
- package/dist/sessions/session-callbacks.d.ts +0 -2
- package/dist/sessions/session-callbacks.js +1 -3
- package/dist/shared/decrypt-stored-pii.d.ts +1 -0
- package/dist/shared/decrypt-stored-pii.js +6 -1
- package/dist/shared/index.d.ts +1 -1
- package/dist/shared/index.js +1 -1
- package/dist/shared/mail-template-body.d.ts +6 -2
- package/dist/shared/mail-template-body.js +7 -4
- package/dist/shared/purge-files-reaching-subject.d.ts +1 -0
- package/dist/shared/purge-files-reaching-subject.js +19 -2
- package/dist/shared/run-in-sub-transaction.js +5 -5
- package/dist/step-dispatcher/deliver-dispatch-request.js +16 -3
- package/dist/step-dispatcher/feature.js +1 -0
- package/dist/step-dispatcher/mail-runner.d.ts +1 -0
- package/dist/subscription-dodo/client.d.ts +17 -0
- package/dist/subscription-dodo/client.js +31 -0
- package/dist/subscription-dodo/constants.d.ts +12 -0
- package/dist/subscription-dodo/constants.js +19 -0
- package/dist/subscription-dodo/feature.d.ts +37 -0
- package/dist/subscription-dodo/feature.js +144 -0
- package/dist/subscription-dodo/index.d.ts +2 -0
- package/dist/subscription-dodo/index.js +4 -0
- package/dist/subscription-dodo/plugin-methods.d.ts +23 -0
- package/dist/subscription-dodo/plugin-methods.js +260 -0
- package/dist/subscription-dodo/runtime.d.ts +27 -0
- package/dist/subscription-dodo/runtime.js +89 -0
- package/dist/subscription-dodo/verify-webhook.d.ts +37 -0
- package/dist/subscription-dodo/verify-webhook.js +189 -0
- package/dist/subscription-stripe/feature.js +1 -0
- package/dist/subscription-stripe/runtime.js +7 -6
- package/dist/tags/feature.js +1 -0
- package/dist/template-resolver/api.d.ts +3 -2
- package/dist/template-resolver/api.js +24 -2
- package/dist/template-resolver/compile-document-body.js +1 -0
- package/dist/template-resolver/document-ops.d.ts +5 -0
- package/dist/template-resolver/document-ops.js +18 -2
- package/dist/template-resolver/feature.js +4 -4
- package/dist/template-resolver/handlers/bundle-import.js +3 -3
- package/dist/template-resolver/handlers/copy-locale.write.d.ts +5 -1
- package/dist/template-resolver/handlers/copy-locale.write.js +22 -7
- package/dist/template-resolver/handlers/diff-to-system.query.js +3 -2
- package/dist/template-resolver/handlers/document-base.js +3 -2
- package/dist/template-resolver/handlers/find-by-id.query.js +3 -2
- package/dist/template-resolver/handlers/layout-source.query.d.ts +4 -0
- package/dist/template-resolver/handlers/layout-source.query.js +26 -6
- package/dist/template-resolver/handlers/list.query.js +5 -6
- package/dist/template-resolver/handlers/overview.query.d.ts +3 -0
- package/dist/template-resolver/handlers/overview.query.js +21 -10
- package/dist/template-resolver/handlers/patch-draft.write.js +1 -1
- package/dist/template-resolver/handlers/send-test-status.query.d.ts +2 -1
- package/dist/template-resolver/handlers/send-test-status.query.js +18 -15
- package/dist/template-resolver/handlers/tenant-override-counts.query.js +1 -3
- package/dist/template-resolver/handlers/tenant-override-selection.d.ts +2 -1
- package/dist/template-resolver/handlers/tenant-overrides.query.js +28 -5
- package/dist/template-resolver/handlers/usage.query.d.ts +2 -1
- package/dist/template-resolver/handlers/usage.query.js +26 -16
- package/dist/template-resolver/handlers/validate.query.js +7 -2
- package/dist/template-resolver/seeding.d.ts +1 -0
- package/dist/template-resolver/seeding.js +31 -12
- package/dist/template-resolver/store-template-resource.d.ts +1 -0
- package/dist/template-resolver/store-template-resource.js +65 -10
- package/dist/template-resolver/template-delivery-policy.d.ts +0 -5
- package/dist/template-resolver/template-delivery-policy.js +0 -6
- package/dist/template-resolver/template-meta.js +7 -4
- package/dist/template-resolver/template-preview.js +4 -3
- package/dist/template-resolver/web/constants.d.ts +2 -2
- package/dist/template-resolver/web/constants.js +3 -3
- package/dist/template-resolver/web/i18n.js +3 -0
- package/dist/template-resolver/web/index.d.ts +1 -0
- package/dist/template-resolver/web/index.js +1 -0
- package/dist/template-resolver/web/mail-layout-constants.d.ts +5 -0
- package/dist/template-resolver/web/mail-layout-constants.js +7 -0
- package/dist/template-resolver/web/qualified-names.d.ts +1 -0
- package/dist/template-resolver/web/qualified-names.js +1 -0
- package/dist/tenant/feature.js +1 -0
- package/dist/tenant/handlers/member-directory.query.d.ts +1 -0
- package/dist/tenant/handlers/member-directory.query.js +10 -3
- package/dist/tenant/handlers/team-list.query.js +3 -3
- package/dist/tenant/handlers/tenant-directory.query.d.ts +1 -0
- package/dist/tenant/handlers/tenant-directory.query.js +8 -4
- package/dist/tenant-lifecycle/constants.d.ts +1 -0
- package/dist/tenant-lifecycle/constants.js +4 -0
- package/dist/tenant-lifecycle/run-tenant-destroy.js +2 -2
- package/dist/tenant-lifecycle/stages.js +22 -1
- package/dist/tier-engine/handlers/set-tenant-tier.write.d.ts +2 -3
- package/dist/tier-engine/handlers/set-tenant-tier.write.js +4 -1
- package/dist/tier-engine/web/constants.js +2 -2
- package/dist/user/feature.js +1 -0
- package/dist/user-data-rights/feature.js +1 -0
- package/dist/user-data-rights/index.d.ts +1 -1
- package/dist/user-data-rights/index.js +1 -1
- package/dist/user-data-rights/run-export-jobs.js +3 -4
- package/dist/user-data-rights/run-forget-cleanup.js +2 -1
- package/dist/user-data-rights/web/constants.d.ts +6 -0
- package/dist/user-data-rights/web/constants.js +6 -0
- package/dist/user-data-rights/web/index.d.ts +1 -0
- package/dist/user-data-rights/web/index.js +1 -0
- package/dist/waitlist/feature.js +1 -0
- package/dist/waitlist/handlers/export-purge.write.js +2 -1
- package/dist/waitlist/handlers/submit.write.js +14 -10
- package/dist/waitlist/import.d.ts +3 -1
- package/dist/waitlist/import.js +26 -2
- package/dist/waitlist/legacy-migrate.js +18 -7
- package/dist/waitlist/link-on-signup.js +18 -6
- package/dist/waitlist/options.d.ts +5 -1
- package/dist/workflow-runner/handlers/resume-run.write.js +1 -1
- package/package.json +19 -9
- package/src/agent-tools/changes.json +12 -0
- package/src/audit/changes.json +12 -0
- package/src/auth-email-password/changes.json +12 -0
- package/src/auth-mfa/changes.json +12 -0
- package/src/billing-foundation/changes.json +55 -0
- package/src/cap-counter/changes.json +30 -0
- package/src/channel-email/changes.json +6 -0
- package/src/config/changes.json +18 -0
- package/src/crypto-shredding/changes.json +18 -0
- package/src/delivery/changes.json +42 -0
- package/src/document-ingest-foundation/changes.json +6 -0
- package/src/form-draft/changes.json +6 -0
- package/src/host-routing/changes.json +12 -0
- package/src/inbound-mail-foundation/changes.json +18 -0
- package/src/jobs/changes.json +13 -0
- package/src/mail-foundation/changes.json +6 -0
- package/src/mail-transport-smtp/changes.json +6 -0
- package/src/managed-pages/changes.json +6 -0
- package/src/renderer-pdf/changes.json +6 -0
- package/src/secrets/changes.json +6 -0
- package/src/sessions/changes.json +7 -0
- package/src/step-dispatcher/changes.json +12 -0
- package/src/subscription-dodo/changes.json +14 -0
- package/src/subscription-stripe/changes.json +12 -0
- package/src/template-resolver/changes.json +84 -0
- package/src/tenant/changes.json +6 -0
- package/src/tenant-lifecycle/changes.json +12 -0
- package/src/tier-engine/changes.json +6 -0
- package/src/user-data-rights/changes.json +12 -0
- package/src/waitlist/changes.json +36 -0
- package/dist/billing-foundation/consumer-protection/stored-user-email.d.ts +0 -1
- package/dist/billing-foundation/consumer-protection/stored-user-email.js +0 -8
|
@@ -4,5 +4,5 @@ export { buildAgentManifest } from "./agent-manifest.ts";
|
|
|
4
4
|
export { AGENT_TOOLS_FEATURE_NAME, createAgentToolsFeature } from "./feature.ts";
|
|
5
5
|
export { buildToolCatalog, OPEN_FORM_TOOL_NAME, toolNameForQn } from "./tool-catalog.ts";
|
|
6
6
|
export type { ToolCallRequest, ToolCallResult, ToolDispatcher } from "./tool-dispatch.ts";
|
|
7
|
-
export { dispatchToolCall } from "./tool-dispatch.ts";
|
|
7
|
+
export { dispatchToolCall, TOOL_CALL_ERROR_CODES } from "./tool-dispatch.ts";
|
|
8
8
|
export type { AgentManifest, AgentManifestEntity, AgentManifestFeature, AgentManifestField, AgentManifestHandler, AgentManifestNav, AgentManifestOptions, AgentManifestScreen, AgentManifestWorkspace, AgentToolMode, RegistryManifestView, RegistrySearchView, ToolCatalog, ToolCatalogOptions, ToolDefinition, ToolDispatchDescriptor, } from "./types.ts";
|
|
@@ -2,4 +2,4 @@ export { AgentDocGapKinds, findAgentDocGaps, findHandlerTranslationGaps, formatA
|
|
|
2
2
|
export { buildAgentManifest } from "./agent-manifest.js";
|
|
3
3
|
export { AGENT_TOOLS_FEATURE_NAME, createAgentToolsFeature } from "./feature.js";
|
|
4
4
|
export { buildToolCatalog, OPEN_FORM_TOOL_NAME, toolNameForQn } from "./tool-catalog.js";
|
|
5
|
-
export { dispatchToolCall } from "./tool-dispatch.js";
|
|
5
|
+
export { dispatchToolCall, TOOL_CALL_ERROR_CODES } from "./tool-dispatch.js";
|
|
@@ -37,11 +37,16 @@ export type ToolCallRequest = {
|
|
|
37
37
|
/** Receives the cause of unexpected (5xx) failures, which the tool result never carries. */
|
|
38
38
|
readonly log?: Pick<Logger, "error">;
|
|
39
39
|
};
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
*
|
|
46
|
-
*
|
|
40
|
+
export declare const TOOL_CALL_ERROR_CODES: {
|
|
41
|
+
readonly invalidToolCall: "invalid_tool_call";
|
|
42
|
+
readonly unknownTool: "unknown_tool";
|
|
43
|
+
};
|
|
44
|
+
/** Executes one LLM-issued tool call. Never throws: every failure comes back as `{ ok: false }`
|
|
45
|
+
* so the agent loop can feed the error back to the model instead of crashing.
|
|
46
|
+
* Every failure carries a `code`: the handler's own code for handler and dispatcher failures
|
|
47
|
+
* (429 adds `retryable`), `invalid_tool_call` for bad arguments and `unknown_tool` for a tool
|
|
48
|
+
* that is not in the table. Client errors (< 500) keep their message, server failures (>= 500,
|
|
49
|
+
* unknown throws) are logged and returned as a generic message.
|
|
50
|
+
* `request.user` must be the caller's real identity: server-kind tools run through the same
|
|
51
|
+
* permission-checked pipeline as an HTTP request. */
|
|
47
52
|
export declare function dispatchToolCall(request: ToolCallRequest): Promise<ToolCallResult>;
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { isKumikoError } from "@cosmicdrift/kumiko-framework/errors";
|
|
2
|
-
import {
|
|
2
|
+
import { runAsAgentToolCallEntry } from "@cosmicdrift/kumiko-framework/internal/request-context";
|
|
3
3
|
import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
|
|
4
4
|
import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
5
5
|
import { FILTER_OPS, isRecord } from "./filter-ops.js";
|
|
6
|
+
export const TOOL_CALL_ERROR_CODES = {
|
|
7
|
+
invalidToolCall: "invalid_tool_call",
|
|
8
|
+
unknownTool: "unknown_tool",
|
|
9
|
+
};
|
|
10
|
+
function invalidToolCall(error) {
|
|
11
|
+
return { ok: false, error, code: TOOL_CALL_ERROR_CODES.invalidToolCall };
|
|
12
|
+
}
|
|
13
|
+
function unknownTool(error) {
|
|
14
|
+
return { ok: false, error, code: TOOL_CALL_ERROR_CODES.unknownTool };
|
|
15
|
+
}
|
|
6
16
|
const HTTP_TOO_MANY_REQUESTS = 429;
|
|
7
17
|
const HTTP_SERVER_ERROR = 500;
|
|
8
18
|
const UNEXPECTED_FAILURE_MESSAGE = "Tool failed unexpectedly";
|
|
@@ -52,14 +62,14 @@ function isFilterOp(value) {
|
|
|
52
62
|
function buildSearchPayload(toolName, toolInput) {
|
|
53
63
|
const query = toolInput["query"];
|
|
54
64
|
if (typeof query !== "string" || query.length === 0) {
|
|
55
|
-
return
|
|
65
|
+
return invalidToolCall(`Tool "${toolName}" requires a non-empty string "query" argument`);
|
|
56
66
|
}
|
|
57
67
|
return { ok: true, value: { search: query, limit: SEARCH_RESULT_LIMIT } };
|
|
58
68
|
}
|
|
59
69
|
function buildFindByPayload(toolName, fieldName, toolInput) {
|
|
60
70
|
const value = toolInput[fieldName];
|
|
61
71
|
if (value === undefined) {
|
|
62
|
-
return
|
|
72
|
+
return invalidToolCall(`Tool "${toolName}" requires a "${fieldName}" argument`);
|
|
63
73
|
}
|
|
64
74
|
return {
|
|
65
75
|
ok: true,
|
|
@@ -75,28 +85,28 @@ function buildEntityListPayload(list, input) {
|
|
|
75
85
|
const search = input["search"];
|
|
76
86
|
if (search !== undefined) {
|
|
77
87
|
if (typeof search !== "string") {
|
|
78
|
-
return
|
|
88
|
+
return invalidToolCall(`"search" must be a string`);
|
|
79
89
|
}
|
|
80
90
|
if (list.searchableFields.length === 0) {
|
|
81
|
-
return
|
|
91
|
+
return invalidToolCall("This entity has no searchable fields");
|
|
82
92
|
}
|
|
83
93
|
payload["search"] = search;
|
|
84
94
|
}
|
|
85
95
|
const filters = input["filters"];
|
|
86
96
|
if (filters !== undefined) {
|
|
87
97
|
if (!Array.isArray(filters)) {
|
|
88
|
-
return
|
|
98
|
+
return invalidToolCall(`"filters" must be an array`);
|
|
89
99
|
}
|
|
90
100
|
const validated = [];
|
|
91
101
|
for (const entry of filters) {
|
|
92
102
|
if (!isRecord(entry) || typeof entry["field"] !== "string" || !("value" in entry)) {
|
|
93
|
-
return
|
|
103
|
+
return invalidToolCall(`Each filter must be { field, op, value }`);
|
|
94
104
|
}
|
|
95
105
|
if (!list.filterableFields.includes(entry["field"])) {
|
|
96
|
-
return
|
|
106
|
+
return invalidToolCall(`Field "${entry["field"]}" is not filterable on this entity`);
|
|
97
107
|
}
|
|
98
108
|
if (!isFilterOp(entry["op"])) {
|
|
99
|
-
return
|
|
109
|
+
return invalidToolCall(`Unsupported filter operator "${String(entry["op"])}"`);
|
|
100
110
|
}
|
|
101
111
|
validated.push({ field: entry["field"], op: entry["op"], value: entry["value"] });
|
|
102
112
|
}
|
|
@@ -147,7 +157,7 @@ async function dispatchServerQuery(descriptor, dispatcher, user, input) {
|
|
|
147
157
|
if (descriptor.detail) {
|
|
148
158
|
const id = input["id"];
|
|
149
159
|
if (typeof id !== "string" || id.length === 0) {
|
|
150
|
-
return
|
|
160
|
+
return invalidToolCall(`This tool requires a non-empty string "id" argument`);
|
|
151
161
|
}
|
|
152
162
|
const data = await dispatcher.query(descriptor.qn, { id }, user);
|
|
153
163
|
return { ok: true, data };
|
|
@@ -171,15 +181,15 @@ async function dispatchServerWrite(descriptor, dispatcher, user, input, runId, t
|
|
|
171
181
|
if (descriptor.injectsVersion) {
|
|
172
182
|
const id = input["id"];
|
|
173
183
|
if (typeof id !== "string" || id.length === 0) {
|
|
174
|
-
return
|
|
184
|
+
return invalidToolCall(`This tool requires a non-empty string "id" argument`);
|
|
175
185
|
}
|
|
176
186
|
if (!descriptor.detailQn) {
|
|
177
|
-
return
|
|
187
|
+
return invalidToolCall("Tool is misconfigured: no detail handler to read version from");
|
|
178
188
|
}
|
|
179
189
|
const current = await dispatcher.query(descriptor.detailQn, { id }, user);
|
|
180
190
|
const version = isRecord(current) && typeof current["version"] === "number" ? current["version"] : undefined;
|
|
181
191
|
if (version === undefined) {
|
|
182
|
-
return
|
|
192
|
+
return invalidToolCall(`Could not read the current "version" for id "${id}"`);
|
|
183
193
|
}
|
|
184
194
|
payload = { ...input, version };
|
|
185
195
|
}
|
|
@@ -211,37 +221,37 @@ function dispatchClientNavigate(descriptor, input) {
|
|
|
211
221
|
if (typeof entity === "string") {
|
|
212
222
|
const screenId = descriptor.entityScreens.get(entity);
|
|
213
223
|
if (!screenId)
|
|
214
|
-
return
|
|
224
|
+
return invalidToolCall(`Unknown entity "${entity}" for navigation`);
|
|
215
225
|
const id = input["id"];
|
|
216
226
|
if (typeof id !== "string" || id.length === 0) {
|
|
217
|
-
return
|
|
227
|
+
return invalidToolCall(`Navigating to "${entity}" requires a non-empty string "id"`);
|
|
218
228
|
}
|
|
219
229
|
return { ok: true, data: { kind: "navigate", screenId, params: { id } } };
|
|
220
230
|
}
|
|
221
231
|
const screenId = input["screenId"];
|
|
222
232
|
if (typeof screenId === "string") {
|
|
223
233
|
if (!descriptor.screenIds.has(screenId)) {
|
|
224
|
-
return
|
|
234
|
+
return invalidToolCall(`Unknown screen "${screenId}"`);
|
|
225
235
|
}
|
|
226
236
|
const params = input["params"];
|
|
227
237
|
if (params !== undefined) {
|
|
228
238
|
if (!isRecord(params) || Object.values(params).some((value) => typeof value !== "string")) {
|
|
229
|
-
return
|
|
239
|
+
return invalidToolCall(`"params" must be an object of string values`);
|
|
230
240
|
}
|
|
231
241
|
return { ok: true, data: { kind: "navigate", screenId, params } };
|
|
232
242
|
}
|
|
233
243
|
return { ok: true, data: { kind: "navigate", screenId, params: {} } };
|
|
234
244
|
}
|
|
235
|
-
return
|
|
245
|
+
return invalidToolCall(`navigate requires either { entity, id } or { screenId }`);
|
|
236
246
|
}
|
|
237
247
|
function dispatchClientOpenForm(descriptor, input) {
|
|
238
248
|
const handlerQn = input["handlerQn"];
|
|
239
249
|
if (typeof handlerQn !== "string") {
|
|
240
|
-
return
|
|
250
|
+
return invalidToolCall(`open_form requires a string "handlerQn" argument`);
|
|
241
251
|
}
|
|
242
252
|
const screenId = descriptor.formScreens.get(handlerQn);
|
|
243
253
|
if (!screenId)
|
|
244
|
-
return
|
|
254
|
+
return invalidToolCall(`No form screen mounted for handler "${handlerQn}"`);
|
|
245
255
|
return {
|
|
246
256
|
ok: true,
|
|
247
257
|
data: { kind: "open_form", screenId, handlerQn, prefill: input["prefill"] ?? {} },
|
|
@@ -253,14 +263,14 @@ function isStringArray(value) {
|
|
|
253
263
|
function dispatchClientAskUser(input) {
|
|
254
264
|
const question = input["question"];
|
|
255
265
|
if (typeof question !== "string" || question.length === 0) {
|
|
256
|
-
return
|
|
266
|
+
return invalidToolCall(`ask_user requires a non-empty string "question" argument`);
|
|
257
267
|
}
|
|
258
268
|
const options = input["options"];
|
|
259
269
|
if (options === undefined) {
|
|
260
270
|
return { ok: true, data: { kind: "ask_user", question } };
|
|
261
271
|
}
|
|
262
272
|
if (!isStringArray(options)) {
|
|
263
|
-
return
|
|
273
|
+
return invalidToolCall(`ask_user "options" argument must be an array of strings`);
|
|
264
274
|
}
|
|
265
275
|
return { ok: true, data: { kind: "ask_user", question, options } };
|
|
266
276
|
}
|
|
@@ -274,22 +284,23 @@ function dispatchClient(descriptor, input) {
|
|
|
274
284
|
return dispatchClientAskUser(input);
|
|
275
285
|
}
|
|
276
286
|
}
|
|
277
|
-
/** Executes one LLM-issued tool call. Never throws
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
287
|
+
/** Executes one LLM-issued tool call. Never throws: every failure comes back as `{ ok: false }`
|
|
288
|
+
* so the agent loop can feed the error back to the model instead of crashing.
|
|
289
|
+
* Every failure carries a `code`: the handler's own code for handler and dispatcher failures
|
|
290
|
+
* (429 adds `retryable`), `invalid_tool_call` for bad arguments and `unknown_tool` for a tool
|
|
291
|
+
* that is not in the table. Client errors (< 500) keep their message, server failures (>= 500,
|
|
292
|
+
* unknown throws) are logged and returned as a generic message.
|
|
293
|
+
* `request.user` must be the caller's real identity: server-kind tools run through the same
|
|
294
|
+
* permission-checked pipeline as an HTTP request. */
|
|
284
295
|
export async function dispatchToolCall(request) {
|
|
285
296
|
const { dispatcher, user, toolName, input, dispatchTable, runId, toolCallId } = request;
|
|
286
297
|
const classifyFailure = createFailureClassifier(toolName, request.log ?? fallbackLog);
|
|
287
298
|
const descriptor = dispatchTable.get(toolName);
|
|
288
299
|
if (!descriptor) {
|
|
289
|
-
return
|
|
300
|
+
return unknownTool(`Unknown tool "${toolName}"`);
|
|
290
301
|
}
|
|
291
302
|
try {
|
|
292
|
-
return await
|
|
303
|
+
return await runAsAgentToolCallEntry(async () => {
|
|
293
304
|
switch (descriptor.kind) {
|
|
294
305
|
case "search":
|
|
295
306
|
return await dispatchSearch(descriptor, dispatcher, user, toolName, input);
|
package/dist/audit/feature.js
CHANGED
|
@@ -23,6 +23,7 @@ import { AUDIT_I18N } from "./web/i18n.js";
|
|
|
23
23
|
// framework).
|
|
24
24
|
export function createAuditFeature() {
|
|
25
25
|
return defineFeature("audit", (r) => {
|
|
26
|
+
r.adoptsBundledLists();
|
|
26
27
|
r.describe("Exposes the framework's event store as a paginated, filterable audit log via the `audit:query:list` handler (accessible to `Admin` and `SystemAdmin` roles). No separate table or projection \u2014 the event store is the audit trail by construction: every entity write already records who, when, what entity, and the event payload with PII stripped. A SystemAdmin can pass `scope: \"system\"` to read the app-instance system events such as `app.started`, and reads the events of every tenant (system tenant excluded) through `audit:query:list-all-tenants` and the `audit-log-all-tenants` screen. Filter by `aggregateType`, `aggregateId`, `eventType`, `userId`, or time range. Also records `audit:event:escape-hatch-used` whenever a handler uses one of the framework's escape hatches (unsafeRaw, acknowledgeCrossTenant, db.global() writes, or a granted identity switch).");
|
|
27
28
|
r.uiHints({
|
|
28
29
|
displayLabel: "Audit Log",
|
|
@@ -12,7 +12,7 @@ import { redactPiiCiphertext } from "../../shared/index.js";
|
|
|
12
12
|
import { eventLabelKeyOf, TECHNICAL_EVENT_TYPES } from "../event-classification.js";
|
|
13
13
|
import { AUDIT_EVENT_ACTIONS, INCLUDE_TECHNICAL_FILTER, } from "../web/constants.js";
|
|
14
14
|
const MAX_LIMIT = 100;
|
|
15
|
-
// At most 18 digits keeps the id inside bigint, so an oversized cursor or id is a
|
|
15
|
+
// At most 18 digits keeps the id inside bigint, so an oversized cursor or id is a validation error (400), not a 500.
|
|
16
16
|
const CURSOR_PATTERN = /^\d{1,18}$/;
|
|
17
17
|
export const auditListPayloadShape = {
|
|
18
18
|
cursor: z.string().regex(CURSOR_PATTERN, "cursor must be a positive integer").optional(),
|
|
@@ -3,4 +3,5 @@ export type RequestAccountUnlockOptions = TokenRequestOptions;
|
|
|
3
3
|
export type RequestUnlockData = TokenRequestData<"unlock-requested">;
|
|
4
4
|
export declare function createRequestAccountUnlockHandler(opts: RequestAccountUnlockOptions): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"request-account-unlock", import("zod").ZodObject<{
|
|
5
5
|
email: import("zod").ZodEmail;
|
|
6
|
+
returnTo: import("zod").ZodCatch<import("zod").ZodOptional<import("zod").ZodString>>;
|
|
6
7
|
}, import("zod/v4/core").$strip>, TokenRequestData<"unlock-requested">, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -18,5 +18,6 @@ export function createRequestAccountUnlockHandler(opts) {
|
|
|
18
18
|
// empty) lockout state — harmless no-op if they weren't locked.
|
|
19
19
|
extraSilentSkip: () => false,
|
|
20
20
|
notificationType: AuthMailNotificationTypes.accountUnlock,
|
|
21
|
+
forwardsReturnTo: false,
|
|
21
22
|
}, opts);
|
|
22
23
|
}
|
|
@@ -3,4 +3,5 @@ export type RequestEmailVerificationOptions = TokenRequestOptions;
|
|
|
3
3
|
export type RequestVerificationData = TokenRequestData<"verification-requested">;
|
|
4
4
|
export declare function createRequestEmailVerificationHandler(opts: RequestEmailVerificationOptions): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"request-email-verification", import("zod").ZodObject<{
|
|
5
5
|
email: import("zod").ZodEmail;
|
|
6
|
+
returnTo: import("zod").ZodCatch<import("zod").ZodOptional<import("zod").ZodString>>;
|
|
6
7
|
}, import("zod/v4/core").$strip>, TokenRequestData<"verification-requested">, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -17,5 +17,6 @@ export function createRequestEmailVerificationHandler(opts) {
|
|
|
17
17
|
// caller sees the same 200 regardless of whether a token was minted.
|
|
18
18
|
extraSilentSkip: (user) => user.emailVerified === true,
|
|
19
19
|
notificationType: AuthMailNotificationTypes.emailVerification,
|
|
20
|
+
forwardsReturnTo: false,
|
|
20
21
|
}, opts);
|
|
21
22
|
}
|
|
@@ -3,4 +3,5 @@ export type RequestPasswordResetOptions = TokenRequestOptions;
|
|
|
3
3
|
export type RequestResetData = TokenRequestData<"reset-requested">;
|
|
4
4
|
export declare function createRequestPasswordResetHandler(opts: RequestPasswordResetOptions): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"request-password-reset", import("zod").ZodObject<{
|
|
5
5
|
email: import("zod").ZodEmail;
|
|
6
|
+
returnTo: import("zod").ZodCatch<import("zod").ZodOptional<import("zod").ZodString>>;
|
|
6
7
|
}, import("zod/v4/core").$strip>, TokenRequestData<"reset-requested">, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -17,5 +17,6 @@ export function createRequestPasswordResetHandler(opts) {
|
|
|
17
17
|
// state. The sessions feature handles the post-change revocation.
|
|
18
18
|
extraSilentSkip: () => false,
|
|
19
19
|
notificationType: AuthMailNotificationTypes.passwordReset,
|
|
20
|
+
forwardsReturnTo: true,
|
|
20
21
|
}, opts);
|
|
21
22
|
}
|
|
@@ -29,4 +29,5 @@ export declare function createSignupRequestHandler(opts: SignupRequestOptions):
|
|
|
29
29
|
entityType: z.ZodString;
|
|
30
30
|
token: z.ZodString;
|
|
31
31
|
}, z.core.$strip>>;
|
|
32
|
+
returnTo: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
32
33
|
}, z.core.$strip>, SignupRequestData, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -29,6 +29,7 @@ import { resolveHandlerMailLocale } from "../magic-link-mail.js";
|
|
|
29
29
|
import { AUTH_SELF_REGISTRATION_FEATURE } from "../self-registration-toggle.js";
|
|
30
30
|
import { issueSignupActivation } from "../signup-activation.js";
|
|
31
31
|
import { storeSignupHandover, takeOverSignupHandoverForResend } from "../signup-token-store.js";
|
|
32
|
+
import { MAX_RETURN_PATH_LENGTH } from "../web/return-path.js";
|
|
32
33
|
const SignupRequestSchema = z.object({
|
|
33
34
|
email: z.email(),
|
|
34
35
|
// Cross-device try-first handover: see shared/signup-handover.ts for why the
|
|
@@ -40,6 +41,8 @@ const SignupRequestSchema = z.object({
|
|
|
40
41
|
token: z.string().min(1),
|
|
41
42
|
})
|
|
42
43
|
.optional(),
|
|
44
|
+
// A bad returnTo must not void the signup; the mail builder drops unsafe values.
|
|
45
|
+
returnTo: z.string().max(MAX_RETURN_PATH_LENGTH).optional().catch(undefined),
|
|
43
46
|
});
|
|
44
47
|
const HANDOVER_GRANT_VERIFY_REASON = "verifies a tenant-handover grant against its root row in the source tenant (read-only anchor lookup) before binding it to the signup token";
|
|
45
48
|
export function createSignupRequestHandler(opts) {
|
|
@@ -95,6 +98,7 @@ export function createSignupRequestHandler(opts) {
|
|
|
95
98
|
email,
|
|
96
99
|
appUrl: opts.appUrl,
|
|
97
100
|
timeZone: ctx.tz.user,
|
|
101
|
+
...(event.payload.returnTo !== undefined && { returnTo: event.payload.returnTo }),
|
|
98
102
|
...(opts.tokenTtlMinutes !== undefined && { tokenTtlMinutes: opts.tokenTtlMinutes }),
|
|
99
103
|
...(opts.appName !== undefined && { appName: opts.appName }),
|
|
100
104
|
...(locale !== undefined && { locale }),
|
|
@@ -25,6 +25,7 @@ export type TokenRequestSpec<TName extends string, TSuccessKind extends string>
|
|
|
25
25
|
readonly notConfiguredI18nKey: string;
|
|
26
26
|
readonly extraSilentSkip: (user: AuthUserRow) => boolean;
|
|
27
27
|
readonly notificationType: string;
|
|
28
|
+
readonly forwardsReturnTo: boolean;
|
|
28
29
|
};
|
|
29
30
|
export type TokenRequestOptions = {
|
|
30
31
|
readonly hmacSecret: string;
|
|
@@ -41,4 +42,5 @@ export type TokenRequestOptions = {
|
|
|
41
42
|
};
|
|
42
43
|
export declare function createTokenRequestHandler<TName extends string, TSuccessKind extends string>(spec: TokenRequestSpec<TName, TSuccessKind>, opts: TokenRequestOptions): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<TName, z.ZodObject<{
|
|
43
44
|
email: z.ZodEmail;
|
|
45
|
+
returnTo: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
44
46
|
}, z.core.$strip>, TokenRequestData<TSuccessKind>, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -19,8 +19,11 @@ import { UserQueries } from "../../user/index.js";
|
|
|
19
19
|
import { parseAuthUserRow } from "../auth-user-row.js";
|
|
20
20
|
import { AUTH_EMAIL_PAYLOAD_RATE_LIMITS } from "../email-rate-limits.js";
|
|
21
21
|
import { dispatchMagicLinkMail, resolveHandlerMailLocale } from "../magic-link-mail.js";
|
|
22
|
+
import { MAX_RETURN_PATH_LENGTH } from "../web/return-path.js";
|
|
22
23
|
const RequestTokenSchema = z.object({
|
|
23
24
|
email: z.email(),
|
|
25
|
+
// A bad returnTo must not void the request; the mail builder drops unsafe values.
|
|
26
|
+
returnTo: z.string().max(MAX_RETURN_PATH_LENGTH).optional().catch(undefined),
|
|
24
27
|
});
|
|
25
28
|
// The requester is anonymous, so ctx.tz.user is only the tenant default; the recipient's
|
|
26
29
|
// own profile zone is the one the expiry should be read in.
|
|
@@ -79,6 +82,8 @@ export function createTokenRequestHandler(spec, opts) {
|
|
|
79
82
|
timeZone: recipientTimeZone(user, ctx.tz.user),
|
|
80
83
|
...(opts.appName !== undefined && { appName: opts.appName }),
|
|
81
84
|
locale,
|
|
85
|
+
...(spec.forwardsReturnTo &&
|
|
86
|
+
event.payload.returnTo !== undefined && { returnTo: event.payload.returnTo }),
|
|
82
87
|
});
|
|
83
88
|
const data = {
|
|
84
89
|
kind: spec.successKind,
|
|
@@ -15,6 +15,8 @@ export type MagicLinkMailParams = {
|
|
|
15
15
|
readonly issuedAt?: string;
|
|
16
16
|
readonly timeZone?: string;
|
|
17
17
|
readonly role?: string;
|
|
18
|
+
/** Page the landing page should lead to afterwards; an unsafe value is left out of the link. */
|
|
19
|
+
readonly returnTo?: string;
|
|
18
20
|
};
|
|
19
21
|
export declare function resolveHandlerMailLocale(ctx: {
|
|
20
22
|
readonly locale?: string;
|
|
@@ -10,12 +10,16 @@ import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
|
|
|
10
10
|
import { mailT, resolveMailLocale } from "@cosmicdrift/kumiko-framework/i18n";
|
|
11
11
|
import { tokenMailExpiry } from "./email-templates.js";
|
|
12
12
|
import { authMailTemplateLocale } from "./mail-notifications.js";
|
|
13
|
+
import { isSafeReturnPath, RETURN_TO_QUERY_PARAM } from "./web/return-path.js";
|
|
13
14
|
function resolveAppUrl(appUrl, locale) {
|
|
14
15
|
return typeof appUrl === "function" ? appUrl(locale) : appUrl;
|
|
15
16
|
}
|
|
16
|
-
function
|
|
17
|
+
function appendTokenAndReturnTo(appUrl, token, returnTo) {
|
|
18
|
+
const query = new URLSearchParams({ token });
|
|
19
|
+
if (isSafeReturnPath(returnTo))
|
|
20
|
+
query.set(RETURN_TO_QUERY_PARAM, returnTo);
|
|
17
21
|
const sep = appUrl.includes("?") ? "&" : "?";
|
|
18
|
-
return `${appUrl}${sep}
|
|
22
|
+
return `${appUrl}${sep}${query.toString()}`;
|
|
19
23
|
}
|
|
20
24
|
// X-Locale on the request wins over the handler's static opts.locale, then
|
|
21
25
|
// ctx.locale's resolved default (always set — boot default / "en"). Reading
|
|
@@ -49,7 +53,7 @@ export async function dispatchMagicLinkMail(notify, spec, params) {
|
|
|
49
53
|
route: { email: params.email },
|
|
50
54
|
data: {
|
|
51
55
|
appName: params.appName ?? spec.fallbackAppName ?? mailT(templateLocale, "auth.mail.appNameDefault"),
|
|
52
|
-
actionUrl:
|
|
56
|
+
actionUrl: appendTokenAndReturnTo(resolveAppUrl(params.appUrl, urlLocale), params.token, params.returnTo),
|
|
53
57
|
validFor,
|
|
54
58
|
validUntil,
|
|
55
59
|
...(params.role !== undefined && { role: params.role }),
|
|
@@ -10,6 +10,7 @@ export type IssueSignupActivationArgs = {
|
|
|
10
10
|
readonly locale?: string;
|
|
11
11
|
readonly timeZone?: string;
|
|
12
12
|
readonly tokenTtlMinutes?: number;
|
|
13
|
+
readonly returnTo?: string;
|
|
13
14
|
/** Runs after the token is stored and before the mail goes out. */
|
|
14
15
|
readonly onTokenStored?: (stored: {
|
|
15
16
|
readonly token: string;
|
|
@@ -35,6 +35,7 @@ export async function issueSignupActivation(args) {
|
|
|
35
35
|
...(args.timeZone !== undefined && { timeZone: args.timeZone }),
|
|
36
36
|
...(args.appName !== undefined && { appName: args.appName }),
|
|
37
37
|
...(args.locale !== undefined && { locale: args.locale }),
|
|
38
|
+
...(args.returnTo !== undefined && { returnTo: args.returnTo }),
|
|
38
39
|
});
|
|
39
40
|
return { email, token, expiresAt };
|
|
40
41
|
}
|
|
@@ -54,7 +54,7 @@ export type AuthTokenFailure = {
|
|
|
54
54
|
};
|
|
55
55
|
export type ResetPasswordFailure = AuthTokenFailure;
|
|
56
56
|
export type VerifyEmailFailure = AuthTokenFailure;
|
|
57
|
-
export declare function requestPasswordReset(email: string): Promise<{
|
|
57
|
+
export declare function requestPasswordReset(email: string, returnTo?: string): Promise<{
|
|
58
58
|
ok: true;
|
|
59
59
|
} | {
|
|
60
60
|
ok: false;
|
|
@@ -93,7 +93,7 @@ export declare function confirmAccountUnlock(token: string): Promise<{
|
|
|
93
93
|
export declare function requestSignup(email: string, handover?: {
|
|
94
94
|
readonly entityType: string;
|
|
95
95
|
readonly token: string;
|
|
96
|
-
}): Promise<{
|
|
96
|
+
}, returnTo?: string): Promise<{
|
|
97
97
|
ok: true;
|
|
98
98
|
} | {
|
|
99
99
|
ok: false;
|
|
@@ -115,12 +115,12 @@ async function parseTokenFailure(res) {
|
|
|
115
115
|
// email doesn't exist, the caller sees `{ ok: true }` — no account
|
|
116
116
|
// enumeration. Server only triggers the mail internally when a user is
|
|
117
117
|
// found. 429 → typed rate-limit failure. 5xx → unknown-error.
|
|
118
|
-
export async function requestPasswordReset(email) {
|
|
118
|
+
export async function requestPasswordReset(email, returnTo) {
|
|
119
119
|
const res = await fetch("/api/auth/request-password-reset", {
|
|
120
120
|
method: "POST",
|
|
121
121
|
credentials: "same-origin",
|
|
122
122
|
headers: { "Content-Type": "application/json", ...csrfHeader() },
|
|
123
|
-
body: JSON.stringify({ email }),
|
|
123
|
+
body: JSON.stringify({ email, ...(returnTo !== undefined && { returnTo }) }),
|
|
124
124
|
});
|
|
125
125
|
if (res.ok)
|
|
126
126
|
return { ok: true };
|
|
@@ -208,12 +208,16 @@ export async function requestSignup(email,
|
|
|
208
208
|
// grant so the server can verify + rebind it to the signup token. The grant never
|
|
209
209
|
// travels any further than this request — never in the URL, the mail,
|
|
210
210
|
// or the confirm response.
|
|
211
|
-
handover) {
|
|
211
|
+
handover, returnTo) {
|
|
212
212
|
const res = await fetch("/api/auth/signup-request", {
|
|
213
213
|
method: "POST",
|
|
214
214
|
credentials: "same-origin",
|
|
215
215
|
headers: { "Content-Type": "application/json", ...csrfHeader() },
|
|
216
|
-
body: JSON.stringify({
|
|
216
|
+
body: JSON.stringify({
|
|
217
|
+
email,
|
|
218
|
+
...(handover !== undefined && { handover }),
|
|
219
|
+
...(returnTo !== undefined && { returnTo }),
|
|
220
|
+
}),
|
|
217
221
|
});
|
|
218
222
|
if (res.ok)
|
|
219
223
|
return { ok: true };
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { isSafeReturnPath } from "./return-path.ts";
|
|
1
2
|
export declare const NEXT_QUERY_PARAM = "next";
|
|
2
|
-
export declare
|
|
3
|
+
export declare const isSafeNextPath: typeof isSafeReturnPath;
|
|
3
4
|
export declare function readNextFromSearch(search: string): string | null;
|
|
5
|
+
export declare function readReturnToFromWindow(): string | null;
|
|
6
|
+
export declare function appendReturnTo(href: string, returnTo: string | null): string;
|
|
7
|
+
export declare function readNextFromWindow(): string | null;
|
|
4
8
|
export declare function assertNavigableUrl(url: string, optionName: string): void;
|
|
5
9
|
export declare function buildLoginRedirectUrl(loginUrl: string, returnPath: string, currentOrigin: string): string;
|
|
6
10
|
export declare function followNextAfterLogin(location?: Pick<Location, "search" | "pathname" | "replace">): boolean;
|
|
@@ -1,36 +1,31 @@
|
|
|
1
1
|
// Redirect targets for external login pages. `next` travels through the query
|
|
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
|
+
import { isSafeReturnPath, RETURN_TO_QUERY_PARAM, readReturnToFromSearch } from "./return-path.js";
|
|
4
5
|
export const NEXT_QUERY_PARAM = "next";
|
|
5
|
-
|
|
6
|
-
for (let i = 0; i < candidate.length; i++) {
|
|
7
|
-
const code = candidate.charCodeAt(i);
|
|
8
|
-
if (code <= 0x20 || code === 0x7f)
|
|
9
|
-
return true;
|
|
10
|
-
}
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
// The URL parse is a backstop: browsers strip tabs/newlines, so "/\t/evil.com"
|
|
14
|
-
// would otherwise normalize to "//evil.com" past the leading-slash check.
|
|
15
|
-
export function isSafeNextPath(candidate) {
|
|
16
|
-
if (typeof candidate !== "string" || !candidate.startsWith("/"))
|
|
17
|
-
return false;
|
|
18
|
-
if (candidate[1] === "/" || candidate.includes("\\"))
|
|
19
|
-
return false;
|
|
20
|
-
if (hasControlOrWhitespace(candidate))
|
|
21
|
-
return false;
|
|
22
|
-
try {
|
|
23
|
-
const parsed = new URL(candidate, "https://next.invalid");
|
|
24
|
-
return parsed.origin === "https://next.invalid" && !parsed.pathname.startsWith("//");
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
6
|
+
export const isSafeNextPath = isSafeReturnPath;
|
|
30
7
|
export function readNextFromSearch(search) {
|
|
31
8
|
const candidate = new URLSearchParams(search).get(NEXT_QUERY_PARAM);
|
|
32
9
|
return isSafeNextPath(candidate) ? candidate : null;
|
|
33
10
|
}
|
|
11
|
+
// The returnTo a mail link or sibling auth page carried in this page's own URL; null on native, where there is no window.
|
|
12
|
+
export function readReturnToFromWindow() {
|
|
13
|
+
return typeof window === "undefined" ? null : readReturnToFromSearch(window.location.search);
|
|
14
|
+
}
|
|
15
|
+
// Adds returnTo to an href of a sibling auth page; a missing value leaves the href untouched.
|
|
16
|
+
export function appendReturnTo(href, returnTo) {
|
|
17
|
+
if (returnTo === null)
|
|
18
|
+
return href;
|
|
19
|
+
const hashAt = href.indexOf("#");
|
|
20
|
+
const base = hashAt === -1 ? href : href.slice(0, hashAt);
|
|
21
|
+
const hash = hashAt === -1 ? "" : href.slice(hashAt);
|
|
22
|
+
const separator = base.includes("?") ? "&" : "?";
|
|
23
|
+
return `${base}${separator}${RETURN_TO_QUERY_PARAM}=${encodeURIComponent(returnTo)}${hash}`;
|
|
24
|
+
}
|
|
25
|
+
// The safe `next` of this page's URL; null on native, where there is no window.
|
|
26
|
+
export function readNextFromWindow() {
|
|
27
|
+
return typeof window === "undefined" ? null : readNextFromSearch(window.location.search);
|
|
28
|
+
}
|
|
34
29
|
// loginUrl/postLogoutUrl come from app code (trusted), but a typo like
|
|
35
30
|
// "javascript:" must fail at startup instead of becoming a navigation target.
|
|
36
31
|
export function assertNavigableUrl(url, optionName) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { stripBasePath, useBasePath } from "@cosmicdrift/kumiko-renderer";
|
|
2
3
|
import { DEFAULT_AUTH_PATHS, DEFAULT_FORGOT_PASSWORD_PATH } from "./auth-paths.js";
|
|
3
4
|
import { ConfirmAccountUnlockScreen } from "./confirm-account-unlock-screen.js";
|
|
4
5
|
import { ForgotPasswordScreen } from "./forgot-password-screen.js";
|
|
@@ -7,8 +8,9 @@ import { ResetPasswordScreen } from "./reset-password-screen.js";
|
|
|
7
8
|
function hasToken(search) {
|
|
8
9
|
return (new URLSearchParams(search).get("token") ?? "") !== "";
|
|
9
10
|
}
|
|
10
|
-
function pickDefaultAuthPage(pathname, search) {
|
|
11
|
-
const
|
|
11
|
+
function pickDefaultAuthPage(pathname, search, basePath) {
|
|
12
|
+
const trimmed = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;
|
|
13
|
+
const path = stripBasePath(trimmed, basePath);
|
|
12
14
|
switch (path) {
|
|
13
15
|
case DEFAULT_FORGOT_PASSWORD_PATH:
|
|
14
16
|
return _jsx(ForgotPasswordScreen, {});
|
|
@@ -24,5 +26,6 @@ function pickDefaultAuthPage(pathname, search) {
|
|
|
24
26
|
// (outer) still win on the same path. The pages are public: no session needed.
|
|
25
27
|
export function DefaultAuthPagesGate({ children }) {
|
|
26
28
|
const { pathname, search } = window.location;
|
|
27
|
-
|
|
29
|
+
const basePath = useBasePath();
|
|
30
|
+
return pickDefaultAuthPage(pathname, search, basePath) ?? _jsx(_Fragment, { children: children });
|
|
28
31
|
}
|
|
@@ -5,5 +5,8 @@ export type ForgotPasswordScreenProps = {
|
|
|
5
5
|
/** href for the "Back to login" link in the success and form states. App-
|
|
6
6
|
* specific — defaults to "/login". */
|
|
7
7
|
readonly loginHref?: string;
|
|
8
|
+
/** Page the reset mail should lead back to after the new password is set; defaults to the
|
|
9
|
+
* `returnTo` query parameter of this page. Unsafe values are dropped. */
|
|
10
|
+
readonly returnTo?: string;
|
|
8
11
|
};
|
|
9
|
-
export declare function ForgotPasswordScreen({ title, subtitle, loginHref, }: ForgotPasswordScreenProps): ReactNode;
|
|
12
|
+
export declare function ForgotPasswordScreen({ title, subtitle, loginHref, returnTo, }: ForgotPasswordScreenProps): ReactNode;
|
|
@@ -12,7 +12,12 @@ import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
|
|
|
12
12
|
import { useState } from "react";
|
|
13
13
|
import { requestPasswordReset } from "./auth-client.js";
|
|
14
14
|
import { AuthCard } from "./auth-form-primitives.js";
|
|
15
|
-
|
|
15
|
+
import { readReturnToFromWindow } from "./auth-redirect.js";
|
|
16
|
+
import { isSafeReturnPath } from "./return-path.js";
|
|
17
|
+
function resolveReturnTo(prop) {
|
|
18
|
+
return (isSafeReturnPath(prop) ? prop : readReturnToFromWindow()) ?? undefined;
|
|
19
|
+
}
|
|
20
|
+
export function ForgotPasswordScreen({ title, subtitle, loginHref = "/login", returnTo, }) {
|
|
16
21
|
const t = useTranslation();
|
|
17
22
|
const { Form, Field, Input, Button, Banner, Link } = usePrimitives();
|
|
18
23
|
const [email, setEmail] = useState("");
|
|
@@ -24,7 +29,7 @@ export function ForgotPasswordScreen({ title, subtitle, loginHref = "/login", })
|
|
|
24
29
|
setSubmitting(true);
|
|
25
30
|
setError(null);
|
|
26
31
|
try {
|
|
27
|
-
const res = await requestPasswordReset(email);
|
|
32
|
+
const res = await requestPasswordReset(email, resolveReturnTo(returnTo));
|
|
28
33
|
if (res.ok) {
|
|
29
34
|
setDone(true);
|
|
30
35
|
}
|