@cosmicdrift/kumiko-bundled-features 0.383.1 → 0.385.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.
Files changed (140) hide show
  1. package/dist/audit/event-classification.d.ts +8 -0
  2. package/dist/audit/event-classification.js +69 -0
  3. package/dist/audit/feature.js +3 -3
  4. package/dist/audit/handlers/audit-events.d.ts +25 -3
  5. package/dist/audit/handlers/audit-events.js +41 -7
  6. package/dist/audit/handlers/details-all-tenants.query.d.ts +3 -0
  7. package/dist/audit/handlers/details-all-tenants.query.js +2 -5
  8. package/dist/audit/handlers/details.query.d.ts +3 -0
  9. package/dist/audit/handlers/details.query.js +5 -3
  10. package/dist/audit/handlers/list-all-tenants.query.d.ts +9 -2
  11. package/dist/audit/handlers/list-all-tenants.query.js +10 -8
  12. package/dist/audit/handlers/list.query.d.ts +8 -0
  13. package/dist/audit/handlers/list.query.js +12 -4
  14. package/dist/audit/screens.d.ts +37 -0
  15. package/dist/audit/screens.js +26 -4
  16. package/dist/audit/web/audit-event-cell.d.ts +4 -0
  17. package/dist/audit/web/audit-event-cell.js +20 -0
  18. package/dist/audit/web/audit-event-labels.d.ts +7 -0
  19. package/dist/audit/web/audit-event-labels.js +23 -0
  20. package/dist/audit/web/client-plugin.js +6 -1
  21. package/dist/audit/web/constants.d.ts +8 -0
  22. package/dist/audit/web/constants.js +7 -0
  23. package/dist/audit/web/i18n.js +48 -1
  24. package/dist/auth-email-password/feature.js +3 -2
  25. package/dist/auth-email-password/handlers/login-options.query.d.ts +5 -0
  26. package/dist/auth-email-password/handlers/login-options.query.js +17 -0
  27. package/dist/auth-email-password/index.d.ts +1 -1
  28. package/dist/auth-email-password/index.js +1 -1
  29. package/dist/auth-email-password/password-policy.d.ts +1 -0
  30. package/dist/auth-email-password/password-policy.js +195 -2087
  31. package/dist/auth-email-password/web/auth-form-primitives.js +9 -12
  32. package/dist/auth-email-password/{auth-paths.d.ts → web/auth-paths.d.ts} +5 -3
  33. package/dist/auth-email-password/{auth-paths.js → web/auth-paths.js} +5 -3
  34. package/dist/auth-email-password/web/client-plugin.d.ts +5 -0
  35. package/dist/auth-email-password/web/client-plugin.js +2 -0
  36. package/dist/auth-email-password/web/constants.d.ts +1 -0
  37. package/dist/auth-email-password/web/constants.js +1 -0
  38. package/dist/auth-email-password/web/default-auth-pages-gate.d.ts +4 -0
  39. package/dist/auth-email-password/web/default-auth-pages-gate.js +28 -0
  40. package/dist/auth-email-password/web/login-screen.d.ts +13 -18
  41. package/dist/auth-email-password/web/login-screen.js +31 -19
  42. package/dist/auth-mfa/schema/user-mfa.d.ts +6 -126
  43. package/dist/billing-foundation/entities.d.ts +8 -168
  44. package/dist/billing-foundation/projection.d.ts +8 -168
  45. package/dist/cap-counter/entity.d.ts +1 -21
  46. package/dist/channel-in-app/handlers/unread-count.query.js +1 -1
  47. package/dist/compliance-profiles/schema/profile-selection.d.ts +2 -28
  48. package/dist/config/table.d.ts +6 -126
  49. package/dist/custom-fields/db/queries/projection.js +1 -1
  50. package/dist/custom-fields/entity.d.ts +4 -84
  51. package/dist/data-retention/schema/tenant-retention-override.d.ts +6 -112
  52. package/dist/delivery/tables.d.ts +6 -126
  53. package/dist/document-ingest-foundation/entity.d.ts +6 -112
  54. package/dist/file-derivatives/feature.js +1 -1
  55. package/dist/folders/entity.d.ts +8 -168
  56. package/dist/form-draft/entity.d.ts +2 -42
  57. package/dist/inbound-mail-foundation/entities.d.ts +25 -525
  58. package/dist/inbound-mail-foundation/projection.d.ts +20 -420
  59. package/dist/jobs/feature.js +22 -117
  60. package/dist/jobs/handlers/consumer.write.d.ts +37 -0
  61. package/dist/jobs/handlers/consumer.write.js +59 -0
  62. package/dist/jobs/handlers/consumers.query.d.ts +52 -0
  63. package/dist/jobs/handlers/consumers.query.js +114 -0
  64. package/dist/jobs/job-run-table.d.ts +6 -126
  65. package/dist/jobs/screens.d.ts +6 -0
  66. package/dist/jobs/screens.js +241 -0
  67. package/dist/jobs/web/constants.d.ts +20 -0
  68. package/dist/jobs/web/constants.js +21 -0
  69. package/dist/jobs/web/i18n.js +41 -0
  70. package/dist/ledger/entity.d.ts +13 -273
  71. package/dist/ledger/web/index.js +1 -1
  72. package/dist/managed-pages/table.d.ts +12 -252
  73. package/dist/notes-history/entity.d.ts +14 -280
  74. package/dist/notes-history/handlers/add-note.write.js +1 -1
  75. package/dist/personal-access-tokens/i18n.d.ts +1 -0
  76. package/dist/personal-access-tokens/i18n.js +2 -0
  77. package/dist/personal-access-tokens/index.d.ts +1 -0
  78. package/dist/personal-access-tokens/index.js +1 -0
  79. package/dist/personal-access-tokens/schema/api-token.d.ts +6 -126
  80. package/dist/secrets/i18n.d.ts +0 -3
  81. package/dist/secrets/i18n.js +0 -3
  82. package/dist/secrets/table.d.ts +1 -21
  83. package/dist/sessions/constants.d.ts +2 -1
  84. package/dist/sessions/constants.js +2 -1
  85. package/dist/sessions/handlers/detail.query.d.ts +5 -0
  86. package/dist/sessions/handlers/detail.query.js +7 -3
  87. package/dist/sessions/handlers/list.query.d.ts +5 -0
  88. package/dist/sessions/handlers/list.query.js +16 -10
  89. package/dist/sessions/handlers/mine.query.d.ts +5 -0
  90. package/dist/sessions/handlers/mine.query.js +15 -9
  91. package/dist/sessions/schema/user-session.d.ts +4 -84
  92. package/dist/sessions/screens.js +22 -8
  93. package/dist/sessions/web/client-plugin.d.ts +6 -0
  94. package/dist/sessions/web/client-plugin.js +11 -0
  95. package/dist/sessions/web/constants.d.ts +3 -0
  96. package/dist/sessions/web/constants.js +3 -0
  97. package/dist/sessions/web/default-translations.d.ts +2 -0
  98. package/dist/sessions/web/default-translations.js +3 -0
  99. package/dist/sessions/web/i18n.js +5 -1
  100. package/dist/sessions/web/index.d.ts +1 -0
  101. package/dist/sessions/web/index.js +1 -0
  102. package/dist/sessions/web/session-device-cell.d.ts +3 -0
  103. package/dist/sessions/web/session-device-cell.js +16 -0
  104. package/dist/sessions/web/user-agent.d.ts +26 -0
  105. package/dist/sessions/web/user-agent.js +58 -0
  106. package/dist/shared/run-in-sub-transaction.js +1 -1
  107. package/dist/tags/entity.d.ts +9 -189
  108. package/dist/template-resolver/feature.d.ts +1 -0
  109. package/dist/template-resolver/feature.js +2 -0
  110. package/dist/template-resolver/handlers/overview.query.d.ts +5 -0
  111. package/dist/template-resolver/handlers/overview.query.js +19 -2
  112. package/dist/template-resolver/handlers/tenant-override-counts.query.d.ts +17 -0
  113. package/dist/template-resolver/handlers/tenant-override-counts.query.js +35 -0
  114. package/dist/template-resolver/handlers/tenant-override-selection.d.ts +12 -0
  115. package/dist/template-resolver/handlers/tenant-override-selection.js +34 -0
  116. package/dist/template-resolver/handlers/tenant-overrides.query.js +3 -29
  117. package/dist/template-resolver/table.d.ts +20 -350
  118. package/dist/template-resolver/user-content-table.d.ts +12 -238
  119. package/dist/template-resolver/web/qualified-names.d.ts +1 -0
  120. package/dist/template-resolver/web/qualified-names.js +1 -0
  121. package/dist/tenant/invitation-table.d.ts +6 -126
  122. package/dist/tenant/membership-table.d.ts +4 -84
  123. package/dist/tenant/schema/tenant.d.ts +6 -126
  124. package/dist/tier-engine/entity.d.ts +1 -21
  125. package/dist/tier-engine/tier-resolver.d.ts +1 -21
  126. package/dist/user/schema/user.d.ts +12 -252
  127. package/dist/user-data-rights/feature.js +33 -33
  128. package/dist/user-data-rights/schema/download-attempt.d.ts +14 -294
  129. package/dist/user-data-rights/schema/download-token.d.ts +8 -168
  130. package/dist/user-data-rights/schema/export-job.d.ts +8 -154
  131. package/dist/user-data-rights/web/i18n.js +4 -1
  132. package/dist/waitlist/entity.d.ts +16 -322
  133. package/package.json +17 -9
  134. package/src/audit/changes.json +6 -0
  135. package/src/auth-email-password/changes.json +12 -0
  136. package/src/jobs/changes.json +6 -0
  137. package/src/personal-access-tokens/changes.json +7 -0
  138. package/src/sessions/changes.json +6 -0
  139. package/src/template-resolver/changes.json +6 -0
  140. package/src/user-data-rights/changes.json +18 -0
