@hartl-services/medusa-base 2.6.0 → 2.7.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 (149) hide show
  1. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/ImageSettingInput.d.ts +3 -2
  2. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/ImageSettingInput.js +4 -4
  3. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginActionsSection.js +4 -4
  4. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginBreadcrumb.d.ts +16 -0
  5. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginBreadcrumb.js +25 -0
  6. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginDetailPage.js +1 -1
  7. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginFeatureSections.d.ts +5 -4
  8. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginFeatureSections.js +8 -5
  9. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginFeaturesCard.d.ts +4 -4
  10. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginFeaturesCard.js +24 -27
  11. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginFeaturesSettingsPage.d.ts +2 -2
  12. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginFeaturesSettingsPage.js +9 -9
  13. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginHubCard.d.ts +5 -1
  14. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginHubCard.js +11 -15
  15. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginPageShell.d.ts +10 -4
  16. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginPageShell.js +10 -5
  17. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginSettingsForm.d.ts +0 -1
  18. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginSettingsForm.js +73 -26
  19. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginStatusChips.d.ts +5 -1
  20. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginStatusChips.js +8 -6
  21. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/ShopMasterDataBanner.d.ts +11 -0
  22. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/ShopMasterDataBanner.js +15 -0
  23. package/.medusa/server/src/admin/admin-components/features/plugin-features/hooks/usePluginFeatureEnabled.d.ts +36 -5
  24. package/.medusa/server/src/admin/admin-components/features/plugin-features/hooks/usePluginFeatures.d.ts +144 -20
  25. package/.medusa/server/src/admin/admin-components/features/plugin-features/hooks/usePluginSettingsForm.d.ts +36 -5
  26. package/.medusa/server/src/admin/admin-components/features/plugin-features/hooks/usePluginSwitch.d.ts +15 -0
  27. package/.medusa/server/src/admin/admin-components/features/plugin-features/hooks/usePluginSwitch.js +66 -0
  28. package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/plugin-features-admin-client.d.ts +144 -20
  29. package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/plugin-view.d.ts +49 -0
  30. package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/plugin-view.js +72 -0
  31. package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/settings-draft.d.ts +1 -12
  32. package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/settings-draft.js +4 -20
  33. package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/settings-layout.d.ts +43 -0
  34. package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/settings-layout.js +66 -0
  35. package/.medusa/server/src/admin/admin-components/i18n/texts.d.ts +22 -2
  36. package/.medusa/server/src/admin/admin-components/i18n/texts.js +29 -7
  37. package/.medusa/server/src/admin/admin-components/index.d.ts +12 -0
  38. package/.medusa/server/src/admin/admin-components/index.js +11 -0
  39. package/.medusa/server/src/admin/admin-components/shared/ui/AdminPageHeader.d.ts +22 -0
  40. package/.medusa/server/src/admin/admin-components/shared/ui/AdminPageHeader.js +8 -0
  41. package/.medusa/server/src/admin/admin-components/shared/ui/AdminSection.d.ts +26 -0
  42. package/.medusa/server/src/admin/admin-components/shared/ui/AdminSection.js +12 -0
  43. package/.medusa/server/src/admin/admin-components/shared/ui/BreadcrumbTrail.d.ts +23 -0
  44. package/.medusa/server/src/admin/admin-components/shared/ui/BreadcrumbTrail.js +20 -0
  45. package/.medusa/server/src/admin/admin-components/shared/ui/FileDropField.d.ts +21 -0
  46. package/.medusa/server/src/admin/admin-components/shared/ui/FileDropField.js +33 -0
  47. package/.medusa/server/src/admin/admin-components/shared/ui/FocusModalShell.d.ts +27 -0
  48. package/.medusa/server/src/admin/admin-components/shared/ui/FocusModalShell.js +13 -0
  49. package/.medusa/server/src/admin/admin-components/shared/ui/InfoTooltip.d.ts +13 -0
  50. package/.medusa/server/src/admin/admin-components/shared/ui/InfoTooltip.js +8 -0
  51. package/.medusa/server/src/admin/admin-components/shared/ui/MultiSelectDropdown.d.ts +22 -0
  52. package/.medusa/server/src/admin/admin-components/shared/ui/MultiSelectDropdown.js +14 -0
  53. package/.medusa/server/src/admin/admin-components/shared/ui/PdfPreviewModal.js +1 -1
  54. package/.medusa/server/src/admin/admin-components/shared/ui/PluginIcon.d.ts +10 -0
  55. package/.medusa/server/src/admin/admin-components/shared/ui/PluginIcon.js +10 -0
  56. package/.medusa/server/src/admin/admin-components/shared/ui/SwitchRow.d.ts +27 -0
  57. package/.medusa/server/src/admin/admin-components/shared/ui/SwitchRow.js +15 -0
  58. package/.medusa/server/src/admin/admin-components/shared/ui/WidgetHeader.d.ts +15 -0
  59. package/.medusa/server/src/admin/admin-components/shared/ui/WidgetHeader.js +8 -0
  60. package/.medusa/server/src/admin/admin-components/shared/ui/file-drop.d.ts +13 -0
  61. package/.medusa/server/src/admin/admin-components/shared/ui/file-drop.js +16 -0
  62. package/.medusa/server/src/admin/admin-components/shared/ui/multi-select.d.ts +13 -0
  63. package/.medusa/server/src/admin/admin-components/shared/ui/multi-select.js +11 -0
  64. package/.medusa/server/src/admin/admin-components/shared/ui/plugin-icon.d.ts +12 -0
  65. package/.medusa/server/src/admin/admin-components/shared/ui/plugin-icon.js +29 -0
  66. package/.medusa/server/src/admin/index.js +3123 -2019
  67. package/.medusa/server/src/admin/index.mjs +3126 -2022
  68. package/.medusa/server/src/api/admin-first-login-password-policy.js +56 -0
  69. package/.medusa/server/src/api/answer-actor-type-casing-not-found.js +26 -0
  70. package/.medusa/server/src/api/answer-code-provider-route-not-found.js +34 -0
  71. package/.medusa/server/src/api/code-account-password-routes.js +92 -0
  72. package/.medusa/server/src/api/middlewares.js +82 -17
  73. package/.medusa/server/src/api/password-policy.js +55 -10
  74. package/.medusa/server/src/api/require-login-code-customer-email.js +40 -0
  75. package/.medusa/server/src/api/store/customer-auth/login-code/route.js +17 -0
  76. package/.medusa/server/src/api/store/customer-auth/password-policy/route.js +19 -0
  77. package/.medusa/server/src/customer-auth/application/prepare-code-account-password-reset.js +111 -0
  78. package/.medusa/server/src/customer-auth/application/request-login-code.js +145 -0
  79. package/.medusa/server/src/customer-auth/customer-auth-methods-snapshot.js +55 -0
  80. package/.medusa/server/src/customer-auth/customer-password-policy.js +40 -0
  81. package/.medusa/server/src/customer-auth/domain/account-linking.js +53 -0
  82. package/.medusa/server/src/customer-auth/domain/auth-methods.js +32 -0
  83. package/.medusa/server/src/customer-auth/domain/login-code.js +39 -0
  84. package/.medusa/server/src/customer-auth/login-code-accounts.js +146 -0
  85. package/.medusa/server/src/customer-auth/login-code-state.js +139 -0
  86. package/.medusa/server/src/customer-auth/plugin-features.js +80 -0
  87. package/.medusa/server/src/customer-auth/remove-provider-identity.js +18 -0
  88. package/.medusa/server/src/customer-auth/start-customer-auth-methods.js +20 -0
  89. package/.medusa/server/src/documents/plugin-features.js +8 -2
  90. package/.medusa/server/src/email/application/deliver-ephemeral-auth-mail.js +186 -0
  91. package/.medusa/server/src/email/application/deliver-login-code.js +89 -0
  92. package/.medusa/server/src/email/application/handle-customer-verification-event.js +29 -168
  93. package/.medusa/server/src/email/application/handle-password-reset-event.js +27 -166
  94. package/.medusa/server/src/email/customer-verification-snapshot.js +20 -108
  95. package/.medusa/server/src/email/domain/business-keys.js +15 -2
  96. package/.medusa/server/src/email/plugin-features.js +8 -3
  97. package/.medusa/server/src/email/rendering/default-templates.js +2 -1
  98. package/.medusa/server/src/email/templates/blocks/dynamic-blocks.js +19 -1
  99. package/.medusa/server/src/email/templates/defaults/documents.js +7 -1
  100. package/.medusa/server/src/email/templates/defaults/subjects.js +3 -1
  101. package/.medusa/server/src/email/templates/defaults/texts.js +5 -2
  102. package/.medusa/server/src/email/templates/render.js +2 -1
  103. package/.medusa/server/src/email/templates/sample-data.js +2 -1
  104. package/.medusa/server/src/email/templates/validate.js +8 -3
  105. package/.medusa/server/src/email/types/email-kinds.js +4 -2
  106. package/.medusa/server/src/email/types/template-data.js +9 -1
  107. package/.medusa/server/src/modules/customer-auth/index.js +14 -0
  108. package/.medusa/server/src/modules/customer-auth/service.js +35 -0
  109. package/.medusa/server/src/modules/email/migrations/Migration20261010090000.js +20 -0
  110. package/.medusa/server/src/modules/plugin-features/domain/plugin-settings.js +4 -1
  111. package/.medusa/server/src/modules/plugin-features/service.js +26 -1
  112. package/.medusa/server/src/providers/email-otp/index.js +8 -0
  113. package/.medusa/server/src/providers/email-otp/service.js +96 -0
  114. package/.medusa/server/src/public/descriptor.js +117 -5
  115. package/.medusa/server/src/public/effective-features.js +22 -1
  116. package/.medusa/server/src/public/index.js +4 -2
  117. package/.medusa/server/src/public/plugin-icons.js +28 -0
  118. package/.medusa/server/src/public/shop-master-data.js +33 -14
  119. package/.medusa/server/src/shared/lib/index.js +2 -1
  120. package/.medusa/server/src/shared/lib/refreshing-snapshot.js +122 -0
  121. package/.medusa/server/src/shop-config/build-food-shop-config.js +38 -1
  122. package/README.md +436 -87
  123. package/dist/customer-auth/customer-auth-methods-snapshot.d.ts +25 -0
  124. package/dist/customer-auth/customer-auth-methods-snapshot.js +54 -0
  125. package/dist/customer-auth/domain/auth-methods.d.ts +16 -0
  126. package/dist/customer-auth/domain/auth-methods.js +31 -0
  127. package/dist/customer-auth/plugin-features.d.ts +2 -0
  128. package/dist/customer-auth/plugin-features.js +79 -0
  129. package/dist/email/customer-verification-snapshot.d.ts +3 -15
  130. package/dist/email/customer-verification-snapshot.js +19 -107
  131. package/dist/email/types/email-kinds.d.ts +4 -3
  132. package/dist/email/types/email-kinds.js +3 -1
  133. package/dist/email/types/template-data.d.ts +8 -0
  134. package/dist/email/types/template-data.js +8 -0
  135. package/dist/public/descriptor.js +116 -4
  136. package/dist/public/effective-features.d.ts +10 -0
  137. package/dist/public/effective-features.js +21 -0
  138. package/dist/public/index.d.ts +2 -1
  139. package/dist/public/index.js +3 -1
  140. package/dist/public/plugin-icons.d.ts +7 -0
  141. package/dist/public/plugin-icons.js +27 -0
  142. package/dist/public/shop-master-data.js +32 -13
  143. package/dist/public/types.d.ts +63 -1
  144. package/dist/shared/lib/index.d.ts +1 -0
  145. package/dist/shared/lib/index.js +1 -0
  146. package/dist/shared/lib/refreshing-snapshot.d.ts +52 -0
  147. package/dist/shared/lib/refreshing-snapshot.js +121 -0
  148. package/dist/shop-config/build-food-shop-config.js +37 -0
  149. package/package.json +8 -3
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prepareCodeAccountPasswordReset = void 0;
4
+ const utils_1 = require("@medusajs/framework/utils");
5
+ const customer_auth_1 = require("@hartl-services/medusa-food-supplements-contracts/schemas/customer-auth");
6
+ const transaction_1 = require("../../shared/lib/transaction");
7
+ const auth_methods_1 = require("../domain/auth-methods");
8
+ const login_code_accounts_1 = require("../login-code-accounts");
9
+ const remove_provider_identity_1 = require("../remove-provider-identity");
10
+ /** `code_provider` of the verification that records "proven by a code login". */
11
+ const VERIFICATION_CODE_PROVIDER = "email-otp";
12
+ /**
13
+ * Records that the address was proven for the auth identity: a verified Medusa
14
+ * auth verification (`entity_type` "email"), the one check the linking rule,
15
+ * the clean-up after a code login and `customer_verification` all use. A
16
+ * leftover unverified row of an earlier password identity is verified instead
17
+ * of duplicated.
18
+ */
19
+ const confirmAddress = async (auth, authIdentityId, entityId) => {
20
+ const now = new Date();
21
+ const [existing] = await auth.listAuthVerifications({ auth_identity_id: authIdentityId, entity_id: entityId, entity_type: "email" }, { select: ["id", "verified_at"], take: 1 });
22
+ if (existing?.id) {
23
+ if (!existing.verified_at) {
24
+ await auth.updateAuthVerifications({ id: existing.id, verified_at: now });
25
+ }
26
+ return;
27
+ }
28
+ try {
29
+ await auth.createAuthVerifications({
30
+ auth_identity_id: authIdentityId,
31
+ entity_id: entityId,
32
+ entity_type: "email",
33
+ code_provider: VERIFICATION_CODE_PROVIDER,
34
+ requested_at: now,
35
+ verified_at: now,
36
+ });
37
+ }
38
+ catch (error) {
39
+ if (!(0, transaction_1.isUniqueConstraintViolation)(error)) {
40
+ throw error;
41
+ }
42
+ // A parallel request wrote the row; it is the same fact.
43
+ }
44
+ };
45
+ /**
46
+ * Makes "Passwort vergessen" work for an account that so far only logged in
47
+ * with codes, and returns the identifier Medusa's reset route has to use
48
+ * (null: leave the request as it is). Medusa looks the provider identity up by
49
+ * the exact spelling and mails the address it was given, so:
50
+ *
51
+ * 1. Exactly one `emailpass` identity with a customer matches the address
52
+ * (letter case ignored): its stored spelling is the identifier, so a
53
+ * differently cased input still gets the mail.
54
+ * 2. Otherwise, if the address has a code login whose account carries a
55
+ * customer of that address, the address was proven by a code: customer-less
56
+ * `emailpass` identities of the address (left by a failed registration) are
57
+ * deleted for good, and an `emailpass` identity with the normalized address
58
+ * is added to the account, together with a verified auth verification of
59
+ * the address so `customer_verification` never blocks it and the code login
60
+ * never deletes it as unconfirmed. The identity has no password at all, and
61
+ * `emailpass` fails closed for a missing password, so nobody can log in
62
+ * with it before the reset sets one. The normalized address is the
63
+ * identifier.
64
+ * 3. Anything else (unknown address, several accounts) changes nothing.
65
+ *
66
+ * A parallel request that creates the identity first wins the unique index on
67
+ * `(entity_id, provider)`; the identity is then read again.
68
+ */
69
+ const prepareCodeAccountPasswordReset = async (container, identifier) => {
70
+ const parsed = customer_auth_1.RequestLoginCodeSchema.safeParse({ email: identifier });
71
+ if (!parsed.success) {
72
+ return null;
73
+ }
74
+ const normalized = (0, customer_auth_1.normalizeLoginEmail)(parsed.data.email);
75
+ const sql = container.resolve(utils_1.ContainerRegistrationKeys.PG_CONNECTION);
76
+ const auth = container.resolve(utils_1.Modules.AUTH);
77
+ const passwordIdentities = await (0, login_code_accounts_1.findPasswordIdentities)(sql, normalized);
78
+ const withCustomer = passwordIdentities.filter(({ customerId }) => customerId);
79
+ if (withCustomer.length === 1) {
80
+ return withCustomer[0].entityId;
81
+ }
82
+ if (withCustomer.length > 1) {
83
+ return null;
84
+ }
85
+ const authIdentityId = await (0, login_code_accounts_1.findCodeAccountAuthIdentityId)(sql, normalized);
86
+ if (!authIdentityId) {
87
+ return null;
88
+ }
89
+ for (const orphan of passwordIdentities) {
90
+ await (0, remove_provider_identity_1.removeProviderIdentity)(auth, orphan);
91
+ }
92
+ // The verification first: if the identity then fails to appear, a verified
93
+ // row of an address the code login proved is all that remains.
94
+ await confirmAddress(auth, authIdentityId, normalized);
95
+ try {
96
+ await auth.createProviderIdentities({
97
+ auth_identity_id: authIdentityId,
98
+ provider: auth_methods_1.EMAIL_PASSWORD_AUTH_PROVIDER,
99
+ entity_id: normalized,
100
+ });
101
+ }
102
+ catch (error) {
103
+ if (!(0, transaction_1.isUniqueConstraintViolation)(error)) {
104
+ throw error;
105
+ }
106
+ }
107
+ const created = (await (0, login_code_accounts_1.findPasswordIdentities)(sql, normalized)).find(({ entityId }) => entityId === normalized);
108
+ return created?.customerId ? normalized : null;
109
+ };
110
+ exports.prepareCodeAccountPasswordReset = prepareCodeAccountPasswordReset;
111
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJlcGFyZS1jb2RlLWFjY291bnQtcGFzc3dvcmQtcmVzZXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvY3VzdG9tZXItYXV0aC9hcHBsaWNhdGlvbi9wcmVwYXJlLWNvZGUtYWNjb3VudC1wYXNzd29yZC1yZXNldC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFDQSxxREFBOEU7QUFDOUUsMkdBR2dGO0FBRWhGLDhEQUEwRTtBQUMxRSx5REFBcUU7QUFDckUsZ0VBRytCO0FBRS9CLDBFQUFvRTtBQUVwRSxpRkFBaUY7QUFDakYsTUFBTSwwQkFBMEIsR0FBRyxXQUFXLENBQUE7QUF1QjlDOzs7Ozs7R0FNRztBQUNILE1BQU0sY0FBYyxHQUFHLEtBQUssRUFDMUIsSUFBNEIsRUFDNUIsY0FBc0IsRUFDdEIsUUFBZ0IsRUFDRCxFQUFFO0lBQ2pCLE1BQU0sR0FBRyxHQUFHLElBQUksSUFBSSxFQUFFLENBQUE7SUFDdEIsTUFBTSxDQUFDLFFBQVEsQ0FBQyxHQUFHLE1BQU0sSUFBSSxDQUFDLHFCQUFxQixDQUNqRCxFQUFFLGdCQUFnQixFQUFFLGNBQWMsRUFBRSxTQUFTLEVBQUUsUUFBUSxFQUFFLFdBQVcsRUFBRSxPQUFPLEVBQUUsRUFDL0UsRUFBRSxNQUFNLEVBQUUsQ0FBQyxJQUFJLEVBQUUsYUFBYSxDQUFDLEVBQUUsSUFBSSxFQUFFLENBQUMsRUFBRSxDQUMzQyxDQUFBO0lBQ0QsSUFBSSxRQUFRLEVBQUUsRUFBRSxFQUFFLENBQUM7UUFDakIsSUFBSSxDQUFDLFFBQVEsQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUMxQixNQUFNLElBQUksQ0FBQyx1QkFBdUIsQ0FBQyxFQUFFLEVBQUUsRUFBRSxRQUFRLENBQUMsRUFBRSxFQUFFLFdBQVcsRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFBO1FBQzNFLENBQUM7UUFDRCxPQUFNO0lBQ1IsQ0FBQztJQUNELElBQUksQ0FBQztRQUNILE1BQU0sSUFBSSxDQUFD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
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requestLoginCode = void 0;
4
+ const utils_1 = require("@medusajs/framework/utils");
5
+ const customer_auth_1 = require("@hartl-services/medusa-food-supplements-contracts/schemas/customer-auth");
6
+ const deliver_login_code_1 = require("../../email/application/deliver-login-code");
7
+ const enforcement_1 = require("../../public/enforcement");
8
+ const transaction_1 = require("../../shared/lib/transaction");
9
+ const account_linking_1 = require("../domain/account-linking");
10
+ const login_code_1 = require("../domain/login-code");
11
+ const login_code_accounts_1 = require("../login-code-accounts");
12
+ const login_code_state_1 = require("../login-code-state");
13
+ const plugin_features_1 = require("../plugin-features");
14
+ const remove_provider_identity_1 = require("../remove-provider-identity");
15
+ /**
16
+ * The key material of the code hashes: the shop's JWT secret, the same value
17
+ * the `email-otp` auth provider gets as its `hmacSecret` option (the shop
18
+ * preset sets both from `JWT_SECRET`). Both derive the HMAC key from it with
19
+ * `hashLoginCode`.
20
+ */
21
+ const resolveJwtSecret = (container) => {
22
+ const secret = container.resolve(utils_1.ContainerRegistrationKeys.CONFIG_MODULE).projectConfig?.http?.jwtSecret;
23
+ if (typeof secret !== "string" || !secret.trim()) {
24
+ throw new Error("projectConfig.http.jwtSecret is not set");
25
+ }
26
+ return secret;
27
+ };
28
+ const logSafely = (container, level, message) => {
29
+ try {
30
+ container.resolve(utils_1.ContainerRegistrationKeys.LOGGER)[level](message);
31
+ }
32
+ catch {
33
+ // The caller runs detached: a broken logger must not become an
34
+ // unhandled rejection.
35
+ }
36
+ };
37
+ const errorClass = (error) => error instanceof Error ? error.name : "unknown error";
38
+ /**
39
+ * The address's code-login identity, created when missing. A parallel first
40
+ * request loses the unique index (entity_id, provider) and reads the winner's
41
+ * row instead (create-or-get).
42
+ */
43
+ const ensureProviderIdentity = async (sql, entityId, create) => {
44
+ try {
45
+ await create();
46
+ }
47
+ catch (error) {
48
+ if (!(0, transaction_1.isUniqueConstraintViolation)(error)) {
49
+ throw error;
50
+ }
51
+ }
52
+ return await (0, login_code_accounts_1.findOwnLoginIdentity)(sql, entityId);
53
+ };
54
+ /**
55
+ * Issues a login code for `email` and mails it. Never rejects and never
56
+ * reveals anything to the caller's client (the route answers 202 first): a
57
+ * failure is logged with a fixed message and the error class only, as the
58
+ * message of a database error can carry the address.
59
+ *
60
+ * 1. Find the address's own code-login identity and the customer accounts
61
+ * that registered it through `emailpass` (letter case ignored).
62
+ * 2. Decide where the code goes (`decideLoginCodeTarget`, the anti-takeover
63
+ * rule): use or create the code identity, link it to the account, or send
64
+ * nothing.
65
+ * 3. Store a new code with one conditional UPDATE that also enforces the
66
+ * resend interval and the daily budget of codes per address; no row means
67
+ * throttled or capped and no mail.
68
+ * 4. Mail the code (`auth.login_code`); it is never persisted or logged.
69
+ */
70
+ const requestLoginCode = async (container, email) => {
71
+ try {
72
+ const entityId = (0, customer_auth_1.normalizeLoginEmail)(email);
73
+ const hmacSecret = resolveJwtSecret(container);
74
+ const auth = container.resolve(utils_1.Modules.AUTH);
75
+ const sql = container.resolve(utils_1.ContainerRegistrationKeys.PG_CONNECTION);
76
+ const [own, accounts, emailPasswordEnabled] = await Promise.all([
77
+ (0, login_code_accounts_1.findOwnLoginIdentity)(sql, entityId),
78
+ (0, login_code_accounts_1.findEmailPasswordAccounts)(sql, entityId),
79
+ (0, enforcement_1.isPluginFeatureEnabled)(container, plugin_features_1.customerAuthPluginFeatures, customer_auth_1.CUSTOMER_AUTH_FEATURES.emailPassword),
80
+ ]);
81
+ const decision = (0, account_linking_1.decideLoginCodeTarget)({
82
+ emailPasswordEnabled,
83
+ normalizedEmail: entityId,
84
+ own,
85
+ accounts,
86
+ });
87
+ if (decision.action === "ambiguous") {
88
+ logSafely(container, "warn", "Login code not sent: more than one customer account matches the address (they differ only in letter case).");
89
+ return { status: "ambiguous" };
90
+ }
91
+ if (decision.action === "blocked") {
92
+ logSafely(container, "info", "Login code not sent: the address belongs to an account that may not be linked (address not confirmed or different from the customer's).");
93
+ return { status: "blocked" };
94
+ }
95
+ let target = own;
96
+ if (decision.action === "link") {
97
+ if (own) {
98
+ // A customer-less code identity (and its auth identity if it is then empty).
99
+ await (0, remove_provider_identity_1.removeProviderIdentity)(auth, own);
100
+ }
101
+ target = await ensureProviderIdentity(sql, entityId, () => auth.createProviderIdentities({
102
+ auth_identity_id: decision.authIdentityId,
103
+ provider: customer_auth_1.EMAIL_OTP_AUTH_PROVIDER,
104
+ entity_id: entityId,
105
+ }));
106
+ }
107
+ else if (decision.action === "create") {
108
+ target = await ensureProviderIdentity(sql, entityId, () => auth.createAuthIdentities({
109
+ provider_identities: [
110
+ { provider: customer_auth_1.EMAIL_OTP_AUTH_PROVIDER, entity_id: entityId },
111
+ ],
112
+ }));
113
+ }
114
+ if (!target) {
115
+ logSafely(container, "error", "Login code not sent: the code identity could not be created.");
116
+ return { status: "failed" };
117
+ }
118
+ const code = (0, login_code_1.generateLoginCode)();
119
+ const stored = await (0, login_code_state_1.storeLoginCode)(sql, target.providerIdentityId, (0, login_code_1.hashLoginCode)(hmacSecret, entityId, code));
120
+ if (stored === "capped") {
121
+ logSafely(container, "warn", "Login code not sent: an address used up its daily budget of codes.");
122
+ return { status: "capped" };
123
+ }
124
+ if (stored === "throttled") {
125
+ return { status: "throttled" };
126
+ }
127
+ const mail = await (0, deliver_login_code_1.sendLoginCodeMail)(container, {
128
+ code,
129
+ customerId: target.customerId,
130
+ expiresAt: new Date(Date.now() + customer_auth_1.LOGIN_CODE_TTL_MINUTES * 60_000),
131
+ to: entityId,
132
+ });
133
+ if (mail.status !== "sent") {
134
+ logSafely(container, "error", `Login code mail was not delivered (${mail.status}${mail.code ? `: ${mail.code}` : ""}).`);
135
+ return { status: "failed" };
136
+ }
137
+ return { status: "sent" };
138
+ }
139
+ catch (error) {
140
+ logSafely(container, "error", `A login code could not be requested (${errorClass(error)}).`);
141
+ return { status: "failed" };
142
+ }
143
+ };
144
+ exports.requestLoginCode = requestLoginCode;
145
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVxdWVzdC1sb2dpbi1jb2RlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vc3JjL2N1c3RvbWVyLWF1dGgvYXBwbGljYXRpb24vcmVxdWVzdC1sb2dpbi1jb2RlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUtBLHFEQUdrQztBQUNsQywyR0FLZ0Y7QUFFaEYsbUZBQThFO0FBQzlFLDBEQUFpRTtBQUNqRSw4REFBMEU7QUFDMUUsK0RBR2tDO0FBQ2xDLHFEQUc2QjtBQUM3QixnRUFHK0I7QUFDL0IsMERBQXVFO0FBQ3ZFLHdEQUErRDtBQUMvRCwwRUFBb0U7QUFhcEU7Ozs7O0dBS0c7QUFDSCxNQUFNLGdCQUFnQixHQUFHLENBQUMsU0FBMEIsRUFBVSxFQUFFO0lBQzlELE1BQU0sTUFBTSxHQUFHLFNBQVMsQ0FBQyxPQUFPLENBRTdCLGlDQUF5QixDQUFDLGFBQWEsQ0FBQyxDQUFDLGFBQWEsRUFBRSxJQUFJLEVBQUUsU0FBUyxDQUFBO0lBQzFFLElBQUksT0FBTyxNQUFNLEtBQUssUUFBUSxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksRUFBRSxFQUFFLENBQUM7UUFDakQsTUFBTSxJQUFJLEtBQUssQ0FBQyx5Q0FBeUMsQ0FBQyxDQUFBO0lBQzVELENBQUM7SUFDRCxPQUFPLE1BQU0sQ0FBQTtBQUNmLENBQUMsQ0FBQTtBQUVELE1BQU0sU0FBUyxHQUFHLENBQ2hCLFNBQTBCLEVBQzFCLEtBQWdDLEVBQ2hDLE9BQWUsRUFDVCxFQUFFO0lBQ1IsSUFBSSxDQUFDO1FBQ0gsU0FBUyxDQUFDLE9BQU8sQ0FBUyxpQ0FBeUIsQ0FBQyxNQUFNLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQTtJQUM3RSxDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AsK0RBQStEO1FBQy9ELHVCQUF1QjtJQUN6QixDQUFDO0FBQ0gsQ0FBQyxDQUFBO0FBRUQsTUFBTSxVQUFVLEdBQUcsQ0FBQyxLQUFjLEVBQVUs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
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setCustomerAuthMethodsForTests = exports.customerAuthMethods = exports.startCustomerAuthMethodsSnapshot = exports.stopCustomerAuthMethodsSnapshot = exports.refreshCustomerAuthMethodsSnapshot = void 0;
4
+ const refreshing_snapshot_1 = require("../shared/lib/refreshing-snapshot");
5
+ const auth_methods_1 = require("./domain/auth-methods");
6
+ const plugin_features_1 = require("./plugin-features");
7
+ /**
8
+ * In-memory copy of the login methods customers may use, for the one place
9
+ * that cannot await them: Medusa reads
10
+ * `projectConfig.http.authMethodsPerActor.customer` synchronously on every
11
+ * request to an `/auth/customer/<provider>` route. The shop preset puts
12
+ * `customerAuthMethods()` behind a getter there, so a switch in the Admin
13
+ * takes effect without a restart. Loading, the 30 s refresh and the failure
14
+ * behavior are those of `createRefreshingSnapshot`.
15
+ *
16
+ * Start and fallback value are the descriptor defaults (`["emailpass"]`),
17
+ * never an empty list: an empty allowlist blocks every customer login, so a
18
+ * failed read must not produce one.
19
+ */
20
+ const snapshot = (0, refreshing_snapshot_1.createRefreshingSnapshot)({
21
+ key: "@hartl-services/medusa-base/customer-auth-methods-snapshot",
22
+ initial: Object.freeze((0, auth_methods_1.defaultCustomerAuthMethods)(plugin_features_1.customerAuthPluginFeatures)),
23
+ describe: (methods) => `Customer login methods could not be read, keeping [${methods.join(", ")}]`,
24
+ });
25
+ /** Reads the switches once; never rejects. Concurrent calls share one read. */
26
+ const refreshCustomerAuthMethodsSnapshot = () => snapshot.refresh();
27
+ exports.refreshCustomerAuthMethodsSnapshot = refreshCustomerAuthMethodsSnapshot;
28
+ /** Stops the periodic refresh (application shutdown). The value stays. */
29
+ const stopCustomerAuthMethodsSnapshot = () => snapshot.stop();
30
+ exports.stopCustomerAuthMethodsSnapshot = stopCustomerAuthMethodsSnapshot;
31
+ /**
32
+ * Registers the reader, loads the switches once and keeps them fresh every
33
+ * 30 s (application start). `read` returns the effective feature map of
34
+ * `customer_auth`; `warn` is the container logger.
35
+ */
36
+ const startCustomerAuthMethodsSnapshot = (input) => snapshot.start({
37
+ read: async () => Object.freeze((0, auth_methods_1.deriveCustomerAuthMethods)(await input.read())),
38
+ warn: input.warn,
39
+ });
40
+ exports.startCustomerAuthMethodsSnapshot = startCustomerAuthMethodsSnapshot;
41
+ /**
42
+ * The value of `authMethodsPerActor.customer`: the provider ids a customer
43
+ * may use. Synchronous; starts a background refresh when the copy is stale.
44
+ * A fresh array per call.
45
+ */
46
+ const customerAuthMethods = () => [...snapshot.current()];
47
+ exports.customerAuthMethods = customerAuthMethods;
48
+ /**
49
+ * Test hook: sets the methods and counts as just refreshed, so they hold for
50
+ * 30 s without a reader; `null` forgets reader and value (back to the
51
+ * defaults).
52
+ */
53
+ const setCustomerAuthMethodsForTests = (methods) => snapshot.setForTests(methods === null ? null : Object.freeze([...methods]));
54
+ exports.setCustomerAuthMethodsForTests = setCustomerAuthMethodsForTests;
55
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3VzdG9tZXItYXV0aC1tZXRob2RzLXNuYXBzaG90LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2N1c3RvbWVyLWF1dGgvY3VzdG9tZXItYXV0aC1tZXRob2RzLXNuYXBzaG90LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDJFQUE0RTtBQUU1RSx3REFHOEI7QUFDOUIsdURBQThEO0FBRTlEOzs7Ozs7Ozs7Ozs7R0FZRztBQUNILE1BQU0sUUFBUSxHQUFHLElBQUEsOENBQXdCLEVBQW9CO0lBQzNELEdBQUcsRUFBRSw0REFBNEQ7SUFDakUsT0FBTyxFQUFFLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBQSx5Q0FBMEIsRUFBQyw0Q0FBMEIsQ0FBQyxDQUFDO0lBQzlFLFFBQVEsRUFBRSxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQ3BCLHNEQUFzRCxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHO0NBQzlFLENBQUMsQ0FBQTtBQUVGLCtFQUErRTtBQUN4RSxNQUFNLGtDQUFrQyxHQUFHLEdBQWtCLEVBQUUsQ0FDcEUsUUFBUSxDQUFDLE9BQU8sRUFBRSxDQUFBO0FBRFAsUUFBQSxrQ0FBa0Msc0NBQzNCO0FBRXBCLDBFQUEwRTtBQUNuRSxNQUFNLCtCQUErQixHQUFHLEdBQVMsRUFBRSxDQUFDLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQTtBQUE3RCxRQUFBLCtCQUErQixtQ0FBOEI7QUFFMUU7Ozs7R0FJRztBQUNJLE1BQU0sZ0NBQWdDLEdBQUcsQ0FDOUMsS0FHRSxFQUNhLEVBQUUsQ0FDakIsUUFBUSxDQUFDLEtBQUssQ0FBQztJQUNiLElBQUksRUFBRSxLQUFLLElBQUksRUFBRSxDQUNmLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBQSx3Q0FBeUIsRUFBQyxNQUFNLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO0lBQzlELElBQUksRUFBRSxLQUFLLENBQUMsSUFBSTtDQUNqQixDQUFDLENBQUE7QUFWUyxRQUFBLGdDQUFnQyxvQ0FVekM7QUFFSjs7OztHQUlHO0FBQ0ksTUFBTSxtQkFBbUIsR0FBRyxHQUFhLEVBQUUsQ0FBQyxDQUFDLEdBQUcsUUFBUSxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUE7QUFBN0QsUUFBQSxtQkFBbUIsdUJBQTBDO0FBRTFFOzs7O0dBSUc7QUFDSSxNQUFNLDhCQUE4QixHQUFHLENBQzVDLE9BQWlDLEVBQzNCLEVBQUUsQ0FBQyxRQUFRLENBQUMsV0FBVyxDQUFDLE9BQU8sS0FBSyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFBO0FBRnpFLFFBQUEsOEJBQThCLGtDQUUyQyJ9
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveCustomerPasswordPolicy = exports.toCustomerPasswordPolicy = void 0;
4
+ const password_policy_1 = require("@hartl-services/medusa-food-supplements-contracts/schemas/password-policy");
5
+ const settings_1 = require("../public/settings");
6
+ const plugin_features_1 = require("./plugin-features");
7
+ /**
8
+ * Pure part: turns the resolved `customer_auth` settings into the policy.
9
+ * A missing or mistyped value (or a length outside the allowed range) falls
10
+ * back to the default for that rule, so a broken value never blocks a
11
+ * registration with an unusable rule.
12
+ */
13
+ const toCustomerPasswordPolicy = (values) => {
14
+ const { min, max } = password_policy_1.CUSTOMER_PASSWORD_MIN_LENGTH_RANGE;
15
+ const length = values.password_min_length;
16
+ const flag = (key, fallback) => typeof values[key] === "boolean" ? values[key] : fallback;
17
+ return {
18
+ min_length: typeof length === "number" &&
19
+ Number.isInteger(length) &&
20
+ length >= min &&
21
+ length <= max
22
+ ? length
23
+ : password_policy_1.DEFAULT_CUSTOMER_PASSWORD_POLICY.min_length,
24
+ require_mixed_case: flag("password_require_mixed_case", password_policy_1.DEFAULT_CUSTOMER_PASSWORD_POLICY.require_mixed_case),
25
+ require_digit: flag("password_require_digit", password_policy_1.DEFAULT_CUSTOMER_PASSWORD_POLICY.require_digit),
26
+ require_symbol: flag("password_require_symbol", password_policy_1.DEFAULT_CUSTOMER_PASSWORD_POLICY.require_symbol),
27
+ };
28
+ };
29
+ exports.toCustomerPasswordPolicy = toCustomerPasswordPolicy;
30
+ /**
31
+ * The password rules customers currently have to meet: stored in the Admin >
32
+ * declared default, per rule. Read failures propagate; the callers decide
33
+ * what to do (the middleware falls back to the default policy).
34
+ */
35
+ const resolveCustomerPasswordPolicy = async (container) => {
36
+ const { values } = await (0, settings_1.getPluginSettings)(container, plugin_features_1.customerAuthPluginFeatures);
37
+ return (0, exports.toCustomerPasswordPolicy)(values);
38
+ };
39
+ exports.resolveCustomerPasswordPolicy = resolveCustomerPasswordPolicy;
40
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3VzdG9tZXItcGFzc3dvcmQtcG9saWN5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2N1c3RvbWVyLWF1dGgvY3VzdG9tZXItcGFzc3dvcmQtcG9saWN5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUNBLCtHQUlrRjtBQUVsRixpREFBc0Q7QUFDdEQsdURBQThEO0FBRTlEOzs7OztHQUtHO0FBQ0ksTUFBTSx3QkFBd0IsR0FBRyxDQUN0QyxNQUF5QyxFQUNqQixFQUFFO0lBQzFCLE1BQU0sRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsb0RBQWtDLENBQUE7SUFDdkQsTUFBTSxNQUFNLEdBQUcsTUFBTSxDQUFDLG1CQUFtQixDQUFBO0lBQ3pDLE1BQU0sSUFBSSxHQUFHLENBQUMsR0FBVyxFQUFFLFFBQWlCLEVBQVcsRUFBRSxDQUN2RCxPQUFPLE1BQU0sQ0FBQyxHQUFHLENBQUMsS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFFLE1BQU0sQ0FBQyxHQUFHLENBQWEsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFBO0lBRXhFLE9BQU87UUFDTCxVQUFVLEVBQ1IsT0FBTyxNQUFNLEtBQUssUUFBUTtZQUMxQixNQUFNLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQztZQUN4QixNQUFNLElBQUksR0FBRztZQUNiLE1BQU0sSUFBSSxHQUFHO1lBQ1gsQ0FBQyxDQUFDLE1BQU07WUFDUixDQUFDLENBQUMsa0RBQWdDLENBQUMsVUFBVTtRQUNqRCxrQkFBa0IsRUFBRSxJQUFJLENBQ3RCLDZCQUE2QixFQUM3QixrREFBZ0MsQ0FBQyxrQkFBa0IsQ0FDcEQ7UUFDRCxhQUFhLEVBQUUsSUFBSSxDQUNqQix3QkFBd0IsRUFDeEIsa0RBQWdDLENBQUMsYUFBYSxDQUMvQztRQUNELGNBQWMsRUFBRSxJQUFJLENBQ2xCLHlCQUF5QixFQUN6QixrREFBZ0MsQ0FBQyxjQUFjLENBQ2hEO0tBQ0YsQ0FBQTtBQUNILENBQUMsQ0FBQTtBQTdCWSxRQUFBLHdCQUF3Qiw0QkE2QnBDO0FBRUQ7Ozs7R0FJRztBQUNJLE1BQU0sNkJBQTZCLEdBQUcsS0FBSyxFQUNoRCxTQUEwQixFQUNPLEVBQUU7SUFDbkMsTUFBTSxFQUFFLE1BQU0sRUFBRSxHQUFHLE1BQU0sSUFBQSw0QkFBaUIsRUFDeEMsU0FBUyxFQUNULDRDQUEwQixDQUMzQixDQUFBO0lBQ0QsT0FBTyxJQUFBLGdDQUF3QixFQUFDLE1BQU0sQ0FBQyxDQUFBO0FBQ3pDLENBQUMsQ0FBQTtBQVJZLFFBQUEsNkJBQTZCLGlDQVF6QyJ9
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decideLoginCodeTarget = void 0;
4
+ /**
5
+ * Where a login code for an address goes. The rule that prevents account
6
+ * takeover: an existing `emailpass` account is linked only when password
7
+ * login is off in this shop, or when that address was confirmed through
8
+ * Medusa's auth verification, and only when its customer record has that very
9
+ * address. Otherwise anyone could register an account with somebody else's
10
+ * address and password, and the owner of the address would land in that
11
+ * account the first time they ask for a code. A customer record is never
12
+ * matched by `customer.email` alone, and guest customers are never touched.
13
+ *
14
+ * - An `email-otp` identity that already belongs to a customer is used as is.
15
+ * - Otherwise accounts are the candidates. First those that must not be
16
+ * linked are dropped (unconfirmed while password login is on, customer
17
+ * address differs); a candidate that cannot be linked never makes the
18
+ * address ambiguous next to a linkable one. None left (but some existed)
19
+ * -> send nothing: an unconfirmed password account somebody else
20
+ * registered for the address then blocks the code login while password
21
+ * login is on, until the owner resets that password and confirms the
22
+ * address (or the shop switches password login off). Of the rest, confirmed
23
+ * ones are preferred, then the one whose stored address equals the
24
+ * requested spelling exactly; if more than one is still left -> send
25
+ * nothing. No account at all -> use the customer-less own identity or
26
+ * create one.
27
+ */
28
+ const decideLoginCodeTarget = (input) => {
29
+ const { emailPasswordEnabled, normalizedEmail, own, accounts } = input;
30
+ if (own?.customerId) {
31
+ return { action: "use_own" };
32
+ }
33
+ if (!accounts.length) {
34
+ return own ? { action: "use_own" } : { action: "create" };
35
+ }
36
+ let remaining = accounts.filter((account) => account.customerEmailMatches && (!emailPasswordEnabled || account.confirmed));
37
+ if (!remaining.length) {
38
+ return { action: "blocked" };
39
+ }
40
+ if (remaining.length > 1) {
41
+ const confirmed = remaining.filter((account) => account.confirmed);
42
+ remaining = confirmed.length ? confirmed : remaining;
43
+ }
44
+ if (remaining.length > 1) {
45
+ const exact = remaining.filter((account) => account.entityId === normalizedEmail);
46
+ remaining = exact.length ? exact : remaining;
47
+ }
48
+ return remaining.length === 1
49
+ ? { action: "link", authIdentityId: remaining[0].authIdentityId }
50
+ : { action: "ambiguous" };
51
+ };
52
+ exports.decideLoginCodeTarget = decideLoginCodeTarget;
53
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWNjb3VudC1saW5raW5nLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vc3JjL2N1c3RvbWVyLWF1dGgvZG9tYWluL2FjY291bnQtbGlua2luZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFxQ0E7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBdUJHO0FBQ0ksTUFBTSxxQkFBcUIsR0FBRyxDQUNuQyxLQU1FLEVBQ3VCLEVBQUU7SUFDM0IsTUFBTSxFQUFFLG9CQUFvQixFQUFFLGVBQWUsRUFBRSxHQUFHLEVBQUUsUUFBUSxFQUFFLEdBQUcsS0FBSyxDQUFBO0lBQ3RFLElBQUksR0FBRyxFQUFFLFVBQVUsRUFBRSxDQUFDO1FBQ3BCLE9BQU8sRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLENBQUE7SUFDOUIsQ0FBQztJQUNELElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxFQUFFLENBQUM7UUFDckIsT0FBTyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsQ0FBQTtJQUMzRCxDQUFDO0lBRUQsSUFBSSxTQUFTLEdBQUcsUUFBUSxDQUFDLE1BQU0sQ0FDN0IsQ0FBQyxPQUFPLEVBQUUsRUFBRSxDQUNWLE9BQU8sQ0FBQyxvQkFBb0IsSUFBSSxDQUFDLENBQUMsb0JBQW9CLElBQUksT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUMvRSxDQUFBO0lBQ0QsSUFBSSxDQUFDLFNBQVMsQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUN0QixPQUFPLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxDQUFBO0lBQzlCLENBQUM7SUFDRCxJQUFJLFNBQVMsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7UUFDekIsTUFBTSxTQUFTLEdBQUcsU0FBUyxDQUFDLE1BQU0sQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFBO1FBQ2xFLFNBQVMsR0FBRyxTQUFTLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQTtJQUN0RCxDQUFDO0lBQ0QsSUFBSSxTQUFTLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQ3pCLE1BQU0sS0FBSyxHQUFHLFNBQVMsQ0FBQyxNQUFNLENBQzVCLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQyxPQUFPLENBQUMsUUFBUSxLQUFLLGVBQWUsQ0FDbEQsQ0FBQTtRQUNELFNBQVMsR0FBRyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQTtJQUM5QyxDQUFDO0lBQ0QsT0FBTyxTQUFTLENBQUMsTUFBTSxLQUFLLENBQUM7UUFDM0IsQ0FBQyxDQUFDLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxjQUFjLEVBQUUsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLGNBQWMsRUFBRTtRQUNqRSxDQUFDLENBQUMsRUFBRSxNQUFNLEVBQUUsV0FBVyxFQUFFLENBQUE7QUFDN0IsQ0FBQyxDQUFBO0FBckNZLFFBQUEscUJBQXFCLHlCQXFDakMifQ==
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defaultCustomerAuthMethods = exports.deriveCustomerAuthMethods = exports.EMAIL_PASSWORD_AUTH_PROVIDER = void 0;
4
+ const customer_auth_1 = require("@hartl-services/medusa-food-supplements-contracts/schemas/customer-auth");
5
+ /** Medusa's built-in e-mail + password provider. */
6
+ exports.EMAIL_PASSWORD_AUTH_PROVIDER = "emailpass";
7
+ /**
8
+ * The auth providers a customer may use for the given effective switches:
9
+ * `emailpass` while `email_password` is on, `email-otp` while `email_otp` is
10
+ * on. Empty only when both are off, which blocks every customer login (Medusa
11
+ * treats an empty allowlist as "nothing allowed"). A fresh array per call.
12
+ */
13
+ const deriveCustomerAuthMethods = (features) => [
14
+ ...(features[customer_auth_1.CUSTOMER_AUTH_FEATURES.emailPassword] === true
15
+ ? [exports.EMAIL_PASSWORD_AUTH_PROVIDER]
16
+ : []),
17
+ ...(features[customer_auth_1.CUSTOMER_AUTH_FEATURES.emailOtp] === true
18
+ ? [customer_auth_1.EMAIL_OTP_AUTH_PROVIDER]
19
+ : []),
20
+ ];
21
+ exports.deriveCustomerAuthMethods = deriveCustomerAuthMethods;
22
+ /**
23
+ * The methods of a shop whose switches were never stored: the defaults of
24
+ * the descriptor. This is the start value and the fallback of the snapshot,
25
+ * so an unreadable database never locks every customer out.
26
+ */
27
+ const defaultCustomerAuthMethods = (descriptor) => (0, exports.deriveCustomerAuthMethods)(Object.fromEntries(descriptor.features.map((feature) => [
28
+ feature.key,
29
+ feature.default_enabled,
30
+ ])));
31
+ exports.defaultCustomerAuthMethods = defaultCustomerAuthMethods;
32
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aC1tZXRob2RzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vc3JjL2N1c3RvbWVyLWF1dGgvZG9tYWluL2F1dGgtbWV0aG9kcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwyR0FHZ0Y7QUFJaEYsb0RBQW9EO0FBQ3ZDLFFBQUEsNEJBQTRCLEdBQUcsV0FBVyxDQUFBO0FBRXZEOzs7OztHQUtHO0FBQ0ksTUFBTSx5QkFBeUIsR0FBRyxDQUN2QyxRQUF1RCxFQUM3QyxFQUFFLENBQUM7SUFDYixHQUFHLENBQUMsUUFBUSxDQUFDLHNDQUFzQixDQUFDLGFBQWEsQ0FBQyxLQUFLLElBQUk7UUFDekQsQ0FBQyxDQUFDLENBQUMsb0NBQTRCLENBQUM7UUFDaEMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztJQUNQLEdBQUcsQ0FBQyxRQUFRLENBQUMsc0NBQXNCLENBQUMsUUFBUSxDQUFDLEtBQUssSUFBSTtRQUNwRCxDQUFDLENBQUMsQ0FBQyx1Q0FBdUIsQ0FBQztRQUMzQixDQUFDLENBQUMsRUFBRSxDQUFDO0NBQ1IsQ0FBQTtBQVRZLFFBQUEseUJBQXlCLDZCQVNyQztBQUVEOzs7O0dBSUc7QUFDSSxNQUFNLDBCQUEwQixHQUFHLENBQ3hDLFVBQW1DLEVBQ3pCLEVBQUUsQ0FDWixJQUFBLGlDQUF5QixFQUN2QixNQUFNLENBQUMsV0FBVyxDQUNoQixVQUFVLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUM7SUFDbkMsT0FBTyxDQUFDLEdBQUc7SUFDWCxPQUFPLENBQUMsZUFBZTtDQUN4QixDQUFDLENBQ0gsQ0FDRixDQUFBO0FBVlUsUUFBQSwwQkFBMEIsOEJBVXBDIn0=
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loginCodeHashesEqual = exports.hashLoginCode = exports.deriveLoginCodeKey = exports.generateLoginCode = exports.LOGIN_CODE_REJECTED_MESSAGE = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const customer_auth_1 = require("@hartl-services/medusa-food-supplements-contracts/schemas/customer-auth");
6
+ /** One message for a wrong, expired, used-up or unknown code: never says which. */
7
+ exports.LOGIN_CODE_REJECTED_MESSAGE = "Der Code ist falsch oder abgelaufen. Fordere bei Bedarf einen neuen Code an.";
8
+ /** HKDF `info` label of the login code key. */
9
+ const LOGIN_CODE_KEY_INFO = "email-otp-login-code";
10
+ /** A uniformly random code of exactly `LOGIN_CODE_LENGTH` digits, leading zeros kept. */
11
+ const generateLoginCode = () => String((0, node_crypto_1.randomInt)(0, 10 ** customer_auth_1.LOGIN_CODE_LENGTH)).padStart(customer_auth_1.LOGIN_CODE_LENGTH, "0");
12
+ exports.generateLoginCode = generateLoginCode;
13
+ /**
14
+ * The HMAC key of the login code hashes, derived from the shop's JWT secret
15
+ * (HKDF-SHA256, fixed purpose label) so the secret itself never keys the
16
+ * hashes and a code hash is of no use anywhere else. Both sides, the request
17
+ * use case and the `email-otp` provider, call this one function with the same
18
+ * secret (`projectConfig.http.jwtSecret`).
19
+ */
20
+ const deriveLoginCodeKey = (secret) => Buffer.from((0, node_crypto_1.hkdfSync)("sha256", secret, "", LOGIN_CODE_KEY_INFO, 32));
21
+ exports.deriveLoginCodeKey = deriveLoginCodeKey;
22
+ /**
23
+ * What is stored instead of the code: hex HMAC-SHA256 over the address and
24
+ * the code, so a copy of the row neither reveals the code (the space is only
25
+ * a million values, a plain hash would be brute-forced at once) nor works for
26
+ * another address. `secret` is the raw JWT secret; the key is derived here.
27
+ */
28
+ const hashLoginCode = (secret, entityId, code) => (0, node_crypto_1.createHmac)("sha256", (0, exports.deriveLoginCodeKey)(secret))
29
+ .update(`email-otp:v1:${entityId}:${code}`)
30
+ .digest("hex");
31
+ exports.hashLoginCode = hashLoginCode;
32
+ /** Constant-time comparison of two hex digests; false for other lengths. */
33
+ const loginCodeHashesEqual = (left, right) => {
34
+ const a = Buffer.from(left, "utf8");
35
+ const b = Buffer.from(right, "utf8");
36
+ return a.length === b.length && (0, node_crypto_1.timingSafeEqual)(a, b);
37
+ };
38
+ exports.loginCodeHashesEqual = loginCodeHashesEqual;
39
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG9naW4tY29kZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9jdXN0b21lci1hdXRoL2RvbWFpbi9sb2dpbi1jb2RlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDZDQUE4RTtBQUU5RSwyR0FBMkc7QUFFM0csbUZBQW1GO0FBQ3RFLFFBQUEsMkJBQTJCLEdBQ3RDLDhFQUE4RSxDQUFBO0FBRWhGLCtDQUErQztBQUMvQyxNQUFNLG1CQUFtQixHQUFHLHNCQUFzQixDQUFBO0FBRWxELHlGQUF5RjtBQUNsRixNQUFNLGlCQUFpQixHQUFHLEdBQVcsRUFBRSxDQUM1QyxNQUFNLENBQUMsSUFBQSx1QkFBUyxFQUFDLENBQUMsRUFBRSxFQUFFLElBQUksaUNBQWlCLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxpQ0FBaUIsRUFBRSxHQUFHLENBQUMsQ0FBQTtBQURuRSxRQUFBLGlCQUFpQixxQkFDa0Q7QUFFaEY7Ozs7OztHQU1HO0FBQ0ksTUFBTSxrQkFBa0IsR0FBRyxDQUFDLE1BQWMsRUFBVSxFQUFFLENBQzNELE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBQSxzQkFBUSxFQUFDLFFBQVEsRUFBRSxNQUFNLEVBQUUsRUFBRSxFQUFFLG1CQUFtQixFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUE7QUFEekQsUUFBQSxrQkFBa0Isc0JBQ3VDO0FBRXRFOzs7OztHQUtHO0FBQ0ksTUFBTSxhQUFhLEdBQUcsQ0FDM0IsTUFBYyxFQUNkLFFBQWdCLEVBQ2hCLElBQVksRUFDSixFQUFFLENBQ1YsSUFBQSx3QkFBVSxFQUFDLFFBQVEsRUFBRSxJQUFBLDBCQUFrQixFQUFDLE1BQU0sQ0FBQyxDQUFDO0tBQzdDLE1BQU0sQ0FBQyxnQkFBZ0IsUUFBUSxJQUFJLElBQUksRUFBRSxDQUFDO0tBQzFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQTtBQVBMLFFBQUEsYUFBYSxpQkFPUjtBQUVsQiw0RUFBNEU7QUFDckUsTUFBTSxvQkFBb0IsR0FBRyxDQUFDLElBQVksRUFBRSxLQUFhLEVBQVcsRUFBRTtJQUMzRSxNQUFNLENBQUMsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxNQUFNLENBQUMsQ0FBQTtJQUNuQyxNQUFNLENBQUMsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssRUFBRSxNQUFNLENBQUMsQ0FBQTtJQUNwQyxPQUFPLENBQUMsQ0FBQyxNQUFNLEtBQUssQ0FBQyxDQUFDLE1BQU0sSUFBSSxJQUFBLDZCQUFlLEVBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFBO0FBQ3ZELENBQUMsQ0FBQTtBQUpZLFFBQUEsb0JBQW9CLHdCQUloQyJ9