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 +14 -0
- package/dist/server/index.js +158 -160
- package/dist/server/index.mjs +156 -158
- package/package.json +1 -1
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
|
|
package/dist/server/index.js
CHANGED
|
@@ -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$
|
|
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
|
-
|
|
57
|
-
|
|
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
|
|
1113
|
-
const {
|
|
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
|
|
1941
|
+
const errors = [];
|
|
1943
1942
|
const warnings = [];
|
|
1944
1943
|
if (!emailConfig.to || !emailConfig.to.includes("@")) {
|
|
1945
|
-
|
|
1944
|
+
errors.push("Invalid recipient email address");
|
|
1946
1945
|
}
|
|
1947
1946
|
if (!emailConfig.from || !emailConfig.from.includes("@")) {
|
|
1948
|
-
|
|
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:
|
|
1961
|
-
errors
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
|
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 {
|
package/dist/server/index.mjs
CHANGED
|
@@ -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$
|
|
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
|
-
|
|
44
|
-
|
|
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$
|
|
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
|
|
1100
|
-
const {
|
|
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
|
|
1928
|
+
const errors = [];
|
|
1930
1929
|
const warnings = [];
|
|
1931
1930
|
if (!emailConfig.to || !emailConfig.to.includes("@")) {
|
|
1932
|
-
|
|
1931
|
+
errors.push("Invalid recipient email address");
|
|
1933
1932
|
}
|
|
1934
1933
|
if (!emailConfig.from || !emailConfig.from.includes("@")) {
|
|
1935
|
-
|
|
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:
|
|
1948
|
-
errors
|
|
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$
|
|
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 {
|
|
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
|
-
|
|
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$
|
|
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$
|
|
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$
|
|
6357
|
+
const fs = require$$0$4;
|
|
6360
6358
|
let baileys = null;
|
|
6361
6359
|
const getMagicMailWhatsApp = (strapi2) => {
|
|
6362
6360
|
try {
|
package/package.json
CHANGED