@@ -1,18 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // Shared Web-Primitives für die Auth-Screens. Nur noch Layout/Style-
3
- // Helpers — Form/Field/Input/Button/Banner kommen jetzt über
4
- // usePrimitives() aus dem Framework-Vertrag, damit Native dieselben
5
- // Auth-Screens rendern kann (renderer-native registriert eigene
6
- // Implementations).
2
+ // Shared web primitives for the auth screens. Only layout/style helpers are
3
+ // left here; Form/Field/Input/Button/Banner come through usePrimitives() from
4
+ // the framework contract, so apps and tests can override the implementations
5
+ // via PrimitivesProvider.
7
6
  //
8
- // <AuthCard> — Card-Wrapper für die Auth-Screen-Layouts
9
- // (full-screen, zentriert, max-w-sm). Web-only;
10
- // eine Native-Variante landet bei Bedarf
11
- // daneben (z.B. SafeArea + ScrollView).
12
- // parseUrlToken — URL-Param-Helper (window.location.search).
7
+ // <AuthCard> — card wrapper for the auth screen layouts
8
+ // (full-screen, centered, max-w-sm). Web-only.
9
+ // parseUrlToken — URL param helper (window.location.search).
13
10
  //
14
- // Link-Styles laufen über das Link-Primitive (variant="button"/"muted") —
15
- // die früheren authButtonClass/authMutedLinkClass sind dorthin gewandert.
11
+ // Link styles go through the Link primitive (variant="button"/"muted"); the
12
+ // former authButtonClass/authMutedLinkClass moved there.
16
13
  import { usePrimitives } from "@cosmicdrift/kumiko-renderer";
