@cosmicdrift/kumiko-bundled-features 0.362.0 → 0.363.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/dist/admin-shell/feature.d.ts +3 -0
- package/dist/admin-shell/feature.js +6 -1
- package/dist/agent-tools/agent-manifest.js +2 -1
- package/dist/agent-tools/filter-ops.d.ts +1 -1
- package/dist/agent-tools/filter-ops.js +1 -3
- package/dist/agent-tools/tool-dispatch.js +2 -1
- package/dist/auth-email-password/email-templates.d.ts +4 -26
- package/dist/auth-email-password/email-templates.js +9 -101
- package/dist/auth-email-password/feature.js +24 -6
- package/dist/auth-email-password/handlers/invite-create.write.js +4 -4
- package/dist/auth-email-password/handlers/request-account-unlock.write.js +2 -4
- package/dist/auth-email-password/handlers/request-email-verification.write.js +2 -4
- package/dist/auth-email-password/handlers/request-password-reset.write.js +2 -4
- package/dist/auth-email-password/handlers/signup-confirm.write.js +3 -4
- package/dist/auth-email-password/handlers/token-request-handler.d.ts +1 -2
- package/dist/auth-email-password/handlers/token-request-handler.js +0 -1
- package/dist/auth-email-password/index.d.ts +2 -2
- package/dist/auth-email-password/index.js +1 -1
- package/dist/auth-email-password/magic-link-mail.d.ts +3 -2
- package/dist/auth-email-password/magic-link-mail.js +19 -14
- package/dist/auth-email-password/mail-notifications.d.ts +32 -0
- package/dist/auth-email-password/mail-notifications.js +116 -0
- package/dist/auth-email-password/signup-activation.d.ts +1 -1
- package/dist/auth-email-password/signup-activation.js +2 -3
- package/dist/auth-foundation/types.js +2 -8
- package/dist/auth-mfa/feature.js +4 -4
- package/dist/auth-mfa/hmac-token-codec.js +4 -4
- package/dist/auth-mfa/mfa-challenge-token.js +3 -3
- package/dist/auth-mfa/mfa-preauth-setup-token.js +3 -3
- package/dist/auth-mfa/mfa-setup-token.js +8 -8
- package/dist/billing-foundation/constants.d.ts +3 -3
- package/dist/billing-foundation/constants.js +5 -4
- package/dist/billing-foundation/consumer-protection/confirmation-mail.d.ts +32 -14
- package/dist/billing-foundation/consumer-protection/confirmation-mail.js +27 -32
- package/dist/billing-foundation/consumer-protection/issue-confirmation.js +3 -3
- package/dist/billing-foundation/consumer-protection/mail-notifications.d.ts +8 -0
- package/dist/billing-foundation/consumer-protection/mail-notifications.js +95 -0
- package/dist/billing-foundation/consumer-protection/register-consent.js +2 -0
- package/dist/billing-foundation/consumer-protection/termination-mail.d.ts +20 -10
- package/dist/billing-foundation/consumer-protection/termination-mail.js +10 -21
- package/dist/billing-foundation/consumer-protection/termination-notify.js +2 -2
- package/dist/billing-foundation/consumer-protection/termination-pages.js +1 -3
- package/dist/billing-foundation/consumer-protection/termination-request.js +2 -2
- package/dist/billing-foundation/handlers/process-event.write.js +2 -2
- package/dist/billing-foundation/handlers/process-payment-event.write.js +1 -1
- package/dist/billing-foundation/subscription-tier-sync.js +3 -4
- package/dist/cap-overview/handlers/tenant-caps-list.query.js +2 -1
- package/dist/cap-overview/web/cap-usage-cell.js +5 -4
- package/dist/channel-email/email-channel.d.ts +2 -2
- package/dist/channel-email/email-channel.js +89 -26
- package/dist/channel-email/feature.js +3 -3
- package/dist/channel-email/index.d.ts +1 -1
- package/dist/channel-email/index.js +1 -1
- package/dist/channel-email/pii-guard.js +10 -0
- package/dist/channel-email/smtp-transport.js +13 -0
- package/dist/channel-email/types.d.ts +8 -0
- package/dist/config/handlers/set.write.js +1 -2
- package/dist/custom-fields/lib/value-schema.js +10 -11
- package/dist/custom-fields/wire-user-data-rights.js +7 -6
- package/dist/data-retention/index.d.ts +1 -0
- package/dist/data-retention/index.js +1 -0
- package/dist/data-retention/run-retention-cleanup.js +6 -6
- package/dist/delivery/attachment-ref.d.ts +39 -0
- package/dist/delivery/attachment-ref.js +64 -0
- package/dist/delivery/channel-context.d.ts +8 -4
- package/dist/delivery/channel-context.js +76 -2
- package/dist/delivery/constants.d.ts +1 -0
- package/dist/delivery/constants.js +1 -0
- package/dist/delivery/delivery-service.d.ts +4 -1
- package/dist/delivery/delivery-service.js +38 -32
- package/dist/delivery/feature.js +1 -1
- package/dist/delivery/index.d.ts +5 -2
- package/dist/delivery/index.js +4 -1
- package/dist/delivery/jobs.js +25 -5
- package/dist/delivery/notify-factory.js +4 -1
- package/dist/delivery/testing.d.ts +4 -1
- package/dist/delivery/types.d.ts +20 -14
- package/dist/delivery/unsubscribe.js +4 -1
- package/dist/derivatives-sharp/render.js +1 -18
- package/dist/document-ingest-foundation/pages.js +4 -4
- package/dist/feature-toggles/toggle-sync-signal.js +4 -4
- package/dist/file-foundation/index.d.ts +1 -0
- package/dist/file-foundation/index.js +1 -0
- package/dist/file-foundation/read-file-ref-content.d.ts +27 -0
- package/dist/file-foundation/read-file-ref-content.js +49 -0
- package/dist/folders/web/folder-manager.js +3 -2
- package/dist/form-draft/db/queries/cleanup.js +4 -4
- package/dist/form-draft/db/queries/draft-count.js +2 -2
- package/dist/form-draft/db/queries/owned-file-refs.js +2 -2
- package/dist/form-draft/release-file-refs.js +3 -2
- package/dist/inbound-mail-foundation/handlers/connect-account.write.js +1 -1
- package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +6 -6
- package/dist/inbound-mail-foundation/handlers/scope-visibility.d.ts +1 -1
- package/dist/inbound-mail-foundation/oauth-state.js +7 -7
- package/dist/inbound-mail-foundation/types.js +4 -4
- package/dist/inbound-mail-foundation/watch-supervisor.js +22 -13
- package/dist/inbound-provider-imap/imap-client.js +4 -3
- package/dist/ledger/recurring.js +1 -3
- package/dist/managed-pages/branding.d.ts +2 -0
- package/dist/managed-pages/branding.js +24 -2
- package/dist/managed-pages/feature.js +1 -0
- package/dist/managed-pages/i18n.js +1 -0
- package/dist/managed-pages/screens/branding-screen.js +8 -1
- package/dist/page-render/branding.d.ts +1 -0
- package/dist/page-render/branding.js +1 -0
- package/dist/page-render/markdown.js +1 -1
- package/dist/personal-access-tokens/feature.js +1 -1
- package/dist/personal-access-tokens/handlers/list.query.js +2 -1
- package/dist/personal-access-tokens/revoke-for-user.js +2 -1
- package/dist/renderer-foundation/api.d.ts +13 -13
- package/dist/renderer-foundation/api.js +1 -1
- package/dist/renderer-foundation/constants.js +2 -4
- package/dist/renderer-foundation/feature.js +7 -7
- package/dist/renderer-foundation/index.d.ts +2 -1
- package/dist/renderer-foundation/index.js +1 -0
- package/dist/renderer-foundation/types.d.ts +92 -32
- package/dist/renderer-pdf/feature.js +7 -1
- package/dist/sessions/db/queries/cleanup.js +2 -2
- package/dist/sessions/feature.js +1 -1
- package/dist/sessions/handlers/list.query.js +2 -1
- package/dist/shared/attachment-file-name.d.ts +3 -0
- package/dist/shared/attachment-file-name.js +35 -0
- package/dist/shared/index.d.ts +3 -0
- package/dist/shared/index.js +3 -0
- package/dist/shared/is-tenant-db.js +1 -1
- package/dist/shared/mail-template-body.d.ts +4 -0
- package/dist/shared/mail-template-body.js +17 -0
- package/dist/shared/sharp-input-limits.d.ts +6 -0
- package/dist/shared/sharp-input-limits.js +22 -0
- package/dist/shared/signup-handover.js +2 -2
- package/dist/tags/handlers/assign-tag.write.js +3 -1
- package/dist/tags/web/tag-chip.js +2 -0
- package/dist/template-resolver/api.d.ts +30 -2
- package/dist/template-resolver/api.js +40 -5
- package/dist/template-resolver/compile-document-body.d.ts +8 -0
- package/dist/template-resolver/compile-document-body.js +163 -0
- package/dist/template-resolver/constants.d.ts +13 -4
- package/dist/template-resolver/constants.js +30 -10
- package/dist/template-resolver/document-sanitizer.d.ts +32 -0
- package/dist/template-resolver/document-sanitizer.js +201 -0
- package/dist/template-resolver/document-schema.d.ts +21 -0
- package/dist/template-resolver/document-schema.js +296 -0
- package/dist/template-resolver/feature.d.ts +18 -0
- package/dist/template-resolver/feature.js +50 -6
- package/dist/template-resolver/handlers/bundle-import.d.ts +20 -0
- package/dist/template-resolver/handlers/bundle-import.js +147 -0
- package/dist/template-resolver/handlers/collection-set.write.js +25 -2
- package/dist/template-resolver/handlers/copy-locale.write.d.ts +19 -0
- package/dist/template-resolver/handlers/copy-locale.write.js +116 -0
- package/dist/template-resolver/handlers/diff-to-system.query.d.ts +22 -0
- package/dist/template-resolver/handlers/diff-to-system.query.js +64 -0
- package/dist/template-resolver/handlers/export-bundle.query.d.ts +35 -0
- package/dist/template-resolver/handlers/export-bundle.query.js +68 -0
- package/dist/template-resolver/handlers/find-by-id.query.d.ts +1 -0
- package/dist/template-resolver/handlers/find-by-id.query.js +3 -3
- package/dist/template-resolver/handlers/import-bundle-system.write.d.ts +39 -0
- package/dist/template-resolver/handlers/import-bundle-system.write.js +36 -0
- package/dist/template-resolver/handlers/import-bundle.write.d.ts +40 -0
- package/dist/template-resolver/handlers/import-bundle.write.js +19 -0
- package/dist/template-resolver/handlers/layout-source.query.d.ts +26 -0
- package/dist/template-resolver/handlers/layout-source.query.js +64 -0
- package/dist/template-resolver/handlers/list.query.d.ts +4 -1
- package/dist/template-resolver/handlers/list.query.js +5 -7
- package/dist/template-resolver/handlers/overview.query.d.ts +59 -0
- package/dist/template-resolver/handlers/overview.query.js +165 -0
- package/dist/template-resolver/handlers/preview.query.d.ts +12 -0
- package/dist/template-resolver/handlers/preview.query.js +24 -0
- package/dist/template-resolver/handlers/reset-to-system.write.d.ts +8 -0
- package/dist/template-resolver/handlers/reset-to-system.write.js +50 -0
- package/dist/template-resolver/handlers/resource-link-pdf.write.d.ts +8 -0
- package/dist/template-resolver/handlers/resource-link-pdf.write.js +32 -0
- package/dist/template-resolver/handlers/resource-remove.write.d.ts +9 -0
- package/dist/template-resolver/handlers/resource-remove.write.js +44 -0
- package/dist/template-resolver/handlers/resource-shared.d.ts +9 -0
- package/dist/template-resolver/handlers/resource-shared.js +14 -0
- package/dist/template-resolver/handlers/resource-upload.write.d.ts +13 -0
- package/dist/template-resolver/handlers/resource-upload.write.js +98 -0
- package/dist/template-resolver/handlers/send-test.write.d.ts +11 -0
- package/dist/template-resolver/handlers/send-test.write.js +81 -0
- package/dist/template-resolver/handlers/set.write.d.ts +4 -1
- package/dist/template-resolver/handlers/set.write.js +25 -3
- package/dist/template-resolver/handlers/shared.d.ts +9 -5
- package/dist/template-resolver/handlers/shared.js +6 -2
- package/dist/template-resolver/handlers/template-gates.d.ts +36 -0
- package/dist/template-resolver/handlers/template-gates.js +131 -0
- package/dist/template-resolver/handlers/template-write.d.ts +45 -0
- package/dist/template-resolver/handlers/template-write.js +109 -0
- package/dist/template-resolver/handlers/tenant-overrides.query.d.ts +17 -0
- package/dist/template-resolver/handlers/tenant-overrides.query.js +48 -0
- package/dist/template-resolver/handlers/toggle-status.write.d.ts +12 -0
- package/dist/template-resolver/handlers/toggle-status.write.js +27 -11
- package/dist/template-resolver/handlers/upsert-system.write.d.ts +5 -8
- package/dist/template-resolver/handlers/upsert-system.write.js +14 -59
- package/dist/template-resolver/handlers/upsert-tenant.write.d.ts +5 -8
- package/dist/template-resolver/handlers/upsert-tenant.write.js +10 -61
- package/dist/template-resolver/handlers/usage.query.d.ts +17 -0
- package/dist/template-resolver/handlers/usage.query.js +15 -0
- package/dist/template-resolver/handlers/validate.query.d.ts +74 -0
- package/dist/template-resolver/handlers/validate.query.js +80 -0
- package/dist/template-resolver/index.d.ts +18 -1
- package/dist/template-resolver/index.js +16 -1
- package/dist/template-resolver/layout-regions.d.ts +23 -0
- package/dist/template-resolver/layout-regions.js +125 -0
- package/dist/template-resolver/linked-resources.d.ts +17 -0
- package/dist/template-resolver/linked-resources.js +75 -0
- package/dist/template-resolver/preview-display-html.d.ts +7 -0
- package/dist/template-resolver/preview-display-html.js +27 -0
- package/dist/template-resolver/print-document.d.ts +1 -0
- package/dist/template-resolver/print-document.js +55 -0
- package/dist/template-resolver/qualified-names.d.ts +30 -0
- package/dist/template-resolver/qualified-names.js +30 -0
- package/dist/template-resolver/record-mapping.d.ts +2 -0
- package/dist/template-resolver/record-mapping.js +21 -0
- package/dist/template-resolver/resource-image.d.ts +15 -0
- package/dist/template-resolver/resource-image.js +34 -0
- package/dist/template-resolver/seeding.d.ts +14 -1
- package/dist/template-resolver/seeding.js +105 -15
- package/dist/template-resolver/store-template-resource.d.ts +64 -0
- package/dist/template-resolver/store-template-resource.js +264 -0
- package/dist/template-resolver/table.d.ts +47 -8
- package/dist/template-resolver/table.js +11 -0
- package/dist/template-resolver/template-bundle.d.ts +36 -0
- package/dist/template-resolver/template-bundle.js +60 -0
- package/dist/template-resolver/template-diff.d.ts +7 -0
- package/dist/template-resolver/template-diff.js +68 -0
- package/dist/template-resolver/template-meta.d.ts +60 -0
- package/dist/template-resolver/template-meta.js +101 -0
- package/dist/template-resolver/template-preview.d.ts +86 -0
- package/dist/template-resolver/template-preview.js +374 -0
- package/dist/template-resolver/template-resources.d.ts +14 -0
- package/dist/template-resolver/template-resources.js +42 -0
- package/dist/template-resolver/template-usage.d.ts +12 -0
- package/dist/template-resolver/template-usage.js +30 -0
- package/dist/template-resolver/template-validation.d.ts +22 -0
- package/dist/template-resolver/template-validation.js +172 -0
- package/dist/template-resolver/testing.js +5 -5
- package/dist/template-resolver/user-content-table.d.ts +14 -8
- package/dist/template-resolver/web/client-plugin.d.ts +1 -0
- package/dist/template-resolver/web/client-plugin.js +9 -6
- package/dist/template-resolver/web/document-editor-extensions.d.ts +15 -0
- package/dist/template-resolver/web/document-editor-extensions.js +107 -0
- package/dist/template-resolver/web/document-editor.d.ts +3 -0
- package/dist/template-resolver/web/document-editor.js +11 -0
- package/dist/template-resolver/web/document-tiptap-editor.d.ts +3 -0
- package/dist/template-resolver/web/document-tiptap-editor.js +376 -0
- package/dist/template-resolver/web/document-toolbar-blocks.d.ts +33 -0
- package/dist/template-resolver/web/document-toolbar-blocks.js +15 -0
- package/dist/template-resolver/web/editor-handle.d.ts +3 -0
- package/dist/template-resolver/web/editor-handle.js +24 -0
- package/dist/template-resolver/web/i18n.js +76 -0
- package/dist/template-resolver/web/index.d.ts +4 -0
- package/dist/template-resolver/web/index.js +4 -0
- package/dist/template-resolver/web/placeholder-line-editor-lazy.d.ts +4 -0
- package/dist/template-resolver/web/placeholder-line-editor-lazy.js +16 -0
- package/dist/template-resolver/web/placeholder-line-editor.d.ts +15 -0
- package/dist/template-resolver/web/placeholder-line-editor.js +94 -0
- package/dist/template-resolver/web/placeholder-text.d.ts +4 -0
- package/dist/template-resolver/web/placeholder-text.js +34 -0
- package/dist/template-resolver/web/use-stable-by-key.d.ts +1 -0
- package/dist/template-resolver/web/use-stable-by-key.js +13 -0
- package/dist/tenant/handlers/team-list.query.js +3 -2
- package/dist/tenant/seeding.js +31 -6
- package/dist/tenant-lifecycle/handlers/cancel-destruction.write.d.ts +1 -1
- package/dist/tenant-lifecycle/handlers/cancel-destruction.write.js +2 -1
- package/dist/user/db/queries/stream-tenant-backfill.js +4 -4
- package/dist/user/handlers/detail.query.js +2 -1
- package/dist/user-data-rights/db/queries/forget-cleanup.d.ts +2 -2
- package/dist/user-data-rights/email-templates.d.ts +1 -29
- package/dist/user-data-rights/email-templates.js +5 -106
- package/dist/user-data-rights/feature.js +53 -35
- package/dist/user-data-rights/handlers/cancel-deletion.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/cancel-deletion.write.js +2 -1
- package/dist/user-data-rights/handlers/request-deletion.write.js +5 -7
- package/dist/user-data-rights/lib/default-mailers.d.ts +15 -7
- package/dist/user-data-rights/lib/default-mailers.js +100 -48
- package/dist/user-data-rights/lib/update-user-lifecycle.js +1 -1
- package/dist/user-data-rights/mail-notifications.d.ts +13 -0
- package/dist/user-data-rights/mail-notifications.js +118 -0
- package/dist/user-data-rights/web/privacy-center-screen.js +2 -1
- package/dist/user-profile/web/profile-screen.js +2 -1
- package/dist/waitlist/feature.js +10 -0
- package/dist/waitlist/handlers/submit.write.js +3 -4
- package/dist/waitlist/mail.d.ts +19 -6
- package/dist/waitlist/mail.js +50 -14
- package/package.json +28 -17
- package/src/admin-shell/changes.json +6 -0
- package/src/auth-email-password/changes.json +14 -0
- package/src/billing-foundation/changes.json +7 -0
- package/src/channel-email/changes.json +19 -0
- package/src/data-retention/changes.json +6 -0
- package/src/delivery/changes.json +48 -0
- package/src/managed-pages/changes.json +6 -0
- package/src/renderer-foundation/changes.json +39 -0
- package/src/template-resolver/changes.json +118 -0
- package/src/user-data-rights/changes.json +7 -0
- package/src/waitlist/changes.json +7 -0
- package/dist/renderer-simple/feature.d.ts +0 -10
- package/dist/renderer-simple/feature.js +0 -42
- package/dist/renderer-simple/index.d.ts +0 -3
- package/dist/renderer-simple/index.js +0 -2
- package/dist/renderer-simple/mail-blocks.d.ts +0 -21
- package/dist/renderer-simple/mail-blocks.js +0 -107
- package/dist/renderer-simple/resolve-variables.d.ts +0 -7
- package/dist/renderer-simple/resolve-variables.js +0 -47
- package/dist/renderer-simple/simple-renderer.d.ts +0 -24
- package/dist/renderer-simple/simple-renderer.js +0 -200
- package/src/renderer-simple/changes.json +0 -27
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { QnTypes, qn, templateVariable, } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { hasMailTranslationKey, listMailLocales, mailT, resolveMailLocale, } from "@cosmicdrift/kumiko-framework/i18n";
|
|
3
|
+
import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
|
|
4
|
+
import { AUTH_EMAIL_PASSWORD_FEATURE } from "./constants.js";
|
|
5
|
+
export const AUTH_MAIL_KINDS = {
|
|
6
|
+
passwordReset: { name: "password-reset", translationKey: "reset" },
|
|
7
|
+
accountUnlock: { name: "account-unlock", translationKey: "unlock" },
|
|
8
|
+
emailVerification: { name: "email-verification", translationKey: "verify" },
|
|
9
|
+
signupActivation: { name: "signup-activation", translationKey: "activation" },
|
|
10
|
+
invite: { name: "invite", translationKey: "invite" },
|
|
11
|
+
};
|
|
12
|
+
export function sendsMagicLinkMail(flows) {
|
|
13
|
+
return Boolean(flows.passwordReset ||
|
|
14
|
+
flows.emailVerification ||
|
|
15
|
+
flows.signup ||
|
|
16
|
+
flows.invite ||
|
|
17
|
+
flows.accountUnlock);
|
|
18
|
+
}
|
|
19
|
+
function authMailType(kind) {
|
|
20
|
+
return qn(AUTH_EMAIL_PASSWORD_FEATURE, QnTypes.notify, AUTH_MAIL_KINDS[kind].name);
|
|
21
|
+
}
|
|
22
|
+
export const AuthMailNotificationTypes = {
|
|
23
|
+
passwordReset: authMailType("passwordReset"),
|
|
24
|
+
accountUnlock: authMailType("accountUnlock"),
|
|
25
|
+
emailVerification: authMailType("emailVerification"),
|
|
26
|
+
signupActivation: authMailType("signupActivation"),
|
|
27
|
+
invite: authMailType("invite"),
|
|
28
|
+
};
|
|
29
|
+
function subjectKey(kind) {
|
|
30
|
+
return `auth.mail.${AUTH_MAIL_KINDS[kind].translationKey}.subject`;
|
|
31
|
+
}
|
|
32
|
+
const ALL_AUTH_MAIL_KINDS = [
|
|
33
|
+
"passwordReset",
|
|
34
|
+
"accountUnlock",
|
|
35
|
+
"emailVerification",
|
|
36
|
+
"signupActivation",
|
|
37
|
+
"invite",
|
|
38
|
+
];
|
|
39
|
+
const SUBJECT_KEY_BY_TYPE = new Map(ALL_AUTH_MAIL_KINDS.map((kind) => [AuthMailNotificationTypes[kind], subjectKey(kind)]));
|
|
40
|
+
// A registered locale whose bundle lacks the subject key has no template (see
|
|
41
|
+
// authMailLocalizedTemplate); the renderer has no locale fallback, so the
|
|
42
|
+
// sender must pick a locale that has one or the mail would not go out at all.
|
|
43
|
+
export function authMailTemplateLocale(notificationType, requestedLocale) {
|
|
44
|
+
const locale = resolveMailLocale(requestedLocale);
|
|
45
|
+
const key = SUBJECT_KEY_BY_TYPE.get(notificationType);
|
|
46
|
+
return key !== undefined && hasMailTranslationKey(locale, key) ? locale : "en";
|
|
47
|
+
}
|
|
48
|
+
export const AUTH_MAIL_VARIABLES = {
|
|
49
|
+
appName: templateVariable.text({ required: true }),
|
|
50
|
+
actionUrl: templateVariable.link({ required: true }),
|
|
51
|
+
validFor: templateVariable.text({ required: true }),
|
|
52
|
+
validUntil: templateVariable.text({ required: true }),
|
|
53
|
+
};
|
|
54
|
+
export const INVITE_MAIL_VARIABLES = {
|
|
55
|
+
...AUTH_MAIL_VARIABLES,
|
|
56
|
+
role: templateVariable.text({ required: true }),
|
|
57
|
+
};
|
|
58
|
+
// Translation placeholder -> template variable. The markers survive HTML
|
|
59
|
+
// escaping, so only the translated text is escaped, never the placeholders.
|
|
60
|
+
const PLACEHOLDER_VARIABLES = {
|
|
61
|
+
app: "appName",
|
|
62
|
+
duration: "validFor",
|
|
63
|
+
when: "validUntil",
|
|
64
|
+
role: "role",
|
|
65
|
+
};
|
|
66
|
+
const MARKER_OPEN = "\uE000";
|
|
67
|
+
const MARKER_CLOSE = "\uE001";
|
|
68
|
+
const MARKER_PATTERN = new RegExp(`${MARKER_OPEN}(\\w+)${MARKER_CLOSE}`, "g");
|
|
69
|
+
function markers() {
|
|
70
|
+
return Object.fromEntries(Object.keys(PLACEHOLDER_VARIABLES).map((name) => [
|
|
71
|
+
name,
|
|
72
|
+
`${MARKER_OPEN}${name}${MARKER_CLOSE}`,
|
|
73
|
+
]));
|
|
74
|
+
}
|
|
75
|
+
function translatedHtml(locale, key) {
|
|
76
|
+
return escapeHtml(mailT(locale, key, markers())).replace(MARKER_PATTERN, (_, name) => {
|
|
77
|
+
const variable = PLACEHOLDER_VARIABLES[name];
|
|
78
|
+
return variable === undefined
|
|
79
|
+
? ""
|
|
80
|
+
: `<span data-variable="${escapeHtmlAttr(variable)}"></span>`;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function mailBody(locale, translationKey) {
|
|
84
|
+
const html = (part) => translatedHtml(locale, `auth.mail.${translationKey}.${part}`);
|
|
85
|
+
return [
|
|
86
|
+
`<p>${html("greeting")}</p>`,
|
|
87
|
+
`<p>${html("intro")}</p>`,
|
|
88
|
+
`<p><a data-button href="{{actionUrl}}">${html("button")}</a></p>`,
|
|
89
|
+
`<p>${html("expiry")}</p>`,
|
|
90
|
+
`<p>${html("ignore")}</p>`,
|
|
91
|
+
].join("");
|
|
92
|
+
}
|
|
93
|
+
// Evaluated when the registry is built, after every locale feature has
|
|
94
|
+
// registered its mail translations. English always ships; another locale only
|
|
95
|
+
// gets a template when it has its own subject, so a missing translation never
|
|
96
|
+
// silently becomes an English mail in that locale's slot.
|
|
97
|
+
export function authMailLocalizedTemplate(kind) {
|
|
98
|
+
const { translationKey } = AUTH_MAIL_KINDS[kind];
|
|
99
|
+
const kindSubjectKey = subjectKey(kind);
|
|
100
|
+
return () => {
|
|
101
|
+
const locales = [
|
|
102
|
+
"en",
|
|
103
|
+
...listMailLocales().filter((locale) => locale !== "en" && hasMailTranslationKey(locale, kindSubjectKey)),
|
|
104
|
+
];
|
|
105
|
+
return Object.fromEntries(locales.map((locale) => [
|
|
106
|
+
locale,
|
|
107
|
+
{
|
|
108
|
+
body: mailBody(locale, translationKey),
|
|
109
|
+
meta: {
|
|
110
|
+
subject: mailT(locale, kindSubjectKey, { app: "{{appName}}" }),
|
|
111
|
+
category: "transactional",
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
]));
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { NotifyFn } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import type { Redis } from "ioredis";
|
|
3
|
-
export declare const SIGNUP_ACTIVATION_NOTIFICATION_TYPE
|
|
3
|
+
export declare const SIGNUP_ACTIVATION_NOTIFICATION_TYPE: string;
|
|
4
4
|
export type IssueSignupActivationArgs = {
|
|
5
5
|
readonly redis: Redis;
|
|
6
6
|
readonly notify: NotifyFn | undefined;
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
import { generateToken } from "@cosmicdrift/kumiko-framework/api";
|
|
7
7
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
8
8
|
import { AUTH_SIGNUP_DEFAULT_TTL_MINUTES } from "./constants.js";
|
|
9
|
-
import { renderActivationEmail } from "./email-templates.js";
|
|
10
9
|
import { dispatchMagicLinkMail } from "./magic-link-mail.js";
|
|
10
|
+
import { AuthMailNotificationTypes } from "./mail-notifications.js";
|
|
11
11
|
import { invalidateExistingSignupToken, normalizeEmail, storeSignupToken, } from "./signup-token-store.js";
|
|
12
|
-
export const SIGNUP_ACTIVATION_NOTIFICATION_TYPE =
|
|
12
|
+
export const SIGNUP_ACTIVATION_NOTIFICATION_TYPE = AuthMailNotificationTypes.signupActivation;
|
|
13
13
|
export async function issueSignupActivation(args) {
|
|
14
14
|
const ttlSeconds = (args.tokenTtlMinutes ?? AUTH_SIGNUP_DEFAULT_TTL_MINUTES) * 60;
|
|
15
15
|
// At most one live signup token per email.
|
|
@@ -26,7 +26,6 @@ export async function issueSignupActivation(args) {
|
|
|
26
26
|
await dispatchMagicLinkMail(args.notify, {
|
|
27
27
|
handlerName: "signup-request",
|
|
28
28
|
notificationType: SIGNUP_ACTIVATION_NOTIFICATION_TYPE,
|
|
29
|
-
renderContent: renderActivationEmail,
|
|
30
29
|
}, {
|
|
31
30
|
email,
|
|
32
31
|
appUrl: args.appUrl,
|
|
@@ -47,10 +47,7 @@ export function tokenShapeKey(shape) {
|
|
|
47
47
|
// exactly one implementation must be registered (boot-fails on 0 or ≥2).
|
|
48
48
|
export const EXT_SESSION_STORE = "sessionStore";
|
|
49
49
|
export function isSessionStoreProvider(o) {
|
|
50
|
-
return
|
|
51
|
-
o !== null &&
|
|
52
|
-
"build" in o &&
|
|
53
|
-
typeof o.build === "function");
|
|
50
|
+
return typeof o === "object" && o !== null && "build" in o && typeof o.build === "function";
|
|
54
51
|
}
|
|
55
52
|
// Tenant-resolution extension points (#1373). Both are single-provider and
|
|
56
53
|
// optional — zero registered means today's single-tenant / header-cookie
|
|
@@ -69,8 +66,5 @@ export function isTenantResolverProvider(o) {
|
|
|
69
66
|
isTenantResolverTrust(o.trust));
|
|
70
67
|
}
|
|
71
68
|
export function isTenantExistenceProvider(o) {
|
|
72
|
-
return
|
|
73
|
-
o !== null &&
|
|
74
|
-
"build" in o &&
|
|
75
|
-
typeof o.build === "function");
|
|
69
|
+
return typeof o === "object" && o !== null && "build" in o && typeof o.build === "function";
|
|
76
70
|
}
|
package/dist/auth-mfa/feature.js
CHANGED
|
@@ -25,7 +25,7 @@ export function bindMfaRevokeAllOtherSessionsFromFeature(feature) {
|
|
|
25
25
|
if (exports && typeof exports === "object" && "bindRevokeAllOtherSessions" in exports) {
|
|
26
26
|
const { bindRevokeAllOtherSessions } = exports;
|
|
27
27
|
if (typeof bindRevokeAllOtherSessions === "function") {
|
|
28
|
-
// @cast-boundary exports
|
|
28
|
+
// @cast-boundary feature-exports — feature.exports is untyped by design
|
|
29
29
|
return bindRevokeAllOtherSessions;
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -38,7 +38,7 @@ export function mfaStatusCheckerFromFeature(feature) {
|
|
|
38
38
|
if (exports && typeof exports === "object" && "checkMfaStatus" in exports) {
|
|
39
39
|
const { checkMfaStatus } = exports;
|
|
40
40
|
if (typeof checkMfaStatus === "function") {
|
|
41
|
-
// @cast-boundary exports
|
|
41
|
+
// @cast-boundary feature-exports — feature.exports is untyped by design
|
|
42
42
|
return checkMfaStatus;
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -53,7 +53,7 @@ export function mfaVerifierFromFeature(feature) {
|
|
|
53
53
|
if (exports && typeof exports === "object" && "verifyMfaCode" in exports) {
|
|
54
54
|
const { verifyMfaCode } = exports;
|
|
55
55
|
if (typeof verifyMfaCode === "function") {
|
|
56
|
-
// @cast-boundary exports
|
|
56
|
+
// @cast-boundary feature-exports — feature.exports is untyped by design
|
|
57
57
|
return verifyMfaCode;
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -68,7 +68,7 @@ export function bindRevokeAllPatTokensFromFeature(feature) {
|
|
|
68
68
|
if (exports && typeof exports === "object" && "bindRevokeAllPatTokens" in exports) {
|
|
69
69
|
const { bindRevokeAllPatTokens } = exports;
|
|
70
70
|
if (typeof bindRevokeAllPatTokens === "function") {
|
|
71
|
-
// @cast-boundary exports
|
|
71
|
+
// @cast-boundary feature-exports — feature.exports is untyped by design
|
|
72
72
|
return bindRevokeAllPatTokens;
|
|
73
73
|
}
|
|
74
74
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
2
2
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
3
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
3
4
|
function hmacSign(input, secret) {
|
|
4
5
|
return createHmac("sha256", secret).update(input).digest("base64url");
|
|
5
6
|
}
|
|
@@ -8,13 +9,12 @@ function hmacSign(input, secret) {
|
|
|
8
9
|
// replayed as a challenge token), since both share the same secret and payload shape.
|
|
9
10
|
export function createHmacTokenCodec(domain, isPayload) {
|
|
10
11
|
function splitEncodedBody(value) {
|
|
11
|
-
if (
|
|
12
|
+
if (!isPlainObject(value))
|
|
12
13
|
return null;
|
|
13
|
-
const
|
|
14
|
-
const expiresAtMs = v["expiresAtMs"];
|
|
14
|
+
const expiresAtMs = value["expiresAtMs"];
|
|
15
15
|
if (typeof expiresAtMs !== "number")
|
|
16
16
|
return null;
|
|
17
|
-
const { expiresAtMs: _drop, ...rest } =
|
|
17
|
+
const { expiresAtMs: _drop, ...rest } = value;
|
|
18
18
|
return isPayload(rest) ? { payload: rest, expiresAtMs } : null;
|
|
19
19
|
}
|
|
20
20
|
return {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
2
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
2
3
|
import { createHmacTokenCodec } from "./hmac-token-codec.js";
|
|
3
4
|
function isPayload(value) {
|
|
4
|
-
if (
|
|
5
|
+
if (!isPlainObject(value))
|
|
5
6
|
return false;
|
|
6
|
-
|
|
7
|
-
return typeof v["userId"] === "string" && typeof v["tenantId"] === "string";
|
|
7
|
+
return typeof value["userId"] === "string" && typeof value["tenantId"] === "string";
|
|
8
8
|
}
|
|
9
9
|
// Domain-separated from mfa-preauth-setup (see hmac-token-codec). Single-use is
|
|
10
10
|
// enforced by the consumer via burnToken (purpose "mfa-challenge"); this codec
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
2
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
2
3
|
import { createHmacTokenCodec } from "./hmac-token-codec.js";
|
|
3
4
|
function isPayload(value) {
|
|
4
|
-
if (
|
|
5
|
+
if (!isPlainObject(value))
|
|
5
6
|
return false;
|
|
6
|
-
|
|
7
|
-
return typeof v["userId"] === "string" && typeof v["tenantId"] === "string";
|
|
7
|
+
return typeof value["userId"] === "string" && typeof value["tenantId"] === "string";
|
|
8
8
|
}
|
|
9
9
|
// Domain "mfa-preauth-setup" must never equal the challenge domain: a setup
|
|
10
10
|
// token has to fail verification as a challenge token and vice versa. The
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// generated secret material, not just re-derive it from a lookup.
|
|
12
12
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
13
13
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
14
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
14
15
|
function sign(input, secret) {
|
|
15
16
|
return createHmac("sha256", secret).update(input).digest("base64url");
|
|
16
17
|
}
|
|
@@ -22,15 +23,14 @@ export function signMfaSetupToken(payload, ttlMinutes, secret, now = Temporal.No
|
|
|
22
23
|
return { token: `${bodyB64}.${signature}`, expiresAt };
|
|
23
24
|
}
|
|
24
25
|
function isEncodedBody(value) {
|
|
25
|
-
if (
|
|
26
|
+
if (!isPlainObject(value))
|
|
26
27
|
return false;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
typeof
|
|
33
|
-
(v["tenantId"] === undefined || typeof v["tenantId"] === "string"));
|
|
28
|
+
return (typeof value["userId"] === "string" &&
|
|
29
|
+
typeof value["totpSecretBase32"] === "string" &&
|
|
30
|
+
Array.isArray(value["recoveryCodeHashes"]) &&
|
|
31
|
+
value["recoveryCodeHashes"].every((h) => typeof h === "string") &&
|
|
32
|
+
typeof value["expiresAtMs"] === "number" &&
|
|
33
|
+
(value["tenantId"] === undefined || typeof value["tenantId"] === "string"));
|
|
34
34
|
}
|
|
35
35
|
export function verifyMfaSetupToken(token, secret, now = Temporal.Now.instant()) {
|
|
36
36
|
const parts = token.split(".");
|
|
@@ -3,11 +3,11 @@ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
|
3
3
|
export declare const BILLING_FOUNDATION_FEATURE: "billing-foundation";
|
|
4
4
|
export declare const SUBSCRIPTION_PROVIDER_EXTENSION: "subscriptionProvider";
|
|
5
5
|
/** delivery notification type of the § 312f contract confirmation mail. */
|
|
6
|
-
export declare const CONTRACT_CONFIRMATION_NOTIFICATION_TYPE: "billing-foundation:contract-confirmation";
|
|
6
|
+
export declare const CONTRACT_CONFIRMATION_NOTIFICATION_TYPE: "billing-foundation:notify:contract-confirmation";
|
|
7
7
|
/** delivery notification types of the § 312k termination receipt and the
|
|
8
8
|
* operator notice. */
|
|
9
|
-
export declare const CONTRACT_TERMINATION_RECEIPT_NOTIFICATION_TYPE: "billing-foundation:contract-termination-receipt";
|
|
10
|
-
export declare const CONTRACT_TERMINATION_OPERATOR_NOTIFICATION_TYPE: "billing-foundation:contract-termination-operator-notice";
|
|
9
|
+
export declare const CONTRACT_TERMINATION_RECEIPT_NOTIFICATION_TYPE: "billing-foundation:notify:contract-termination-receipt";
|
|
10
|
+
export declare const CONTRACT_TERMINATION_OPERATOR_NOTIFICATION_TYPE: "billing-foundation:notify:contract-termination-operator-notice";
|
|
11
11
|
export declare const SubscriptionFoundationHandlers: {
|
|
12
12
|
/** Programmatic entry-point für den webhook-handler. Receives the
|
|
13
13
|
* parsed SubscriptionEvent (vom Plugin schon verifiziert) + macht
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
/// <reference types="temporal-polyfill/global" preserve="true" />
|
|
2
2
|
// @runtime client
|
|
3
3
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
4
|
+
import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
4
5
|
// Feature name
|
|
5
6
|
export const BILLING_FOUNDATION_FEATURE = "billing-foundation";
|
|
6
7
|
// Extension-point name fuer Provider-Plugins (subscription-stripe,
|
|
7
8
|
// subscription-mollie, ...).
|
|
8
9
|
export const SUBSCRIPTION_PROVIDER_EXTENSION = "subscriptionProvider";
|
|
9
10
|
/** delivery notification type of the § 312f contract confirmation mail. */
|
|
10
|
-
export const CONTRACT_CONFIRMATION_NOTIFICATION_TYPE = "billing-foundation:contract-confirmation";
|
|
11
|
+
export const CONTRACT_CONFIRMATION_NOTIFICATION_TYPE = "billing-foundation:notify:contract-confirmation";
|
|
11
12
|
/** delivery notification types of the § 312k termination receipt and the
|
|
12
13
|
* operator notice. */
|
|
13
|
-
export const CONTRACT_TERMINATION_RECEIPT_NOTIFICATION_TYPE = "billing-foundation:contract-termination-receipt";
|
|
14
|
-
export const CONTRACT_TERMINATION_OPERATOR_NOTIFICATION_TYPE = "billing-foundation:contract-termination-operator-notice";
|
|
14
|
+
export const CONTRACT_TERMINATION_RECEIPT_NOTIFICATION_TYPE = "billing-foundation:notify:contract-termination-receipt";
|
|
15
|
+
export const CONTRACT_TERMINATION_OPERATOR_NOTIFICATION_TYPE = "billing-foundation:notify:contract-termination-operator-notice";
|
|
15
16
|
// Qualified write handler names (QN format: scope:type:name).
|
|
16
17
|
export const SubscriptionFoundationHandlers = {
|
|
17
18
|
/** Programmatic entry-point für den webhook-handler. Receives the
|
|
@@ -195,5 +196,5 @@ export const DEFAULT_PURCHASE_ROLES = ["TenantAdmin", "SystemAdmin"];
|
|
|
195
196
|
// `<plugin-name>:config:price-to-tier`-Key.
|
|
196
197
|
export const TERMINATION_SCOPES = ["tenant-host", "platform"];
|
|
197
198
|
export function isTerminationScope(value) {
|
|
198
|
-
return TERMINATION_SCOPES
|
|
199
|
+
return isOneOf(TERMINATION_SCOPES, value);
|
|
199
200
|
}
|
|
@@ -1,18 +1,37 @@
|
|
|
1
1
|
import type { CheckoutConsentRecordedPayload } from "../events.js";
|
|
2
|
-
|
|
2
|
+
import { type ConsentLocale } from "./consent-text.js";
|
|
3
|
+
type ConfirmationLabels = {
|
|
4
|
+
readonly subject: string;
|
|
5
|
+
readonly intro: string;
|
|
6
|
+
readonly plan: string;
|
|
7
|
+
readonly item: string;
|
|
8
|
+
readonly oneOffPayment: string;
|
|
9
|
+
readonly price: string;
|
|
10
|
+
readonly oneOff: string;
|
|
11
|
+
readonly contractStart: string;
|
|
12
|
+
readonly currentPeriodEnd: string;
|
|
13
|
+
readonly consentHeading: string;
|
|
14
|
+
readonly consentGivenAt: string;
|
|
15
|
+
readonly termsHeading: string;
|
|
16
|
+
readonly contact: string;
|
|
17
|
+
readonly intervals: Readonly<Record<string, {
|
|
18
|
+
one: string;
|
|
19
|
+
many: string;
|
|
20
|
+
}>>;
|
|
21
|
+
readonly every: string;
|
|
22
|
+
};
|
|
23
|
+
export declare const CONFIRMATION_LABELS: Readonly<Record<ConsentLocale, ConfirmationLabels>>;
|
|
24
|
+
type TextRow = {
|
|
3
25
|
readonly text: string;
|
|
4
|
-
} | {
|
|
5
|
-
readonly heading: string;
|
|
6
|
-
} | {
|
|
7
|
-
readonly markdown: string;
|
|
8
26
|
};
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
readonly
|
|
12
|
-
readonly
|
|
13
|
-
readonly
|
|
27
|
+
/** Variables of the contract confirmation template. */
|
|
28
|
+
export type ContractConfirmationVariables = {
|
|
29
|
+
readonly contractLines: readonly TextRow[];
|
|
30
|
+
readonly consentGivenAt: string;
|
|
31
|
+
readonly termsParagraphs: readonly TextRow[];
|
|
32
|
+
readonly operatorEmail: string;
|
|
14
33
|
};
|
|
15
|
-
export type
|
|
34
|
+
export type ContractConfirmationArgs = {
|
|
16
35
|
readonly consent: CheckoutConsentRecordedPayload;
|
|
17
36
|
readonly timeZone: string;
|
|
18
37
|
/** Translated plan name; falls back to the raw tier. */
|
|
@@ -26,6 +45,5 @@ export type RenderContractConfirmationArgs = {
|
|
|
26
45
|
readonly operatorEmail: string;
|
|
27
46
|
readonly termsContent: string;
|
|
28
47
|
};
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
export declare function renderContractConfirmation(args: RenderContractConfirmationArgs): ContractConfirmationContent;
|
|
48
|
+
export declare function contractConfirmationVariables(args: ContractConfirmationArgs): ContractConfirmationVariables;
|
|
49
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
2
|
-
import {
|
|
3
|
-
const
|
|
2
|
+
import { resolveConsentLocale } from "./consent-text.js";
|
|
3
|
+
export const CONFIRMATION_LABELS = {
|
|
4
4
|
de: {
|
|
5
5
|
subject: "Vertragsbestätigung",
|
|
6
6
|
intro: "Vielen Dank für deine Bestellung. Hiermit bestätigen wir den Vertragsschluss mit folgendem Inhalt.",
|
|
@@ -47,7 +47,7 @@ const LABELS = {
|
|
|
47
47
|
},
|
|
48
48
|
};
|
|
49
49
|
function formatPrice(consent, locale) {
|
|
50
|
-
const labels =
|
|
50
|
+
const labels = CONFIRMATION_LABELS[locale];
|
|
51
51
|
if (consent.unitAmount === null || consent.currency === null)
|
|
52
52
|
return "-";
|
|
53
53
|
let amount;
|
|
@@ -89,40 +89,35 @@ function formatDate(iso, locale, timeZone) {
|
|
|
89
89
|
timeZoneName: "short",
|
|
90
90
|
}).format(epochMilliseconds);
|
|
91
91
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
// The mail template only knows plain text variables, so the Markdown terms
|
|
93
|
+
// become one row per paragraph; heading markers are dropped.
|
|
94
|
+
function termsParagraphsOf(markdown) {
|
|
95
|
+
return markdown
|
|
96
|
+
.split(/\n\s*\n/)
|
|
97
|
+
.map((paragraph) => paragraph.replace(/^#{1,6}\s+/gm, "").trim())
|
|
98
|
+
.filter((paragraph) => paragraph.length > 0)
|
|
99
|
+
.map((text) => ({ text }));
|
|
100
|
+
}
|
|
101
|
+
export function contractConfirmationVariables(args) {
|
|
95
102
|
const locale = resolveConsentLocale(args.consent.locale);
|
|
96
|
-
const labels =
|
|
97
|
-
const
|
|
98
|
-
const { consent } = args;
|
|
99
|
-
const { timeZone } = args;
|
|
103
|
+
const labels = CONFIRMATION_LABELS[locale];
|
|
104
|
+
const { consent, timeZone } = args;
|
|
100
105
|
const subjectLine = consent.mode === "payment"
|
|
101
106
|
? `${labels.item}: ${args.itemLabel ?? labels.oneOffPayment}`
|
|
102
107
|
: `${labels.plan}: ${args.tierLabel ?? consent.tier ?? labels.oneOffPayment}`;
|
|
103
|
-
const contractLines = [
|
|
104
|
-
subjectLine,
|
|
105
|
-
`${labels.price}: ${formatPrice(consent, locale)}`,
|
|
106
|
-
`${labels.contractStart}: ${formatDate(args.contractStartIso, locale, timeZone)}`,
|
|
107
|
-
...(args.currentPeriodEndIso !== undefined
|
|
108
|
-
? [`${labels.currentPeriodEnd}: ${formatDate(args.currentPeriodEndIso, locale, timeZone)}`]
|
|
109
|
-
: []),
|
|
110
|
-
];
|
|
111
108
|
const vatNote = args.vatNote[locale] ?? args.vatNote[resolveConsentLocale()];
|
|
112
109
|
return {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
{
|
|
117
|
-
...
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
],
|
|
126
|
-
footer: `${labels.contact} ${args.operatorEmail}`,
|
|
110
|
+
contractLines: [
|
|
111
|
+
subjectLine,
|
|
112
|
+
`${labels.price}: ${formatPrice(consent, locale)}`,
|
|
113
|
+
`${labels.contractStart}: ${formatDate(args.contractStartIso, locale, timeZone)}`,
|
|
114
|
+
...(args.currentPeriodEndIso !== undefined
|
|
115
|
+
? [`${labels.currentPeriodEnd}: ${formatDate(args.currentPeriodEndIso, locale, timeZone)}`]
|
|
116
|
+
: []),
|
|
117
|
+
...(vatNote !== undefined ? [vatNote] : []),
|
|
118
|
+
].map((text) => ({ text })),
|
|
119
|
+
consentGivenAt: formatDate(args.consentGivenAtIso, locale, timeZone),
|
|
120
|
+
termsParagraphs: termsParagraphsOf(args.termsContent),
|
|
121
|
+
operatorEmail: args.operatorEmail,
|
|
127
122
|
};
|
|
128
123
|
}
|
|
@@ -11,7 +11,7 @@ import { UserQueries } from "../../user/index.js";
|
|
|
11
11
|
import { paymentAggregateId, subscriptionAggregateId } from "../aggregate-id.js";
|
|
12
12
|
import { CONTRACT_CONFIRMATION_NOTIFICATION_TYPE } from "../constants.js";
|
|
13
13
|
import { CHECKOUT_CONSENT_RECORDED_EVENT_QN, CONTRACT_CONFIRMATION_ISSUED_EVENT_QN, checkoutConsentRecordedPayloadSchema, contractConfirmationIssuedPayloadSchema, PAYMENT_AGGREGATE_TYPE, SUBSCRIPTION_AGGREGATE_TYPE, } from "../events.js";
|
|
14
|
-
import {
|
|
14
|
+
import { contractConfirmationVariables } from "./confirmation-mail.js";
|
|
15
15
|
import { resolveConsentLocale } from "./consent-text.js";
|
|
16
16
|
export const issueContractConfirmationSchema = z
|
|
17
17
|
.object({
|
|
@@ -128,7 +128,7 @@ export function createIssueContractConfirmationHandler(options, consumerProtecti
|
|
|
128
128
|
? translatedOrUndefined(i18n.t(consent.itemLabelKey, locale, consent.itemLabelParams), consent.itemLabelKey)
|
|
129
129
|
: undefined;
|
|
130
130
|
const issuedAtIso = options.now().toString();
|
|
131
|
-
const
|
|
131
|
+
const variables = contractConfirmationVariables({
|
|
132
132
|
consent,
|
|
133
133
|
timeZone: pickTimeZone(recipient.data.timezone, ctx.tz.tenant),
|
|
134
134
|
...(tierLabel !== undefined && { tierLabel }),
|
|
@@ -159,7 +159,7 @@ export function createIssueContractConfirmationHandler(options, consumerProtecti
|
|
|
159
159
|
await stream.appendOne({ type: CONTRACT_CONFIRMATION_ISSUED_EVENT_QN, payload: issued });
|
|
160
160
|
await ctx.notify(CONTRACT_CONFIRMATION_NOTIFICATION_TYPE, {
|
|
161
161
|
route: { email: recipient.data.email },
|
|
162
|
-
data:
|
|
162
|
+
data: variables,
|
|
163
163
|
locale,
|
|
164
164
|
priority: "critical",
|
|
165
165
|
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type FeatureRegistrar, type NotificationLocalizedTemplate, type TemplateVariables } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
export declare const TERMINATION_RECEIPT_VARIABLES: TemplateVariables;
|
|
3
|
+
export declare const OPERATOR_NOTICE_VARIABLES: TemplateVariables;
|
|
4
|
+
export declare const CONTRACT_CONFIRMATION_VARIABLES: TemplateVariables;
|
|
5
|
+
export declare function terminationReceiptTemplate(): NotificationLocalizedTemplate;
|
|
6
|
+
export declare function operatorNoticeTemplate(): NotificationLocalizedTemplate;
|
|
7
|
+
export declare function contractConfirmationTemplate(): NotificationLocalizedTemplate;
|
|
8
|
+
export declare function registerConsumerProtectionMails(r: FeatureRegistrar): void;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { templateVariable, } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { mailHeading, mailParagraph, mailRepeatedRows } from "../../shared/index.js";
|
|
3
|
+
import { CONFIRMATION_LABELS } from "./confirmation-mail.js";
|
|
4
|
+
import { CONSENT_LOCALES, CONSENT_TEXTS } from "./consent-text.js";
|
|
5
|
+
import { TERMINATION_TEXTS } from "./termination-texts.js";
|
|
6
|
+
// Slot syntax for the translated texts: `{name}` becomes a template variable.
|
|
7
|
+
const OPERATOR_EMAIL_SLOT = "{operatorEmail}";
|
|
8
|
+
const textRows = () => templateVariable.list({ text: templateVariable.text({ required: true }) }, { required: true });
|
|
9
|
+
export const TERMINATION_RECEIPT_VARIABLES = {
|
|
10
|
+
subject: templateVariable.text({ required: true }),
|
|
11
|
+
receivedAt: templateVariable.text({ required: true }),
|
|
12
|
+
requestId: templateVariable.text({ required: true }),
|
|
13
|
+
details: textRows(),
|
|
14
|
+
operatorEmail: templateVariable.text({ required: true }),
|
|
15
|
+
};
|
|
16
|
+
export const OPERATOR_NOTICE_VARIABLES = {
|
|
17
|
+
subject: templateVariable.text({ required: true }),
|
|
18
|
+
lines: textRows(),
|
|
19
|
+
operatorEmail: templateVariable.text({ required: true }),
|
|
20
|
+
};
|
|
21
|
+
export const CONTRACT_CONFIRMATION_VARIABLES = {
|
|
22
|
+
contractLines: textRows(),
|
|
23
|
+
consentGivenAt: templateVariable.text({ required: true }),
|
|
24
|
+
termsParagraphs: textRows(),
|
|
25
|
+
operatorEmail: templateVariable.text({ required: true }),
|
|
26
|
+
};
|
|
27
|
+
function perLocale(build) {
|
|
28
|
+
return Object.fromEntries(CONSENT_LOCALES.map((locale) => {
|
|
29
|
+
const { body, subject } = build(locale);
|
|
30
|
+
return [locale, { body, meta: { subject, category: "transactional" } }];
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
export function terminationReceiptTemplate() {
|
|
34
|
+
return perLocale((locale) => {
|
|
35
|
+
const texts = TERMINATION_TEXTS[locale];
|
|
36
|
+
return {
|
|
37
|
+
subject: "{{subject}}",
|
|
38
|
+
body: [
|
|
39
|
+
mailParagraph(texts.receiptGreeting),
|
|
40
|
+
mailParagraph(texts.receiptIntro),
|
|
41
|
+
mailParagraph(`${texts.receivedAt}: {receivedAt}`),
|
|
42
|
+
mailParagraph(`${texts.requestId}: {requestId}`),
|
|
43
|
+
mailRepeatedRows("details", "text"),
|
|
44
|
+
mailParagraph(texts.receiptNextSteps),
|
|
45
|
+
mailParagraph(texts.receiptRevoke(OPERATOR_EMAIL_SLOT)),
|
|
46
|
+
mailParagraph(`${texts.contact} {operatorEmail}`),
|
|
47
|
+
].join(""),
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export function operatorNoticeTemplate() {
|
|
52
|
+
return perLocale((locale) => ({
|
|
53
|
+
subject: "{{subject}}",
|
|
54
|
+
body: [
|
|
55
|
+
mailParagraph(TERMINATION_TEXTS[locale].operatorIntro),
|
|
56
|
+
mailRepeatedRows("lines", "text"),
|
|
57
|
+
mailParagraph("{operatorEmail}"),
|
|
58
|
+
].join(""),
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
export function contractConfirmationTemplate() {
|
|
62
|
+
return perLocale((locale) => {
|
|
63
|
+
const labels = CONFIRMATION_LABELS[locale];
|
|
64
|
+
const consent = CONSENT_TEXTS[locale];
|
|
65
|
+
return {
|
|
66
|
+
subject: labels.subject,
|
|
67
|
+
body: [
|
|
68
|
+
mailParagraph(labels.intro),
|
|
69
|
+
mailRepeatedRows("contractLines", "text"),
|
|
70
|
+
mailHeading(labels.consentHeading),
|
|
71
|
+
mailParagraph(consent.earlyPerformance),
|
|
72
|
+
mailParagraph(consent.withdrawalLoss),
|
|
73
|
+
mailParagraph(`${labels.consentGivenAt}: {consentGivenAt}`),
|
|
74
|
+
mailHeading(labels.termsHeading),
|
|
75
|
+
mailRepeatedRows("termsParagraphs", "text"),
|
|
76
|
+
mailParagraph(`${labels.contact} {operatorEmail}`),
|
|
77
|
+
].join(""),
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
// The registered notification names are the tail of the QN types in constants.ts.
|
|
82
|
+
export function registerConsumerProtectionMails(r) {
|
|
83
|
+
r.notification("contract-confirmation", {
|
|
84
|
+
variables: CONTRACT_CONFIRMATION_VARIABLES,
|
|
85
|
+
localizedTemplate: contractConfirmationTemplate,
|
|
86
|
+
});
|
|
87
|
+
r.notification("contract-termination-receipt", {
|
|
88
|
+
variables: TERMINATION_RECEIPT_VARIABLES,
|
|
89
|
+
localizedTemplate: terminationReceiptTemplate,
|
|
90
|
+
});
|
|
91
|
+
r.notification("contract-termination-operator-notice", {
|
|
92
|
+
variables: OPERATOR_NOTICE_VARIABLES,
|
|
93
|
+
localizedTemplate: operatorNoticeTemplate,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
@@ -2,6 +2,7 @@ import { paymentAggregateId, subscriptionAggregateId } from "../aggregate-id.js"
|
|
|
2
2
|
import { SubscriptionFoundationHandlers, SubscriptionStatuses } from "../constants.js";
|
|
3
3
|
import { CHECKOUT_CONSENT_RECORDED_EVENT_SHORT, CONTRACT_CONFIRMATION_ISSUED_EVENT_SHORT, checkoutConsentRecordedPayloadSchema, contractConfirmationIssuedPayloadSchema, INVOICE_PAID_EVENT_QN, PAYMENT_RECEIVED_EVENT_QN, SUBSCRIPTION_CREATED_EVENT_QN, SUBSCRIPTION_UPDATED_EVENT_QN, } from "../events.js";
|
|
4
4
|
import { createIssueContractConfirmationHandler } from "./issue-confirmation.js";
|
|
5
|
+
import { registerConsumerProtectionMails } from "./mail-notifications.js";
|
|
5
6
|
import { registerContractTermination } from "./register-termination.js";
|
|
6
7
|
const CONFIRMATION_TRIGGERS = [
|
|
7
8
|
{
|
|
@@ -22,6 +23,7 @@ function isConfirmableSubscriptionStatus(status) {
|
|
|
22
23
|
}
|
|
23
24
|
export function registerConsumerProtection(r, options, consumerProtection) {
|
|
24
25
|
r.requires("template-resolver", "delivery", "user", "tenant");
|
|
26
|
+
registerConsumerProtectionMails(r);
|
|
25
27
|
registerContractTermination(r, options, consumerProtection);
|
|
26
28
|
r.defineEvent(CHECKOUT_CONSENT_RECORDED_EVENT_SHORT, checkoutConsentRecordedPayloadSchema, {
|
|
27
29
|
piiFields: "none",
|