@cosmicdrift/kumiko-bundled-features 0.48.1 → 0.51.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 (90) hide show
  1. package/package.json +9 -6
  2. package/src/auth-email-password/__tests__/signup-flow.integration.test.ts +51 -0
  3. package/src/auth-email-password/constants.ts +6 -0
  4. package/src/auth-email-password/errors.ts +19 -0
  5. package/src/auth-email-password/handlers/request-email-verification.write.ts +1 -0
  6. package/src/auth-email-password/handlers/request-password-reset.write.ts +1 -0
  7. package/src/auth-email-password/handlers/signup-confirm.write.ts +22 -12
  8. package/src/auth-email-password/handlers/signup-request.write.ts +12 -10
  9. package/src/auth-email-password/i18n.ts +4 -0
  10. package/src/auth-email-password/password-hashing.ts +1 -0
  11. package/src/auth-email-password/reset-token.ts +2 -0
  12. package/src/auth-email-password/seeding.ts +19 -4
  13. package/src/auth-email-password/signup-token-store.ts +1 -0
  14. package/src/auth-email-password/verification-token.ts +2 -0
  15. package/src/billing-foundation/aggregate-id.ts +1 -0
  16. package/src/cap-counter/aggregate-id.ts +2 -0
  17. package/src/config/__tests__/app-override-visibility.integration.test.ts +143 -0
  18. package/src/config/__tests__/backing-secrets.integration.test.ts +188 -0
  19. package/src/config/__tests__/cascade.integration.test.ts +111 -1
  20. package/src/config/__tests__/config.integration.test.ts +60 -0
  21. package/src/config/__tests__/env-overrides.test.ts +134 -0
  22. package/src/config/__tests__/inherited-redaction.integration.test.ts +180 -0
  23. package/src/config/__tests__/read-redaction.test.ts +112 -0
  24. package/src/config/__tests__/settings-hub-feature-name.test.ts +14 -0
  25. package/src/config/constants.ts +3 -1
  26. package/src/config/feature.ts +5 -2
  27. package/src/config/handlers/cascade.query.ts +13 -2
  28. package/src/config/handlers/readiness.query.ts +1 -0
  29. package/src/config/handlers/reset.write.ts +23 -2
  30. package/src/config/handlers/set.write.ts +36 -2
  31. package/src/config/handlers/values.query.ts +39 -13
  32. package/src/config/index.ts +1 -1
  33. package/src/config/read-redaction.ts +54 -0
  34. package/src/config/resolver.ts +163 -4
  35. package/src/config/web/client-plugin.ts +24 -0
  36. package/src/config/web/i18n.ts +25 -0
  37. package/src/config/web/index.ts +3 -0
  38. package/src/config/write-helpers.ts +37 -0
  39. package/src/custom-fields/aggregate-id.ts +1 -0
  40. package/src/custom-fields/wire-for-entity.ts +1 -0
  41. package/src/delivery/upsert-preference.ts +1 -0
  42. package/src/file-provider-inmemory/feature.ts +1 -1
  43. package/src/file-provider-s3/feature.ts +1 -1
  44. package/src/jobs/__tests__/projection-rebuild-job.integration.test.ts +162 -0
  45. package/src/jobs/feature.ts +13 -0
  46. package/src/jobs/handlers/projection-rebuild.job.ts +36 -0
  47. package/src/legal-pages/README.md +16 -13
  48. package/src/legal-pages/__tests__/legal-pages.integration.test.ts +15 -8
  49. package/src/legal-pages/feature.ts +9 -4
  50. package/src/legal-pages/markdown.ts +6 -56
  51. package/src/legal-pages/security-headers.ts +1 -0
  52. package/src/mail-transport-inmemory/feature.ts +1 -1
  53. package/src/mail-transport-smtp/feature.ts +1 -1
  54. package/src/managed-pages/__tests__/managed-pages.integration.test.ts +536 -0
  55. package/src/managed-pages/branding.ts +142 -0
  56. package/src/managed-pages/css-gate.ts +24 -0
  57. package/src/managed-pages/feature.ts +246 -0
  58. package/src/managed-pages/handlers/branding.query.ts +30 -0
  59. package/src/managed-pages/handlers/by-slug.query.ts +35 -0
  60. package/src/managed-pages/handlers/set.write.ts +113 -0
  61. package/src/managed-pages/index.ts +30 -0
  62. package/src/managed-pages/screens/branding-screen.ts +85 -0
  63. package/src/managed-pages/screens/page-screens.ts +82 -0
  64. package/src/managed-pages/seeding.ts +99 -0
  65. package/src/managed-pages/table.ts +58 -0
  66. package/src/page-render/__tests__/branding.test.ts +57 -0
  67. package/src/page-render/__tests__/css-sanitize.test.ts +215 -0
  68. package/src/page-render/__tests__/markdown.test.ts +41 -0
  69. package/src/page-render/branding.ts +99 -0
  70. package/src/page-render/css-sanitize.ts +344 -0
  71. package/src/page-render/index.ts +13 -0
  72. package/src/page-render/layout.ts +100 -0
  73. package/src/page-render/markdown.ts +39 -0
  74. package/src/page-render/security-headers.ts +16 -0
  75. package/src/step-dispatcher/mail-runner.ts +1 -0
  76. package/src/subscription-stripe/runtime.ts +1 -0
  77. package/src/subscription-stripe/verify-webhook.ts +1 -0
  78. package/src/tenant/__tests__/multi-tenant.integration.test.ts +48 -0
  79. package/src/tenant/handlers/list.query.ts +1 -1
  80. package/src/tenant/handlers/memberships.query.ts +16 -15
  81. package/src/tenant/handlers/toggle-enabled.write.ts +1 -1
  82. package/src/tenant/handlers/update.write.ts +1 -1
  83. package/src/text-content/api.ts +1 -0
  84. package/src/tier-engine/aggregate-id.ts +1 -0
  85. package/src/user/handlers/me.query.ts +1 -1
  86. package/src/user-data-rights/db/queries/export-jobs.ts +1 -0
  87. package/src/user-data-rights/deletion-token.ts +2 -0
  88. package/src/user-data-rights/feature.ts +1 -1
  89. package/src/user-data-rights/run-export-jobs.ts +2 -0
  90. package/src/user-profile/handlers/change-email.write.ts +1 -0