17
14
  import { BareFormProvider, cn } from "@cosmicdrift/kumiko-renderer/web";
18
15
  import { createContext, useContext, useEffect, useState } from "react";
@@ -1,19 +1,21 @@
1
1
  /** A plain path, or a function picking the path per resolved mail locale
2
2
  * (e.g. `/en/reset-password`, `/de/reset-password`). */
3
3
  export type AuthPath = string | ((locale: string) => string);
4
- /** Pfad-Konstanten der 4 Auth-Seiten (relativ zur App-baseUrl). */
5
4
  export type AuthPaths = {
6
5
  readonly resetPassword: AuthPath;
7
6
  readonly verifyEmail: AuthPath;
8
7
  readonly signupComplete: AuthPath;
9
8
  readonly inviteAccept: AuthPath;
9
+ readonly unlockAccount: AuthPath;
10
10
  };
11
- /** Konventions-Pfade — alle Kumiko-Apps nutzen dieselben. Apps überschreiben
12
- * nur die Ausnahme via `makeAuthPaths({ ... })`. */
11
+ /** Shared by every Kumiko app; apps override only the exceptions via `makeAuthPaths({ ... })`. */
13
12
  export declare const DEFAULT_AUTH_PATHS: {
14
13
  readonly resetPassword: "/reset-password";
15
14
  readonly verifyEmail: "/verify-email";
16
15
  readonly signupComplete: "/signup/complete";
17
16
  readonly inviteAccept: "/invite/accept";
17
+ readonly unlockAccount: "/unlock-account";
18
18
  };
19
+ /** Client page that requests a reset mail; no mail links to it, so it is not part of AuthPaths. */
20
+ export declare const DEFAULT_FORGOT_PASSWORD_PATH = "/forgot-password";
19
21
  export declare function makeAuthPaths(overrides?: Partial<AuthPaths>): AuthPaths;
@@ -1,14 +1,16 @@
1
- // Convention paths for the four auth pages (relative to the app base URL).
1
+ // Convention paths for the five auth pages (relative to the app base URL).
2
2
  // resolveAuthMail builds each flow's magic-link appUrl from baseUrl + these;
3
3
  // the handlers append `?token=…` and mail via delivery (ctx.notify).
