@cosmicdrift/kumiko-bundled-features 0.353.0 → 0.354.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-tools/agent-doc-lint.d.ts +2 -0
- package/dist/agent-tools/agent-doc-lint.js +61 -1
- package/dist/agent-tools/agent-manifest.d.ts +1 -0
- package/dist/agent-tools/agent-manifest.js +9 -3
- package/dist/agent-tools/feature.js +7 -2
- package/dist/agent-tools/index.d.ts +1 -1
- package/dist/agent-tools/index.js +1 -1
- package/dist/agent-tools/tool-dispatch.js +1 -1
- package/dist/auth-email-password/handlers/change-password.write.js +1 -0
- package/dist/auth-email-password/handlers/invite-accept-with-login.write.js +2 -2
- package/dist/auth-email-password/handlers/invite-accept.write.js +2 -2
- package/dist/auth-email-password/handlers/invite-create.write.js +17 -0
- package/dist/auth-email-password/handlers/invite-signup-complete.write.js +4 -1
- package/dist/auth-email-password/handlers/login.write.js +1 -0
- package/dist/auth-email-password/handlers/reset-password.write.js +1 -0
- package/dist/auth-email-password/handlers/signup-confirm.write.js +4 -1
- package/dist/auth-email-password/handlers/signup-request.write.js +51 -77
- package/dist/auth-email-password/handlers/token-request-handler.js +1 -0
- package/dist/auth-email-password/handlers/verify-email.write.js +1 -0
- package/dist/auth-email-password/index.d.ts +2 -0
- package/dist/auth-email-password/index.js +2 -0
- package/dist/auth-email-password/seeding.d.ts +2 -0
- package/dist/auth-email-password/seeding.js +30 -8
- package/dist/auth-email-password/signup-activation.d.ts +24 -0
- package/dist/auth-email-password/signup-activation.js +41 -0
- package/dist/auth-email-password/signup-token-store.d.ts +5 -0
- package/dist/auth-email-password/signup-token-store.js +8 -3
- package/dist/auth-email-password/web/auth-gate.d.ts +2 -2
- package/dist/auth-email-password/web/auth-gate.js +5 -5
- package/dist/auth-email-password/web/auth-redirect.d.ts +1 -0
- package/dist/auth-email-password/web/auth-redirect.js +14 -0
- package/dist/auth-email-password/web/login-screen.js +2 -2
- package/dist/auth-email-password/web/user-menu.js +1 -1
- package/dist/auth-mfa/feature.js +5 -0
- package/dist/auth-mfa/handlers/enable-confirm-preauth.write.js +1 -0
- package/dist/auth-mfa/handlers/verify.write.js +1 -0
- package/dist/auth-mfa/web/mfa-recovery-codes-reveal.js +1 -1
- package/dist/auth-mfa/web/mfa-setup-preauth-screen.d.ts +1 -1
- package/dist/auth-mfa/web/mfa-setup-preauth-screen.js +3 -3
- package/dist/auth-mfa/web/mfa-verify-screen.d.ts +1 -1
- package/dist/auth-mfa/web/mfa-verify-screen.js +1 -1
- package/dist/billing-foundation/billing-info-query.d.ts +5 -2
- package/dist/channel-in-app/in-app-channel.js +1 -1
- package/dist/channel-in-app/tables.js +4 -2
- package/dist/compliance-profiles/schema/profile-selection.d.ts +2 -0
- package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +1 -0
- package/dist/crypto-shredding/handlers/forget-subject.write.js +110 -61
- package/dist/delivery/db/queries/address-opt-outs.d.ts +3 -1
- package/dist/delivery/db/queries/address-opt-outs.js +12 -7
- package/dist/delivery/delivery-service.js +60 -12
- package/dist/delivery/unsubscribe.js +66 -14
- package/dist/feature-toggles/feature.js +2 -3
- package/dist/feature-toggles/toggle-runtime.d.ts +5 -1
- package/dist/feature-toggles/toggle-runtime.js +30 -6
- package/dist/feature-toggles/toggle-sync-signal.d.ts +3 -2
- package/dist/feature-toggles/toggle-sync-signal.js +29 -10
- package/dist/file-derivatives/handlers/public-variant.query.d.ts +1 -0
- package/dist/file-derivatives/handlers/public-variant.query.js +7 -1
- package/dist/files-tenant-data/handlers/sweep-orphaned-derivatives.job.js +5 -0
- package/dist/files-tenant-data/hooks.js +58 -22
- package/dist/folders/handlers/delete-folder.write.js +31 -4
- package/dist/foundation-shared/index.d.ts +1 -1
- package/dist/foundation-shared/index.js +1 -1
- package/dist/foundation-shared/mail-host-policy.d.ts +4 -0
- package/dist/foundation-shared/mail-host-policy.js +13 -4
- package/dist/inbound-provider-imap/feature.js +2 -0
- package/dist/inbound-provider-imap/imap-client.js +3 -4
- package/dist/jobs/constants.d.ts +1 -0
- package/dist/jobs/constants.js +1 -0
- package/dist/jobs/db/queries/retention.d.ts +1 -0
- package/dist/jobs/db/queries/retention.js +5 -0
- package/dist/jobs/db/queries/stale-run-sweep.d.ts +2 -0
- package/dist/jobs/db/queries/stale-run-sweep.js +13 -2
- package/dist/jobs/feature.js +7 -1
- package/dist/jobs/handlers/list.query.js +1 -1
- package/dist/jobs/handlers/tenant-failures.query.js +1 -1
- package/dist/jobs/handlers/tenant-runs.query.d.ts +17 -0
- package/dist/jobs/handlers/tenant-runs.query.js +72 -0
- package/dist/jobs/index.d.ts +2 -0
- package/dist/jobs/index.js +1 -0
- package/dist/jobs/job-run-logger.d.ts +2 -2
- package/dist/jobs/job-run-logger.js +98 -9
- package/dist/jobs/tenant-job-failure-table.js +15 -2
- package/dist/jobs/tenant-job-run-table.d.ts +4 -0
- package/dist/jobs/tenant-job-run-table.js +34 -0
- package/dist/ledger/entity.d.ts +3 -0
- package/dist/ledger/entity.js +13 -5
- package/dist/ledger/feature.js +3 -0
- package/dist/ledger/handlers/confirm-schedule-period.write.js +9 -3
- package/dist/ledger/handlers/create-transaction.write.js +1 -0
- package/dist/ledger/handlers/reverse-transaction.write.js +1 -0
- package/dist/ledger/schedule-subject-pair.d.ts +2 -0
- package/dist/ledger/schedule-subject-pair.js +24 -0
- package/dist/mail-transport-smtp/feature.d.ts +3 -4
- package/dist/mail-transport-smtp/feature.js +4 -13
- package/dist/notes-history/entity.js +4 -0
- package/dist/personal-access-tokens/handlers/create.write.js +1 -0
- package/dist/sessions/feature.d.ts +1 -0
- package/dist/sessions/feature.js +9 -8
- package/dist/sessions/handlers/detail.query.d.ts +1 -1
- package/dist/sessions/handlers/detail.query.js +2 -2
- package/dist/sessions/handlers/list.query.d.ts +1 -1
- package/dist/sessions/handlers/list.query.js +5 -3
- package/dist/sessions/screens.d.ts +2 -2
- package/dist/sessions/screens.js +6 -7
- package/dist/shared/encrypt-for-direct-write.js +10 -2
- package/dist/shared/index.d.ts +2 -2
- package/dist/shared/index.js +2 -2
- package/dist/shared/parent-visibility.d.ts +2 -1
- package/dist/shared/parent-visibility.js +21 -0
- package/dist/shared/row-bound-grant.js +16 -2
- package/dist/shared/session-locale-field.d.ts +1 -0
- package/dist/shared/session-locale-field.js +6 -0
- package/dist/template-resolver/api.d.ts +2 -2
- package/dist/template-resolver/handlers/list.query.d.ts +2 -0
- package/dist/template-resolver/handlers/list.query.js +2 -2
- package/dist/template-resolver/table.d.ts +8 -0
- package/dist/template-resolver/user-content-table.d.ts +4 -0
- package/dist/tenant/feature.js +7 -1
- package/dist/tenant/handlers/toggle-enabled.write.js +2 -0
- package/dist/tenant/handlers/update-member-roles.write.js +1 -0
- package/dist/tenant/invitation-table.d.ts +40 -0
- package/dist/tenant/invitation-table.js +6 -1
- package/dist/tenant/invited-membership.d.ts +8 -6
- package/dist/tenant/invited-membership.js +28 -7
- package/dist/tenant/schema/tenant.d.ts +2 -0
- package/dist/tenant-handover/transfer-graph.d.ts +2 -21
- package/dist/tenant-handover/transfer-graph.js +8 -75
- package/dist/tenant-lifecycle/boot-checks.d.ts +1 -0
- package/dist/tenant-lifecycle/boot-checks.js +13 -0
- package/dist/tenant-lifecycle/constants.d.ts +3 -0
- package/dist/tenant-lifecycle/constants.js +5 -0
- package/dist/tenant-lifecycle/events.d.ts +5 -0
- package/dist/tenant-lifecycle/events.js +5 -0
- package/dist/tenant-lifecycle/feature.js +5 -3
- package/dist/tenant-lifecycle/run-tenant-destroy.d.ts +7 -5
- package/dist/tenant-lifecycle/run-tenant-destroy.js +57 -17
- package/dist/tenant-lifecycle/stages.d.ts +6 -1
- package/dist/tenant-lifecycle/stages.js +26 -3
- package/dist/tenant-lifecycle/testing.d.ts +8 -0
- package/dist/tenant-lifecycle/testing.js +42 -0
- package/dist/tier-engine/handlers/set-tenant-tier.write.js +1 -0
- package/dist/user/handlers/detail.query.js +2 -0
- package/dist/user/handlers/list.query.js +4 -0
- package/dist/user/handlers/update.write.js +1 -0
- package/dist/user/schema/user.d.ts +2 -0
- package/dist/user/seeding.d.ts +1 -0
- package/dist/user/seeding.js +20 -0
- package/dist/user-data-rights/handlers/request-deletion.write.js +1 -0
- package/dist/user-data-rights/index.d.ts +1 -0
- package/dist/user-data-rights/index.js +1 -0
- package/dist/user-data-rights/schema/download-attempt.js +17 -9
- package/dist/user-data-rights/schema/export-job.d.ts +2 -0
- package/dist/user-profile/handlers/change-email.write.js +8 -2
- package/dist/waitlist/entity.d.ts +2 -0
- package/dist/workflow-runner/event-trigger.d.ts +1 -0
- package/dist/workflow-runner/event-trigger.js +60 -15
- package/dist/workflow-runner/handlers/resume-run.write.js +5 -2
- package/dist/workflow-runner/runner.d.ts +2 -1
- package/dist/workflow-runner/runner.js +5 -1
- package/package.json +35 -10
- package/src/auth-email-password/changes.json +24 -0
- package/src/auth-mfa/changes.json +7 -0
- package/src/billing-foundation/changes.json +6 -0
- package/src/channel-in-app/changes.json +8 -1
- package/src/crypto-shredding/changes.json +12 -0
- package/src/delivery/changes.json +6 -0
- package/src/files/changes.json +24 -0
- package/src/files-tenant-data/changes.json +5 -0
- package/src/folders/changes.json +6 -0
- package/src/inbound-provider-imap/changes.json +6 -0
- package/src/jobs/changes.json +30 -0
- package/src/ledger/changes.json +23 -0
- package/src/notes-history/changes.json +13 -0
- package/src/template-resolver/changes.json +5 -0
- package/src/tenant/changes.json +12 -0
- package/src/tenant-handover/changes.json +6 -0
- package/src/tenant-lifecycle/changes.json +22 -0
- package/src/tier-engine/changes.json +5 -0
- package/src/user/changes.json +15 -0
- package/src/user-data-rights/changes.json +6 -0
|
@@ -4,6 +4,7 @@ export declare const AgentDocGapKinds: {
|
|
|
4
4
|
readonly handlerWithoutDescription: "handler-without-description";
|
|
5
5
|
readonly customScreenWithoutDescription: "custom-screen-without-description";
|
|
6
6
|
readonly exposedEntityWithoutDescription: "exposed-entity-without-description";
|
|
7
|
+
readonly handlerWithoutTranslation: "handler-without-translation";
|
|
7
8
|
};
|
|
8
9
|
export type AgentDocGapKind = (typeof AgentDocGapKinds)[keyof typeof AgentDocGapKinds];
|
|
9
10
|
export type AgentDocGap = {
|
|
@@ -13,4 +14,5 @@ export type AgentDocGap = {
|
|
|
13
14
|
readonly message: string;
|
|
14
15
|
};
|
|
15
16
|
export declare function findAgentDocGaps(features: readonly FeatureDefinition[]): readonly AgentDocGap[];
|
|
17
|
+
export declare function findHandlerTranslationGaps(features: readonly FeatureDefinition[]): readonly AgentDocGap[];
|
|
16
18
|
export declare function formatAgentDocGap(gap: AgentDocGap): string;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { isAgentVisibleScreen, QnTypes, qn, resolveAgentExposure, toKebab, } from "@cosmicdrift/kumiko-framework/engine";
|
|
1
|
+
import { handlerFieldLabelKey, handlerTitleKey, isAgentVisibleScreen, QnTypes, qn, resolveAgentExposure, toKebab, } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import * as z from "zod";
|
|
3
|
+
import { systemOnlyPropertyNames } from "./agent-manifest.js";
|
|
3
4
|
export const AgentDocGapKinds = {
|
|
4
5
|
handlerSchemaNotExpressible: "handler-schema-not-expressible",
|
|
5
6
|
handlerWithoutDescription: "handler-without-description",
|
|
6
7
|
customScreenWithoutDescription: "custom-screen-without-description",
|
|
7
8
|
exposedEntityWithoutDescription: "exposed-entity-without-description",
|
|
9
|
+
handlerWithoutTranslation: "handler-without-translation",
|
|
8
10
|
};
|
|
9
11
|
// No shared screen-type union exists in the codebase to import (ScreenDefinition
|
|
10
12
|
// is a discriminated union keyed by inline string literals) — name the one
|
|
@@ -107,6 +109,57 @@ function exposedEntityDocGaps(feature) {
|
|
|
107
109
|
}
|
|
108
110
|
return gaps;
|
|
109
111
|
}
|
|
112
|
+
function actionFormHandlerQns(features) {
|
|
113
|
+
const handlerQns = new Set();
|
|
114
|
+
for (const feature of features) {
|
|
115
|
+
for (const screen of Object.values(feature.screens ?? {})) {
|
|
116
|
+
if (screen.type === "actionForm")
|
|
117
|
+
handlerQns.add(screen.handler);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return handlerQns;
|
|
121
|
+
}
|
|
122
|
+
function agentVisibleInputFieldNames(schema) {
|
|
123
|
+
if (!(schema instanceof z.ZodObject))
|
|
124
|
+
return [];
|
|
125
|
+
const fieldNames = Object.keys(schema.shape);
|
|
126
|
+
try {
|
|
127
|
+
const systemOnly = new Set(systemOnlyPropertyNames(z.toJSONSchema(schema, { io: "input" })));
|
|
128
|
+
return fieldNames.filter((field) => !systemOnly.has(field));
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// skip: a schema without JSON Schema form is reported as its own gap kind
|
|
132
|
+
return fieldNames;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function translationGapsOfFeature(feature, coveredHandlerQns) {
|
|
136
|
+
const gaps = [];
|
|
137
|
+
// The client resolves translation keys verbatim (no feature prefix), so only
|
|
138
|
+
// fully spelled-out keys count.
|
|
139
|
+
const translated = new Set(Object.keys(feature.translations ?? {}));
|
|
140
|
+
for (const [name, def] of Object.entries(feature.writeHandlers ?? {})) {
|
|
141
|
+
if (!resolveAgentExposure(def, "write").expose)
|
|
142
|
+
continue;
|
|
143
|
+
if (feature.handlerEntityMappings?.[name] !== undefined)
|
|
144
|
+
continue;
|
|
145
|
+
const handlerQn = qn(toKebab(feature.name), QnTypes.write, toKebab(name));
|
|
146
|
+
if (coveredHandlerQns.has(handlerQn))
|
|
147
|
+
continue;
|
|
148
|
+
const missing = [
|
|
149
|
+
handlerTitleKey(handlerQn),
|
|
150
|
+
...agentVisibleInputFieldNames(def.schema).map((field) => handlerFieldLabelKey(handlerQn, field)),
|
|
151
|
+
].filter((key) => !translated.has(key));
|
|
152
|
+
if (missing.length === 0)
|
|
153
|
+
continue;
|
|
154
|
+
gaps.push({
|
|
155
|
+
qn: handlerQn,
|
|
156
|
+
feature: feature.name,
|
|
157
|
+
kind: AgentDocGapKinds.handlerWithoutTranslation,
|
|
158
|
+
message: `This write handler has no entity and no actionForm screen, so its title and field labels can only come from translations — missing keys: ${missing.join(", ")}.`,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return gaps;
|
|
162
|
+
}
|
|
110
163
|
export function findAgentDocGaps(features) {
|
|
111
164
|
const gaps = [];
|
|
112
165
|
for (const feature of features) {
|
|
@@ -119,6 +172,13 @@ export function findAgentDocGaps(features) {
|
|
|
119
172
|
// depend on it (mirrors agent-manifest.ts's sortedByKey rationale).
|
|
120
173
|
return [...gaps].sort((a, b) => compareByCodePoint(a.qn, b.qn));
|
|
121
174
|
}
|
|
175
|
+
// Kept out of findAgentDocGaps on purpose: the published agent-manifest guard
|
|
176
|
+
// fails on every gap that function returns, and these are advisory.
|
|
177
|
+
export function findHandlerTranslationGaps(features) {
|
|
178
|
+
const coveredHandlerQns = actionFormHandlerQns(features);
|
|
179
|
+
const gaps = features.flatMap((feature) => translationGapsOfFeature(feature, coveredHandlerQns));
|
|
180
|
+
return [...gaps].sort((a, b) => compareByCodePoint(a.qn, b.qn));
|
|
181
|
+
}
|
|
122
182
|
export function formatAgentDocGap(gap) {
|
|
123
183
|
return `${gap.qn} — ${gap.message}`;
|
|
124
184
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentManifest, AgentManifestOptions, RegistryManifestView } from "./types.js";
|
|
2
|
+
export declare function systemOnlyPropertyNames(jsonSchema: Record<string, unknown>): readonly string[];
|
|
2
3
|
/** Registry snapshot → AI-agent manifest. Pure, deterministic, no I/O — mirrors
|
|
3
4
|
* `buildToolCatalog`'s contract, just describing the app's shape instead of
|
|
4
5
|
* generating callable tools from it. */
|
|
@@ -119,13 +119,19 @@ function buildFeatures(features) {
|
|
|
119
119
|
}
|
|
120
120
|
// Agent dispatch never runs as a system identity, so properties the engine only
|
|
121
121
|
// honors for system callers would be silently dropped — hide them from the tool.
|
|
122
|
-
function
|
|
122
|
+
export function systemOnlyPropertyNames(jsonSchema) {
|
|
123
123
|
const properties = jsonSchema["properties"];
|
|
124
124
|
if (typeof properties !== "object" || properties === null)
|
|
125
|
-
return
|
|
126
|
-
|
|
125
|
+
return [];
|
|
126
|
+
return Object.entries(properties)
|
|
127
127
|
.filter(([, property]) => property[SYSTEM_ONLY_JSON_SCHEMA_KEY] === true)
|
|
128
128
|
.map(([name]) => name);
|
|
129
|
+
}
|
|
130
|
+
function withoutSystemOnlyProperties(jsonSchema) {
|
|
131
|
+
const properties = jsonSchema["properties"];
|
|
132
|
+
if (typeof properties !== "object" || properties === null)
|
|
133
|
+
return jsonSchema;
|
|
134
|
+
const hidden = systemOnlyPropertyNames(jsonSchema);
|
|
129
135
|
if (hidden.length === 0)
|
|
130
136
|
return jsonSchema;
|
|
131
137
|
const required = jsonSchema["required"];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
-
import { findAgentDocGaps, formatAgentDocGap } from "./agent-doc-lint.js";
|
|
2
|
+
import { findAgentDocGaps, findHandlerTranslationGaps, formatAgentDocGap, } from "./agent-doc-lint.js";
|
|
3
3
|
export const AGENT_TOOLS_FEATURE_NAME = "agent-tools";
|
|
4
4
|
export function createAgentToolsFeature() {
|
|
5
5
|
return defineFeature(AGENT_TOOLS_FEATURE_NAME, (r) => {
|
|
@@ -14,8 +14,13 @@ export function createAgentToolsFeature() {
|
|
|
14
14
|
// would turn every incrementally-documented app into an outage.
|
|
15
15
|
r.bootCheck(({ features }) => {
|
|
16
16
|
const gaps = findAgentDocGaps(features);
|
|
17
|
+
const warningCount = findHandlerTranslationGaps(features).length;
|
|
18
|
+
if (warningCount > 0) {
|
|
19
|
+
// biome-ignore lint/suspicious/noConsole: operator-visibility at boot for agent-doc gaps
|
|
20
|
+
console.warn(`[agent-tools] ${warningCount} agent-exposed write handler(s) lack title/field-label translations — run \`kumiko agent lint\` for details.`);
|
|
21
|
+
}
|
|
17
22
|
if (gaps.length === 0) {
|
|
18
|
-
// skip: no documentation gaps, nothing to warn about
|
|
23
|
+
// skip: no blocking documentation gaps, nothing more to warn about
|
|
19
24
|
return;
|
|
20
25
|
}
|
|
21
26
|
// biome-ignore lint/suspicious/noConsole: operator-visibility at boot for agent-doc gaps
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { AgentDocGap, AgentDocGapKind } from "./agent-doc-lint.js";
|
|
2
|
-
export { AgentDocGapKinds, findAgentDocGaps, formatAgentDocGap } from "./agent-doc-lint.js";
|
|
2
|
+
export { AgentDocGapKinds, findAgentDocGaps, findHandlerTranslationGaps, formatAgentDocGap, } from "./agent-doc-lint.js";
|
|
3
3
|
export { buildAgentManifest } from "./agent-manifest.js";
|
|
4
4
|
export { AGENT_TOOLS_FEATURE_NAME, createAgentToolsFeature } from "./feature.js";
|
|
5
5
|
export { buildToolCatalog, OPEN_FORM_TOOL_NAME, toolNameForQn } from "./tool-catalog.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { AgentDocGapKinds, findAgentDocGaps, formatAgentDocGap } from "./agent-doc-lint.js";
|
|
1
|
+
export { AgentDocGapKinds, findAgentDocGaps, findHandlerTranslationGaps, formatAgentDocGap, } from "./agent-doc-lint.js";
|
|
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";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runAsDirectCallEntry } from "@cosmicdrift/kumiko-framework/
|
|
1
|
+
import { runAsDirectCallEntry } from "@cosmicdrift/kumiko-framework/internal/request-context";
|
|
2
2
|
import { FILTER_OPS, isRecord } from "./filter-ops.js";
|
|
3
3
|
const SEARCH_RESULT_LIMIT = 10;
|
|
4
4
|
const DEFAULT_LIST_LIMIT = 10;
|
|
@@ -16,6 +16,7 @@ export const changePasswordWrite = defineWriteHandler({
|
|
|
16
16
|
}),
|
|
17
17
|
access: { roles: access.authenticated },
|
|
18
18
|
escapeHatch: {
|
|
19
|
+
grants: ["systemIdentity"],
|
|
19
20
|
reason: "Reads the caller's own passwordHash (privileged-only field) via ctx.queryAs(SYSTEM, ...) " +
|
|
20
21
|
"and writes the new hash via ctx.writeAs(SYSTEM, user:update) — the caller's own " +
|
|
21
22
|
"identity can't reach either, field-access on passwordHash is privileged-only.",
|
|
@@ -29,7 +29,7 @@ import { decryptStoredPii, sessionLocaleField, sessionTimezoneField } from "../.
|
|
|
29
29
|
// kumiko-lint-ignore cross-feature-import invite-flow
|
|
30
30
|
import { INVITATION_STATUS, tenantInvitationEntity, tenantInvitationsTable, } from "../../tenant/invitation-table.js";
|
|
31
31
|
// kumiko-lint-ignore cross-feature-import membership grant for a privileged cross-tenant add
|
|
32
|
-
import { grantInvitedMembershipRole
|
|
32
|
+
import { grantInvitedMembershipRole } from "../../tenant/invited-membership.js";
|
|
33
33
|
// kumiko-lint-ignore cross-feature-import global roles granted on invite accept live on the user row
|
|
34
34
|
import { grantInvitedGlobalRoles, parseInvitedGlobalRoles, } from "../../user/invited-global-roles.js";
|
|
35
35
|
// kumiko-lint-ignore cross-feature-import login-style password-check
|
|
@@ -118,7 +118,7 @@ export function createInviteAcceptWithLoginHandler(opts = {}) {
|
|
|
118
118
|
userId,
|
|
119
119
|
tenantId: invitationTenantId,
|
|
120
120
|
role: invitationRole,
|
|
121
|
-
|
|
121
|
+
invitation,
|
|
122
122
|
});
|
|
123
123
|
if (!grant.isSuccess)
|
|
124
124
|
return grant;
|
|
@@ -23,7 +23,7 @@ import { decryptStoredPii } from "../../shared/index.js";
|
|
|
23
23
|
// kumiko-lint-ignore cross-feature-import invite-flow lebt in auth-email-password (Magic-Link), DB-row-owner ist tenant-feature
|
|
24
24
|
import { INVITATION_STATUS, tenantInvitationEntity, tenantInvitationsTable, } from "../../tenant/invitation-table.js";
|
|
25
25
|
// kumiko-lint-ignore cross-feature-import membership grant for a privileged cross-tenant add (like provisionSignupAccount)
|
|
26
|
-
import { grantInvitedMembershipRole
|
|
26
|
+
import { grantInvitedMembershipRole } from "../../tenant/invited-membership.js";
|
|
27
27
|
// kumiko-lint-ignore cross-feature-import global roles granted on invite accept live on the user row
|
|
28
28
|
import { grantInvitedGlobalRoles, parseInvitedGlobalRoles, } from "../../user/invited-global-roles.js";
|
|
29
29
|
// kumiko-lint-ignore cross-feature-import auth handler reads user-row für email-match
|
|
@@ -93,7 +93,7 @@ export function createInviteAcceptHandler() {
|
|
|
93
93
|
userId: event.user.id,
|
|
94
94
|
tenantId: invitationTenantId,
|
|
95
95
|
role: invitationRole,
|
|
96
|
-
|
|
96
|
+
invitation,
|
|
97
97
|
});
|
|
98
98
|
if (!grant.isSuccess)
|
|
99
99
|
return grant;
|
|
@@ -24,10 +24,14 @@ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
|
24
24
|
import * as z from "zod";
|
|
25
25
|
// kumiko-lint-ignore cross-feature-import invite-flow lebt in auth-email-password (Magic-Link-Pattern), DB-row-owner ist tenant-feature
|
|
26
26
|
import { INVITATION_STATUS, tenantInvitationEntity, tenantInvitationsTable, } from "../../tenant/invitation-table.js";
|
|
27
|
+
// kumiko-lint-ignore cross-feature-import membership version read is owned by the tenant feature
|
|
28
|
+
import { membershipVersionOf } from "../../tenant/invited-membership.js";
|
|
27
29
|
// kumiko-lint-ignore cross-feature-import membership-role validation owned by tenant-feature
|
|
28
30
|
import { findForbiddenMembershipRole, findForbiddenRoleAssignment, reservedMembershipRoleError, unassignableMembershipRoleError, } from "../../tenant/membership-roles.js";
|
|
29
31
|
// kumiko-lint-ignore cross-feature-import global-role options are owned by the user feature
|
|
30
32
|
import { GLOBAL_ROLE_OPTIONS } from "../../user/constants.js";
|
|
33
|
+
// kumiko-lint-ignore cross-feature-import invitee lookup by email reads the user row
|
|
34
|
+
import { userTable } from "../../user/schema/user.js";
|
|
31
35
|
import { AUTH_INVITE_DEFAULT_TTL_MINUTES } from "../constants.js";
|
|
32
36
|
import { renderInviteEmail } from "../email-templates.js";
|
|
33
37
|
import { inviteAlreadyAccepted } from "../errors.js";
|
|
@@ -61,6 +65,16 @@ function checkAssignableInviteRole(opts, inviterRoles, role, ctx) {
|
|
|
61
65
|
}
|
|
62
66
|
return undefined;
|
|
63
67
|
}
|
|
68
|
+
// 0 = the invitee has no membership in the tenant yet (or no account at all).
|
|
69
|
+
async function currentMembershipVersion(ctx, tenantId, email) {
|
|
70
|
+
const user = await ctx.db.global(userTable).fetchOne({
|
|
71
|
+
email,
|
|
72
|
+
isDeleted: false,
|
|
73
|
+
});
|
|
74
|
+
if (!user)
|
|
75
|
+
return 0;
|
|
76
|
+
return membershipVersionOf(ctx.db, tenantId, user.id);
|
|
77
|
+
}
|
|
64
78
|
async function issueInvitation(opts, ttlSeconds, request, inviter, ctx, refuseAccepted = false) {
|
|
65
79
|
if (!ctx.redis) {
|
|
66
80
|
return writeFailure(new InternalError({ message: "invite-create requires ctx.redis for token store" }));
|
|
@@ -72,6 +86,7 @@ async function issueInvitation(opts, ttlSeconds, request, inviter, ctx, refuseAc
|
|
|
72
86
|
// The unique index allows one row per (tenantId, email). Whatever its
|
|
73
87
|
// status, a re-invite resets it to pending with a fresh token.
|
|
74
88
|
const existing = await ctx.db.fetchOne(tenantInvitationsTable, { tenantId, email });
|
|
89
|
+
const membershipVersion = await currentMembershipVersion(ctx, tenantId, email);
|
|
75
90
|
let invitationId;
|
|
76
91
|
if (existing) {
|
|
77
92
|
// Provisioning callers (waitlist) must not flip an accepted invite back to
|
|
@@ -96,6 +111,7 @@ async function issueInvitation(opts, ttlSeconds, request, inviter, ctx, refuseAc
|
|
|
96
111
|
globalRoles: [...request.globalRoles],
|
|
97
112
|
status: INVITATION_STATUS.pending,
|
|
98
113
|
invitedBy: inviter.id,
|
|
114
|
+
membershipVersion,
|
|
99
115
|
expiresAt,
|
|
100
116
|
},
|
|
101
117
|
}, inviter, ctx.db);
|
|
@@ -109,6 +125,7 @@ async function issueInvitation(opts, ttlSeconds, request, inviter, ctx, refuseAc
|
|
|
109
125
|
globalRoles: [...request.globalRoles],
|
|
110
126
|
status: INVITATION_STATUS.pending,
|
|
111
127
|
invitedBy: inviter.id,
|
|
128
|
+
membershipVersion,
|
|
112
129
|
expiresAt,
|
|
113
130
|
}, inviter, ctx.db);
|
|
114
131
|
if (!createResult.isSuccess)
|
|
@@ -21,7 +21,7 @@ import { createEventStoreExecutor, createTenantDb, } from "@cosmicdrift/kumiko-f
|
|
|
21
21
|
import { buildSessionRoles, createSystemUser, defineWriteHandler, } from "@cosmicdrift/kumiko-framework/engine";
|
|
22
22
|
import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
23
23
|
import * as z from "zod";
|
|
24
|
-
import { decryptStoredPii } from "../../shared/index.js";
|
|
24
|
+
import { decryptStoredPii, registrationLocaleFromRequest } from "../../shared/index.js";
|
|
25
25
|
// kumiko-lint-ignore cross-feature-import invite-flow
|
|
26
26
|
import { INVITATION_STATUS, tenantInvitationEntity, tenantInvitationsTable, } from "../../tenant/invitation-table.js";
|
|
27
27
|
// kumiko-lint-ignore cross-feature-import reserved-role check owned by tenant-feature
|
|
@@ -92,11 +92,13 @@ export function createInviteSignupCompleteHandler(opts = {}) {
|
|
|
92
92
|
// @cast-boundary db-runner — helpers use only the query API that
|
|
93
93
|
// DbConnection and DbTx share.
|
|
94
94
|
const dbConn = ctx.db.unsafeRaw();
|
|
95
|
+
const registrationLocale = registrationLocaleFromRequest();
|
|
95
96
|
const { id: userId } = await seedUserWithPassword(dbConn, {
|
|
96
97
|
email: invitationEmail,
|
|
97
98
|
password: event.payload.password,
|
|
98
99
|
displayName: invitationEmail.split("@")[0] ?? invitationEmail,
|
|
99
100
|
emailVerified: true,
|
|
101
|
+
...registrationLocale,
|
|
100
102
|
...(invitationGlobalRoles.length > 0 && { roles: invitationGlobalRoles }),
|
|
101
103
|
});
|
|
102
104
|
// Membership-Add via seed-helper (gleiches Pattern wie
|
|
@@ -136,6 +138,7 @@ export function createInviteSignupCompleteHandler(opts = {}) {
|
|
|
136
138
|
id: userId,
|
|
137
139
|
tenantId: invitationTenantId,
|
|
138
140
|
roles: mergedRoles,
|
|
141
|
+
...registrationLocale,
|
|
139
142
|
};
|
|
140
143
|
committed = true;
|
|
141
144
|
return {
|
|
@@ -151,6 +151,7 @@ export function createLoginHandler(opts = {}) {
|
|
|
151
151
|
access: { roles: ["anonymous"] },
|
|
152
152
|
rateLimit: { per: "ip+handler", limit: 20, windowSeconds: 60 },
|
|
153
153
|
escapeHatch: {
|
|
154
|
+
grants: ["systemIdentity", "unsafeRaw"],
|
|
154
155
|
reason: "Unauthenticated login has no caller identity yet — it looks up the user row by " +
|
|
155
156
|
"email via ctx.queryAs(SYSTEM, ...) and resolves tenant membership via " +
|
|
156
157
|
"ctx.resolveActiveMembership before a session exists. It also reads the MFA " +
|
|
@@ -23,6 +23,7 @@ export function createResetPasswordHandler(opts) {
|
|
|
23
23
|
access: { roles: ["anonymous"] },
|
|
24
24
|
rateLimit: { per: "ip+handler", limit: 20, windowSeconds: 60 },
|
|
25
25
|
escapeHatch: {
|
|
26
|
+
grants: ["systemIdentity"],
|
|
26
27
|
reason: "Anonymous token holder has no session — runConfirmTokenFlow loads and updates the " +
|
|
27
28
|
"target user via ctx.writeAs(SYSTEM, user:update) to set the new password hash.",
|
|
28
29
|
},
|
|
@@ -24,7 +24,7 @@ import { ConflictError, InternalError, reraiseAsKumikoError, writeFailure, } fro
|
|
|
24
24
|
import { generateUniqueName } from "@cosmicdrift/kumiko-framework/random";
|
|
25
25
|
import { generateId } from "@cosmicdrift/kumiko-framework/utils";
|
|
26
26
|
import * as z from "zod";
|
|
27
|
-
import { findSignupHandoverProvider, SIGNUP_HANDOVER_BENIGN_CLAIM_REJECTION_CODE, } from "../../shared/index.js";
|
|
27
|
+
import { findSignupHandoverProvider, registrationLocaleFromRequest, SIGNUP_HANDOVER_BENIGN_CLAIM_REJECTION_CODE, } from "../../shared/index.js";
|
|
28
28
|
// kumiko-lint-ignore cross-feature-import signup-confirm reads tenants.key for slug-uniqueness check (TOCTOU + DB-unique-index zusammen)
|
|
29
29
|
import { tenantTable } from "../../tenant/schema/tenant.js";
|
|
30
30
|
import { invalidSignupToken, signupEmailAlreadyRegistered } from "../errors.js";
|
|
@@ -116,6 +116,7 @@ export function createSignupConfirmHandler(opts = {}) {
|
|
|
116
116
|
// Display-Name aus email-prefix als sinnvolles Default; User kann
|
|
117
117
|
// den Tenant-Namen + sein eigenes displayName später ändern.
|
|
118
118
|
const displayName = email.split("@")[0] ?? email;
|
|
119
|
+
const registrationLocale = registrationLocaleFromRequest();
|
|
119
120
|
let provisioned;
|
|
120
121
|
try {
|
|
121
122
|
provisioned = await provisionSignupAccount(dbConn, {
|
|
@@ -126,6 +127,7 @@ export function createSignupConfirmHandler(opts = {}) {
|
|
|
126
127
|
tenantKey,
|
|
127
128
|
// Generated slug as default name: no personal data (the email) in the tenant name.
|
|
128
129
|
tenantName: tenantKey,
|
|
130
|
+
...registrationLocale,
|
|
129
131
|
},
|
|
130
132
|
// #1463: seedTenant's postSave hooks (tier-engine's auto-default-
|
|
131
133
|
// tier, an app's auto-default-compliance) must fire on self-signup
|
|
@@ -150,6 +152,7 @@ export function createSignupConfirmHandler(opts = {}) {
|
|
|
150
152
|
id: provisioned.userId,
|
|
151
153
|
tenantId: provisioned.tenantId,
|
|
152
154
|
roles: [...INITIAL_SIGNUP_ROLES],
|
|
155
|
+
...registrationLocale,
|
|
153
156
|
};
|
|
154
157
|
// Claim BEFORE spending the signup token or the binding below and
|
|
155
158
|
// before `committed` flips: if the claim throws, the `finally` still
|
|
@@ -20,18 +20,14 @@
|
|
|
20
20
|
// stays always-200 + resend-idempotent so the request path leaks nothing;
|
|
21
21
|
// suppressing the link on the request side would be defense-in-depth, but
|
|
22
22
|
// with an enumeration risk of its own (separate concern).
|
|
23
|
-
import { generateToken } from "@cosmicdrift/kumiko-framework/api";
|
|
24
23
|
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
25
24
|
import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
26
|
-
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
27
25
|
import * as z from "zod";
|
|
28
26
|
import { findSignupHandoverProvider } from "../../shared/index.js";
|
|
29
|
-
import {
|
|
30
|
-
import { renderActivationEmail } from "../email-templates.js";
|
|
31
|
-
import { dispatchMagicLinkMail, resolveHandlerMailLocale } from "../magic-link-mail.js";
|
|
27
|
+
import { resolveHandlerMailLocale } from "../magic-link-mail.js";
|
|
32
28
|
import { AUTH_SELF_REGISTRATION_FEATURE } from "../self-registration-toggle.js";
|
|
33
|
-
import {
|
|
34
|
-
|
|
29
|
+
import { issueSignupActivation } from "../signup-activation.js";
|
|
30
|
+
import { storeSignupHandover, takeOverSignupHandoverForResend } from "../signup-token-store.js";
|
|
35
31
|
const SignupRequestSchema = z.object({
|
|
36
32
|
email: z.email(),
|
|
37
33
|
// Cross-device try-first handover (kumiko-framework#3035 follow-up,
|
|
@@ -47,8 +43,6 @@ const SignupRequestSchema = z.object({
|
|
|
47
43
|
});
|
|
48
44
|
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";
|
|
49
45
|
export function createSignupRequestHandler(opts) {
|
|
50
|
-
const ttlMinutes = opts.tokenTtlMinutes ?? AUTH_SIGNUP_DEFAULT_TTL_MINUTES;
|
|
51
|
-
const ttlSeconds = ttlMinutes * 60;
|
|
52
46
|
return defineWriteHandler({
|
|
53
47
|
name: "signup-request",
|
|
54
48
|
schema: SignupRequestSchema,
|
|
@@ -93,84 +87,64 @@ export function createSignupRequestHandler(opts) {
|
|
|
93
87
|
// the visitor just retyped their email) must still carry the earlier
|
|
94
88
|
// verified grant over to the new token instead of silently dropping it.
|
|
95
89
|
const priorBinding = await takeOverSignupHandoverForResend(ctx.redis, email);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
await
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const provider = findSignupHandoverProvider(ctx.registry);
|
|
122
|
-
if (!provider) {
|
|
123
|
-
ctx.log?.warn("signup-request: handover payload present but no signupHandover provider mounted");
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
try {
|
|
127
|
-
const verified = await provider.verifyGrant({
|
|
128
|
-
db: ctx.db.unsafeRaw(),
|
|
129
|
-
registry: ctx.registry,
|
|
130
|
-
entityType,
|
|
131
|
-
token: grantToken,
|
|
132
|
-
});
|
|
133
|
-
if (verified) {
|
|
134
|
-
binding = verified;
|
|
90
|
+
const locale = resolveHandlerMailLocale(ctx, opts.locale);
|
|
91
|
+
const redis = ctx.redis;
|
|
92
|
+
const issued = await issueSignupActivation({
|
|
93
|
+
redis,
|
|
94
|
+
notify: ctx.notify,
|
|
95
|
+
email,
|
|
96
|
+
appUrl: opts.appUrl,
|
|
97
|
+
timeZone: ctx.tz.user,
|
|
98
|
+
...(opts.tokenTtlMinutes !== undefined && { tokenTtlMinutes: opts.tokenTtlMinutes }),
|
|
99
|
+
...(opts.appName !== undefined && { appName: opts.appName }),
|
|
100
|
+
...(locale !== undefined && { locale }),
|
|
101
|
+
// A handover grant in this request re-verifies and replaces the prior
|
|
102
|
+
// binding; anything short of a fresh, verified grant (no provider
|
|
103
|
+
// mounted, or verification failed) falls back to it instead —
|
|
104
|
+
// dropping a still-live binding on a bad request would strand the
|
|
105
|
+
// visitor's run. A throw here must never skip the mail, or the
|
|
106
|
+
// signup itself silently breaks for a bug in a provider this handler
|
|
107
|
+
// doesn't own.
|
|
108
|
+
onTokenStored: async ({ token, ttlSeconds }) => {
|
|
109
|
+
let binding = priorBinding;
|
|
110
|
+
if (event.payload.handover) {
|
|
111
|
+
const { entityType, token: grantToken } = event.payload.handover;
|
|
112
|
+
const provider = findSignupHandoverProvider(ctx.registry);
|
|
113
|
+
if (!provider) {
|
|
114
|
+
ctx.log?.warn("signup-request: handover payload present but no signupHandover provider mounted");
|
|
135
115
|
}
|
|
136
116
|
else {
|
|
137
|
-
|
|
117
|
+
try {
|
|
118
|
+
const verified = await provider.verifyGrant({
|
|
119
|
+
db: ctx.db.unsafeRaw(),
|
|
120
|
+
registry: ctx.registry,
|
|
121
|
+
entityType,
|
|
122
|
+
token: grantToken,
|
|
123
|
+
});
|
|
124
|
+
if (verified) {
|
|
125
|
+
binding = verified;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
ctx.log?.warn("signup-request: handover grant did not verify", { entityType });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
ctx.log?.warn("signup-request: handover grant verification threw", { entityType });
|
|
133
|
+
}
|
|
138
134
|
}
|
|
139
135
|
}
|
|
140
|
-
|
|
141
|
-
|
|
136
|
+
if (binding) {
|
|
137
|
+
await storeSignupHandover(redis, { token, binding, ttlSeconds });
|
|
142
138
|
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
if (binding) {
|
|
146
|
-
await storeSignupHandover(ctx.redis, { token, binding, ttlSeconds });
|
|
147
|
-
}
|
|
148
|
-
// normalizeEmail from the store — one source of truth for
|
|
149
|
-
// normalization; the delivery recipient + lookup path consistently
|
|
150
|
-
// get the same format.
|
|
151
|
-
const normalizedEmail = normalizeEmail(email);
|
|
152
|
-
const locale = resolveHandlerMailLocale(ctx, opts.locale);
|
|
153
|
-
await dispatchMagicLinkMail(ctx.notify, {
|
|
154
|
-
handlerName: "signup-request",
|
|
155
|
-
notificationType: SIGNUP_NOTIFICATION_TYPE,
|
|
156
|
-
renderContent: renderActivationEmail,
|
|
157
|
-
}, {
|
|
158
|
-
email: normalizedEmail,
|
|
159
|
-
appUrl: opts.appUrl,
|
|
160
|
-
token,
|
|
161
|
-
expiresAt: expiresAtIso,
|
|
162
|
-
issuedAt: issuedAt.toString(),
|
|
163
|
-
timeZone: ctx.tz.user,
|
|
164
|
-
...(opts.appName !== undefined && { appName: opts.appName }),
|
|
165
|
-
locale,
|
|
139
|
+
},
|
|
166
140
|
});
|
|
167
141
|
return {
|
|
168
142
|
isSuccess: true,
|
|
169
143
|
data: {
|
|
170
144
|
kind: "signup-requested",
|
|
171
|
-
email:
|
|
172
|
-
token,
|
|
173
|
-
expiresAt:
|
|
145
|
+
email: issued.email,
|
|
146
|
+
token: issued.token,
|
|
147
|
+
expiresAt: issued.expiresAt,
|
|
174
148
|
},
|
|
175
149
|
};
|
|
176
150
|
},
|
|
@@ -35,6 +35,7 @@ export function createTokenRequestHandler(spec, opts) {
|
|
|
35
35
|
rateLimit: { per: "ip+handler", limit: 10, windowSeconds: 60 },
|
|
36
36
|
additionalRateLimits: [{ per: { payloadField: "email" }, limit: 5, windowSeconds: 86400 }],
|
|
37
37
|
escapeHatch: {
|
|
38
|
+
grants: ["systemIdentity"],
|
|
38
39
|
reason: "Anonymous requester has no session — looks up the target user by email via " +
|
|
39
40
|
"ctx.queryAs(SYSTEM, user:findForAuth) to mint the out-of-band token.",
|
|
40
41
|
},
|
|
@@ -21,6 +21,7 @@ export function createVerifyEmailHandler(opts) {
|
|
|
21
21
|
access: { roles: ["anonymous"] },
|
|
22
22
|
rateLimit: { per: "ip+handler", limit: 20, windowSeconds: 60 },
|
|
23
23
|
escapeHatch: {
|
|
24
|
+
grants: ["systemIdentity"],
|
|
24
25
|
reason: "Anonymous token holder has no session — runConfirmTokenFlow loads and updates the " +
|
|
25
26
|
"target user via ctx.writeAs(SYSTEM, user:update) to set emailVerified.",
|
|
26
27
|
},
|
|
@@ -9,3 +9,5 @@ export type { AccountLockoutOptions, AccountUnlockOptions, AuthEmailPasswordOpti
|
|
|
9
9
|
export { authEmailPasswordEnvSchema, createAuthEmailPasswordFeature } from "./feature.js";
|
|
10
10
|
export { AUTH_SELF_REGISTRATION_FEATURE, createAuthSelfRegistrationToggleFeature, } from "./self-registration-toggle.js";
|
|
11
11
|
export { signToken, TokenPurpose, type VerifyResult, verifyToken } from "./signed-token.js";
|
|
12
|
+
export { type IssuedSignupActivation, type IssueSignupActivationArgs, issueSignupActivation, SIGNUP_ACTIVATION_NOTIFICATION_TYPE, } from "./signup-activation.js";
|
|
13
|
+
export { invalidateExistingSignupToken, normalizeEmail, SIGNUP_TOKEN_KEY_PREFIXES, storeSignupToken, } from "./signup-token-store.js";
|
|
@@ -17,3 +17,5 @@ export { AUTH_SELF_REGISTRATION_FEATURE, createAuthSelfRegistrationToggleFeature
|
|
|
17
17
|
// statt eigene HMAC-Logik zu duplizieren. Purpose-string diskriminiert
|
|
18
18
|
// Cross-Replay zwischen Flows.
|
|
19
19
|
export { signToken, TokenPurpose, verifyToken } from "./signed-token.js";
|
|
20
|
+
export { issueSignupActivation, SIGNUP_ACTIVATION_NOTIFICATION_TYPE, } from "./signup-activation.js";
|
|
21
|
+
export { invalidateExistingSignupToken, normalizeEmail, SIGNUP_TOKEN_KEY_PREFIXES, storeSignupToken, } from "./signup-token-store.js";
|
|
@@ -64,6 +64,8 @@ export type ProvisionSignupAccountOptions = {
|
|
|
64
64
|
readonly tenantName: string;
|
|
65
65
|
readonly tenantId: TenantId;
|
|
66
66
|
readonly memberRoles?: readonly string[];
|
|
67
|
+
/** Stored on the new user row so the first session already carries the locale claim. */
|
|
68
|
+
readonly locale?: string;
|
|
67
69
|
};
|
|
68
70
|
export declare function provisionSignupAccount(db: DbConnection, options: ProvisionSignupAccountOptions, hooks?: SeedTenantHooks): Promise<{
|
|
69
71
|
readonly userId: string;
|