@@ -141,12 +141,9 @@ describe("legal-pages :: edge-cases", () => {
141
141
  expect(body).toContain("Tenant-Admin");
142
142
  });
143
143
 
144
- test("Markdown-Body mit <script> wird NICHT escaped (dokumentiertes XSS-Verhalten, siehe README)", async () => {
145
- // Bewusstes Verhalten: marked.parse rendered HTML 1:1, kein
146
- // DOMPurify-Layer aktuell. Dokumentiert in legal-pages/README.md
147
- // ('XSS — bewusst aktuell nicht gesichert'). Test pinnt das
148
- // Verhalten — wenn es sich ändert (z.B. DOMPurify dazu), schlägt
149
- // dieser Test fehl und der Wechsel ist dokumentiert.
144
+ test("Markdown-Body mit <script> wird escaped (XSS-Härtung)", async () => {
145
+ // Server-Render ist gegen untrusted Tenant-Authoren gehärtet:
146
+ // Raw-HTML im Markdown-Body wird als Text escaped (kein Passthrough).
150
147
  await seedTextBlock(db, {
151
148
  tenantId: SYSTEM_TENANT_ID,
152
149
  slug: "imprint",
@@ -158,8 +155,8 @@ describe("legal-pages :: edge-cases", () => {
158
155
  const res = await stack.app.request("/legal/impressum");
159
156
  expect(res.status).toBe(200);
160
157
  const html = await res.text();
161
- // Aktuelles Verhalten: script-tag bleibt unescaped im Output
162
- expect(html).toContain("<script>window.x=1</script>");
158
+ expect(html).not.toContain("<script>window.x=1</script>");
159
+ expect(html).toContain("&lt;script&gt;");
163
160
  });
164
161
  });
165
162
 
@@ -170,6 +167,16 @@ describe("legal-pages :: cache-control", () => {
170
167
  });
171
168
  });
172
169
 
170
+ describe("legal-pages :: security headers", () => {
171
+ test("server-gerenderte Pages tragen CSP + Hardening-Header", async () => {
172
+ const res = await stack.app.request("/legal/impressum");
173
+ expect(res.headers.get("content-security-policy")).toContain("script-src 'none'");
174
+ expect(res.headers.get("x-content-type-options")).toBe("nosniff");
175
+ expect(res.headers.get("x-frame-options")).toBe("SAMEORIGIN");
176
+ expect(res.headers.get("referrer-policy")).toBe("strict-origin-when-cross-origin");
177
+ });
178
+ });
179
+
173
180
  describe("markdown render helpers", () => {
174
181
  test("renderMarkdownToHtml converts markdown to HTML", () => {
175
182
  const html = renderMarkdownToHtml("# Title\n\n**bold**");
@@ -9,6 +9,7 @@ import {
9
9
  } from "@cosmicdrift/kumiko-framework/engine";
10
10
  import { LEGAL_REQUIRED_BLOCKS, LEGAL_ROUTES } from "./constants";
11
11
  import { renderMarkdownToHtml, wrapInLayout } from "./markdown";
12
+ import { securePageHeaders } from "./security-headers";
12
13
 
13
14
  // QN-Konstante als dokumentierter Public-Contract des text-content-
14
15
  // Features. Ein magic-string statt eines Code-Imports ist hier explizit
@@ -113,10 +114,14 @@ export function createLegalPagesFeature(opts: LegalPagesOptions = {}): FeatureDe
113
114
  lang: route.lang,
114
115
  });
115
116
 
116
- return c.body(html, 200, {
117
- "content-type": "text/html; charset=utf-8",
118
- "cache-control": "public, max-age=300",
119
- });
117
+ return c.body(
118
+ html,
119
+ 200,
120
+ securePageHeaders({
121
+ "content-type": "text/html; charset=utf-8",
122
+ "cache-control": "public, max-age=300",
123
+ }),
124
+ );
120
125
  },
121
126
  });
122
127
  }