4
- /** Konventions-Pfade — alle Kumiko-Apps nutzen dieselben. Apps überschreiben
5
- * nur die Ausnahme via `makeAuthPaths({ ... })`. */
4
+ /** Shared by every Kumiko app; apps override only the exceptions via `makeAuthPaths({ ... })`. */
6
5
  export const DEFAULT_AUTH_PATHS = {
7
6
  resetPassword: "/reset-password",
8
7
  verifyEmail: "/verify-email",
9
8
  signupComplete: "/signup/complete",
10
9
  inviteAccept: "/invite/accept",
10
+ unlockAccount: "/unlock-account",
11
11
  };
12
+ /** Client page that requests a reset mail; no mail links to it, so it is not part of AuthPaths. */
13
+ export const DEFAULT_FORGOT_PASSWORD_PATH = "/forgot-password";
12
14
  export function makeAuthPaths(overrides = {}) {
13
15
  return { ...DEFAULT_AUTH_PATHS, ...overrides };
14
16
  }
@@ -31,6 +31,11 @@ export type EmailPasswordClientOptions = {
31
31
  * Für Branding ("Sign in" → "Login to Acme") oder weitere Sprachen
32
32
  * (`fr`, `es`, …) zusätzlich. */
33
33
  readonly translations?: TranslationsByLocale;
34
+ /** Serve the forgot-password, reset-password and unlock-account pages at
35
+ * their convention paths for signed-out visitors. Apps with their own
36
+ * screens at those paths either register an earlier gate (it wins) or
37
+ * set this to false. Default: true. */
38
+ readonly defaultAuthPages?: boolean;
34
39
  };
