@cosmicdrift/kumiko-samples 0.340.0 → 0.342.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/packages/bundled-features/package.json +6 -1
- package/packages/bundled-features/src/audit/constants.ts +5 -0
- package/packages/bundled-features/src/audit/feature.ts +47 -6
- package/packages/bundled-features/src/audit/run-escape-hatch-retention.ts +53 -0
- package/packages/bundled-features/src/auth-email-password/bootstrap.ts +325 -0
- package/packages/bundled-features/src/auth-email-password/changes.json +30 -0
- package/packages/bundled-features/src/auth-email-password/constants.ts +5 -0
- package/packages/bundled-features/src/auth-email-password/errors.ts +9 -0
- package/packages/bundled-features/src/auth-email-password/feature.ts +2 -0
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +13 -1
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +12 -0
- package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +210 -113
- package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +6 -1
- package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/index.ts +13 -0
- package/packages/bundled-features/src/auth-email-password/invite-token-store.ts +6 -0
- package/packages/bundled-features/src/auth-email-password/magic-link-mail.ts +1 -0
- package/packages/bundled-features/src/auth-email-password/web/auth-form-primitives.tsx +24 -6
- package/packages/bundled-features/src/auth-email-password/web/confirm-account-unlock-screen.tsx +12 -18
- package/packages/bundled-features/src/auth-email-password/web/forgot-password-screen.tsx +4 -4
- package/packages/bundled-features/src/auth-email-password/web/invite-accept-screen.tsx +65 -69
- package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +93 -95
- package/packages/bundled-features/src/auth-email-password/web/request-account-unlock-screen.tsx +4 -4
- package/packages/bundled-features/src/auth-email-password/web/reset-password-screen.tsx +8 -10
- package/packages/bundled-features/src/auth-email-password/web/session-bootstrap-error.tsx +13 -15
- package/packages/bundled-features/src/auth-email-password/web/signup-complete-screen.tsx +43 -49
- package/packages/bundled-features/src/auth-email-password/web/signup-screen.tsx +4 -4
- package/packages/bundled-features/src/auth-email-password/web/verify-email-screen.tsx +12 -18
- package/packages/bundled-features/src/auth-mfa/changes.json +6 -0
- package/packages/bundled-features/src/auth-mfa/config.ts +12 -3
- package/packages/bundled-features/src/auth-mfa/feature.ts +7 -2
- package/packages/bundled-features/src/auth-mfa/mfa-status-checker.ts +2 -1
- package/packages/bundled-features/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +83 -85
- package/packages/bundled-features/src/auth-mfa/web/mfa-verify-screen.tsx +24 -26
- package/packages/bundled-features/src/billing-foundation/changes.json +6 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +1 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +1 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-notify.ts +1 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-pages.ts +83 -34
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-texts.ts +4 -1
- package/packages/bundled-features/src/channel-discord/discord-channel.ts +5 -0
- package/packages/bundled-features/src/channel-discord/feature.ts +16 -1
- package/packages/bundled-features/src/channel-discord/index.ts +7 -1
- package/packages/bundled-features/src/channel-email/changes.json +6 -0
- package/packages/bundled-features/src/channel-email/email-channel.ts +9 -1
- package/packages/bundled-features/src/channel-email/pii-guard.ts +6 -5
- package/packages/bundled-features/src/channel-email/smtp-transport.ts +27 -1
- package/packages/bundled-features/src/channel-email/types.ts +4 -0
- package/packages/bundled-features/src/channel-slack/feature.ts +12 -1
- package/packages/bundled-features/src/channel-slack/index.ts +6 -1
- package/packages/bundled-features/src/channel-slack/slack-channel.ts +5 -0
- package/packages/bundled-features/src/channel-teams/feature.ts +12 -1
- package/packages/bundled-features/src/channel-teams/index.ts +6 -1
- package/packages/bundled-features/src/channel-teams/teams-channel.ts +5 -0
- package/packages/bundled-features/src/channel-telegram/changes.json +9 -0
- package/packages/bundled-features/src/channel-telegram/feature.ts +8 -2
- package/packages/bundled-features/src/channel-telegram/index.ts +8 -1
- package/packages/bundled-features/src/channel-telegram/telegram-channel.ts +39 -11
- package/packages/bundled-features/src/delivery/changes.json +7 -0
- package/packages/bundled-features/src/delivery/chat-webhook-channel.ts +18 -5
- package/packages/bundled-features/src/delivery/chat-webhook-sender.ts +19 -10
- package/packages/bundled-features/src/delivery/delivery-service.ts +78 -32
- package/packages/bundled-features/src/delivery/index.ts +4 -0
- package/packages/bundled-features/src/delivery/jobs.ts +3 -1
- package/packages/bundled-features/src/delivery/types.ts +10 -4
- package/packages/bundled-features/src/files/changes.json +13 -1
- package/packages/bundled-features/src/legal-pages/README.md +26 -3
- package/packages/bundled-features/src/legal-pages/feature.ts +27 -85
- package/packages/bundled-features/src/legal-pages/lib/boot-check.ts +57 -0
- package/packages/bundled-features/src/legal-pages/lib/validate-routes.ts +21 -0
- package/packages/bundled-features/src/page-render/index.ts +2 -0
- package/packages/bundled-features/src/page-render/layout.ts +17 -0
- package/packages/bundled-features/src/page-render/trailing-slash.ts +15 -0
- package/packages/bundled-features/src/renderer-simple/changes.json +6 -0
- package/packages/bundled-features/src/renderer-simple/feature.ts +1 -0
- package/packages/bundled-features/src/renderer-simple/index.ts +6 -1
- package/packages/bundled-features/src/renderer-simple/simple-renderer.ts +103 -19
- package/packages/bundled-features/src/secrets/announce-secret-change.ts +30 -0
- package/packages/bundled-features/src/secrets/changes.json +12 -0
- package/packages/bundled-features/src/secrets/feature.ts +1 -1
- package/packages/bundled-features/src/secrets/handlers/delete.write.ts +2 -0
- package/packages/bundled-features/src/secrets/handlers/set.write.ts +5 -1
- package/packages/bundled-features/src/secrets/i18n.ts +4 -0
- package/packages/bundled-features/src/secrets/secrets-context.ts +9 -0
- package/packages/bundled-features/src/secrets/write-gate.ts +41 -0
- package/packages/bundled-features/src/tenant/changes.json +6 -0
- package/packages/bundled-features/src/tenant/constants.ts +1 -0
- package/packages/bundled-features/src/tenant/handlers/add-member.write.ts +27 -3
- package/packages/bundled-features/src/tenant/handlers/invitations.query.ts +15 -1
- package/packages/bundled-features/src/tenant/index.ts +1 -0
- package/packages/bundled-features/src/tenant/invitation-table.ts +10 -0
- package/packages/bundled-features/src/tenant-lifecycle/lifecycle-gate.ts +2 -0
- package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +2 -2
- package/packages/bundled-features/src/tier-engine/feature.ts +92 -14
- package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +18 -13
- package/packages/bundled-features/src/user/invited-global-roles.ts +58 -0
- package/packages/bundled-features/src/user-data-rights/feature.ts +3 -0
- package/packages/bundled-features/src/waitlist/changes.json +23 -0
- package/packages/bundled-features/src/waitlist/constants.ts +44 -0
- package/packages/bundled-features/src/waitlist/entity.ts +76 -0
- package/packages/bundled-features/src/waitlist/feature.ts +50 -0
- package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +175 -0
- package/packages/bundled-features/src/waitlist/handlers/list.query.ts +12 -0
- package/packages/bundled-features/src/waitlist/handlers/reject.write.ts +120 -0
- package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +163 -0
- package/packages/bundled-features/src/waitlist/i18n.ts +33 -0
- package/packages/bundled-features/src/waitlist/index.ts +15 -0
- package/packages/bundled-features/src/waitlist/lib.ts +36 -0
- package/packages/bundled-features/src/waitlist/mail.ts +114 -0
- package/packages/bundled-features/src/waitlist/options.ts +42 -0
- package/packages/bundled-features/src/waitlist/payloads.ts +55 -0
- package/packages/bundled-features/src/waitlist/screens.ts +43 -0
- package/packages/bundled-features/src/waitlist/user-data-hooks.ts +82 -0
- package/samples/apps/use-all-bundled/feature-manifest.json +63 -7
- package/samples/apps/use-all-bundled/kumiko/migrations/0033_waitlist.rebuild.json +4 -0
- package/samples/apps/use-all-bundled/schema/generate.ts +5 -0
- package/samples/apps/use-all-bundled/schema/schema.generated.ts +3 -0
- package/samples/apps/use-all-bundled/src/run-config.ts +2 -0
- package/samples/recipes/chat-channels/README.md +2 -0
- package/samples/recipes/managed-config/README.md +1 -1
- package/samples/recipes/rate-limiting/README.md +21 -0
|
@@ -50,12 +50,10 @@ export function VerifyEmailScreen({
|
|
|
50
50
|
if (status === "missing-token") {
|
|
51
51
|
return (
|
|
52
52
|
<AuthCard title={title ?? t("auth.verifyEmail.errorTitle")}>
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
</Link>
|
|
58
|
-
</div>
|
|
53
|
+
<p className="text-sm text-muted-foreground">{t("auth.verifyEmail.missingToken")}</p>
|
|
54
|
+
<Link href={loginHref} variant="muted">
|
|
55
|
+
{t("auth.verifyEmail.goToLogin")}
|
|
56
|
+
</Link>
|
|
59
57
|
</AuthCard>
|
|
60
58
|
);
|
|
61
59
|
}
|
|
@@ -75,12 +73,10 @@ export function VerifyEmailScreen({
|
|
|
75
73
|
if (status === "success") {
|
|
76
74
|
return (
|
|
77
75
|
<AuthCard title={title ?? t("auth.verifyEmail.successTitle")}>
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
</Link>
|
|
83
|
-
</div>
|
|
76
|
+
<p className="text-sm text-muted-foreground">{t("auth.verifyEmail.successBody")}</p>
|
|
77
|
+
<Link href={loginHref} variant="button">
|
|
78
|
+
{t("auth.verifyEmail.goToLogin")}
|
|
79
|
+
</Link>
|
|
84
80
|
</AuthCard>
|
|
85
81
|
);
|
|
86
82
|
}
|
|
@@ -88,12 +84,10 @@ export function VerifyEmailScreen({
|
|
|
88
84
|
// status === "error"
|
|
89
85
|
return (
|
|
90
86
|
<AuthCard title={title ?? t("auth.verifyEmail.errorTitle")}>
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
</Link>
|
|
96
|
-
</div>
|
|
87
|
+
<p className="text-sm text-muted-foreground">{t("auth.verifyEmail.errorBody")}</p>
|
|
88
|
+
<Link href={loginHref} variant="muted">
|
|
89
|
+
{t("auth.verifyEmail.goToLogin")}
|
|
90
|
+
</Link>
|
|
97
91
|
</AuthCard>
|
|
98
92
|
);
|
|
99
93
|
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.341.0",
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"title": "MFA required policy is writable by the system only",
|
|
6
|
+
"migration": "Tenant admins can no longer write auth-mfa:config:required (write access is now system-only). Set the app-wide default with createAuthMfaFeature({ requiredPolicy }) / the mfa options and per-tenant overrides with runBootstrap tenants[].config or a system write (createSystemUser). Code that wrote the key as TenantAdmin/Admin/SystemAdmin over HTTP must switch to one of these."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.335.0",
|
|
4
10
|
"type": "fix",
|
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
// UI). Flipping this away from "optional" before PR3 locks out every
|
|
9
9
|
// unenrolled matching user with no recovery path. Fine for a tenant that
|
|
10
10
|
// enrolls its admins out-of-band first; a footgun otherwise.
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
access,
|
|
13
|
+
type ConfigKeyHandle,
|
|
14
|
+
createTenantConfig,
|
|
15
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
12
16
|
|
|
13
17
|
export const MFA_REQUIRED_POLICIES = ["optional", "admins", "all"] as const;
|
|
14
18
|
export type MfaRequiredPolicy = (typeof MFA_REQUIRED_POLICIES)[number];
|
|
@@ -18,9 +22,14 @@ export const mfaRequiredConfigHandle: ConfigKeyHandle<"select"> = {
|
|
|
18
22
|
type: "select",
|
|
19
23
|
};
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
// Write access is machine-only: a TenantAdmin must not be able to relax the
|
|
26
|
+
// policy back to "optional". The app-wide default comes from
|
|
27
|
+
// `createAuthMfaFeature({ requiredPolicy })`, per-tenant overrides are set by
|
|
28
|
+
// system writes (runBootstrap `tenants[].config`, jobs, SystemAdmin tooling).
|
|
29
|
+
export function mfaRequiredConfigKey(defaultPolicy: MfaRequiredPolicy = "optional") {
|
|
22
30
|
return createTenantConfig("select", {
|
|
23
|
-
default:
|
|
31
|
+
default: defaultPolicy,
|
|
24
32
|
options: [...MFA_REQUIRED_POLICIES],
|
|
33
|
+
write: access.system,
|
|
25
34
|
});
|
|
26
35
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
-
import { mfaRequiredConfigKey } from "./config.js";
|
|
3
|
+
import { type MfaRequiredPolicy, mfaRequiredConfigKey } from "./config.js";
|
|
4
4
|
import { createDisableHandler } from "./handlers/disable.write.js";
|
|
5
5
|
import { createEnableConfirmHandler } from "./handlers/enable-confirm.write.js";
|
|
6
6
|
import { createEnableConfirmPreauthHandler } from "./handlers/enable-confirm-preauth.write.js";
|
|
@@ -33,6 +33,10 @@ export type AuthMfaFeatureOptions = {
|
|
|
33
33
|
// secret from setupTokenSecret — a compromised setup-token secret must
|
|
34
34
|
// not also forge login challenges.
|
|
35
35
|
readonly challengeTokenSecret: string;
|
|
36
|
+
// App-wide MFA enforcement default when no tenant row overrides it.
|
|
37
|
+
// Tenant admins cannot change the policy (system-only write), so this plus
|
|
38
|
+
// system writes is the only way to set it. Defaults to "optional".
|
|
39
|
+
readonly requiredPolicy?: MfaRequiredPolicy;
|
|
36
40
|
};
|
|
37
41
|
|
|
38
42
|
export type BindMfaRevokeAllOtherSessions = (
|
|
@@ -127,7 +131,7 @@ export function createAuthMfaFeature(opts: AuthMfaFeatureOptions): FeatureDefini
|
|
|
127
131
|
// the app boots clean and the first MFA login dies at runtime on an
|
|
128
132
|
// unknown handler instead of failing at mount time.
|
|
129
133
|
r.requires("tenant");
|
|
130
|
-
r.config("required", mfaRequiredConfigKey());
|
|
134
|
+
r.config("required", mfaRequiredConfigKey(opts.requiredPolicy));
|
|
131
135
|
|
|
132
136
|
// Declarative secretMint screen — mint (no input) -> reveal QR/secret/
|
|
133
137
|
// recovery-codes -> confirm with a code. App places it via r.nav in its
|
|
@@ -225,6 +229,7 @@ export function createAuthMfaFeature(opts: AuthMfaFeatureOptions): FeatureDefini
|
|
|
225
229
|
// at app-boot time.
|
|
226
230
|
const checkMfaStatus = createMfaStatusChecker({
|
|
227
231
|
challengeTokenSecret: opts.challengeTokenSecret,
|
|
232
|
+
...(opts.requiredPolicy && { requiredPolicy: opts.requiredPolicy }),
|
|
228
233
|
});
|
|
229
234
|
|
|
230
235
|
// No late-bind needed — same reasoning as checkMfaStatus: only needs the
|
|
@@ -40,6 +40,7 @@ export type MfaStatusChecker = (
|
|
|
40
40
|
|
|
41
41
|
export function createMfaStatusChecker(opts: {
|
|
42
42
|
readonly challengeTokenSecret: string;
|
|
43
|
+
readonly requiredPolicy?: MfaRequiredPolicy;
|
|
43
44
|
}): MfaStatusChecker {
|
|
44
45
|
return async (ctx, userId, tenantId, roles) => {
|
|
45
46
|
declareEscapeHatch({
|
|
@@ -70,7 +71,7 @@ export function createMfaStatusChecker(opts: {
|
|
|
70
71
|
// against the declared `options` at write-time by the config feature.
|
|
71
72
|
const policy = ((await ctx.configResolver?.get(
|
|
72
73
|
mfaRequiredConfigHandle.name,
|
|
73
|
-
mfaRequiredConfigKey(),
|
|
74
|
+
mfaRequiredConfigKey(opts.requiredPolicy),
|
|
74
75
|
tenantId,
|
|
75
76
|
userId,
|
|
76
77
|
scopedDb,
|
|
@@ -164,106 +164,104 @@ export function MfaSetupPreauthScreen({
|
|
|
164
164
|
title={title ?? t("auth.mfa.setup.title")}
|
|
165
165
|
subtitle={subtitle ?? t("auth.mfa.setup.subtitle")}
|
|
166
166
|
>
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
) : null}
|
|
167
|
+
{error !== null ? (
|
|
168
|
+
<Banner variant="error">
|
|
169
|
+
{error === "too_many_attempts" && retryAfterSeconds !== null
|
|
170
|
+
? t("auth.mfa.errors.tooManyAttemptsWithSeconds", { seconds: retryAfterSeconds })
|
|
171
|
+
: t(reasonToKey(error))}
|
|
172
|
+
</Banner>
|
|
173
|
+
) : null}
|
|
175
174
|
|
|
176
|
-
|
|
175
|
+
{!setup && (
|
|
176
|
+
<Section
|
|
177
|
+
testId="mfa-setup-preauth-intro"
|
|
178
|
+
actions={
|
|
179
|
+
<Button
|
|
180
|
+
variant="primary"
|
|
181
|
+
onClick={() => void startSetup()}
|
|
182
|
+
loading={busy}
|
|
183
|
+
disabled={busy}
|
|
184
|
+
>
|
|
185
|
+
{t("auth.mfa.setup.start")}
|
|
186
|
+
</Button>
|
|
187
|
+
}
|
|
188
|
+
>
|
|
189
|
+
<span className="text-sm text-muted-foreground">{t("auth.mfa.setup.intro")}</span>
|
|
190
|
+
</Section>
|
|
191
|
+
)}
|
|
192
|
+
|
|
193
|
+
{setup && (
|
|
194
|
+
<Form onSubmit={onSubmit}>
|
|
177
195
|
<Section
|
|
178
|
-
testId="mfa-setup-preauth-
|
|
196
|
+
testId="mfa-setup-preauth-setup"
|
|
179
197
|
actions={
|
|
180
198
|
<Button
|
|
199
|
+
type="submit"
|
|
181
200
|
variant="primary"
|
|
182
|
-
onClick={() => void startSetup()}
|
|
183
201
|
loading={busy}
|
|
184
|
-
disabled={busy}
|
|
202
|
+
disabled={busy || !acknowledged || code.length !== 6}
|
|
185
203
|
>
|
|
186
|
-
{t("auth.mfa.setup.
|
|
204
|
+
{t("auth.mfa.setup.confirm")}
|
|
187
205
|
</Button>
|
|
188
206
|
}
|
|
189
207
|
>
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
|
|
208
|
+
<div className="flex flex-col items-center gap-2 text-center">
|
|
209
|
+
<span className="text-sm font-semibold">{t("auth.mfa.enable.scanTitle")}</span>
|
|
210
|
+
{/* qrcode's own SVG string output, not user input — safe to inline */}
|
|
211
|
+
<div
|
|
212
|
+
className="h-40 w-40"
|
|
213
|
+
// biome-ignore lint/security/noDangerouslySetInnerHtml: qrcode-generated SVG, no user input
|
|
214
|
+
dangerouslySetInnerHTML={{ __html: setup.qrSvg }}
|
|
215
|
+
/>
|
|
216
|
+
<span className="text-xs text-muted-foreground">
|
|
217
|
+
{t("auth.mfa.enable.manualEntry")}
|
|
218
|
+
</span>
|
|
219
|
+
<code className="inline-block break-all rounded bg-muted px-3 py-2 font-mono text-sm">
|
|
220
|
+
{setup.secretParam}
|
|
221
|
+
</code>
|
|
222
|
+
</div>
|
|
193
223
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
disabled={busy || !acknowledged || code.length !== 6}
|
|
204
|
-
>
|
|
205
|
-
{t("auth.mfa.setup.confirm")}
|
|
206
|
-
</Button>
|
|
207
|
-
}
|
|
208
|
-
>
|
|
209
|
-
<div className="flex flex-col items-center gap-2 text-center">
|
|
210
|
-
<span className="text-sm font-semibold">{t("auth.mfa.enable.scanTitle")}</span>
|
|
211
|
-
{/* qrcode's own SVG string output, not user input — safe to inline */}
|
|
212
|
-
<div
|
|
213
|
-
className="h-40 w-40"
|
|
214
|
-
// biome-ignore lint/security/noDangerouslySetInnerHtml: qrcode-generated SVG, no user input
|
|
215
|
-
dangerouslySetInnerHTML={{ __html: setup.qrSvg }}
|
|
216
|
-
/>
|
|
217
|
-
<span className="text-xs text-muted-foreground">
|
|
218
|
-
{t("auth.mfa.enable.manualEntry")}
|
|
219
|
-
</span>
|
|
220
|
-
<code className="inline-block break-all rounded bg-muted px-3 py-2 font-mono text-sm">
|
|
221
|
-
{setup.secretParam}
|
|
222
|
-
</code>
|
|
223
|
-
</div>
|
|
224
|
-
|
|
225
|
-
<div className="flex flex-col gap-2">
|
|
226
|
-
<span className="text-sm font-semibold">{t("auth.mfa.enable.recoveryTitle")}</span>
|
|
227
|
-
<span className="text-xs text-muted-foreground">
|
|
228
|
-
{t("auth.mfa.enable.recoveryHint")}
|
|
229
|
-
</span>
|
|
230
|
-
<code className="block whitespace-pre-wrap break-all rounded bg-muted px-3 py-2 font-mono text-sm">
|
|
231
|
-
{setup.recoveryCodes.join("\n")}
|
|
232
|
-
</code>
|
|
233
|
-
<Field id="mfa-setup-preauth-ack" label={t("auth.mfa.enable.acknowledge")}>
|
|
234
|
-
<Input
|
|
235
|
-
kind="boolean"
|
|
236
|
-
id="mfa-setup-preauth-ack"
|
|
237
|
-
name="mfa-setup-preauth-ack"
|
|
238
|
-
value={acknowledged}
|
|
239
|
-
onChange={setAcknowledged}
|
|
240
|
-
/>
|
|
241
|
-
</Field>
|
|
242
|
-
</div>
|
|
243
|
-
|
|
244
|
-
<Field id="mfa-setup-preauth-code" label={t("auth.mfa.enable.code")} required>
|
|
224
|
+
<div className="flex flex-col gap-2">
|
|
225
|
+
<span className="text-sm font-semibold">{t("auth.mfa.enable.recoveryTitle")}</span>
|
|
226
|
+
<span className="text-xs text-muted-foreground">
|
|
227
|
+
{t("auth.mfa.enable.recoveryHint")}
|
|
228
|
+
</span>
|
|
229
|
+
<code className="block whitespace-pre-wrap break-all rounded bg-muted px-3 py-2 font-mono text-sm">
|
|
230
|
+
{setup.recoveryCodes.join("\n")}
|
|
231
|
+
</code>
|
|
232
|
+
<Field id="mfa-setup-preauth-ack" label={t("auth.mfa.enable.acknowledge")}>
|
|
245
233
|
<Input
|
|
246
|
-
kind="
|
|
247
|
-
id="mfa-setup-preauth-
|
|
248
|
-
name="mfa-setup-preauth-
|
|
249
|
-
value={
|
|
250
|
-
onChange={
|
|
251
|
-
disabled={busy || !acknowledged}
|
|
252
|
-
autoComplete="one-time-code"
|
|
234
|
+
kind="boolean"
|
|
235
|
+
id="mfa-setup-preauth-ack"
|
|
236
|
+
name="mfa-setup-preauth-ack"
|
|
237
|
+
value={acknowledged}
|
|
238
|
+
onChange={setAcknowledged}
|
|
253
239
|
/>
|
|
254
240
|
</Field>
|
|
255
|
-
</
|
|
256
|
-
</Form>
|
|
257
|
-
)}
|
|
241
|
+
</div>
|
|
258
242
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
243
|
+
<Field id="mfa-setup-preauth-code" label={t("auth.mfa.enable.code")} required>
|
|
244
|
+
<Input
|
|
245
|
+
kind="text"
|
|
246
|
+
id="mfa-setup-preauth-code"
|
|
247
|
+
name="mfa-setup-preauth-code"
|
|
248
|
+
value={code}
|
|
249
|
+
onChange={setCode}
|
|
250
|
+
disabled={busy || !acknowledged}
|
|
251
|
+
autoComplete="one-time-code"
|
|
252
|
+
/>
|
|
253
|
+
</Field>
|
|
254
|
+
</Section>
|
|
255
|
+
</Form>
|
|
256
|
+
)}
|
|
257
|
+
|
|
258
|
+
{onCancel ? (
|
|
259
|
+
<div className="flex justify-center">
|
|
260
|
+
<Button type="button" variant="secondary" onClick={onCancel} disabled={busy}>
|
|
261
|
+
{t("auth.mfa.verify.backToLogin")}
|
|
262
|
+
</Button>
|
|
263
|
+
</div>
|
|
264
|
+
) : null}
|
|
267
265
|
</AuthCard>
|
|
268
266
|
);
|
|
269
267
|
}
|
|
@@ -85,33 +85,31 @@ export function MfaVerifyScreen({
|
|
|
85
85
|
title={title ?? t("auth.mfa.verify.title")}
|
|
86
86
|
subtitle={subtitle ?? t("auth.mfa.verify.subtitle")}
|
|
87
87
|
>
|
|
88
|
-
<
|
|
89
|
-
<
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
88
|
+
<Form onSubmit={onSubmit}>
|
|
89
|
+
<Field id="mfa-verify-code" label={t("auth.mfa.verify.code")} required>
|
|
90
|
+
<Input
|
|
91
|
+
kind="text"
|
|
92
|
+
id="mfa-verify-code"
|
|
93
|
+
name="mfa-verify-code"
|
|
94
|
+
value={code}
|
|
95
|
+
onChange={setCode}
|
|
96
|
+
disabled={submitting}
|
|
97
|
+
required
|
|
98
|
+
autoComplete="one-time-code"
|
|
99
|
+
/>
|
|
100
|
+
</Field>
|
|
101
|
+
{error !== null ? <Banner variant="error">{t(reasonToKey(error))}</Banner> : null}
|
|
102
|
+
<Button type="submit" loading={submitting} disabled={submitting}>
|
|
103
|
+
{submitting
|
|
104
|
+
? t("auth.mfa.verify.submitting")
|
|
105
|
+
: (submitLabel ?? t("auth.mfa.verify.submit"))}
|
|
106
|
+
</Button>
|
|
107
|
+
{onCancel ? (
|
|
108
|
+
<Button type="button" variant="secondary" onClick={onCancel} disabled={submitting}>
|
|
109
|
+
{t("auth.mfa.verify.backToLogin")}
|
|
107
110
|
</Button>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
{t("auth.mfa.verify.backToLogin")}
|
|
111
|
-
</Button>
|
|
112
|
-
) : null}
|
|
113
|
-
</Form>
|
|
114
|
-
</div>
|
|
111
|
+
) : null}
|
|
112
|
+
</Form>
|
|
115
113
|
</AuthCard>
|
|
116
114
|
);
|
|
117
115
|
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.341.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "App layout around termination pages, trailing-slash redirects, receipt greeting",
|
|
6
|
+
"detail": "`createContractTerminationRoutes` accepts a `wrapLayout`, the same function `createLegalPagesFeature` takes. It wraps every termination page (form, review, result, 429, error); the security headers stay with the framework. The layout also receives `alternates`, a map from locale to the path of the same page, so it can render a language switch from plain links. `page-render` exports the shared type `PublicPageWrapLayout`; `LegalPagesWrapLayout` is now an alias of it. The page body sits in `<div data-kumiko-page=\"contract-termination\">` with `data-kumiko-*` hooks on the form, field groups, buttons and review table. The page CSP keeps `script-src 'none'`, so a layout must not rely on JavaScript.\nThe default `wrapInLayout` stylesheet gains base rules for input, select, textarea and button, so unstyled forms stay readable.\nThe termination pages and every legal page now answer the other trailing-slash form of their path (`/legal/kuendigen/`, `/legal/impressum/`) with a 301 to the configured path for GET and HEAD, query string kept. POST has no alias.\nThe termination receipt mail text changed: it now starts with a greeting line (\"Hallo,\" / \"Hello,\") and the English sentence starts capitalized (\"We confirm receipt ...\"). The receipt content still depends only on the declarant's input."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.340.0",
|
|
4
10
|
"type": "improvement",
|
package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts
CHANGED
|
@@ -87,6 +87,7 @@ export function renderTerminationReceipt(
|
|
|
87
87
|
subject,
|
|
88
88
|
header: subject,
|
|
89
89
|
sections: [
|
|
90
|
+
{ text: texts.receiptGreeting },
|
|
90
91
|
{ text: texts.receiptIntro },
|
|
91
92
|
{ text: `${texts.receivedAt}: ${formatReceivedAt(args.receivedAtIso, args.locale)}` },
|
|
92
93
|
{ text: `${texts.requestId}: ${args.requestId}` },
|