strapi-plugin-magic-link-v5 5.5.3 → 5.5.5

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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [5.5.5](https://github.com/Schero94/strapi-plugin-magic-link-v5/compare/v5.5.4...v5.5.5) (2026-04-21)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **boot:** hoist crypto self-test require to top level + fix wrong path ([521c73f](https://github.com/Schero94/strapi-plugin-magic-link-v5/commit/521c73f02acf671b08c3360781c77eeafd51b4c8))
7
+
8
+ ## [5.5.4](https://github.com/Schero94/strapi-plugin-magic-link-v5/compare/v5.5.3...v5.5.4) (2026-04-21)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **build:** avoid destructured requires that pack-up rewrites into undefined ([7aea267](https://github.com/Schero94/strapi-plugin-magic-link-v5/commit/7aea2671686a2c0ea7d6a5f1293d7a830628c98e))
14
+
1
15
  ## [5.5.3](https://github.com/Schero94/strapi-plugin-magic-link-v5/compare/v5.5.2...v5.5.3) (2026-04-21)
2
16
 
3
17
 
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
+ const require$$0$3 = require("crypto");
2
3
  const require$$0$6 = require("lodash");
3
4
  const require$$1$2 = require("nanoid");
4
- const require$$0$3 = require("fs");
5
+ const require$$0$4 = require("fs");
5
6
  const require$$1 = require("path");
6
- const require$$0$4 = require("crypto");
7
7
  const require$$0$5 = require("zod");
8
8
  const require$$1$1 = require("@strapi/utils");
9
9
  const require$$1$3 = require("os");
@@ -11,11 +11,11 @@ const require$$0$7 = require("rate-limiter-flexible");
11
11
  const require$$1$4 = require("speakeasy");
12
12
  const require$$2 = require("qrcode");
13
13
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
14
+ const require$$0__default = /* @__PURE__ */ _interopDefault(require$$0$3);
14
15
  const require$$0__default$3 = /* @__PURE__ */ _interopDefault(require$$0$6);
15
16
  const require$$1__default$2 = /* @__PURE__ */ _interopDefault(require$$1$2);
16
- const require$$0__default = /* @__PURE__ */ _interopDefault(require$$0$3);
17
- const require$$1__default = /* @__PURE__ */ _interopDefault(require$$1);
18
17
  const require$$0__default$1 = /* @__PURE__ */ _interopDefault(require$$0$4);
18
+ const require$$1__default = /* @__PURE__ */ _interopDefault(require$$1);
19
19
  const require$$0__default$2 = /* @__PURE__ */ _interopDefault(require$$0$5);
20
20
  const require$$1__default$1 = /* @__PURE__ */ _interopDefault(require$$1$1);
21
21
  const require$$1__default$3 = /* @__PURE__ */ _interopDefault(require$$1$3);
@@ -26,6 +26,143 @@ var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof win
26
26
  function getDefaultExportFromCjs(x) {
27
27
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
28
28
  }
29
+ const crypto$3 = require$$0__default.default;
30
+ let warnedMissingKey = false;
31
+ let warnedMissingPepper = false;
32
+ const isProduction = () => process.env.NODE_ENV === "production";
33
+ const devFallbackMaterial = () => process.env.ADMIN_JWT_SECRET || (Array.isArray(process.env.APP_KEYS) ? process.env.APP_KEYS[0] : process.env.APP_KEYS) || "magic-link-dev-fallback-DO-NOT-USE-IN-PRODUCTION";
34
+ const getEncryptionKey = () => {
35
+ const raw = process.env.MAGIC_LINK_ENCRYPTION_KEY;
36
+ if (raw && raw.length > 0) {
37
+ return crypto$3.createHash("sha256").update(raw).digest();
38
+ }
39
+ if (isProduction()) {
40
+ throw new Error(
41
+ `[magic-link] MAGIC_LINK_ENCRYPTION_KEY env var is required in production. Generate one with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`
42
+ );
43
+ }
44
+ if (!warnedMissingKey) {
45
+ warnedMissingKey = true;
46
+ console.warn(
47
+ "[magic-link] MAGIC_LINK_ENCRYPTION_KEY not set — using dev fallback. Set this env var before going to production (it is NOT rotatable)."
48
+ );
49
+ }
50
+ return crypto$3.createHash("sha256").update(devFallbackMaterial()).digest();
51
+ };
52
+ const getOtpPepper = () => {
53
+ const raw = process.env.MAGIC_LINK_OTP_PEPPER || process.env.OTP_PEPPER;
54
+ if (raw && raw.length >= 16) {
55
+ return raw;
56
+ }
57
+ if (isProduction()) {
58
+ throw new Error(
59
+ `[magic-link] MAGIC_LINK_OTP_PEPPER env var is required in production (min 16 characters). Generate one with: node -e "console.log(require('crypto').randomBytes(24).toString('hex'))"`
60
+ );
61
+ }
62
+ if (!warnedMissingPepper) {
63
+ warnedMissingPepper = true;
64
+ console.warn(
65
+ "[magic-link] MAGIC_LINK_OTP_PEPPER not set — using dev fallback. Set this before deploying to production."
66
+ );
67
+ }
68
+ return "magic-link-otp-dev-pepper-DO-NOT-USE-IN-PRODUCTION";
69
+ };
70
+ const hash = (value) => {
71
+ if (!value) return null;
72
+ return crypto$3.createHash("sha256").update(String(value)).digest("hex");
73
+ };
74
+ const hashToken = (token2, salt = null) => {
75
+ if (!token2) return { hash: null, salt: null };
76
+ const tokenSalt = salt || crypto$3.randomBytes(16).toString("hex");
77
+ const hashedToken = crypto$3.createHash("sha256").update(token2 + tokenSalt).digest("hex");
78
+ return { hash: hashedToken, salt: tokenSalt };
79
+ };
80
+ const verifyToken = (token2, storedHash, salt) => {
81
+ if (!token2 || !storedHash) return false;
82
+ const { hash: computedHash } = hashToken(token2, salt);
83
+ return crypto$3.timingSafeEqual(
84
+ Buffer.from(computedHash, "hex"),
85
+ Buffer.from(storedHash, "hex")
86
+ );
87
+ };
88
+ const encrypt = (value) => {
89
+ if (!value) return null;
90
+ const key = getEncryptionKey();
91
+ const iv = crypto$3.randomBytes(16);
92
+ const cipher = crypto$3.createCipheriv("aes-256-gcm", key, iv);
93
+ let encrypted = cipher.update(String(value), "utf8", "hex");
94
+ encrypted += cipher.final("hex");
95
+ const authTag = cipher.getAuthTag();
96
+ return Buffer.from(
97
+ iv.toString("hex") + ":" + authTag.toString("hex") + ":" + encrypted
98
+ ).toString("base64");
99
+ };
100
+ const decrypt = (encryptedValue) => {
101
+ if (!encryptedValue) return null;
102
+ const key = getEncryptionKey();
103
+ const data = Buffer.from(encryptedValue, "base64").toString("utf8");
104
+ const parts = data.split(":");
105
+ if (parts.length !== 3) {
106
+ throw new Error("[Crypto] decrypt: malformed ciphertext (expected iv:tag:ct)");
107
+ }
108
+ const [ivHex, authTagHex, encryptedHex] = parts;
109
+ if (!ivHex || !authTagHex || !encryptedHex) {
110
+ throw new Error("[Crypto] decrypt: missing ciphertext components");
111
+ }
112
+ const iv = Buffer.from(ivHex, "hex");
113
+ const authTag = Buffer.from(authTagHex, "hex");
114
+ const decipher = crypto$3.createDecipheriv("aes-256-gcm", key, iv);
115
+ decipher.setAuthTag(authTag);
116
+ let decrypted = decipher.update(encryptedHex, "hex", "utf8");
117
+ decrypted += decipher.final("utf8");
118
+ return decrypted;
119
+ };
120
+ const generateSecureRandom = (length = 32) => {
121
+ return crypto$3.randomBytes(Math.ceil(length / 2)).toString("hex").slice(0, length);
122
+ };
123
+ const hashOTP = (code) => {
124
+ if (!code) return null;
125
+ const pepper = getOtpPepper();
126
+ return crypto$3.createHash("sha256").update(code + pepper).digest("hex");
127
+ };
128
+ const verifyOTP = (code, storedHash) => {
129
+ if (!code || !storedHash) return false;
130
+ const computedHash = hashOTP(code);
131
+ try {
132
+ return crypto$3.timingSafeEqual(
133
+ Buffer.from(computedHash, "hex"),
134
+ Buffer.from(storedHash, "hex")
135
+ );
136
+ } catch {
137
+ return false;
138
+ }
139
+ };
140
+ const isEncrypted = (value) => {
141
+ if (!value) return false;
142
+ try {
143
+ const decoded = Buffer.from(value, "base64").toString("utf8");
144
+ const parts = decoded.split(":");
145
+ return parts.length === 3 && parts[0].length === 32 && parts[1].length === 32;
146
+ } catch {
147
+ return false;
148
+ }
149
+ };
150
+ const hashJwt = (jwt2) => {
151
+ if (!jwt2) return null;
152
+ return crypto$3.createHash("sha256").update(jwt2).digest("hex");
153
+ };
154
+ var crypto_1 = {
155
+ hash,
156
+ hashToken,
157
+ verifyToken,
158
+ encrypt,
159
+ decrypt,
160
+ generateSecureRandom,
161
+ hashOTP,
162
+ verifyOTP,
163
+ isEncrypted,
164
+ hashJwt
165
+ };
29
166
  const magicLinkActions = {
30
167
  actions: [
31
168
  {
@@ -51,11 +188,11 @@ const magicLinkActions = {
51
188
  }
52
189
  ]
53
190
  };
191
+ const cryptoUtilsSelfTest = crypto_1;
54
192
  var bootstrap$1 = async ({ strapi: strapi2 }) => {
55
193
  try {
56
- const cryptoUtils2 = require("../utils/crypto");
57
- cryptoUtils2.encrypt("self-test");
58
- cryptoUtils2.hashOTP("000000");
194
+ cryptoUtilsSelfTest.encrypt("self-test");
195
+ cryptoUtilsSelfTest.hashOTP("000000");
59
196
  } catch (e) {
60
197
  strapi2.log.error(`[magic-link] ${e.message}`);
61
198
  throw e;
@@ -793,7 +930,7 @@ This link will expire in 1 hour.`,
793
930
  }
794
931
  }
795
932
  };
796
- const fs$1 = require$$0__default.default;
933
+ const fs$1 = require$$0__default$1.default;
797
934
  const path$1 = require$$1__default.default;
798
935
  class I18n {
799
936
  constructor() {
@@ -935,143 +1072,6 @@ class I18n {
935
1072
  }
936
1073
  const i18n$1 = new I18n();
937
1074
  var i18n_1 = i18n$1;
938
- const crypto$3 = require$$0__default$1.default;
939
- let warnedMissingKey = false;
940
- let warnedMissingPepper = false;
941
- const isProduction = () => process.env.NODE_ENV === "production";
942
- const devFallbackMaterial = () => process.env.ADMIN_JWT_SECRET || (Array.isArray(process.env.APP_KEYS) ? process.env.APP_KEYS[0] : process.env.APP_KEYS) || "magic-link-dev-fallback-DO-NOT-USE-IN-PRODUCTION";
943
- const getEncryptionKey = () => {
944
- const raw = process.env.MAGIC_LINK_ENCRYPTION_KEY;
945
- if (raw && raw.length > 0) {
946
- return crypto$3.createHash("sha256").update(raw).digest();
947
- }
948
- if (isProduction()) {
949
- throw new Error(
950
- `[magic-link] MAGIC_LINK_ENCRYPTION_KEY env var is required in production. Generate one with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`
951
- );
952
- }
953
- if (!warnedMissingKey) {
954
- warnedMissingKey = true;
955
- console.warn(
956
- "[magic-link] MAGIC_LINK_ENCRYPTION_KEY not set — using dev fallback. Set this env var before going to production (it is NOT rotatable)."
957
- );
958
- }
959
- return crypto$3.createHash("sha256").update(devFallbackMaterial()).digest();
960
- };
961
- const getOtpPepper = () => {
962
- const raw = process.env.MAGIC_LINK_OTP_PEPPER || process.env.OTP_PEPPER;
963
- if (raw && raw.length >= 16) {
964
- return raw;
965
- }
966
- if (isProduction()) {
967
- throw new Error(
968
- `[magic-link] MAGIC_LINK_OTP_PEPPER env var is required in production (min 16 characters). Generate one with: node -e "console.log(require('crypto').randomBytes(24).toString('hex'))"`
969
- );
970
- }
971
- if (!warnedMissingPepper) {
972
- warnedMissingPepper = true;
973
- console.warn(
974
- "[magic-link] MAGIC_LINK_OTP_PEPPER not set — using dev fallback. Set this before deploying to production."
975
- );
976
- }
977
- return "magic-link-otp-dev-pepper-DO-NOT-USE-IN-PRODUCTION";
978
- };
979
- const hash = (value) => {
980
- if (!value) return null;
981
- return crypto$3.createHash("sha256").update(String(value)).digest("hex");
982
- };
983
- const hashToken = (token2, salt = null) => {
984
- if (!token2) return { hash: null, salt: null };
985
- const tokenSalt = salt || crypto$3.randomBytes(16).toString("hex");
986
- const hashedToken = crypto$3.createHash("sha256").update(token2 + tokenSalt).digest("hex");
987
- return { hash: hashedToken, salt: tokenSalt };
988
- };
989
- const verifyToken = (token2, storedHash, salt) => {
990
- if (!token2 || !storedHash) return false;
991
- const { hash: computedHash } = hashToken(token2, salt);
992
- return crypto$3.timingSafeEqual(
993
- Buffer.from(computedHash, "hex"),
994
- Buffer.from(storedHash, "hex")
995
- );
996
- };
997
- const encrypt = (value) => {
998
- if (!value) return null;
999
- const key = getEncryptionKey();
1000
- const iv = crypto$3.randomBytes(16);
1001
- const cipher = crypto$3.createCipheriv("aes-256-gcm", key, iv);
1002
- let encrypted = cipher.update(String(value), "utf8", "hex");
1003
- encrypted += cipher.final("hex");
1004
- const authTag = cipher.getAuthTag();
1005
- return Buffer.from(
1006
- iv.toString("hex") + ":" + authTag.toString("hex") + ":" + encrypted
1007
- ).toString("base64");
1008
- };
1009
- const decrypt = (encryptedValue) => {
1010
- if (!encryptedValue) return null;
1011
- const key = getEncryptionKey();
1012
- const data = Buffer.from(encryptedValue, "base64").toString("utf8");
1013
- const parts = data.split(":");
1014
- if (parts.length !== 3) {
1015
- throw new Error("[Crypto] decrypt: malformed ciphertext (expected iv:tag:ct)");
1016
- }
1017
- const [ivHex, authTagHex, encryptedHex] = parts;
1018
- if (!ivHex || !authTagHex || !encryptedHex) {
1019
- throw new Error("[Crypto] decrypt: missing ciphertext components");
1020
- }
1021
- const iv = Buffer.from(ivHex, "hex");
1022
- const authTag = Buffer.from(authTagHex, "hex");
1023
- const decipher = crypto$3.createDecipheriv("aes-256-gcm", key, iv);
1024
- decipher.setAuthTag(authTag);
1025
- let decrypted = decipher.update(encryptedHex, "hex", "utf8");
1026
- decrypted += decipher.final("utf8");
1027
- return decrypted;
1028
- };
1029
- const generateSecureRandom = (length = 32) => {
1030
- return crypto$3.randomBytes(Math.ceil(length / 2)).toString("hex").slice(0, length);
1031
- };
1032
- const hashOTP = (code) => {
1033
- if (!code) return null;
1034
- const pepper = getOtpPepper();
1035
- return crypto$3.createHash("sha256").update(code + pepper).digest("hex");
1036
- };
1037
- const verifyOTP = (code, storedHash) => {
1038
- if (!code || !storedHash) return false;
1039
- const computedHash = hashOTP(code);
1040
- try {
1041
- return crypto$3.timingSafeEqual(
1042
- Buffer.from(computedHash, "hex"),
1043
- Buffer.from(storedHash, "hex")
1044
- );
1045
- } catch {
1046
- return false;
1047
- }
1048
- };
1049
- const isEncrypted = (value) => {
1050
- if (!value) return false;
1051
- try {
1052
- const decoded = Buffer.from(value, "base64").toString("utf8");
1053
- const parts = decoded.split(":");
1054
- return parts.length === 3 && parts[0].length === 32 && parts[1].length === 32;
1055
- } catch {
1056
- return false;
1057
- }
1058
- };
1059
- const hashJwt = (jwt2) => {
1060
- if (!jwt2) return null;
1061
- return crypto$3.createHash("sha256").update(jwt2).digest("hex");
1062
- };
1063
- var crypto_1 = {
1064
- hash,
1065
- hashToken,
1066
- verifyToken,
1067
- encrypt,
1068
- decrypt,
1069
- generateSecureRandom,
1070
- hashOTP,
1071
- verifyOTP,
1072
- isEncrypted,
1073
- hashJwt
1074
- };
1075
1075
  const normalizeEmail$2 = (raw) => {
1076
1076
  if (typeof raw !== "string") return null;
1077
1077
  const trimmed = raw.trim();
@@ -1109,9 +1109,8 @@ async function appendJwtSession$1(strapi2, record) {
1109
1109
  var jwtSessionStore = {
1110
1110
  appendJwtSession: appendJwtSession$1
1111
1111
  };
1112
- const { z } = require$$0__default$2.default;
1113
- const { errors: errors$1 } = require$$1__default$1.default;
1114
- const { ValidationError } = errors$1;
1112
+ const z = require$$0__default$2.default;
1113
+ const { ValidationError } = require$$1__default$1.default.errors;
1115
1114
  const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1116
1115
  const emailSchema = z.string().trim().min(3).max(320).regex(EMAIL_REGEX, "Invalid email").transform((v) => v.toLowerCase());
1117
1116
  const tokenSchema = z.string().min(1).max(512);
@@ -1939,13 +1938,13 @@ const createPlainTextVersion = (html, variables = {}) => {
1939
1938
  return text;
1940
1939
  };
1941
1940
  const validateEmailConfig = (emailConfig) => {
1942
- const errors2 = [];
1941
+ const errors = [];
1943
1942
  const warnings = [];
1944
1943
  if (!emailConfig.to || !emailConfig.to.includes("@")) {
1945
- errors2.push("Invalid recipient email address");
1944
+ errors.push("Invalid recipient email address");
1946
1945
  }
1947
1946
  if (!emailConfig.from || !emailConfig.from.includes("@")) {
1948
- errors2.push("Invalid sender email address");
1947
+ errors.push("Invalid sender email address");
1949
1948
  }
1950
1949
  if (!emailConfig.subject || emailConfig.subject.trim().length === 0) {
1951
1950
  warnings.push("Empty subject line may trigger spam filters");
@@ -1957,8 +1956,8 @@ const validateEmailConfig = (emailConfig) => {
1957
1956
  warnings.push("No HTML version provided");
1958
1957
  }
1959
1958
  return {
1960
- valid: errors2.length === 0,
1961
- errors: errors2,
1959
+ valid: errors.length === 0,
1960
+ errors,
1962
1961
  warnings
1963
1962
  };
1964
1963
  };
@@ -1969,7 +1968,7 @@ var emailHelpers$3 = {
1969
1968
  validateEmailConfig
1970
1969
  };
1971
1970
  const { nanoid: nanoid$1 } = require$$1__default$2.default;
1972
- const crypto$2 = require$$0__default$1.default;
1971
+ const crypto$2 = require$$0__default.default;
1973
1972
  const emailHelpers$2 = emailHelpers$3;
1974
1973
  const cryptoUtils$3 = crypto_1;
1975
1974
  const formatExpiryText$1 = (minutes) => {
@@ -3762,8 +3761,7 @@ const passwordlessCompat = passwordlessCompat$2;
3762
3761
  var middlewares$1 = {
3763
3762
  "passwordless-compat": passwordlessCompat
3764
3763
  };
3765
- const { errors } = require$$1__default$1.default;
3766
- const { PolicyError } = errors;
3764
+ const { PolicyError } = require$$1__default$1.default.errors;
3767
3765
  var licenseCheck$1 = async (policyContext, config2, { strapi: strapi2 }) => {
3768
3766
  try {
3769
3767
  const licenseGuard2 = strapi2.plugin("magic-link").service("license-guard");
@@ -4249,7 +4247,7 @@ var service$1 = ({ strapi: strapi2 }) => ({
4249
4247
  });
4250
4248
  const _ = require$$0__default$3.default;
4251
4249
  const { nanoid } = require$$1__default$2.default;
4252
- const { sanitize } = require$$1__default$1.default;
4250
+ require$$1__default$1.default.sanitize;
4253
4251
  const emailHelpers$1 = emailHelpers$3;
4254
4252
  const cryptoUtils$1 = crypto_1;
4255
4253
  const { normalizeEmail } = email;
@@ -5088,7 +5086,7 @@ function requireFeatures() {
5088
5086
  };
5089
5087
  return features$1;
5090
5088
  }
5091
- const crypto$1 = require$$0__default$1.default;
5089
+ const crypto$1 = require$$0__default.default;
5092
5090
  const os = require$$1__default$3.default;
5093
5091
  const LICENSE_SERVER_URL = "https://magicapi.fitlex.me";
5094
5092
  const envTimeout = Number(process.env.MAGIC_LICENSE_TIMEOUT_MS);
@@ -5725,7 +5723,7 @@ var rateLimiter$1 = ({ strapi: strapi2 }) => {
5725
5723
  }
5726
5724
  };
5727
5725
  };
5728
- const crypto = require$$0__default$1.default;
5726
+ const crypto = require$$0__default.default;
5729
5727
  const speakeasy = require$$1__default$4.default;
5730
5728
  const QRCode = require$$2__default.default;
5731
5729
  const emailHelpers = emailHelpers$3;
@@ -6369,7 +6367,7 @@ If you didn't request this code, you can safely ignore this email.
6369
6367
  };
6370
6368
  };
6371
6369
  const path = require$$1__default.default;
6372
- const fs = require$$0__default.default;
6370
+ const fs = require$$0__default$1.default;
6373
6371
  let baileys = null;
6374
6372
  const getMagicMailWhatsApp = (strapi2) => {
6375
6373
  try {
@@ -1,8 +1,8 @@
1
+ import require$$0$3 from "crypto";
1
2
  import require$$0$6 from "lodash";
2
3
  import require$$1$2 from "nanoid";
3
- import require$$0$3 from "fs";
4
+ import require$$0$4 from "fs";
4
5
  import require$$1 from "path";
5
- import require$$0$4 from "crypto";
6
6
  import require$$0$5 from "zod";
7
7
  import require$$1$1 from "@strapi/utils";
8
8
  import require$$1$3 from "os";
@@ -13,6 +13,143 @@ var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof win
13
13
  function getDefaultExportFromCjs(x) {
14
14
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
15
15
  }
16
+ const crypto$3 = require$$0$3;
17
+ let warnedMissingKey = false;
18
+ let warnedMissingPepper = false;
19
+ const isProduction = () => process.env.NODE_ENV === "production";
20
+ const devFallbackMaterial = () => process.env.ADMIN_JWT_SECRET || (Array.isArray(process.env.APP_KEYS) ? process.env.APP_KEYS[0] : process.env.APP_KEYS) || "magic-link-dev-fallback-DO-NOT-USE-IN-PRODUCTION";
21
+ const getEncryptionKey = () => {
22
+ const raw = process.env.MAGIC_LINK_ENCRYPTION_KEY;
23
+ if (raw && raw.length > 0) {
24
+ return crypto$3.createHash("sha256").update(raw).digest();
25
+ }
26
+ if (isProduction()) {
27
+ throw new Error(
28
+ `[magic-link] MAGIC_LINK_ENCRYPTION_KEY env var is required in production. Generate one with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`
29
+ );
30
+ }
31
+ if (!warnedMissingKey) {
32
+ warnedMissingKey = true;
33
+ console.warn(
34
+ "[magic-link] MAGIC_LINK_ENCRYPTION_KEY not set — using dev fallback. Set this env var before going to production (it is NOT rotatable)."
35
+ );
36
+ }
37
+ return crypto$3.createHash("sha256").update(devFallbackMaterial()).digest();
38
+ };
39
+ const getOtpPepper = () => {
40
+ const raw = process.env.MAGIC_LINK_OTP_PEPPER || process.env.OTP_PEPPER;
41
+ if (raw && raw.length >= 16) {
42
+ return raw;
43
+ }
44
+ if (isProduction()) {
45
+ throw new Error(
46
+ `[magic-link] MAGIC_LINK_OTP_PEPPER env var is required in production (min 16 characters). Generate one with: node -e "console.log(require('crypto').randomBytes(24).toString('hex'))"`
47
+ );
48
+ }
49
+ if (!warnedMissingPepper) {
50
+ warnedMissingPepper = true;
51
+ console.warn(
52
+ "[magic-link] MAGIC_LINK_OTP_PEPPER not set — using dev fallback. Set this before deploying to production."
53
+ );
54
+ }
55
+ return "magic-link-otp-dev-pepper-DO-NOT-USE-IN-PRODUCTION";
56
+ };
57
+ const hash = (value) => {
58
+ if (!value) return null;
59
+ return crypto$3.createHash("sha256").update(String(value)).digest("hex");
60
+ };
61
+ const hashToken = (token2, salt = null) => {
62
+ if (!token2) return { hash: null, salt: null };
63
+ const tokenSalt = salt || crypto$3.randomBytes(16).toString("hex");
64
+ const hashedToken = crypto$3.createHash("sha256").update(token2 + tokenSalt).digest("hex");
65
+ return { hash: hashedToken, salt: tokenSalt };
66
+ };
67
+ const verifyToken = (token2, storedHash, salt) => {
68
+ if (!token2 || !storedHash) return false;
69
+ const { hash: computedHash } = hashToken(token2, salt);
70
+ return crypto$3.timingSafeEqual(
71
+ Buffer.from(computedHash, "hex"),
72
+ Buffer.from(storedHash, "hex")
73
+ );
74
+ };
75
+ const encrypt = (value) => {
76
+ if (!value) return null;
77
+ const key = getEncryptionKey();
78
+ const iv = crypto$3.randomBytes(16);
79
+ const cipher = crypto$3.createCipheriv("aes-256-gcm", key, iv);
80
+ let encrypted = cipher.update(String(value), "utf8", "hex");
81
+ encrypted += cipher.final("hex");
82
+ const authTag = cipher.getAuthTag();
83
+ return Buffer.from(
84
+ iv.toString("hex") + ":" + authTag.toString("hex") + ":" + encrypted
85
+ ).toString("base64");
86
+ };
87
+ const decrypt = (encryptedValue) => {
88
+ if (!encryptedValue) return null;
89
+ const key = getEncryptionKey();
90
+ const data = Buffer.from(encryptedValue, "base64").toString("utf8");
91
+ const parts = data.split(":");
92
+ if (parts.length !== 3) {
93
+ throw new Error("[Crypto] decrypt: malformed ciphertext (expected iv:tag:ct)");
94
+ }
95
+ const [ivHex, authTagHex, encryptedHex] = parts;
96
+ if (!ivHex || !authTagHex || !encryptedHex) {
97
+ throw new Error("[Crypto] decrypt: missing ciphertext components");
98
+ }
99
+ const iv = Buffer.from(ivHex, "hex");
100
+ const authTag = Buffer.from(authTagHex, "hex");
101
+ const decipher = crypto$3.createDecipheriv("aes-256-gcm", key, iv);
102
+ decipher.setAuthTag(authTag);
103
+ let decrypted = decipher.update(encryptedHex, "hex", "utf8");
104
+ decrypted += decipher.final("utf8");
105
+ return decrypted;
106
+ };
107
+ const generateSecureRandom = (length = 32) => {
108
+ return crypto$3.randomBytes(Math.ceil(length / 2)).toString("hex").slice(0, length);
109
+ };
110
+ const hashOTP = (code) => {
111
+ if (!code) return null;
112
+ const pepper = getOtpPepper();
113
+ return crypto$3.createHash("sha256").update(code + pepper).digest("hex");
114
+ };
115
+ const verifyOTP = (code, storedHash) => {
116
+ if (!code || !storedHash) return false;
117
+ const computedHash = hashOTP(code);
118
+ try {
119
+ return crypto$3.timingSafeEqual(
120
+ Buffer.from(computedHash, "hex"),
121
+ Buffer.from(storedHash, "hex")
122
+ );
123
+ } catch {
124
+ return false;
125
+ }
126
+ };
127
+ const isEncrypted = (value) => {
128
+ if (!value) return false;
129
+ try {
130
+ const decoded = Buffer.from(value, "base64").toString("utf8");
131
+ const parts = decoded.split(":");
132
+ return parts.length === 3 && parts[0].length === 32 && parts[1].length === 32;
133
+ } catch {
134
+ return false;
135
+ }
136
+ };
137
+ const hashJwt = (jwt2) => {
138
+ if (!jwt2) return null;
139
+ return crypto$3.createHash("sha256").update(jwt2).digest("hex");
140
+ };
141
+ var crypto_1 = {
142
+ hash,
143
+ hashToken,
144
+ verifyToken,
145
+ encrypt,
146
+ decrypt,
147
+ generateSecureRandom,
148
+ hashOTP,
149
+ verifyOTP,
150
+ isEncrypted,
151
+ hashJwt
152
+ };
16
153
  const magicLinkActions = {
17
154
  actions: [
18
155
  {
@@ -38,11 +175,11 @@ const magicLinkActions = {
38
175
  }
39
176
  ]
40
177
  };
178
+ const cryptoUtilsSelfTest = crypto_1;
41
179
  var bootstrap$1 = async ({ strapi: strapi2 }) => {
42
180
  try {
43
- const cryptoUtils2 = require("../utils/crypto");
44
- cryptoUtils2.encrypt("self-test");
45
- cryptoUtils2.hashOTP("000000");
181
+ cryptoUtilsSelfTest.encrypt("self-test");
182
+ cryptoUtilsSelfTest.hashOTP("000000");
46
183
  } catch (e) {
47
184
  strapi2.log.error(`[magic-link] ${e.message}`);
48
185
  throw e;
@@ -780,7 +917,7 @@ This link will expire in 1 hour.`,
780
917
  }
781
918
  }
782
919
  };
783
- const fs$1 = require$$0$3;
920
+ const fs$1 = require$$0$4;
784
921
  const path$1 = require$$1;
785
922
  class I18n {
786
923
  constructor() {
@@ -922,143 +1059,6 @@ class I18n {
922
1059
  }
923
1060
  const i18n$1 = new I18n();
924
1061
  var i18n_1 = i18n$1;
925
- const crypto$3 = require$$0$4;
926
- let warnedMissingKey = false;
927
- let warnedMissingPepper = false;
928
- const isProduction = () => process.env.NODE_ENV === "production";
929
- const devFallbackMaterial = () => process.env.ADMIN_JWT_SECRET || (Array.isArray(process.env.APP_KEYS) ? process.env.APP_KEYS[0] : process.env.APP_KEYS) || "magic-link-dev-fallback-DO-NOT-USE-IN-PRODUCTION";
930
- const getEncryptionKey = () => {
931
- const raw = process.env.MAGIC_LINK_ENCRYPTION_KEY;
932
- if (raw && raw.length > 0) {
933
- return crypto$3.createHash("sha256").update(raw).digest();
934
- }
935
- if (isProduction()) {
936
- throw new Error(
937
- `[magic-link] MAGIC_LINK_ENCRYPTION_KEY env var is required in production. Generate one with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`
938
- );
939
- }
940
- if (!warnedMissingKey) {
941
- warnedMissingKey = true;
942
- console.warn(
943
- "[magic-link] MAGIC_LINK_ENCRYPTION_KEY not set — using dev fallback. Set this env var before going to production (it is NOT rotatable)."
944
- );
945
- }
946
- return crypto$3.createHash("sha256").update(devFallbackMaterial()).digest();
947
- };
948
- const getOtpPepper = () => {
949
- const raw = process.env.MAGIC_LINK_OTP_PEPPER || process.env.OTP_PEPPER;
950
- if (raw && raw.length >= 16) {
951
- return raw;
952
- }
953
- if (isProduction()) {
954
- throw new Error(
955
- `[magic-link] MAGIC_LINK_OTP_PEPPER env var is required in production (min 16 characters). Generate one with: node -e "console.log(require('crypto').randomBytes(24).toString('hex'))"`
956
- );
957
- }
958
- if (!warnedMissingPepper) {
959
- warnedMissingPepper = true;
960
- console.warn(
961
- "[magic-link] MAGIC_LINK_OTP_PEPPER not set — using dev fallback. Set this before deploying to production."
962
- );
963
- }
964
- return "magic-link-otp-dev-pepper-DO-NOT-USE-IN-PRODUCTION";
965
- };
966
- const hash = (value) => {
967
- if (!value) return null;
968
- return crypto$3.createHash("sha256").update(String(value)).digest("hex");
969
- };
970
- const hashToken = (token2, salt = null) => {
971
- if (!token2) return { hash: null, salt: null };
972
- const tokenSalt = salt || crypto$3.randomBytes(16).toString("hex");
973
- const hashedToken = crypto$3.createHash("sha256").update(token2 + tokenSalt).digest("hex");
974
- return { hash: hashedToken, salt: tokenSalt };
975
- };
976
- const verifyToken = (token2, storedHash, salt) => {
977
- if (!token2 || !storedHash) return false;
978
- const { hash: computedHash } = hashToken(token2, salt);
979
- return crypto$3.timingSafeEqual(
980
- Buffer.from(computedHash, "hex"),
981
- Buffer.from(storedHash, "hex")
982
- );
983
- };
984
- const encrypt = (value) => {
985
- if (!value) return null;
986
- const key = getEncryptionKey();
987
- const iv = crypto$3.randomBytes(16);
988
- const cipher = crypto$3.createCipheriv("aes-256-gcm", key, iv);
989
- let encrypted = cipher.update(String(value), "utf8", "hex");
990
- encrypted += cipher.final("hex");
991
- const authTag = cipher.getAuthTag();
992
- return Buffer.from(
993
- iv.toString("hex") + ":" + authTag.toString("hex") + ":" + encrypted
994
- ).toString("base64");
995
- };
996
- const decrypt = (encryptedValue) => {
997
- if (!encryptedValue) return null;
998
- const key = getEncryptionKey();
999
- const data = Buffer.from(encryptedValue, "base64").toString("utf8");
1000
- const parts = data.split(":");
1001
- if (parts.length !== 3) {
1002
- throw new Error("[Crypto] decrypt: malformed ciphertext (expected iv:tag:ct)");
1003
- }
1004
- const [ivHex, authTagHex, encryptedHex] = parts;
1005
- if (!ivHex || !authTagHex || !encryptedHex) {
1006
- throw new Error("[Crypto] decrypt: missing ciphertext components");
1007
- }
1008
- const iv = Buffer.from(ivHex, "hex");
1009
- const authTag = Buffer.from(authTagHex, "hex");
1010
- const decipher = crypto$3.createDecipheriv("aes-256-gcm", key, iv);
1011
- decipher.setAuthTag(authTag);
1012
- let decrypted = decipher.update(encryptedHex, "hex", "utf8");
1013
- decrypted += decipher.final("utf8");
1014
- return decrypted;
1015
- };
1016
- const generateSecureRandom = (length = 32) => {
1017
- return crypto$3.randomBytes(Math.ceil(length / 2)).toString("hex").slice(0, length);
1018
- };
1019
- const hashOTP = (code) => {
1020
- if (!code) return null;
1021
- const pepper = getOtpPepper();
1022
- return crypto$3.createHash("sha256").update(code + pepper).digest("hex");
1023
- };
1024
- const verifyOTP = (code, storedHash) => {
1025
- if (!code || !storedHash) return false;
1026
- const computedHash = hashOTP(code);
1027
- try {
1028
- return crypto$3.timingSafeEqual(
1029
- Buffer.from(computedHash, "hex"),
1030
- Buffer.from(storedHash, "hex")
1031
- );
1032
- } catch {
1033
- return false;
1034
- }
1035
- };
1036
- const isEncrypted = (value) => {
1037
- if (!value) return false;
1038
- try {
1039
- const decoded = Buffer.from(value, "base64").toString("utf8");
1040
- const parts = decoded.split(":");
1041
- return parts.length === 3 && parts[0].length === 32 && parts[1].length === 32;
1042
- } catch {
1043
- return false;
1044
- }
1045
- };
1046
- const hashJwt = (jwt2) => {
1047
- if (!jwt2) return null;
1048
- return crypto$3.createHash("sha256").update(jwt2).digest("hex");
1049
- };
1050
- var crypto_1 = {
1051
- hash,
1052
- hashToken,
1053
- verifyToken,
1054
- encrypt,
1055
- decrypt,
1056
- generateSecureRandom,
1057
- hashOTP,
1058
- verifyOTP,
1059
- isEncrypted,
1060
- hashJwt
1061
- };
1062
1062
  const normalizeEmail$2 = (raw) => {
1063
1063
  if (typeof raw !== "string") return null;
1064
1064
  const trimmed = raw.trim();
@@ -1096,9 +1096,8 @@ async function appendJwtSession$1(strapi2, record) {
1096
1096
  var jwtSessionStore = {
1097
1097
  appendJwtSession: appendJwtSession$1
1098
1098
  };
1099
- const { z } = require$$0$5;
1100
- const { errors: errors$1 } = require$$1$1;
1101
- const { ValidationError } = errors$1;
1099
+ const z = require$$0$5;
1100
+ const { ValidationError } = require$$1$1.errors;
1102
1101
  const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1103
1102
  const emailSchema = z.string().trim().min(3).max(320).regex(EMAIL_REGEX, "Invalid email").transform((v) => v.toLowerCase());
1104
1103
  const tokenSchema = z.string().min(1).max(512);
@@ -1926,13 +1925,13 @@ const createPlainTextVersion = (html, variables = {}) => {
1926
1925
  return text;
1927
1926
  };
1928
1927
  const validateEmailConfig = (emailConfig) => {
1929
- const errors2 = [];
1928
+ const errors = [];
1930
1929
  const warnings = [];
1931
1930
  if (!emailConfig.to || !emailConfig.to.includes("@")) {
1932
- errors2.push("Invalid recipient email address");
1931
+ errors.push("Invalid recipient email address");
1933
1932
  }
1934
1933
  if (!emailConfig.from || !emailConfig.from.includes("@")) {
1935
- errors2.push("Invalid sender email address");
1934
+ errors.push("Invalid sender email address");
1936
1935
  }
1937
1936
  if (!emailConfig.subject || emailConfig.subject.trim().length === 0) {
1938
1937
  warnings.push("Empty subject line may trigger spam filters");
@@ -1944,8 +1943,8 @@ const validateEmailConfig = (emailConfig) => {
1944
1943
  warnings.push("No HTML version provided");
1945
1944
  }
1946
1945
  return {
1947
- valid: errors2.length === 0,
1948
- errors: errors2,
1946
+ valid: errors.length === 0,
1947
+ errors,
1949
1948
  warnings
1950
1949
  };
1951
1950
  };
@@ -1956,7 +1955,7 @@ var emailHelpers$3 = {
1956
1955
  validateEmailConfig
1957
1956
  };
1958
1957
  const { nanoid: nanoid$1 } = require$$1$2;
1959
- const crypto$2 = require$$0$4;
1958
+ const crypto$2 = require$$0$3;
1960
1959
  const emailHelpers$2 = emailHelpers$3;
1961
1960
  const cryptoUtils$3 = crypto_1;
1962
1961
  const formatExpiryText$1 = (minutes) => {
@@ -3749,8 +3748,7 @@ const passwordlessCompat = passwordlessCompat$2;
3749
3748
  var middlewares$1 = {
3750
3749
  "passwordless-compat": passwordlessCompat
3751
3750
  };
3752
- const { errors } = require$$1$1;
3753
- const { PolicyError } = errors;
3751
+ const { PolicyError } = require$$1$1.errors;
3754
3752
  var licenseCheck$1 = async (policyContext, config2, { strapi: strapi2 }) => {
3755
3753
  try {
3756
3754
  const licenseGuard2 = strapi2.plugin("magic-link").service("license-guard");
@@ -4236,7 +4234,7 @@ var service$1 = ({ strapi: strapi2 }) => ({
4236
4234
  });
4237
4235
  const _ = require$$0$6;
4238
4236
  const { nanoid } = require$$1$2;
4239
- const { sanitize } = require$$1$1;
4237
+ require$$1$1.sanitize;
4240
4238
  const emailHelpers$1 = emailHelpers$3;
4241
4239
  const cryptoUtils$1 = crypto_1;
4242
4240
  const { normalizeEmail } = email;
@@ -5075,7 +5073,7 @@ function requireFeatures() {
5075
5073
  };
5076
5074
  return features$1;
5077
5075
  }
5078
- const crypto$1 = require$$0$4;
5076
+ const crypto$1 = require$$0$3;
5079
5077
  const os = require$$1$3;
5080
5078
  const LICENSE_SERVER_URL = "https://magicapi.fitlex.me";
5081
5079
  const envTimeout = Number(process.env.MAGIC_LICENSE_TIMEOUT_MS);
@@ -5712,7 +5710,7 @@ var rateLimiter$1 = ({ strapi: strapi2 }) => {
5712
5710
  }
5713
5711
  };
5714
5712
  };
5715
- const crypto = require$$0$4;
5713
+ const crypto = require$$0$3;
5716
5714
  const speakeasy = require$$1$4;
5717
5715
  const QRCode = require$$2;
5718
5716
  const emailHelpers = emailHelpers$3;
@@ -6356,7 +6354,7 @@ If you didn't request this code, you can safely ignore this email.
6356
6354
  };
6357
6355
  };
6358
6356
  const path = require$$1;
6359
- const fs = require$$0$3;
6357
+ const fs = require$$0$4;
6360
6358
  let baileys = null;
6361
6359
  const getMagicMailWhatsApp = (strapi2) => {
6362
6360
  try {
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "5.5.3",
2
+ "version": "5.5.5",
3
3
  "keywords": [
4
4
  "strapi",
5
5
  "plugin",