35
40
  export type EmailPasswordClientFeature = {
36
41
  readonly name: "auth-email-password";
@@ -5,6 +5,7 @@
5
5
  // Strings pro Locale, pro Key.
6
6
  import { makeSessionAuthGate, } from "./auth-gate.js";
7
7
  import { emailPasswordClientActions } from "./client-actions.js";
8
+ import { DefaultAuthPagesGate } from "./default-auth-pages-gate.js";
8
9
  import { defaultTranslations, mergeTranslations } from "./i18n.js";
9
10
  export function emailPasswordClient(options = {}) {
10
11
  const translations = mergeTranslations(defaultTranslations, options.translations ?? {});
@@ -12,6 +13,7 @@ export function emailPasswordClient(options = {}) {
12
13
  name: "auth-email-password",
13
14
  providers: [],
14
15
  gates: [
16
+ ...(options.defaultAuthPages === false ? [] : [DefaultAuthPagesGate]),
15
17
  makeSessionAuthGate({
16
18
  loginScreen: options.loginScreen,
17
19
  loginScreenProps: options.loginScreenProps,
@@ -20,6 +20,7 @@ export declare const AuthHandlers: {
20
20
  readonly switchTenantMfaGate: "auth-email-password:write:switch-tenant-mfa-gate";
21
21
  };
22
22
  export declare const AuthQueries: {
23
+ readonly loginOptions: "auth-email-password:query:login-options";
23
24
  readonly signupRegistrationStatus: "auth-email-password:query:signup-registration-status";
24
25
  readonly inviteInfo: "auth-email-password:query:invite-info";
25
26
  };
@@ -45,6 +45,7 @@ export const AuthHandlers = {
45
45
  // Qualified query names. Anonymous-readable status so the (unauthenticated)
46
46
  // signup page can decide whether to show its own link/form.
47
47
  export const AuthQueries = {
48
+ loginOptions: "auth-email-password:query:login-options",
48
49
  signupRegistrationStatus: "auth-email-password:query:signup-registration-status",
49
50
  // Anonymous, read-only invite lookup for the invite-acceptance page —
50
51
  // see invite-info.query.ts. Does not consume the token.
@@ -0,0 +1,4 @@
1
+ import type { ReactNode } from "react";
2
+ export declare function DefaultAuthPagesGate({ children }: {
3
+ readonly children: ReactNode;
4
+ }): ReactNode;
@@ -0,0 +1,28 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { DEFAULT_AUTH_PATHS, DEFAULT_FORGOT_PASSWORD_PATH } from "./auth-paths.js";
3
+ import { ConfirmAccountUnlockScreen } from "./confirm-account-unlock-screen.js";
4
+ import { ForgotPasswordScreen } from "./forgot-password-screen.js";
5
+ import { RequestAccountUnlockScreen } from "./request-account-unlock-screen.js";
6
+ import { ResetPasswordScreen } from "./reset-password-screen.js";
7
+ function hasToken(search) {
8
+ return (new URLSearchParams(search).get("token") ?? "") !== "";
9
+ }
10
+ function pickDefaultAuthPage(pathname, search) {
11
+ const path = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;
12
+ switch (path) {
13
+ case DEFAULT_FORGOT_PASSWORD_PATH:
14
+ return _jsx(ForgotPasswordScreen, {});
15
+ case DEFAULT_AUTH_PATHS.resetPassword:
16
+ return hasToken(search) ? _jsx(ResetPasswordScreen, {}) : null;
17
+ case DEFAULT_AUTH_PATHS.unlockAccount:
18
+ return hasToken(search) ? _jsx(ConfirmAccountUnlockScreen, {}) : _jsx(RequestAccountUnlockScreen, {});
19
+ default:
20
+ return null;
21
+ }
22
+ }
23
+ // Sits inside emailPasswordClient's gate list, so app gates registered earlier
24
+ // (outer) still win on the same path. The pages are public: no session needed.
25
+ export function DefaultAuthPagesGate({ children }) {
26
+ const { pathname, search } = window.location;
27
+ return pickDefaultAuthPage(pathname, search) ?? _jsx(_Fragment, { children: children });
28
+ }
@@ -4,32 +4,27 @@ export type AuthLegalLink = {
4
4
  readonly href: string;
5
5
  };
6
6
  export type LoginScreenProps = {
7
- /** Overridet den `auth.login.title`-i18n-Key. Nur setzen wenn der
8
- * Titel stark app-branded ist und keine Translation braucht. */
7
+ /** Overrides the `auth.login.title` key; only for a branded title that needs no translation. */
9
8
  readonly title?: string;
10
9
  readonly subtitle?: ReactNode;
11
10
  readonly submitLabel?: string;
12
- /** Optional href zum ForgotPasswordScreen. Wenn gesetzt rendert die
13
- * LoginScreen unter dem Submit-Button einen "Passwort vergessen?"-
14
- * Link. Apps die den Reset-Flow NICHT anbieten (z.B. nur Magic-Link),
15
- * setzen das einfach nicht — Login bleibt minimalistisch. */
16
- readonly forgotPasswordHref?: string;
11
+ /** Href of the "Forgot password?" link under the submit button. Unset: the
12
+ * default path once the server offers the reset flow (login-options);
13
+ * `null` hides the link deliberately. */
14
+ readonly forgotPasswordHref?: string | null;
17
15
  /** Optional href to RequestAccountUnlockScreen (#1266). When set, an
18
16
  * account_locked login error renders an "Unlock account?" link inline —
19
17
  * the account-lockout escape hatch, since forgotPasswordHref does NOT
20
18
  * help here: the lockout gate blocks login before password verify even
21
19
  * runs, so it also blocks the post-reset login. Apps without
22
- * accountUnlock configured just leave this unset. */
23
- readonly unlockAccountHref?: string;
24
- /** Optional href zum SignupScreen. Wenn gesetzt rendert die LoginScreen
25
- * einen "Account erstellen"-Link. Apps die kein Self-Signup wollen
26
- * (closed-invite-only) setzen das einfach nicht. */
20
+ * accountUnlock configured just leave this unset. Unset: default path once
21
+ * the server offers the flow (login-options); `null` = deliberately no link. */
22
+ readonly unlockAccountHref?: string | null;
23
+ /** Href of the "Create account" link; invite-only apps leave it unset. */
27
24
  readonly signupHref?: string;
28
- /** Impressum/Datenschutz unterhalb der Card — der Login-Screen ist
29
- * oft die einzige öffentliche Seite einer Admin-Domain und braucht
30
- * dann selbst erreichbare Legal-Links (DE: Impressumspflicht).
31
- * Labels kommen vom Caller (typisch schon übersetzt bzw. Eigennamen
32
- * wie "Impressum"). */
25
+ /** Imprint/privacy links under the card: the login screen is often the only
26
+ * public page of an admin domain, which then must reach its legal pages
27
+ * itself (German imprint duty). Labels come translated from the caller. */
33
28
  readonly legalLinks?: readonly AuthLegalLink[];
34
29
  /** Called when the server responds with an MFA challenge instead of a
35
30
  * session. Apps typically swap this screen out for auth-mfa's
@@ -46,4 +41,4 @@ export type LoginScreenProps = {
46
41
  * error. */
47
42
  readonly onMfaSetupRequired?: (preauthSetupToken: string, accountLabel: string) => void;
48
43
  };
49
- export declare function LoginScreen({ title, subtitle, submitLabel, forgotPasswordHref, unlockAccountHref, signupHref, legalLinks, onMfaChallenge, onMfaSetupRequired, }: LoginScreenProps): ReactNode;
44
+ export declare function LoginScreen(props: LoginScreenProps): ReactNode;
@@ -1,18 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // Default-LoginScreen. Reine E-Mail-+-Passwort-Form, zentriert, Card-
3
- // Layout. Texte kommen aus `useTranslation()` — die Default-Bundles
4
- // (de+en) liefert das Feature selber mit (translations.ts); Apps die
5
- // was anderes wollen, setzen entweder einen eigenen LocaleResolver auf
6
- // App-Level oder reichen Key-Overrides an `emailPasswordClient()`.
7
- //
8
- // Die Reason-Codes aus dem Login-Handler werden 1:1 auf i18n-Keys
9
- // gemappt (reasonToI18nKey) — neue Reason-Codes brauchen nur eine
10
- // neue Zeile im Bundle + hier im Mapping.
11
- import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
2
+ // Login handler reason codes map 1:1 to i18n keys (reasonToI18nKey): a new
3
+ // reason code needs a bundle entry plus a line in that mapping.
4
+ import { useOptionalDispatcher, usePrimitives, useQuery, useTranslation, } from "@cosmicdrift/kumiko-renderer";
12
5
  import { useState } from "react";
13
6
  import { requestEmailVerification, } from "./auth-client.js";
14
7
  import { AuthCard } from "./auth-form-primitives.js";
8
+ import { DEFAULT_AUTH_PATHS, DEFAULT_FORGOT_PASSWORD_PATH } from "./auth-paths.js";
15
9
  import { followNextOrLandingAfterLogin } from "./auth-redirect.js";
10
+ import { AuthQueries } from "./constants.js";
16
11
  import { useSession } from "./session.js";
17
12
  // Map vom Reason-Code des Login-Handlers auf einen i18n-Key plus
18
13
  // optional extrahierte Interpolations-Parameter. Ungekannte Codes
@@ -45,7 +40,7 @@ function reasonToKey(failure) {
45
40
  return { key: "auth.errors.loginFailed" };
46
41
  }
47
42
  }
48
- export function LoginScreen({ title, subtitle, submitLabel, forgotPasswordHref, unlockAccountHref, signupHref, legalLinks, onMfaChallenge, onMfaSetupRequired, }) {
43
+ function LoginScreenBody({ title, subtitle, submitLabel, forgotPasswordHref, unlockAccountHref, signupHref, legalLinks, onMfaChallenge, onMfaSetupRequired, loginOptions, }) {
49
44
  const t = useTranslation();
50
45
  const { Form, Field, Input, Button, Banner, Link } = usePrimitives();
51
46
  const session = useSession();
@@ -54,10 +49,19 @@ export function LoginScreen({ title, subtitle, submitLabel, forgotPasswordHref,
54
49
  const [submitting, setSubmitting] = useState(false);
55
50
  const [error, setError] = useState(null);
56
51
  const [resendStatus, setResendStatus] = useState({ kind: "idle" });
57
- // Tracked, damit der Resend-Button verschwindet sobald der User die
58
- // Email-Eingabe ändert — sonst würde Resend silent an die geänderte
59
- // (potentiell typoed) Adresse gehen ohne User-Feedback.
52
+ // Hides the resend button once the email input changes; otherwise resend
53
+ // would silently go to the edited, possibly mistyped address.
60
54
  const [failedLoginEmail, setFailedLoginEmail] = useState(null);
55
+ const effectiveForgotHref = forgotPasswordHref === undefined
56
+ ? loginOptions?.passwordReset === true
57
+ ? DEFAULT_FORGOT_PASSWORD_PATH
58
+ : null
59
+ : forgotPasswordHref;
60
+ const effectiveUnlockHref = unlockAccountHref === undefined
61
+ ? loginOptions?.accountUnlock === true
62
+ ? DEFAULT_AUTH_PATHS.unlockAccount
63
+ : null
64
+ : unlockAccountHref;
61
65
  const doSubmit = async () => {
62
66
  setSubmitting(true);
63
67
  setError(null);
@@ -93,10 +97,8 @@ export function LoginScreen({ title, subtitle, submitLabel, forgotPasswordHref,
93
97
  e?.preventDefault();
94
98
  void doSubmit();
95
99
  };
96
- // Resend-Bestätigungsmail bei reason=email_not_verified. requestEmail-
97
- // Verification ist silent-success (200 auch wenn kein User existiert),
98
- // sodass kein anti-enumeration-Branching nötig ist; 429 → rate-limit-
99
- // Hint inline, sonstige Fehler → generischer Inline-Hint.
100
+ // requestEmailVerification answers 200 even for unknown users, so no
101
+ // anti-enumeration branching is needed; only 429 gets its own hint.
100
102
  const onResend = async () => {
101
103
  setResendStatus({ kind: "sending" });
102
104
  try {
@@ -122,5 +124,15 @@ export function LoginScreen({ title, subtitle, submitLabel, forgotPasswordHref,
122
124
  email.trim().length > 0 &&
123
125
  email === failedLoginEmail && (_jsx("span", { className: "self-start", children: _jsx(Button, { variant: "link", onClick: () => void onResend(), disabled: resendStatus.kind === "sending", children: resendStatus.kind === "sending"
124
126
  ? t("auth.login.submitting")
125
- : t("auth.login.resendVerification") }) })), error.reason === "account_locked" && unlockAccountHref !== undefined && (_jsx("span", { className: "self-start", children: _jsx(Link, { href: unlockAccountHref, variant: "default", children: t("auth.login.unlockAccount") }) })), resendStatus.kind === "rateLimited" && (_jsx("span", { className: "text-xs", children: t("auth.login.resendRateLimited") })), resendStatus.kind === "error" && (_jsx("span", { className: "text-xs", children: t("auth.login.resendError") }))] }) })) : null, _jsx(Button, { type: "submit", loading: submitting, disabled: submitting, children: submitting ? t("auth.login.submitting") : effectiveSubmit })] }), forgotPasswordHref !== undefined && (_jsx(Link, { href: forgotPasswordHref, variant: "muted", className: "self-center", children: t("auth.login.forgotPassword") })), signupHref !== undefined && (_jsx(Link, { href: signupHref, variant: "muted", className: "self-center", children: t("auth.signup.title") })), legalLinks !== undefined && legalLinks.length > 0 && (_jsx("nav", { "data-testid": "login-legal-links", className: "flex items-center justify-center gap-3 pt-2 border-t border-border/50", children: legalLinks.map((link) => (_jsx(Link, { href: link.href, variant: "muted", className: "text-xs", children: link.label }, link.href))) }))] }));
127
+ : t("auth.login.resendVerification") }) })), error.reason === "account_locked" && effectiveUnlockHref !== null && (_jsx("span", { className: "self-start", children: _jsx(Link, { href: effectiveUnlockHref, variant: "default", children: t("auth.login.unlockAccount") }) })), resendStatus.kind === "rateLimited" && (_jsx("span", { className: "text-xs", children: t("auth.login.resendRateLimited") })), resendStatus.kind === "error" && (_jsx("span", { className: "text-xs", children: t("auth.login.resendError") }))] }) })) : null, _jsx(Button, { type: "submit", loading: submitting, disabled: submitting, children: submitting ? t("auth.login.submitting") : effectiveSubmit })] }), effectiveForgotHref !== null && (_jsx(Link, { href: effectiveForgotHref, variant: "muted", className: "self-center", children: t("auth.login.forgotPassword") })), signupHref !== undefined && (_jsx(Link, { href: signupHref, variant: "muted", className: "self-center", children: t("auth.signup.title") })), legalLinks !== undefined && legalLinks.length > 0 && (_jsx("nav", { "data-testid": "login-legal-links", className: "flex items-center justify-center gap-3 pt-2 border-t border-border/50", children: legalLinks.map((link) => (_jsx(Link, { href: link.href, variant: "muted", className: "text-xs", children: link.label }, link.href))) }))] }));
128
+ }
129
+ function LoginScreenWithQueriedOptions(props) {
130
+ const { data } = useQuery(AuthQueries.loginOptions, {}, { enabled: props.forgotPasswordHref === undefined || props.unlockAccountHref === undefined });
131
+ return _jsx(LoginScreenBody, { ...props, loginOptions: data });
132
+ }
133
+ // Without a mounted dispatcher (standalone mounts, tests) the query is skipped
134
+ // and only explicit hrefs render.
135
+ export function LoginScreen(props) {
136
+ const dispatcher = useOptionalDispatcher();
137
+ return dispatcher === undefined ? (_jsx(LoginScreenBody, { ...props, loginOptions: null })) : (_jsx(LoginScreenWithQueriedOptions, { ...props }));
126
138
  }
