@cosmicdrift/kumiko-bundled-features 0.306.0 → 0.308.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -9
- package/src/__tests__/access-invalidation-consumer.integration.test.ts +107 -0
- package/src/__tests__/access-invalidation-stream.integration.test.ts +1 -1
- package/src/__tests__/credential-scoped-sse-revoke.integration.test.ts +296 -0
- package/src/agent-tools/__tests__/agent-doc-lint.test.ts +1 -1
- package/src/agent-tools/__tests__/agent-manifest.test.ts +1 -1
- package/src/agent-tools/__tests__/agent-optout.test.ts +1 -1
- package/src/agent-tools/__tests__/agent-tools.integration.test.ts +1 -1
- package/src/agent-tools/__tests__/feature.test.ts +1 -1
- package/src/agent-tools/__tests__/tool-catalog.test.ts +1 -1
- package/src/agent-tools/agent-manifest.ts +1 -1
- package/src/audit/__tests__/escape-hatch-audit.integration.test.ts +1 -1
- package/src/audit/escape-hatch-audit-sink.ts +1 -1
- package/src/audit/handlers/details.query.ts +1 -1
- package/src/audit/handlers/list.query.ts +1 -1
- package/src/auth-email-password/__tests__/query-as-member.integration.test.ts +1 -1
- package/src/auth-email-password/changes.json +5 -0
- package/src/auth-email-password/feature.ts +1 -1
- package/src/auth-email-password/handlers/change-password.write.ts +1 -1
- package/src/auth-email-password/handlers/confirm-account-unlock.write.ts +1 -1
- package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +1 -1
- package/src/auth-email-password/handlers/invite-accept.write.ts +1 -1
- package/src/auth-email-password/handlers/invite-create.write.ts +1 -1
- package/src/auth-email-password/handlers/invite-signup-complete.write.ts +2 -2
- package/src/auth-email-password/handlers/login.write.ts +1 -1
- package/src/auth-email-password/handlers/logout.write.ts +1 -1
- package/src/auth-email-password/handlers/reset-password.write.ts +1 -1
- package/src/auth-email-password/handlers/self-registration-status.query.ts +1 -1
- package/src/auth-email-password/handlers/signup-confirm.write.ts +2 -2
- package/src/auth-email-password/handlers/signup-request.write.ts +1 -1
- package/src/auth-email-password/handlers/token-request-handler.ts +1 -1
- package/src/auth-email-password/handlers/verify-email.write.ts +1 -1
- package/src/auth-email-password/password-policy.ts +1 -1
- package/src/auth-mfa/__tests__/session-scoped-sse-revoke.integration.test.ts +270 -0
- package/src/auth-mfa/handlers/disable.write.ts +1 -1
- package/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +2 -2
- package/src/auth-mfa/handlers/enable-confirm.write.ts +1 -1
- package/src/auth-mfa/handlers/enable-start-preauth.write.ts +1 -1
- package/src/auth-mfa/handlers/enable-start.write.ts +1 -1
- package/src/auth-mfa/handlers/regenerate-recovery.write.ts +1 -1
- package/src/auth-mfa/handlers/status.query.ts +1 -1
- package/src/auth-mfa/handlers/verify.write.ts +2 -2
- package/src/billing-foundation/billing-info-query.ts +1 -1
- package/src/billing-foundation/events.ts +1 -1
- package/src/billing-foundation/handlers/create-checkout-session.write.ts +1 -1
- package/src/billing-foundation/handlers/create-portal-session.write.ts +1 -1
- package/src/billing-foundation/handlers/list-subscriptions.query.ts +1 -1
- package/src/billing-foundation/handlers/process-event.write.ts +1 -1
- package/src/billing-foundation/handlers/process-payment-event.write.ts +1 -1
- package/src/cap-counter/__tests__/cap-counter.integration.test.ts +1 -1
- package/src/cap-counter/__tests__/enforce-cap.test.ts +7 -0
- package/src/cap-counter/__tests__/stock-cap-guard.integration.test.ts +1 -1
- package/src/cap-counter/__tests__/with-cap-enforcement.integration.test.ts +1 -1
- package/src/cap-counter/book-cap-usage.ts +1 -1
- package/src/cap-counter/changes.json +5 -0
- package/src/cap-counter/enforce-cap.ts +8 -1
- package/src/cap-counter/handlers/get-counter.query.ts +1 -1
- package/src/cap-counter/handlers/increment-rolling.write.ts +1 -1
- package/src/cap-counter/handlers/increment.write.ts +1 -1
- package/src/cap-counter/handlers/mark-soft-warned.write.ts +1 -1
- package/src/cap-overview/handlers/caps-usage.query.ts +1 -1
- package/src/cap-overview/handlers/tenant-caps-list.query.ts +1 -1
- package/src/cap-overview/handlers/tenant-options.query.ts +1 -1
- package/src/channel-in-app/handlers/inbox.query.ts +1 -1
- package/src/channel-in-app/handlers/mark-all-read.write.ts +1 -1
- package/src/channel-in-app/handlers/mark-read.write.ts +1 -1
- package/src/channel-in-app/handlers/unread-count.query.ts +1 -1
- package/src/compliance-profiles/handlers/for-tenant.query.ts +1 -1
- package/src/compliance-profiles/handlers/list-profiles.query.ts +1 -1
- package/src/compliance-profiles/handlers/needs-profile.query.ts +1 -1
- package/src/compliance-profiles/handlers/set-profile.write.ts +1 -1
- package/src/compliance-profiles/handlers/sub-processors.query.ts +1 -1
- package/src/compliance-profiles-ops/handlers/tenants-missing-profile.query.ts +1 -1
- package/src/config/__tests__/backing-secrets.integration.test.ts +1 -1
- package/src/config/__tests__/config.integration.test.ts +1 -1
- package/src/config/__tests__/tz-resolution.integration.test.ts +1 -1
- package/src/config/handlers/cascade.query.ts +1 -1
- package/src/config/handlers/readiness.query.ts +1 -1
- package/src/config/handlers/reset.write.ts +1 -1
- package/src/config/handlers/schema.query.ts +1 -1
- package/src/config/handlers/set.write.ts +1 -1
- package/src/config/handlers/values.query.ts +1 -1
- package/src/crypto-shredding/handlers/forget-subject.write.ts +1 -1
- package/src/custom-fields/__tests__/audit-integration.integration.test.ts +1 -1
- package/src/custom-fields/__tests__/cross-tenant-field-delete.integration.test.ts +1 -1
- package/src/custom-fields/__tests__/cross-tenant-set-write.integration.test.ts +1 -1
- package/src/custom-fields/__tests__/custom-fields.integration.test.ts +1 -1
- package/src/custom-fields/__tests__/field-access.integration.test.ts +1 -1
- package/src/custom-fields/__tests__/quota.integration.test.ts +1 -1
- package/src/custom-fields/__tests__/retention.integration.test.ts +1 -1
- package/src/custom-fields/__tests__/user-data-rights.integration.test.ts +1 -1
- package/src/custom-fields/events.ts +1 -1
- package/src/custom-fields/feature.ts +1 -1
- package/src/custom-fields/handlers/clear-custom-field.write.ts +1 -1
- package/src/custom-fields/handlers/set-custom-field.write.ts +1 -1
- package/src/custom-fields/lib/value-schema.ts +1 -1
- package/src/custom-fields/schemas.ts +1 -1
- package/src/data-retention/handlers/policy-for.query.ts +1 -1
- package/src/data-retention/override-schema.ts +1 -1
- package/src/delivery/__tests__/delivery-pii-kms.integration.test.ts +1 -1
- package/src/delivery/__tests__/delivery.integration.test.ts +1 -1
- package/src/delivery/events.ts +1 -1
- package/src/delivery/feature.ts +1 -1
- package/src/delivery/handlers/log.query.ts +1 -1
- package/src/delivery/handlers/preferences.query.ts +1 -1
- package/src/delivery/handlers/set-preference.write.ts +1 -1
- package/src/delivery/jobs.ts +1 -1
- package/src/delivery/unsubscribe.ts +1 -1
- package/src/derivatives-sharp/__tests__/derivatives-sharp.integration.test.ts +1 -1
- package/src/document-ingest-foundation/events.ts +1 -1
- package/src/document-ingest-foundation/feature.ts +1 -1
- package/src/feature-toggles/__tests__/feature-toggles.integration.test.ts +1 -1
- package/src/feature-toggles/events.ts +1 -1
- package/src/feature-toggles/handlers/list.query.ts +1 -1
- package/src/feature-toggles/handlers/registered.query.ts +1 -1
- package/src/feature-toggles/handlers/set.write.ts +1 -1
- package/src/file-derivatives/handlers/public-variant.query.ts +1 -1
- package/src/file-foundation/__tests__/file-foundation.integration.test.ts +1 -1
- package/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +1 -1
- package/src/folders/handlers/delete-folder.write.ts +1 -1
- package/src/folders/schemas.ts +1 -1
- package/src/form-draft/schemas.ts +1 -1
- package/src/inbound-mail-foundation/events.ts +1 -1
- package/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
- package/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +1 -1
- package/src/inbound-mail-foundation/handlers/ingest-message.write.ts +1 -1
- package/src/inbound-mail-foundation/handlers/list-accounts.query.ts +1 -1
- package/src/inbound-mail-foundation/handlers/list-messages.query.ts +1 -1
- package/src/inbound-mail-foundation/handlers/update-account.write.ts +1 -1
- package/src/inbound-provider-imap/credential-document.ts +1 -1
- package/src/jobs/__tests__/jobs-catalog.integration.test.ts +1 -1
- package/src/jobs/__tests__/tenant-job-failures.integration.test.ts +1 -1
- package/src/jobs/events.ts +1 -1
- package/src/jobs/handlers/catalog.query.ts +1 -1
- package/src/jobs/handlers/detail.query.ts +1 -1
- package/src/jobs/handlers/list.query.ts +1 -1
- package/src/jobs/handlers/projection-rebuild.job.ts +1 -1
- package/src/jobs/handlers/reindex-entity.job.ts +1 -1
- package/src/jobs/handlers/retry.write.ts +1 -1
- package/src/jobs/handlers/tenant-failures.query.ts +1 -1
- package/src/jobs/handlers/trigger.write.ts +1 -1
- package/src/ledger/handlers/reports.query.ts +1 -1
- package/src/ledger/schemas.ts +1 -1
- package/src/mail-foundation/__tests__/mail-foundation.integration.test.ts +1 -1
- package/src/managed-pages/handlers/branding.query.ts +1 -1
- package/src/managed-pages/handlers/by-slug.query.ts +1 -1
- package/src/managed-pages/handlers/by-tenant-published.query.ts +1 -1
- package/src/managed-pages/handlers/set.write.ts +1 -1
- package/src/notes-history/changes.json +7 -0
- package/src/notes-history/schemas.ts +1 -1
- package/src/notes-history/web/notes-section.tsx +37 -36
- package/src/personal-access-tokens/feature.ts +10 -0
- package/src/personal-access-tokens/handlers/available-scopes.query.ts +1 -1
- package/src/personal-access-tokens/handlers/create.write.ts +1 -1
- package/src/personal-access-tokens/handlers/list.query.ts +1 -1
- package/src/personal-access-tokens/handlers/revoke.write.ts +18 -2
- package/src/personal-access-tokens/index.ts +7 -0
- package/src/personal-access-tokens/pat-revoked-event.ts +23 -0
- package/src/personal-access-tokens/revoke-for-user.ts +31 -0
- package/src/rate-limiting/__tests__/rate-limiting.integration.test.ts +1 -1
- package/src/rate-limiting/handlers/status.query.ts +1 -1
- package/src/readiness/handlers/status.query.ts +1 -1
- package/src/secrets/feature.ts +1 -1
- package/src/secrets/handlers/delete.write.ts +1 -1
- package/src/secrets/handlers/list.query.ts +1 -1
- package/src/secrets/handlers/set.write.ts +1 -1
- package/src/secrets/secrets-context.ts +1 -1
- package/src/seo/handlers/seo-config.query.ts +1 -1
- package/src/sessions/handlers/detail.query.ts +1 -1
- package/src/sessions/handlers/list.query.ts +1 -1
- package/src/sessions/handlers/mine.query.ts +1 -1
- package/src/sessions/handlers/revoke-all-for-user.write.ts +1 -1
- package/src/sessions/handlers/revoke-all-others.write.ts +2 -1
- package/src/sessions/handlers/revoke.write.ts +1 -1
- package/src/sessions/session-callbacks.ts +26 -1
- package/src/sessions/session-revoked-event.ts +9 -1
- package/src/shared/__tests__/row-bound-grant.integration.test.ts +1 -1
- package/src/step-dispatcher/mail-runner.ts +1 -1
- package/src/step-dispatcher/webhook-runner.ts +1 -1
- package/src/subscription-mollie/feature.ts +1 -1
- package/src/subscription-stripe/feature.ts +1 -1
- package/src/tags/schemas.ts +1 -1
- package/src/template-resolver/handlers/by-slug.query.ts +1 -1
- package/src/template-resolver/handlers/by-tenant.query.ts +1 -1
- package/src/template-resolver/handlers/collection-item.query.ts +1 -1
- package/src/template-resolver/handlers/collection-list.query.ts +1 -1
- package/src/template-resolver/handlers/collection-set.write.ts +1 -1
- package/src/template-resolver/handlers/find-by-id.query.ts +1 -1
- package/src/template-resolver/handlers/list.query.ts +1 -1
- package/src/template-resolver/handlers/set.write.ts +1 -1
- package/src/template-resolver/handlers/shared.ts +1 -1
- package/src/template-resolver/handlers/toggle-status.write.ts +1 -1
- package/src/template-resolver/handlers/upsert-tenant.write.ts +1 -1
- package/src/tenant/__tests__/tenant-timezone-boot.integration.test.ts +1 -1
- package/src/tenant/handlers/active-tenant-ids.query.ts +1 -1
- package/src/tenant/handlers/add-member.write.ts +1 -1
- package/src/tenant/handlers/cancel-invitation.write.ts +1 -1
- package/src/tenant/handlers/create.write.ts +1 -1
- package/src/tenant/handlers/invitations.query.ts +1 -1
- package/src/tenant/handlers/list.query.ts +1 -1
- package/src/tenant/handlers/me.query.ts +1 -1
- package/src/tenant/handlers/member-directory.query.ts +1 -1
- package/src/tenant/handlers/members.query.ts +1 -1
- package/src/tenant/handlers/memberships.query.ts +1 -1
- package/src/tenant/handlers/remove-member.write.ts +1 -1
- package/src/tenant/handlers/resolve-user-ids.query.ts +1 -1
- package/src/tenant/handlers/team-list.query.ts +1 -1
- package/src/tenant/handlers/tenant-directory.query.ts +1 -1
- package/src/tenant/handlers/toggle-enabled.write.ts +1 -1
- package/src/tenant/handlers/update-member-roles.write.ts +1 -1
- package/src/tenant/handlers/update.write.ts +1 -1
- package/src/tenant-handover/events.ts +1 -1
- package/src/tenant-handover/handlers/claim.write.ts +1 -1
- package/src/tenant-lifecycle/events.ts +1 -1
- package/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +1 -1
- package/src/tenant-lifecycle/handlers/request-destruction.write.ts +1 -1
- package/src/tenant-settings/tenant-defaults.ts +1 -1
- package/src/tier-engine/__tests__/tier-resolver.integration.test.ts +1 -1
- package/src/tier-engine/feature.ts +1 -1
- package/src/tier-engine/handlers/active-tier.query.ts +1 -1
- package/src/tier-engine/handlers/get-tenant-tier.query.ts +1 -1
- package/src/tier-engine/handlers/set-tenant-tier.write.ts +1 -1
- package/src/user/__tests__/user-global-tenancy.integration.test.ts +1 -1
- package/src/user/handlers/create.write.ts +1 -1
- package/src/user/handlers/find-for-auth.query.ts +1 -1
- package/src/user/handlers/me.query.ts +1 -1
- package/src/user/handlers/update.write.ts +1 -1
- package/src/user/roles-input-schema.ts +1 -1
- package/src/user-data-rights/__tests__/boot-checks.test.ts +1 -1
- package/src/user-data-rights/changes.json +6 -0
- package/src/user-data-rights/feature.ts +1 -0
- package/src/user-data-rights/handlers/cancel-deletion.write.ts +1 -1
- package/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +1 -1
- package/src/user-data-rights/handlers/download-by-job.query.ts +1 -1
- package/src/user-data-rights/handlers/download-by-token.query.ts +1 -1
- package/src/user-data-rights/handlers/export-status.query.ts +1 -1
- package/src/user-data-rights/handlers/lift-restriction.write.ts +1 -1
- package/src/user-data-rights/handlers/list-download-attempts.query.ts +1 -1
- package/src/user-data-rights/handlers/my-audit-log.query.ts +1 -1
- package/src/user-data-rights/handlers/request-deletion-by-email.write.ts +1 -1
- package/src/user-data-rights/handlers/request-deletion.write.ts +1 -1
- package/src/user-data-rights/handlers/request-export.write.ts +1 -1
- package/src/user-data-rights/handlers/restrict-account.write.ts +1 -1
- package/src/user-data-rights/handlers/run-forget-cleanup.write.ts +1 -1
- package/src/user-profile/handlers/change-email.write.ts +1 -1
- package/src/workflow-runner/__tests__/pending-projection.integration.test.ts +1 -1
- package/src/workflow-runner/__tests__/workflow-runner.integration.test.ts +5 -0
- package/src/workflow-runner/handlers/resume-run.write.ts +1 -1
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
type TenantId,
|
|
24
24
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
25
25
|
import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
26
|
-
import
|
|
26
|
+
import * as z from "zod";
|
|
27
27
|
import { decryptStoredPii } from "../../shared";
|
|
28
28
|
// kumiko-lint-ignore cross-feature-import invite-flow lebt in auth-email-password (Magic-Link), DB-row-owner ist tenant-feature
|
|
29
29
|
import {
|
|
@@ -22,7 +22,7 @@ import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
|
|
|
22
22
|
import { access, defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
23
23
|
import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
24
24
|
import { Temporal } from "temporal-polyfill";
|
|
25
|
-
import
|
|
25
|
+
import * as z from "zod";
|
|
26
26
|
// kumiko-lint-ignore cross-feature-import invite-flow lebt in auth-email-password (Magic-Link-Pattern), DB-row-owner ist tenant-feature
|
|
27
27
|
import {
|
|
28
28
|
INVITATION_STATUS,
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
type TenantId,
|
|
32
32
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
33
33
|
import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
34
|
-
import
|
|
34
|
+
import * as z from "zod";
|
|
35
35
|
import { decryptStoredPii } from "../../shared";
|
|
36
36
|
// kumiko-lint-ignore cross-feature-import invite-flow
|
|
37
37
|
import {
|
|
@@ -92,7 +92,7 @@ export function createInviteSignupCompleteHandler() {
|
|
|
92
92
|
>({
|
|
93
93
|
name: "invite-signup-complete",
|
|
94
94
|
schema: InviteSignupCompleteSchema,
|
|
95
|
-
access: { roles: ["anonymous"] },
|
|
95
|
+
access: { roles: ["anonymous"], personalData: "public-intake" },
|
|
96
96
|
rateLimit: { per: "ip+handler", limit: 20, windowSeconds: 60 },
|
|
97
97
|
agent: { expose: false },
|
|
98
98
|
escapeHatch: {
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
11
11
|
import type { WriteFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
12
12
|
import { parseRoles } from "@cosmicdrift/kumiko-framework/utils";
|
|
13
|
-
import
|
|
13
|
+
import * as z from "zod";
|
|
14
14
|
import { verifyDummyPassword, verifyPassword } from "../../shared";
|
|
15
15
|
import { USER_STATUS, UserQueries } from "../../user";
|
|
16
16
|
import { type AuthUserRow, parseAuthUserRow } from "../auth-user-row";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { access, defineWriteHandler, stepsPipeline } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
-
import
|
|
2
|
+
import * as z from "zod";
|
|
3
3
|
|
|
4
4
|
// Logout — JWT is stateless, so server-side we only return OK. A future
|
|
5
5
|
// revocation list / session table can land here without changing the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
3
|
-
import
|
|
3
|
+
import * as z from "zod";
|
|
4
4
|
import { hashPassword } from "../../shared";
|
|
5
5
|
import { AuthErrors } from "../constants";
|
|
6
6
|
import { invalidResetToken } from "../errors";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
-
import
|
|
2
|
+
import * as z from "zod";
|
|
3
3
|
import { AUTH_SELF_REGISTRATION_FEATURE } from "../self-registration-toggle";
|
|
4
4
|
|
|
5
5
|
// Anonymous-readable status for the (unauthenticated) signup page: lets it
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
import { ConflictError, InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
30
30
|
import { generateUniqueName } from "@cosmicdrift/kumiko-framework/random";
|
|
31
31
|
import { generateId } from "@cosmicdrift/kumiko-framework/utils";
|
|
32
|
-
import
|
|
32
|
+
import * as z from "zod";
|
|
33
33
|
// kumiko-lint-ignore cross-feature-import signup-confirm reads tenants.key for slug-uniqueness check (TOCTOU + DB-unique-index zusammen)
|
|
34
34
|
import { tenantTable } from "../../tenant/schema/tenant";
|
|
35
35
|
import { invalidSignupToken, signupEmailAlreadyRegistered } from "../errors";
|
|
@@ -67,7 +67,7 @@ export function createSignupConfirmHandler() {
|
|
|
67
67
|
return defineWriteHandler<"signup-confirm", typeof SignupConfirmSchema, SignupConfirmData>({
|
|
68
68
|
name: "signup-confirm",
|
|
69
69
|
schema: SignupConfirmSchema,
|
|
70
|
-
access: { roles: ["anonymous"] },
|
|
70
|
+
access: { roles: ["anonymous"], personalData: "public-intake" },
|
|
71
71
|
rateLimit: { per: "ip+handler", limit: 20, windowSeconds: 60 },
|
|
72
72
|
agent: { expose: false },
|
|
73
73
|
escapeHatch: {
|
|
@@ -25,7 +25,7 @@ import { generateToken } from "@cosmicdrift/kumiko-framework/api";
|
|
|
25
25
|
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
26
26
|
import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
27
27
|
import { Temporal } from "temporal-polyfill";
|
|
28
|
-
import
|
|
28
|
+
import * as z from "zod";
|
|
29
29
|
import { AUTH_SIGNUP_DEFAULT_TTL_MINUTES } from "../constants";
|
|
30
30
|
import type { AuthMailLocale } from "../email-templates";
|
|
31
31
|
import { renderActivationEmail } from "../email-templates";
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import { createSystemUser, defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
16
16
|
import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
17
17
|
import type { Temporal } from "temporal-polyfill";
|
|
18
|
-
import
|
|
18
|
+
import * as z from "zod";
|
|
19
19
|
import { UserQueries } from "../../user";
|
|
20
20
|
import { type AuthUserRow, parseAuthUserRow } from "../auth-user-row";
|
|
21
21
|
import type { AuthMailContent, AuthMailLocale, RenderTokenContentArgs } from "../email-templates";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
3
|
-
import
|
|
3
|
+
import * as z from "zod";
|
|
4
4
|
import { AuthErrors } from "../constants";
|
|
5
5
|
import { invalidVerificationToken } from "../errors";
|
|
6
6
|
import { verifyVerificationToken } from "../verification-token";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as z from "zod";
|
|
2
2
|
|
|
3
3
|
// Top-10k common-password list (SecLists, MIT license). Filtered to length // kumiko-lint-ignore comment-lang "MIT" false-positives the DE-word regex
|
|
4
4
|
// >=8, since shorter entries are already rejected by `.min(8)` below (keeps
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// Session-scoped SSE revocation (kumiko-framework "session-scoped-revoke").
|
|
2
|
+
//
|
|
3
|
+
// enable-confirm (and disable/regenerate-recovery) call
|
|
4
|
+
// sessionRevokeAllOthers, which appends sessions:event:session-revoked with
|
|
5
|
+
// an optional `keptSessionId` set to the caller's own sid — the ONE sid this
|
|
6
|
+
// write deliberately does NOT revoke. Before this fix,
|
|
7
|
+
// createAccessInvalidationEventConsumer ignored that and called
|
|
8
|
+
// sseBroker.publishAccessInvalidation USERWIDE, which closed the caller's
|
|
9
|
+
// own just-opened SSE stream along with every other session's — the exact
|
|
10
|
+
// bug this test proves fixed: studio's live MFA-status panel never saw the
|
|
11
|
+
// state flip because its own stream died with everyone else's.
|
|
12
|
+
//
|
|
13
|
+
// This is a KEEP-list (spare exactly keptSessionId), not a kill-list
|
|
14
|
+
// (scope to the revoked sessionIds) — the first test below proves why: a
|
|
15
|
+
// third session (C) that was already logged out through the real
|
|
16
|
+
// /api/auth/logout endpoint BEFORE enable-confirm runs never appears in any
|
|
17
|
+
// session-revoked event's sessionIds (logout revokes via the raw
|
|
18
|
+
// sessionRevoker callback with no event at all). A kill-list keyed on
|
|
19
|
+
// sessionIds would silently leave C's still-open stream running; the
|
|
20
|
+
// keep-list closes it like every other non-kept stream, exactly as the
|
|
21
|
+
// pre-scoping userwide invalidation always did.
|
|
22
|
+
//
|
|
23
|
+
// Real stack throughout: setupTestStack + real HTTP (createLiveDispatcher for
|
|
24
|
+
// the SSE streams, stack.http for the writes), never createTestDispatcher.
|
|
25
|
+
|
|
26
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
27
|
+
import { randomBytes } from "node:crypto";
|
|
28
|
+
import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
|
|
29
|
+
import { createLiveDispatcher } from "@cosmicdrift/kumiko-dispatcher-live";
|
|
30
|
+
import type { SessionCreator } from "@cosmicdrift/kumiko-framework/api";
|
|
31
|
+
import { generateToken } from "@cosmicdrift/kumiko-framework/api";
|
|
32
|
+
import { configureEntityFieldEncryption } from "@cosmicdrift/kumiko-framework/db";
|
|
33
|
+
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
34
|
+
import { eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
35
|
+
import {
|
|
36
|
+
setupTestStack,
|
|
37
|
+
type TestStack,
|
|
38
|
+
TestUsers,
|
|
39
|
+
testTenantId,
|
|
40
|
+
unsafeCreateEntityTable,
|
|
41
|
+
unsafePushTables,
|
|
42
|
+
} from "@cosmicdrift/kumiko-framework/stack";
|
|
43
|
+
import {
|
|
44
|
+
createLateBoundHolder,
|
|
45
|
+
createTestEnvelopeCipher,
|
|
46
|
+
resetTestTables,
|
|
47
|
+
} from "@cosmicdrift/kumiko-framework/testing";
|
|
48
|
+
import * as z from "zod";
|
|
49
|
+
import { AuthHandlers } from "../../auth-email-password/constants";
|
|
50
|
+
import { createAuthEmailPasswordFeature } from "../../auth-email-password/feature";
|
|
51
|
+
import { createConfigFeature } from "../../config";
|
|
52
|
+
import { createConfigResolver } from "../../config/resolver";
|
|
53
|
+
import { configValuesTable } from "../../config/table";
|
|
54
|
+
import { makeSessionHelpers } from "../../sessions/__tests__/test-helpers";
|
|
55
|
+
import { createSessionsFeature } from "../../sessions/feature";
|
|
56
|
+
import { userSessionEntity, userSessionTable } from "../../sessions/schema/user-session";
|
|
57
|
+
import { createSessionCallbacks, type SessionCallbacks } from "../../sessions/session-callbacks";
|
|
58
|
+
import { sessionCallbacksFromLateBound, withMintedSession } from "../../sessions/testing";
|
|
59
|
+
import { createTenantFeature } from "../../tenant";
|
|
60
|
+
import { TenantHandlers } from "../../tenant/constants";
|
|
61
|
+
import { tenantMembershipsTable } from "../../tenant/membership-table";
|
|
62
|
+
import { tenantEntity } from "../../tenant/schema/tenant";
|
|
63
|
+
import { createUserFeature } from "../../user/feature";
|
|
64
|
+
import { userEntity, userTable } from "../../user/schema/user";
|
|
65
|
+
import { base32Decode } from "../base32";
|
|
66
|
+
import { AuthMfaHandlers } from "../constants";
|
|
67
|
+
import { bindMfaRevokeAllOtherSessionsFromFeature, createAuthMfaFeature } from "../feature";
|
|
68
|
+
import { userMfaEntity } from "../schema/user-mfa";
|
|
69
|
+
import { currentTotpCode } from "../totp";
|
|
70
|
+
|
|
71
|
+
let stack: TestStack;
|
|
72
|
+
let h: ReturnType<typeof makeSessionHelpers>;
|
|
73
|
+
let sessionCreator: SessionCreator;
|
|
74
|
+
const callbacks = createLateBoundHolder<SessionCallbacks>("session-callbacks");
|
|
75
|
+
|
|
76
|
+
const encryptionKey = randomBytes(32).toString("base64");
|
|
77
|
+
const SETUP_TOKEN_SECRET = "test-mfa-setup-token-secret-at-least-32-bytes!!";
|
|
78
|
+
const TENANT = testTenantId(1);
|
|
79
|
+
|
|
80
|
+
const chunkGates = new Map<string, { promise: Promise<void>; release: () => void }>();
|
|
81
|
+
|
|
82
|
+
function createGate(gateId: string): () => void {
|
|
83
|
+
let release!: () => void;
|
|
84
|
+
const promise = new Promise<void>((resolve) => {
|
|
85
|
+
release = resolve;
|
|
86
|
+
});
|
|
87
|
+
chunkGates.set(gateId, { promise, release });
|
|
88
|
+
return release;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const streamProbeFeature = defineFeature("stream-probe", (r) => {
|
|
92
|
+
r.streamHandler(
|
|
93
|
+
"probe:tail",
|
|
94
|
+
z.object({ gateId: z.string().min(1) }),
|
|
95
|
+
async function* (event) {
|
|
96
|
+
yield { phase: "open" as const };
|
|
97
|
+
const gate = chunkGates.get(event.payload.gateId);
|
|
98
|
+
if (!gate) throw new Error(`missing gate ${event.payload.gateId}`);
|
|
99
|
+
await gate.promise;
|
|
100
|
+
yield { phase: "survived" as const };
|
|
101
|
+
},
|
|
102
|
+
{ access: { roles: ["User"] } },
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
function buildLiveFetch(authJwt: string): {
|
|
107
|
+
readonly fetch: typeof fetch;
|
|
108
|
+
readonly csrfToken: string;
|
|
109
|
+
} {
|
|
110
|
+
const csrfToken = generateToken();
|
|
111
|
+
const cookieHeader = `kumiko_auth=${authJwt}; kumiko_csrf=${csrfToken}`;
|
|
112
|
+
const fetchImpl = (async (url: unknown, init: RequestInit | undefined) => {
|
|
113
|
+
const reqInit: RequestInit = {
|
|
114
|
+
...(init ?? {}),
|
|
115
|
+
headers: {
|
|
116
|
+
...(init?.headers ?? {}),
|
|
117
|
+
Cookie: cookieHeader,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
return stack.app.request(String(url), reqInit);
|
|
121
|
+
}) as unknown as typeof fetch;
|
|
122
|
+
return { fetch: fetchImpl, csrfToken };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function openProbeStream(token: string): Promise<{
|
|
126
|
+
iter: AsyncIterator<{ phase: string }>;
|
|
127
|
+
release: () => void;
|
|
128
|
+
}> {
|
|
129
|
+
const gateId = generateToken();
|
|
130
|
+
const release = createGate(gateId);
|
|
131
|
+
const { fetch, csrfToken } = buildLiveFetch(token);
|
|
132
|
+
const dispatcher = createLiveDispatcher({ fetch, readCsrf: () => csrfToken });
|
|
133
|
+
const iter = dispatcher
|
|
134
|
+
.stream<{ phase: string }>("stream-probe:stream:probe:tail", { gateId })
|
|
135
|
+
[Symbol.asyncIterator]();
|
|
136
|
+
const first = await iter.next();
|
|
137
|
+
expect(first.done).toBe(false);
|
|
138
|
+
expect(first.value).toEqual({ phase: "open" });
|
|
139
|
+
return { iter, release };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
beforeAll(async () => {
|
|
143
|
+
const encryption = createTestEnvelopeCipher(encryptionKey);
|
|
144
|
+
configureEntityFieldEncryption(encryption);
|
|
145
|
+
const resolver = createConfigResolver({ cipher: encryption });
|
|
146
|
+
const bound = sessionCallbacksFromLateBound(callbacks);
|
|
147
|
+
const mfaFeature = createAuthMfaFeature({
|
|
148
|
+
setupTokenSecret: SETUP_TOKEN_SECRET,
|
|
149
|
+
issuer: "Kumiko Test",
|
|
150
|
+
challengeTokenSecret: "test-mfa-challenge-secret-at-least-32-bytes!!",
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
stack = await setupTestStack({
|
|
154
|
+
features: [
|
|
155
|
+
createConfigFeature(),
|
|
156
|
+
createUserFeature(),
|
|
157
|
+
createTenantFeature(),
|
|
158
|
+
createAuthEmailPasswordFeature(),
|
|
159
|
+
authFoundationFeature,
|
|
160
|
+
createSessionsFeature({ autoRevokeOnPasswordChange: bound.asMassRevoker() }),
|
|
161
|
+
mfaFeature,
|
|
162
|
+
streamProbeFeature,
|
|
163
|
+
],
|
|
164
|
+
extraContext: { configResolver: resolver, configEncryption: encryption },
|
|
165
|
+
authConfig: {
|
|
166
|
+
...bound.asAuthConfig(),
|
|
167
|
+
membershipQuery: "tenant:query:memberships",
|
|
168
|
+
loginHandler: AuthHandlers.login,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
callbacks.set(createSessionCallbacks({ db: stack.db }));
|
|
172
|
+
bindMfaRevokeAllOtherSessionsFromFeature(mfaFeature)?.(callbacks.get().sessionRevokeAllOthers);
|
|
173
|
+
|
|
174
|
+
h = makeSessionHelpers(stack, TENANT, bound.asAuthConfig().sessionCreator);
|
|
175
|
+
const boundSessionCreator = bound.asAuthConfig().sessionCreator;
|
|
176
|
+
if (!boundSessionCreator) throw new Error("sessionCreator not bound — check setup order");
|
|
177
|
+
sessionCreator = boundSessionCreator;
|
|
178
|
+
|
|
179
|
+
await unsafeCreateEntityTable(stack.db, userEntity);
|
|
180
|
+
await unsafeCreateEntityTable(stack.db, tenantEntity);
|
|
181
|
+
await unsafeCreateEntityTable(stack.db, userSessionEntity);
|
|
182
|
+
await unsafeCreateEntityTable(stack.db, userMfaEntity);
|
|
183
|
+
await unsafePushTables(stack.db, { configValuesTable, tenantMembershipsTable });
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
afterAll(async () => {
|
|
187
|
+
await stack.cleanup();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
beforeEach(async () => {
|
|
191
|
+
await resetTestTables(stack.db, [
|
|
192
|
+
userTable,
|
|
193
|
+
tenantMembershipsTable,
|
|
194
|
+
userSessionTable,
|
|
195
|
+
eventsTable,
|
|
196
|
+
]);
|
|
197
|
+
chunkGates.clear();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe("session-scoped SSE revoke (enable-confirm keeps the caller's own stream open)", () => {
|
|
201
|
+
test("enable-confirm from session A keeps A's stream alive, closes B's, and also closes C's even though C was already logged out through the eventless /api/auth/logout path", async () => {
|
|
202
|
+
await h.seedUser("mfa-scope-a@example.com", "pw-long-enough");
|
|
203
|
+
const sessionA = await h.login("mfa-scope-a@example.com", "pw-long-enough");
|
|
204
|
+
const sessionB = await h.login("mfa-scope-a@example.com", "pw-long-enough");
|
|
205
|
+
const sessionC = await h.login("mfa-scope-a@example.com", "pw-long-enough");
|
|
206
|
+
|
|
207
|
+
const streamA = await openProbeStream(sessionA.token);
|
|
208
|
+
const streamB = await openProbeStream(sessionB.token);
|
|
209
|
+
const streamC = await openProbeStream(sessionC.token);
|
|
210
|
+
|
|
211
|
+
// C logs out through the REAL endpoint — sessionRevoker (session-
|
|
212
|
+
// callbacks.ts) revokes the row directly, no session-revoked event at
|
|
213
|
+
// all. C's sid therefore never appears in any later event's sessionIds.
|
|
214
|
+
const logoutRes = await h.authedPost("/api/auth/logout", sessionC.token);
|
|
215
|
+
expect(logoutRes.status).toBe(200);
|
|
216
|
+
|
|
217
|
+
const start = await h.authedPost("/api/write", sessionA.token, {
|
|
218
|
+
type: AuthMfaHandlers.enableStart,
|
|
219
|
+
payload: { accountLabel: "mfa-scope-a@example.com" },
|
|
220
|
+
});
|
|
221
|
+
expect(start.status).toBe(200);
|
|
222
|
+
const startBody = (await start.json()) as {
|
|
223
|
+
data: { setupToken: string; otpauthUri: string };
|
|
224
|
+
};
|
|
225
|
+
const secretParam =
|
|
226
|
+
new URLSearchParams(startBody.data.otpauthUri.split("?")[1]).get("secret") ?? "";
|
|
227
|
+
const secret = base32Decode(secretParam);
|
|
228
|
+
|
|
229
|
+
const confirm = await h.authedPost("/api/write", sessionA.token, {
|
|
230
|
+
type: AuthMfaHandlers.enableConfirm,
|
|
231
|
+
payload: { setupToken: startBody.data.setupToken, code: currentTotpCode(secret) },
|
|
232
|
+
});
|
|
233
|
+
expect(confirm.status).toBe(200);
|
|
234
|
+
|
|
235
|
+
await stack.eventDispatcher?.runOnce();
|
|
236
|
+
|
|
237
|
+
// Assert B and C first, without releasing A's gate: their rejections are
|
|
238
|
+
// proof the publish already reached every listener (they fire from the
|
|
239
|
+
// same publish loop), so a still-pending A cannot pass "by being slow".
|
|
240
|
+
await expect(streamB.iter.next()).rejects.toMatchObject({ code: "access_denied" });
|
|
241
|
+
await expect(streamC.iter.next()).rejects.toMatchObject({ code: "access_denied" });
|
|
242
|
+
|
|
243
|
+
streamA.release();
|
|
244
|
+
const survivedChunk = await streamA.iter.next();
|
|
245
|
+
expect(survivedChunk.done).toBe(false);
|
|
246
|
+
expect(survivedChunk.value).toEqual({ phase: "survived" });
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("an invalidation reason with no keptSessionId (role change) still closes every session's stream, unscoped", async () => {
|
|
250
|
+
const { userId } = await h.seedUser("mfa-scope-b@example.com", "pw-long-enough");
|
|
251
|
+
const sessionA = await h.login("mfa-scope-b@example.com", "pw-long-enough");
|
|
252
|
+
const sessionB = await h.login("mfa-scope-b@example.com", "pw-long-enough");
|
|
253
|
+
|
|
254
|
+
const streamA = await openProbeStream(sessionA.token);
|
|
255
|
+
const streamB = await openProbeStream(sessionB.token);
|
|
256
|
+
|
|
257
|
+
const admin = await withMintedSession(sessionCreator, TestUsers.systemAdmin);
|
|
258
|
+
const result = await stack.http.writeOk(
|
|
259
|
+
TenantHandlers.updateMemberRoles,
|
|
260
|
+
{ userId, tenantId: TENANT, roles: ["Admin"] },
|
|
261
|
+
admin,
|
|
262
|
+
);
|
|
263
|
+
expect(result).toBeTruthy();
|
|
264
|
+
|
|
265
|
+
await stack.eventDispatcher?.runOnce();
|
|
266
|
+
|
|
267
|
+
await expect(streamA.iter.next()).rejects.toMatchObject({ code: "access_denied" });
|
|
268
|
+
await expect(streamB.iter.next()).rejects.toMatchObject({ code: "access_denied" });
|
|
269
|
+
});
|
|
270
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
4
|
-
import
|
|
4
|
+
import * as z from "zod";
|
|
5
5
|
import { findUserMfaRow } from "../db/queries";
|
|
6
6
|
import { invalidTotpCode, mfaNotEnabled } from "../errors";
|
|
7
7
|
import { userMfaEntity, userMfaTable } from "../schema/user-mfa";
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
9
9
|
import { parseRoles } from "@cosmicdrift/kumiko-framework/utils";
|
|
10
10
|
import { Temporal } from "temporal-polyfill";
|
|
11
|
-
import
|
|
11
|
+
import * as z from "zod";
|
|
12
12
|
import { burnToken, sessionLocaleField, sessionTimezoneField } from "../../shared";
|
|
13
13
|
import { USER_STATUS, UserQueries } from "../../user";
|
|
14
14
|
import { base32Decode } from "../base32";
|
|
@@ -63,7 +63,7 @@ export function createEnableConfirmPreauthHandler(opts: EnableConfirmPreauthOpti
|
|
|
63
63
|
setupToken: z.string().min(1),
|
|
64
64
|
code: z.string().length(6),
|
|
65
65
|
}),
|
|
66
|
-
access: { roles: ["anonymous"] },
|
|
66
|
+
access: { roles: ["anonymous"], personalData: "public-intake" },
|
|
67
67
|
rateLimit: { per: "ip+handler", limit: 20, windowSeconds: 60 },
|
|
68
68
|
escapeHatch: {
|
|
69
69
|
reason:
|
|
@@ -2,7 +2,7 @@ import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
|
|
|
2
2
|
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
4
4
|
import { Temporal } from "temporal-polyfill";
|
|
5
|
-
import
|
|
5
|
+
import * as z from "zod";
|
|
6
6
|
import { burnToken } from "../../shared";
|
|
7
7
|
import { base32Decode } from "../base32";
|
|
8
8
|
import { findUserMfaRow } from "../db/queries";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createTenantDb } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import { defineWriteHandler, type SessionUser } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
-
import
|
|
3
|
+
import * as z from "zod";
|
|
4
4
|
import { base32Encode } from "../base32";
|
|
5
5
|
import { MFA_SETUP_TOKEN_TTL_MINUTES } from "../constants";
|
|
6
6
|
import { findUserMfaRow } from "../db/queries";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
-
import
|
|
2
|
+
import * as z from "zod";
|
|
3
3
|
import { UserQueries } from "../../user";
|
|
4
4
|
import { base32Encode } from "../base32";
|
|
5
5
|
import { MFA_SETUP_TOKEN_TTL_MINUTES } from "../constants";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
4
|
-
import
|
|
4
|
+
import * as z from "zod";
|
|
5
5
|
import { findUserMfaRow } from "../db/queries";
|
|
6
6
|
import { invalidTotpCode, mfaNotEnabled } from "../errors";
|
|
7
7
|
import { generateRecoveryCodes, hashRecoveryCodes } from "../recovery-codes";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
2
|
import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
-
import
|
|
3
|
+
import * as z from "zod";
|
|
4
4
|
import { userMfaTable } from "../schema/user-mfa";
|
|
5
5
|
|
|
6
6
|
// "Is MFA enabled for me" — the one thing a settings screen needs before it
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
8
8
|
import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
9
9
|
import { parseRoles } from "@cosmicdrift/kumiko-framework/utils";
|
|
10
|
-
import
|
|
10
|
+
import * as z from "zod";
|
|
11
11
|
import { burnToken, sessionLocaleField, sessionTimezoneField } from "../../shared";
|
|
12
12
|
import { USER_STATUS, UserQueries } from "../../user";
|
|
13
13
|
import { MFA_VERIFY_LOCKOUT_MINUTES, MFA_VERIFY_MAX_ATTEMPTS } from "../constants";
|
|
@@ -45,7 +45,7 @@ export function createMfaVerifyHandler(opts: MfaVerifyOptions) {
|
|
|
45
45
|
return defineWriteHandler({
|
|
46
46
|
name: "verify",
|
|
47
47
|
schema: z.object({ challengeToken: z.string().min(1), code: z.string().min(6).max(9) }),
|
|
48
|
-
access: { roles: ["anonymous"] },
|
|
48
|
+
access: { roles: ["anonymous"], personalData: "public-intake" },
|
|
49
49
|
rateLimit: { per: "ip+handler", limit: 20, windowSeconds: 60 },
|
|
50
50
|
escapeHatch: {
|
|
51
51
|
reason:
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import type { TenantDb } from "@cosmicdrift/kumiko-framework/db";
|
|
17
17
|
import type { HandlerContext, TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
18
18
|
import { QnTypes, qn, SYSTEM_TENANT_ID, toKebab } from "@cosmicdrift/kumiko-framework/engine";
|
|
19
|
-
import
|
|
19
|
+
import * as z from "zod";
|
|
20
20
|
// kumiko-lint-ignore cross-feature-import SUBSCRIPTION_STRIPE_FEATURE is a plain string const in constants.ts (no imports); the barrel import (../subscription-stripe) would create a module cycle with billing-foundation via feature.ts → verify-webhook.ts, because billing-foundation/index.ts now re-exports billing-info-query.ts.
|
|
21
21
|
import { SUBSCRIPTION_STRIPE_FEATURE } from "../subscription-stripe/constants";
|
|
22
22
|
import { subscriptionAggregateId } from "./aggregate-id";
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// accounting-export, churn-analytics) listenen direkt auf den event-
|
|
11
11
|
// type ohne payload-discriminator.
|
|
12
12
|
|
|
13
|
-
import
|
|
13
|
+
import * as z from "zod";
|
|
14
14
|
import { BILLING_FOUNDATION_FEATURE, SubscriptionStatuses } from "./constants";
|
|
15
15
|
|
|
16
16
|
// Aggregate-type für den event-store. Eine subscription pro Tenant ist
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// resolved den Tenant.
|
|
16
16
|
|
|
17
17
|
import type { WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
18
|
-
import
|
|
18
|
+
import * as z from "zod";
|
|
19
19
|
import { SUBSCRIPTION_PROVIDER_EXTENSION } from "../constants";
|
|
20
20
|
import type { SubscriptionProviderPlugin } from "../types";
|
|
21
21
|
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
decryptPiiFieldValues,
|
|
16
16
|
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
17
17
|
import type { WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
18
|
-
import
|
|
18
|
+
import * as z from "zod";
|
|
19
19
|
import { subscriptionAggregateId } from "../aggregate-id";
|
|
20
20
|
import { SUBSCRIPTION_PROVIDER_EXTENSION } from "../constants";
|
|
21
21
|
import { SUBSCRIPTION_PII_FIELDS } from "../entities";
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
decryptPiiFieldValues,
|
|
8
8
|
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
9
9
|
import type { QueryHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
10
|
-
import
|
|
10
|
+
import * as z from "zod";
|
|
11
11
|
import { SUBSCRIPTION_PII_FIELDS } from "../entities";
|
|
12
12
|
import { subscriptionsProjectionTable } from "../projection";
|
|
13
13
|
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
encryptPiiFieldValues,
|
|
18
18
|
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
19
19
|
import type { WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
20
|
-
import
|
|
20
|
+
import * as z from "zod";
|
|
21
21
|
import { subscriptionAggregateId } from "../aggregate-id";
|
|
22
22
|
import { SubscriptionEventTypes, SubscriptionStatuses } from "../constants";
|
|
23
23
|
import { SUBSCRIPTION_PII_FIELDS, subscriptionEntity } from "../entities";
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
encryptPiiFieldValues,
|
|
16
16
|
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
17
17
|
import type { WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
18
|
-
import
|
|
18
|
+
import * as z from "zod";
|
|
19
19
|
import { paymentAggregateId } from "../aggregate-id";
|
|
20
20
|
import { PAYMENT_PII_FIELDS, paymentEntity } from "../entities";
|
|
21
21
|
import {
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
unsafeCreateEntityTable,
|
|
24
24
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
25
25
|
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
26
|
-
import
|
|
26
|
+
import * as z from "zod";
|
|
27
27
|
import { CapCounterHandlers, CapCounterQueries } from "../constants";
|
|
28
28
|
import {
|
|
29
29
|
CapExceededError,
|
|
@@ -114,6 +114,13 @@ describe("enforceCap — burstable profile (mails / tokens)", () => {
|
|
|
114
114
|
await expect(enforceCap(ctx, opts)).rejects.toThrow(CapExceededError);
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
+
test("amount checks the last of the booked units: 1190 + 10 units passes, + 11 throws", async () => {
|
|
118
|
+
const ctx = stubCalendarCtx([{ value: 1190, lastSoftWarnedAt: null }]);
|
|
119
|
+
const passing = await enforceCap(ctx, { ...opts, amount: 10 });
|
|
120
|
+
expect(passing).toEqual({ state: "soft-hit", value: 1199, crossed: true });
|
|
121
|
+
await expect(enforceCap(ctx, { ...opts, amount: 11 })).rejects.toThrow(CapExceededError);
|
|
122
|
+
});
|
|
123
|
+
|
|
117
124
|
test("CapExceededError carries cap-name + limit + currentValue", async () => {
|
|
118
125
|
const ctx = stubCalendarCtx([{ value: 1500, lastSoftWarnedAt: null }]);
|
|
119
126
|
try {
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
unsafeCreateEntityTable,
|
|
24
24
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
25
25
|
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
26
|
-
import
|
|
26
|
+
import * as z from "zod";
|
|
27
27
|
import { capCounterEntity } from "../entity";
|
|
28
28
|
import { createStockCapGuard } from "../stock-cap-guard";
|
|
29
29
|
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
unsafeCreateEntityTable,
|
|
25
25
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
26
26
|
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
27
|
-
import
|
|
27
|
+
import * as z from "zod";
|
|
28
28
|
import { bookCapUsage, readRollingCapUsage } from "../book-cap-usage";
|
|
29
29
|
import { CapCounterQueries } from "../constants";
|
|
30
30
|
import type { SoftHitNotifier } from "../enforce-cap";
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
10
10
|
import { eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
11
11
|
import { Temporal } from "temporal-polyfill";
|
|
12
|
-
import
|
|
12
|
+
import * as z from "zod";
|
|
13
13
|
import { capCounterAggregateId, rollingCapAggregateId } from "./aggregate-id";
|
|
14
14
|
import { CAP_COUNTER_ROLLING_AGGREGATE_TYPE, ROLLING_INCREMENTED_EVENT_QN } from "./constants";
|
|
15
15
|
import { capCounterEntity } from "./entity";
|