@@ -1,57 +1,7 @@
1
- import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
2
- import { Marked } from "marked";
1
+ // Re-export aus dem geteilten page-render-Kern (managed-pages nutzt
2
+ // denselben gehärteten Renderer + Default-Layout). Namen bleiben stabil
3
+ // für legal-pages' Public-API (index.ts exportiert renderMarkdownToHtml +
4
+ // wrapInLayout).
3
5
 
4
- // Markdown→HTML mit eigener `marked`-Instance. GFM aus, breaks aus —
5
- // Legal-Pages sind strukturiert genug dass GFM-Tables/Strikethrough/
6
- // Task-Lists nicht nötig sind. Headers + Listen + Links + Code reichen.
7
- //
8
- // Instance statt globaler `marked.setOptions()` damit andere Features
9
- // die `marked` als runtime-dep nutzen ihre eigenen Optionen behalten —
10
- // modul-level side-effect auf shared library wäre brittle bei mehreren
11
- // Konsumenten.
12
- //
13
- // XSS-Schutz: marked rendered tags 1:1, also kann ein böswilliger Text-
14
- // Editor (TenantAdmin) <script>-Tags reinschreiben. Aktuell akzeptiert
15
- // weil nur trusted Roles (TenantAdmin/SystemAdmin) Texte setzen können —
16
- // bei einem Multi-Author-Setup müsste DOMPurify oder isomorphic-dompurify
17
- // dazu. Dokumentiert in README, Phase-2-Hardening.
18
- const markdownRenderer = new Marked({
19
- gfm: false,
20
- breaks: false,
21
- });
22
-
23
- export function renderMarkdownToHtml(markdown: string): string {
24
- // @cast-boundary render-helper marked.parse return-type ist
25
- // `string | Promise<string>` — mit `{ async: false }` runtime-garantiert
26
- // sync (string). Cast nur API-Vertragsfix, kein Type-Loss.
27
- return markdownRenderer.parse(markdown, { async: false }) as string;
28
- }
29
-
30
- // Layout-Wrapper für Legal-Pages — minimaler HTML-Skeleton mit Inline-
31
- // CSS damit die Pages auch ohne App-Layout sauber aussehen. Apps die
32
- // das in ihr eigenes Layout integrieren wollen, nutzen text-content's
33
- // by-slug-query direkt und rendern selbst.
34
- export function wrapInLayout(opts: { title: string; bodyHtml: string; lang: string }): string {
35
- return `<!doctype html>
36
- <html lang="${escapeHtmlAttr(opts.lang)}">
37
- <head>
38
- <meta charset="utf-8">
39
- <meta name="viewport" content="width=device-width, initial-scale=1">
40
- <title>${escapeHtml(opts.title)}</title>
41
- <style>
42
- body { font-family: system-ui, -apple-system, sans-serif; max-width: 720px;
43
- margin: 2rem auto; padding: 0 1rem; line-height: 1.6; color: #222; }
44
- h1, h2, h3 { line-height: 1.2; margin-top: 2rem; }
45
- h1 { font-size: 1.8rem; } h2 { font-size: 1.4rem; } h3 { font-size: 1.15rem; }
46
- a { color: #0066cc; }
47
- code { background: #f4f4f4; padding: 0.1rem 0.3rem; border-radius: 3px; }
48
- hr { border: 0; border-top: 1px solid #ddd; margin: 2rem 0; }
49
- </style>
50
- </head>
51
- <body>
52
- <main>
53
- ${opts.bodyHtml}
54
- </main>
55
- </body>
56
- </html>`;
57
- }
6
+ export { wrapInLayout } from "../page-render/layout";
7
+ export { renderSafeMarkdown as renderMarkdownToHtml } from "../page-render/markdown";
@@ -0,0 +1 @@
1
+ export { securePageHeaders } from "../page-render/security-headers";
@@ -87,7 +87,7 @@ export const mailTransportInMemoryFeature = defineFeature(FEATURE_NAME, (r) => {
87
87
  // Returnt den per-tenant Buffer. Identitätsstabil zwischen calls
88
88
  // damit die Demo-Inbox accumulated bleibt.
89
89
  return getOrCreateTransportForTenant(tenantId);
90
- },
90
+ }, // @wrapper-known semantic-alias
91
91
  };
92
92
  r.useExtension("mailTransport", "inmemory", plugin);
93
93
  });
@@ -110,7 +110,7 @@ export const mailTransportSmtpFeature = defineFeature(FEATURE_NAME, (r) => {
110
110
  // `entityName` "smtp" is what tenants set in mail-foundation's
111
111
  // `provider` config-key to pick this transport.
112
112
  const plugin: MailTransportPlugin = {
113
- build: async (ctx: HandlerContext, tenantId: string) => buildSmtpTransport(ctx, tenantId),
113
+ build: async (ctx: HandlerContext, tenantId: string) => buildSmtpTransport(ctx, tenantId), // @wrapper-known semantic-alias
114
114
  };
115
115
  r.useExtension("mailTransport", "smtp", plugin);
116
116