@@ -1,71 +1,11 @@
1
1
  export declare const userMfaEntity: import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
2
- readonly userId: {
3
- readonly type: "text";
4
- readonly description?: string;
5
- readonly maxLength?: number;
6
- readonly minLength?: number;
7
- readonly autoComplete?: string;
8
- readonly required?: boolean;
9
- readonly searchable?: boolean;
10
- readonly sortable?: boolean;
11
- readonly filterable?: boolean;
12
- readonly encrypted?: boolean;
13
- readonly sensitive?: boolean;
14
- readonly writeOnly?: boolean;
15
- readonly readAsInstruction?: boolean;
16
- readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
17
- readonly default?: string;
18
- readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
19
- readonly multiline?: boolean | {
20
- readonly rows?: number;
21
- };
22
- } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
2
+ readonly userId: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
23
3
  required: true;
24
4
  };
25
- readonly totpSecret: {
26
- readonly type: "text";
27
- readonly description?: string;
28
- readonly maxLength?: number;
29
- readonly minLength?: number;
30
- readonly autoComplete?: string;
31
- readonly required?: boolean;
32
- readonly searchable?: boolean;
33
- readonly sortable?: boolean;
34
- readonly filterable?: boolean;
35
- readonly encrypted?: boolean;
36
- readonly sensitive?: boolean;
37
- readonly writeOnly?: boolean;
38
- readonly readAsInstruction?: boolean;
39
- readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
40
- readonly default?: string;
41
- readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
42
- readonly multiline?: boolean | {
43
- readonly rows?: number;
44
- };
45
- } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
5
+ readonly totpSecret: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
46
6
  required: true;
