strapi-plugin-magic-link-v5 5.5.4 → 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 +7 -0
- package/dist/server/index.js +149 -149
- package/dist/server/index.mjs +147 -147
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
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
|
+
|
|
1
8
|
## [5.5.4](https://github.com/Schero94/strapi-plugin-magic-link-v5/compare/v5.5.3...v5.5.4) (2026-04-21)
|
|
2
9
|
|
|
3
10
|
|
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();
|
|
@@ -1968,7 +1968,7 @@ var emailHelpers$3 = {
|
|
|
1968
1968
|
validateEmailConfig
|
|
1969
1969
|
};
|
|
1970
1970
|
const { nanoid: nanoid$1 } = require$$1__default$2.default;
|
|
1971
|
-
const crypto$2 = require$$0__default
|
|
1971
|
+
const crypto$2 = require$$0__default.default;
|
|
1972
1972
|
const emailHelpers$2 = emailHelpers$3;
|
|
1973
1973
|
const cryptoUtils$3 = crypto_1;
|
|
1974
1974
|
const formatExpiryText$1 = (minutes) => {
|
|
@@ -5086,7 +5086,7 @@ function requireFeatures() {
|
|
|
5086
5086
|
};
|
|
5087
5087
|
return features$1;
|
|
5088
5088
|
}
|
|
5089
|
-
const crypto$1 = require$$0__default
|
|
5089
|
+
const crypto$1 = require$$0__default.default;
|
|
5090
5090
|
const os = require$$1__default$3.default;
|
|
5091
5091
|
const LICENSE_SERVER_URL = "https://magicapi.fitlex.me";
|
|
5092
5092
|
const envTimeout = Number(process.env.MAGIC_LICENSE_TIMEOUT_MS);
|
|
@@ -5723,7 +5723,7 @@ var rateLimiter$1 = ({ strapi: strapi2 }) => {
|
|
|
5723
5723
|
}
|
|
5724
5724
|
};
|
|
5725
5725
|
};
|
|
5726
|
-
const crypto = require$$0__default
|
|
5726
|
+
const crypto = require$$0__default.default;
|
|
5727
5727
|
const speakeasy = require$$1__default$4.default;
|
|
5728
5728
|
const QRCode = require$$2__default.default;
|
|
5729
5729
|
const emailHelpers = emailHelpers$3;
|
|
@@ -6367,7 +6367,7 @@ If you didn't request this code, you can safely ignore this email.
|
|
|
6367
6367
|
};
|
|
6368
6368
|
};
|
|
6369
6369
|
const path = require$$1__default.default;
|
|
6370
|
-
const fs = require$$0__default.default;
|
|
6370
|
+
const fs = require$$0__default$1.default;
|
|
6371
6371
|
let baileys = null;
|
|
6372
6372
|
const getMagicMailWhatsApp = (strapi2) => {
|
|
6373
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();
|
|
@@ -1955,7 +1955,7 @@ var emailHelpers$3 = {
|
|
|
1955
1955
|
validateEmailConfig
|
|
1956
1956
|
};
|
|
1957
1957
|
const { nanoid: nanoid$1 } = require$$1$2;
|
|
1958
|
-
const crypto$2 = require$$0$
|
|
1958
|
+
const crypto$2 = require$$0$3;
|
|
1959
1959
|
const emailHelpers$2 = emailHelpers$3;
|
|
1960
1960
|
const cryptoUtils$3 = crypto_1;
|
|
1961
1961
|
const formatExpiryText$1 = (minutes) => {
|
|
@@ -5073,7 +5073,7 @@ function requireFeatures() {
|
|
|
5073
5073
|
};
|
|
5074
5074
|
return features$1;
|
|
5075
5075
|
}
|
|
5076
|
-
const crypto$1 = require$$0$
|
|
5076
|
+
const crypto$1 = require$$0$3;
|
|
5077
5077
|
const os = require$$1$3;
|
|
5078
5078
|
const LICENSE_SERVER_URL = "https://magicapi.fitlex.me";
|
|
5079
5079
|
const envTimeout = Number(process.env.MAGIC_LICENSE_TIMEOUT_MS);
|
|
@@ -5710,7 +5710,7 @@ var rateLimiter$1 = ({ strapi: strapi2 }) => {
|
|
|
5710
5710
|
}
|
|
5711
5711
|
};
|
|
5712
5712
|
};
|
|
5713
|
-
const crypto = require$$0$
|
|
5713
|
+
const crypto = require$$0$3;
|
|
5714
5714
|
const speakeasy = require$$1$4;
|
|
5715
5715
|
const QRCode = require$$2;
|
|
5716
5716
|
const emailHelpers = emailHelpers$3;
|
|
@@ -6354,7 +6354,7 @@ If you didn't request this code, you can safely ignore this email.
|
|
|
6354
6354
|
};
|
|
6355
6355
|
};
|
|
6356
6356
|
const path = require$$1;
|
|
6357
|
-
const fs = require$$0$
|
|
6357
|
+
const fs = require$$0$4;
|
|
6358
6358
|
let baileys = null;
|
|
6359
6359
|
const getMagicMailWhatsApp = (strapi2) => {
|
|
6360
6360
|
try {
|
package/package.json
CHANGED