47
7
  };
48
- readonly recoveryCodes: {
49
- readonly type: "text";
50
- readonly description?: string;
51
- readonly maxLength?: number;
52
- readonly minLength?: number;
53
- readonly autoComplete?: string;
54
- readonly required?: boolean;
55
- readonly searchable?: boolean;
56
- readonly sortable?: boolean;
57
- readonly filterable?: boolean;
58
- readonly encrypted?: boolean;
59
- readonly sensitive?: boolean;
60
- readonly writeOnly?: boolean;
61
- readonly readAsInstruction?: boolean;
62
- readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
63
- readonly default?: string;
64
- readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
65
- readonly multiline?: boolean | {
66
- readonly rows?: number;
67
- };
68
- } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
8
+ readonly recoveryCodes: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
69
9
  required: true;
70
10
  };
71
11
  readonly enabledAt: {
@@ -100,73 +40,13 @@ export declare const userMfaEntity: import("@cosmicdrift/kumiko-types/fields").E
100
40
  };
101
41
  }, "tenant">;
102
42
  export declare const userMfaTable: import("@cosmicdrift/kumiko-framework/db").EntityTable<import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
103
- readonly userId: {
104
- readonly type: "text";
105
- readonly description?: string;
106
- readonly maxLength?: number;
107
- readonly minLength?: number;
108
- readonly autoComplete?: string;
109
- readonly required?: boolean;
110
- readonly searchable?: boolean;
111
- readonly sortable?: boolean;
112
- readonly filterable?: boolean;
113
- readonly encrypted?: boolean;
114
- readonly sensitive?: boolean;
115
- readonly writeOnly?: boolean;
116
- readonly readAsInstruction?: boolean;
117
- readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
118
- readonly default?: string;
119
- readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
120
- readonly multiline?: boolean | {
121
- readonly rows?: number;
122
- };
123
- } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
43
+ readonly userId: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
124
44
  required: true;
125
45
  };
126
- readonly totpSecret: {
127
- readonly type: "text";
128
- readonly description?: string;
129
- readonly maxLength?: number;
130
- readonly minLength?: number;
131
- readonly autoComplete?: string;
132
- readonly required?: boolean;
133
- readonly searchable?: boolean;
134
- readonly sortable?: boolean;
135
- readonly filterable?: boolean;
136
- readonly encrypted?: boolean;
137
- readonly sensitive?: boolean;
138
- readonly writeOnly?: boolean;
139
- readonly readAsInstruction?: boolean;
140
- readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
141
- readonly default?: string;
142
- readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
143
- readonly multiline?: boolean | {
144
- readonly rows?: number;
145
- };
146
- } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
46
+ readonly totpSecret: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
147
47
  required: true;
148
48
  };
149
- readonly recoveryCodes: {
150
- readonly type: "text";
151
- readonly description?: string;
152
- readonly maxLength?: number;
153
- readonly minLength?: number;
154
- readonly autoComplete?: string;
155
- readonly required?: boolean;
156
- readonly searchable?: boolean;
157
- readonly sortable?: boolean;
158
- readonly filterable?: boolean;
159
- readonly encrypted?: boolean;
160
- readonly sensitive?: boolean;
161
- readonly writeOnly?: boolean;
162
- readonly readAsInstruction?: boolean;
163
- readonly format?: "email" | "url" | "phone" | "password" | "month" | "iban";
164
- readonly default?: string;
165
- readonly access?: import("@cosmicdrift/kumiko-types/fields").FieldAccess;
166
- readonly multiline?: boolean | {
167
- readonly rows?: number;
168
- };
169
- } & import("@cosmicdrift/kumiko-types/fields").ResolvedPiiFlags & {
49
+ readonly recoveryCodes: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
170
50
  required: true;
171
51
  };
172
52
  readonly enabledAt: {