strapi-identity 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/admin/{AdminReset-H453s-DE.mjs → AdminReset-B-WGECOX.mjs} +1 -1
- package/dist/admin/{AdminReset-C0QibZXW.js → AdminReset-CqHhVBS_.js} +1 -1
- package/dist/admin/{ProfileToggle-DQeXCx34.mjs → ProfileToggle-BCtCsOvj.mjs} +298 -38
- package/dist/admin/{ProfileToggle-BFmIWCrN.js → ProfileToggle-BRYjt5Lu.js} +298 -38
- package/dist/admin/{SettingsPage-OwMik_IK.mjs → SettingsPage-7Ytl01jH.mjs} +343 -101
- package/dist/admin/{SettingsPage-DyF7YbsX.js → SettingsPage-DAxGIv_E.js} +342 -100
- package/dist/admin/{ar-i2eiMZkz.js → ar-BYnI7Tsa.js} +36 -23
- package/dist/admin/{ar-BXaam37U.mjs → ar-DwZqj0qM.mjs} +36 -23
- package/dist/admin/{ca-DZ9DbEcQ.mjs → ca-aKVVc8iQ.mjs} +36 -23
- package/dist/admin/{ca-BVpGzY8r.js → ca-sBRHuaFU.js} +36 -23
- package/dist/admin/{cs-Gok16KLy.mjs → cs--prflMHS.mjs} +36 -23
- package/dist/admin/{cs-_PZVkwt0.js → cs-gU7KP3Lx.js} +36 -23
- package/dist/admin/de-BT25lv_6.mjs +49 -0
- package/dist/admin/de-CrlCAUuf.js +49 -0
- package/dist/admin/{dk-B7EOsAdU.js → dk-BNC3WUzY.js} +36 -23
- package/dist/admin/{dk-CI64Xmli.mjs → dk-Ck3AQYU7.mjs} +36 -23
- package/dist/admin/{en-B_vJwdfS.mjs → en-9qzlpde0.mjs} +36 -23
- package/dist/admin/{en-D4KP9t1Y.js → en-DBj0AD5g.js} +36 -23
- package/dist/admin/{es-CHwF7YK-.js → es-D5Sn41_H.js} +36 -23
- package/dist/admin/{es-CqJcXo4j.mjs → es-lh6XoPb7.mjs} +36 -23
- package/dist/admin/{eu-D-snytN8.mjs → eu-Cuz6ijBX.mjs} +36 -23
- package/dist/admin/{eu-DvdbwE5E.js → eu-Qr3RvDPW.js} +36 -23
- package/dist/admin/fr-C4pmkPYn.js +49 -0
- package/dist/admin/fr-ChlDcZsG.mjs +49 -0
- package/dist/admin/{gu-3wJbbAmw.mjs → gu-B6zyD1bW.mjs} +36 -23
- package/dist/admin/{gu-D2LgVfMp.js → gu-BMZL76zM.js} +36 -23
- package/dist/admin/{he-Bjv7eygt.mjs → he-C5V-qZCX.mjs} +36 -23
- package/dist/admin/{he-DnhYpbvN.js → he-H6iBa45A.js} +36 -23
- package/dist/admin/{hi-DDD2E3A3.js → hi-Be8rPk7I.js} +36 -23
- package/dist/admin/{hi-CNiDezU7.mjs → hi-czhOWo6-.mjs} +36 -23
- package/dist/admin/{hu-C1_YkZHU.js → hu-DKp6kOmc.js} +36 -23
- package/dist/admin/{hu-aLaIWmGw.mjs → hu-NbZ3aiYV.mjs} +36 -23
- package/dist/admin/{id-u3wVE6Rv.js → id-DO0bwFgY.js} +36 -23
- package/dist/admin/{id-C8WRgGm1.mjs → id-NH9PvcR5.mjs} +36 -23
- package/dist/admin/{index-D45I6rWF.mjs → index-BfC6z9N5.mjs} +62 -7
- package/dist/admin/{index-BXZI8nMZ.js → index-D03zlFnm.js} +62 -7
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/admin/{it-CjoRoJj1.mjs → it-Cmrey6tg.mjs} +36 -23
- package/dist/admin/{it-CDw6dG9Z.js → it-Df6-7-M7.js} +36 -23
- package/dist/admin/{ja-CewucIUY.mjs → ja-DH3KMqOL.mjs} +36 -23
- package/dist/admin/{ja-CbMXy2ym.js → ja-HuAq9ZwT.js} +36 -23
- package/dist/admin/{ko-D-kAxDtd.mjs → ko-DPN28RE8.mjs} +36 -23
- package/dist/admin/{ko-BEtJPpfJ.js → ko-S9k8KA8K.js} +36 -23
- package/dist/admin/{ml-0fR2_MmA.js → ml-Bh9GGqcW.js} +36 -23
- package/dist/admin/{ml-DR3AaofF.mjs → ml-MsHNacm6.mjs} +36 -23
- package/dist/admin/{ms-COHLS5e5.mjs → ms-TjHAaxTd.mjs} +36 -23
- package/dist/admin/{ms-DLvuGSlk.js → ms-hO5YeEg4.js} +36 -23
- package/dist/admin/{nl-wj6kn642.js → nl-BF98NBwL.js} +36 -23
- package/dist/admin/{nl-DVtHsM2H.mjs → nl-BLILZU8-.mjs} +36 -23
- package/dist/admin/{no-D_0yjyCy.mjs → no-BtVZ-siy.mjs} +36 -23
- package/dist/admin/{no-DVBgWt8q.js → no-bl1OXlfa.js} +36 -23
- package/dist/admin/{pl-C3GNxjVX.mjs → pl-DCSB6LwZ.mjs} +36 -23
- package/dist/admin/{pl-B2ghisbC.js → pl-DCnOWIDw.js} +36 -23
- package/dist/admin/{pt-BR-BbKV8YoX.mjs → pt-BR-CeLqmj88.mjs} +36 -23
- package/dist/admin/{pt-BR-CfgNaB1-.js → pt-BR-D2_UrxTp.js} +36 -23
- package/dist/admin/{pt-DKe8rRWa.js → pt-DIu8RT_X.js} +36 -23
- package/dist/admin/{pt-z4K3cCjf.mjs → pt-fgjdOyW5.mjs} +36 -23
- package/dist/admin/{ru-C85izLFa.mjs → ru-B_hlpAyP.mjs} +36 -23
- package/dist/admin/{ru-BFSm68HC.js → ru-BccMCf0l.js} +36 -23
- package/dist/admin/{sa-B1XoTTrE.mjs → sa-BtuJ_I1t.mjs} +36 -23
- package/dist/admin/{sa-BOPaqylt.js → sa-D3A-fo85.js} +36 -23
- package/dist/admin/{sk-C48lUPuC.mjs → sk-mmuTFlCK.mjs} +36 -23
- package/dist/admin/{sk-Dd-S1612.js → sk-uSLC6KhO.js} +36 -23
- package/dist/admin/{sv-BLma_kJl.mjs → sv-BlaHc5ax.mjs} +36 -23
- package/dist/admin/{sv-lg64Cw78.js → sv-CuKk5tE-.js} +36 -23
- package/dist/admin/{th-DPbm5NrX.js → th-Bv3NKkYO.js} +36 -23
- package/dist/admin/{th-BJEu5n7q.mjs → th-BwyhFaeE.mjs} +36 -23
- package/dist/admin/{tr-DkIUODKq.mjs → tr-BLocNlbZ.mjs} +36 -23
- package/dist/admin/{tr-Bm1QZr4v.js → tr-Bmvs-Hx-.js} +36 -23
- package/dist/admin/{uk-FARzIGx4.js → uk-BDxn-EZU.js} +36 -23
- package/dist/admin/{uk-D7ArtSe3.mjs → uk-CyZ10xtq.mjs} +36 -23
- package/dist/admin/{vi-DS0yslPP.mjs → vi-Bx_UJ8up.mjs} +36 -23
- package/dist/admin/{vi-Bi9B6eTY.js → vi-F_mqQCme.js} +36 -23
- package/dist/admin/{zh-DkEx28ZA.js → zh-CFZJPG5N.js} +36 -23
- package/dist/admin/{zh-DwCvIPSz.mjs → zh-CjJdRa3l.mjs} +36 -23
- package/dist/admin/{zh-Hans-BwwKCR6_.js → zh-Hans-4BhSwSQw.js} +36 -23
- package/dist/admin/{zh-Hans-DP2xZyda.mjs → zh-Hans-s7G2GUHU.mjs} +36 -23
- package/dist/server/index.js +487 -50
- package/dist/server/index.mjs +487 -50
- package/package.json +4 -3
- package/dist/admin/de-BuYn1AYX.mjs +0 -26
- package/dist/admin/de-GItli7en.js +0 -26
- package/dist/admin/fr-Bt6sS5GX.mjs +0 -26
- package/dist/admin/fr-CbCW6hVD.js +0 -26
package/dist/server/index.js
CHANGED
|
@@ -10,12 +10,71 @@ const require$$0__default = /* @__PURE__ */ _interopDefault(require$$0);
|
|
|
10
10
|
const require$$3__default = /* @__PURE__ */ _interopDefault(require$$3);
|
|
11
11
|
const require$$5__default = /* @__PURE__ */ _interopDefault(require$$5);
|
|
12
12
|
const require$$1__default = /* @__PURE__ */ _interopDefault(require$$1);
|
|
13
|
+
const defaultConfig$1 = {
|
|
14
|
+
enabled: false,
|
|
15
|
+
enforce: false,
|
|
16
|
+
issuer: "Strapi",
|
|
17
|
+
from_email: "",
|
|
18
|
+
from_name: "",
|
|
19
|
+
response_email: "",
|
|
20
|
+
subject: "Your One-Time Password",
|
|
21
|
+
text: "Your one-time password is: <%= OTP %>",
|
|
22
|
+
message: `<div style="margin: 0; padding: 0;">
|
|
23
|
+
<table border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#f9f9f9">
|
|
24
|
+
<tr>
|
|
25
|
+
<td align="center" style="padding: 40px 10px;">
|
|
26
|
+
<table border="0" cellpadding="0" cellspacing="0" width="600" bgcolor="#ffffff" style="border: 1px solid #dddddd;">
|
|
27
|
+
<tr>
|
|
28
|
+
<td align="center" style="padding: 40px 40px 20px 40px;">
|
|
29
|
+
<font face="Arial, sans-serif" size="5" color="#333333">
|
|
30
|
+
<strong>Verify Your Account</strong>
|
|
31
|
+
</font>
|
|
32
|
+
</td>
|
|
33
|
+
</tr>
|
|
34
|
+
<tr>
|
|
35
|
+
<td align="center" style="padding: 0 40px 20px 40px;">
|
|
36
|
+
<font face="Arial, sans-serif" size="3" color="#555555" style="line-height: 24px;">
|
|
37
|
+
Please use the following one-time password to complete your registration. This code will expire in 10 minutes.
|
|
38
|
+
</font>
|
|
39
|
+
</td>
|
|
40
|
+
</tr>
|
|
41
|
+
<tr>
|
|
42
|
+
<td align="center" style="padding: 20px 40px;">
|
|
43
|
+
<table border="0" cellpadding="0" cellspacing="0">
|
|
44
|
+
<tr>
|
|
45
|
+
<td align="center" bgcolor="#f4f4f4" style="padding: 20px 30px; font-family: Courier, monospace; font-size: 36px; color: #2d3436;">
|
|
46
|
+
<strong><%= OTP %></strong>
|
|
47
|
+
</td>
|
|
48
|
+
</tr>
|
|
49
|
+
</table>
|
|
50
|
+
</td>
|
|
51
|
+
</tr>
|
|
52
|
+
<tr>
|
|
53
|
+
<td align="center" style="padding: 20px 40px 40px 40px;">
|
|
54
|
+
<font face="Arial, sans-serif" size="2" color="#888888">
|
|
55
|
+
If you did not request this code, please ignore this email.
|
|
56
|
+
</font>
|
|
57
|
+
</td>
|
|
58
|
+
</tr>
|
|
59
|
+
</table>
|
|
60
|
+
<table border="0" cellpadding="0" cellspacing="0" width="600">
|
|
61
|
+
<tr>
|
|
62
|
+
<td align="center" style="padding: 20px;">
|
|
63
|
+
<font face="Arial, sans-serif" size="1" color="#aaaaaa">
|
|
64
|
+
© <%= YEAR %> Your Company Name
|
|
65
|
+
</font>
|
|
66
|
+
</td>
|
|
67
|
+
</tr>
|
|
68
|
+
</table>
|
|
69
|
+
</td>
|
|
70
|
+
</tr>
|
|
71
|
+
</table>
|
|
72
|
+
</div>`
|
|
73
|
+
};
|
|
13
74
|
const bootstrap = async () => {
|
|
14
75
|
const config2 = strapi.documents("plugin::strapi-identity.strapi-identity-config");
|
|
15
76
|
const existingConfig = await config2.count({});
|
|
16
|
-
if (!existingConfig) {
|
|
17
|
-
await config2.create({ data: { enabled: false, enforce: false, issuer: "Strapi" } });
|
|
18
|
-
}
|
|
77
|
+
if (!existingConfig) await config2.create({ data: defaultConfig$1 });
|
|
19
78
|
strapi.admin.services.permission.actionProvider.registerMany([
|
|
20
79
|
{
|
|
21
80
|
uid: "settings.read",
|
|
@@ -3828,7 +3887,7 @@ function requireLodash() {
|
|
|
3828
3887
|
(function(module2, exports$1) {
|
|
3829
3888
|
(function() {
|
|
3830
3889
|
var undefined$1;
|
|
3831
|
-
var VERSION = "4.17.
|
|
3890
|
+
var VERSION = "4.17.23";
|
|
3832
3891
|
var LARGE_ARRAY_SIZE = 200;
|
|
3833
3892
|
var CORE_ERROR_TEXT = "Unsupported core-js use. Try https://npms.io/search?q=ponyfill.", FUNC_ERROR_TEXT = "Expected a function", INVALID_TEMPL_VAR_ERROR_TEXT = "Invalid `variable` option passed into `_.template`";
|
|
3834
3893
|
var HASH_UNDEFINED = "__lodash_hash_undefined__";
|
|
@@ -5756,8 +5815,28 @@ function requireLodash() {
|
|
|
5756
5815
|
}
|
|
5757
5816
|
function baseUnset(object, path) {
|
|
5758
5817
|
path = castPath(path, object);
|
|
5759
|
-
|
|
5760
|
-
|
|
5818
|
+
var index2 = -1, length = path.length;
|
|
5819
|
+
if (!length) {
|
|
5820
|
+
return true;
|
|
5821
|
+
}
|
|
5822
|
+
var isRootPrimitive = object == null || typeof object !== "object" && typeof object !== "function";
|
|
5823
|
+
while (++index2 < length) {
|
|
5824
|
+
var key = path[index2];
|
|
5825
|
+
if (typeof key !== "string") {
|
|
5826
|
+
continue;
|
|
5827
|
+
}
|
|
5828
|
+
if (key === "__proto__" && !hasOwnProperty.call(object, "__proto__")) {
|
|
5829
|
+
return false;
|
|
5830
|
+
}
|
|
5831
|
+
if (key === "constructor" && index2 + 1 < length && typeof path[index2 + 1] === "string" && path[index2 + 1] === "prototype") {
|
|
5832
|
+
if (isRootPrimitive && index2 === 0) {
|
|
5833
|
+
continue;
|
|
5834
|
+
}
|
|
5835
|
+
return false;
|
|
5836
|
+
}
|
|
5837
|
+
}
|
|
5838
|
+
var obj = parent(object, path);
|
|
5839
|
+
return obj == null || delete obj[toKey(last(path))];
|
|
5761
5840
|
}
|
|
5762
5841
|
function baseUpdate(object, path, updater, customizer) {
|
|
5763
5842
|
return baseSet(object, path, updater(baseGet(object, path)), customizer);
|
|
@@ -9540,14 +9619,14 @@ function requireJsonwebtoken() {
|
|
|
9540
9619
|
var jsonwebtokenExports = requireJsonwebtoken();
|
|
9541
9620
|
const jwt = /* @__PURE__ */ getDefaultExportFromCjs(jsonwebtokenExports);
|
|
9542
9621
|
const register = ({ strapi: strapi2 }) => {
|
|
9543
|
-
const
|
|
9544
|
-
const secret2 =
|
|
9545
|
-
const domain =
|
|
9546
|
-
const loginRoute =
|
|
9622
|
+
const { admin: admin2, config: config2, server } = strapi2;
|
|
9623
|
+
const secret2 = config2.get("admin.auth.secret");
|
|
9624
|
+
const domain = config2.get("admin.auth.domain");
|
|
9625
|
+
const loginRoute = admin2.routes.admin.routes.find(
|
|
9547
9626
|
({ method, path }) => method === "POST" && path === "/login"
|
|
9548
9627
|
);
|
|
9549
9628
|
if (loginRoute) replaceLogin(loginRoute, secret2, domain);
|
|
9550
|
-
|
|
9629
|
+
server.use(async (ctx, next) => {
|
|
9551
9630
|
const mfaCookie = ctx.cookies.get("strapi_admin_mfa");
|
|
9552
9631
|
if (mfaCookie && ctx.path.startsWith("/admin/auth")) {
|
|
9553
9632
|
ctx.cookies.set("jwtToken", null, { expires: /* @__PURE__ */ new Date(0) });
|
|
@@ -9575,8 +9654,25 @@ const replaceLogin = (route2, secret2, domain) => {
|
|
|
9575
9654
|
filters: { admin_user: { id: payload.userId }, enabled: true }
|
|
9576
9655
|
});
|
|
9577
9656
|
if (!exists) return;
|
|
9657
|
+
if (exists.type === "email") {
|
|
9658
|
+
try {
|
|
9659
|
+
const adminUser = await strapi.db.query("admin::user").findOne({ where: { id: Number(payload.userId) }, select: ["email"] });
|
|
9660
|
+
if (adminUser?.email) {
|
|
9661
|
+
const otp = await strapi.service("plugin::strapi-identity.secret").generateEmailOTP(payload.userId, "login");
|
|
9662
|
+
await strapi.service("plugin::strapi-identity.email").send(adminUser.email, otp);
|
|
9663
|
+
}
|
|
9664
|
+
} catch (err) {
|
|
9665
|
+
console.log("Error sending login email OTP:", err);
|
|
9666
|
+
}
|
|
9667
|
+
}
|
|
9578
9668
|
ctx.res.removeHeader("set-cookie");
|
|
9579
|
-
const newPayload = {
|
|
9669
|
+
const newPayload = {
|
|
9670
|
+
userId: payload.userId,
|
|
9671
|
+
deviceId,
|
|
9672
|
+
rememberMe,
|
|
9673
|
+
type: "mfa",
|
|
9674
|
+
mfaType: exists.type
|
|
9675
|
+
};
|
|
9580
9676
|
const newToken = jwt.sign(newPayload, secret2, { expiresIn: "5m" });
|
|
9581
9677
|
const expires = new Date(Date.now() + 5 * 60 * 1e3);
|
|
9582
9678
|
const opt = { domain, httpOnly: false, overwrite: true, secure: ctx.request.secure, expires };
|
|
@@ -9589,12 +9685,27 @@ const config$4 = {
|
|
|
9589
9685
|
validator() {
|
|
9590
9686
|
}
|
|
9591
9687
|
};
|
|
9688
|
+
const kind$3 = "collectionType";
|
|
9689
|
+
const collectionName$3 = "mfa-tokens";
|
|
9690
|
+
const info$3 = { "singularName": "mfa-token", "pluralName": "mfa-tokens", "displayName": "MFA Token" };
|
|
9691
|
+
const options$3 = { "draftAndPublish": false };
|
|
9692
|
+
const pluginOptions$3 = { "content-manager": { "visible": false }, "content-type-builder": { "visible": false } };
|
|
9693
|
+
const attributes$3 = { "admin_user": { "type": "relation", "relation": "oneToOne", "target": "admin::user" }, "enabled": { "type": "boolean", "default": false }, "type": { "type": "enumeration", "enum": ["totp", "hotp", "email"], "default": "totp" }, "secret": { "type": "string", "private": true }, "counter": { "type": "biginteger", "default": "0" }, "digits": { "type": "integer", "default": 6 }, "recovery_codes": { "type": "json", "private": true } };
|
|
9694
|
+
const schema$3 = {
|
|
9695
|
+
kind: kind$3,
|
|
9696
|
+
collectionName: collectionName$3,
|
|
9697
|
+
info: info$3,
|
|
9698
|
+
options: options$3,
|
|
9699
|
+
pluginOptions: pluginOptions$3,
|
|
9700
|
+
attributes: attributes$3
|
|
9701
|
+
};
|
|
9702
|
+
const mfaToken = { schema: schema$3 };
|
|
9592
9703
|
const kind$2 = "collectionType";
|
|
9593
|
-
const collectionName$2 = "mfa-
|
|
9594
|
-
const info$2 = { "singularName": "mfa-
|
|
9704
|
+
const collectionName$2 = "mfa-temps";
|
|
9705
|
+
const info$2 = { "singularName": "mfa-temp", "pluralName": "mfa-temps", "displayName": "MFA Temp" };
|
|
9595
9706
|
const options$2 = { "draftAndPublish": false };
|
|
9596
9707
|
const pluginOptions$2 = { "content-manager": { "visible": false }, "content-type-builder": { "visible": false } };
|
|
9597
|
-
const attributes$2 = { "admin_user": { "type": "relation", "relation": "oneToOne", "target": "admin::user" }, "
|
|
9708
|
+
const attributes$2 = { "admin_user": { "type": "relation", "relation": "oneToOne", "target": "admin::user" }, "secret": { "type": "string", "required": false, "private": true } };
|
|
9598
9709
|
const schema$2 = {
|
|
9599
9710
|
kind: kind$2,
|
|
9600
9711
|
collectionName: collectionName$2,
|
|
@@ -9603,13 +9714,13 @@ const schema$2 = {
|
|
|
9603
9714
|
pluginOptions: pluginOptions$2,
|
|
9604
9715
|
attributes: attributes$2
|
|
9605
9716
|
};
|
|
9606
|
-
const
|
|
9607
|
-
const kind$1 = "
|
|
9608
|
-
const collectionName$1 = "
|
|
9609
|
-
const info$1 = { "singularName": "
|
|
9717
|
+
const mfaTemp = { schema: schema$2 };
|
|
9718
|
+
const kind$1 = "singleType";
|
|
9719
|
+
const collectionName$1 = "strapi-identity-config";
|
|
9720
|
+
const info$1 = { "singularName": "strapi-identity-config", "pluralName": "strapi-identity-configs", "displayName": "Strapi Identity Config" };
|
|
9610
9721
|
const options$1 = { "draftAndPublish": false };
|
|
9611
9722
|
const pluginOptions$1 = { "content-manager": { "visible": false }, "content-type-builder": { "visible": false } };
|
|
9612
|
-
const attributes$1 = { "
|
|
9723
|
+
const attributes$1 = { "enabled": { "type": "boolean", "default": false }, "enforce": { "type": "boolean", "default": false }, "issuer": { "type": "string", "required": false, "default": "Strapi" }, "email_enabled": { "type": "boolean", "default": false }, "from_email": { "type": "string", "required": false, "default": "" }, "from_name": { "type": "string", "required": false, "default": "" }, "response_email": { "type": "string", "required": false, "default": "" }, "subject": { "type": "string", "required": false, "default": "" }, "text": { "type": "text", "required": false, "default": "" }, "message": { "type": "text", "required": false, "default": "" } };
|
|
9613
9724
|
const schema$1 = {
|
|
9614
9725
|
kind: kind$1,
|
|
9615
9726
|
collectionName: collectionName$1,
|
|
@@ -9618,13 +9729,13 @@ const schema$1 = {
|
|
|
9618
9729
|
pluginOptions: pluginOptions$1,
|
|
9619
9730
|
attributes: attributes$1
|
|
9620
9731
|
};
|
|
9621
|
-
const
|
|
9622
|
-
const kind = "
|
|
9623
|
-
const collectionName = "
|
|
9624
|
-
const info = { "singularName": "
|
|
9732
|
+
const config$3 = { schema: schema$1 };
|
|
9733
|
+
const kind = "collectionType";
|
|
9734
|
+
const collectionName = "email-otps";
|
|
9735
|
+
const info = { "singularName": "email-otp", "pluralName": "email-otps", "displayName": "Email OTP" };
|
|
9625
9736
|
const options = { "draftAndPublish": false };
|
|
9626
9737
|
const pluginOptions = { "content-manager": { "visible": false }, "content-type-builder": { "visible": false } };
|
|
9627
|
-
const attributes = { "
|
|
9738
|
+
const attributes = { "admin_user": { "type": "relation", "relation": "oneToOne", "target": "admin::user" }, "code_hash": { "type": "string", "required": true, "private": true }, "expires_at": { "type": "string", "required": true }, "attempts": { "type": "integer", "default": 0 }, "purpose": { "type": "enumeration", "enum": ["login", "setup", "disable"], "default": "login" } };
|
|
9628
9739
|
const schema = {
|
|
9629
9740
|
kind,
|
|
9630
9741
|
collectionName,
|
|
@@ -9633,11 +9744,12 @@ const schema = {
|
|
|
9633
9744
|
pluginOptions,
|
|
9634
9745
|
attributes
|
|
9635
9746
|
};
|
|
9636
|
-
const
|
|
9747
|
+
const emailOtp = { schema };
|
|
9637
9748
|
const contentTypes = {
|
|
9638
9749
|
"mfa-token": mfaToken,
|
|
9639
9750
|
"mfa-temp": mfaTemp,
|
|
9640
|
-
"strapi-identity-config": config$3
|
|
9751
|
+
"strapi-identity-config": config$3,
|
|
9752
|
+
"email-otp": emailOtp
|
|
9641
9753
|
};
|
|
9642
9754
|
const admin$2 = ({ strapi: strapi2 }) => ({
|
|
9643
9755
|
async isEnabled(ctx) {
|
|
@@ -9698,6 +9810,17 @@ const config$2 = ({ strapi: strapi2 }) => ({
|
|
|
9698
9810
|
ctx.body = { data: null, error: "Server Error" };
|
|
9699
9811
|
}
|
|
9700
9812
|
},
|
|
9813
|
+
async getEmailStatus(ctx) {
|
|
9814
|
+
try {
|
|
9815
|
+
const emailService = strapi2.config.get("plugin::email");
|
|
9816
|
+
ctx.status = 200;
|
|
9817
|
+
ctx.body = { data: emailService, error: null };
|
|
9818
|
+
} catch (error) {
|
|
9819
|
+
console.log("Error getting email status:", error);
|
|
9820
|
+
ctx.status = 500;
|
|
9821
|
+
ctx.body = { data: null, error: "Server Error" };
|
|
9822
|
+
}
|
|
9823
|
+
},
|
|
9701
9824
|
async updateConfig(ctx) {
|
|
9702
9825
|
const body = ctx.request.body;
|
|
9703
9826
|
try {
|
|
@@ -9764,7 +9887,13 @@ const controller = ({ strapi: strapi2 }) => ({
|
|
|
9764
9887
|
const payload = jwt.verify(token, secret2);
|
|
9765
9888
|
const body = ctx.request.body;
|
|
9766
9889
|
try {
|
|
9767
|
-
const
|
|
9890
|
+
const mfaRecord = await strapi2.documents("plugin::strapi-identity.mfa-token").findFirst({ filters: { admin_user: { id: payload.userId }, enabled: true } });
|
|
9891
|
+
let valid2;
|
|
9892
|
+
if (mfaRecord?.type === "email") {
|
|
9893
|
+
valid2 = await strapi2.service("plugin::strapi-identity.secret").validateEmailOTP(payload.userId, body.code, "login");
|
|
9894
|
+
} else {
|
|
9895
|
+
valid2 = await strapi2.service("plugin::strapi-identity.secret").validateTokenOrRecoveryCode(payload.userId, body.code);
|
|
9896
|
+
}
|
|
9768
9897
|
if (!valid2) {
|
|
9769
9898
|
ctx.status = 400;
|
|
9770
9899
|
ctx.body = { data: null, error: "Invalid MFA code" };
|
|
@@ -9847,9 +9976,9 @@ const controller = ({ strapi: strapi2 }) => ({
|
|
|
9847
9976
|
const user = ctx.state.user;
|
|
9848
9977
|
const secretService = strapi2.service("plugin::strapi-identity.secret");
|
|
9849
9978
|
try {
|
|
9850
|
-
const
|
|
9979
|
+
const info2 = await secretService.getMFAInfo(user.id);
|
|
9851
9980
|
ctx.status = 200;
|
|
9852
|
-
ctx.body = { data: { status:
|
|
9981
|
+
ctx.body = { data: { status: info2?.status || null, type: info2?.type || null }, error: null };
|
|
9853
9982
|
} catch (error) {
|
|
9854
9983
|
ctx.status = 500;
|
|
9855
9984
|
ctx.body = { data: null, error: "Failed to check MFA status" };
|
|
@@ -9868,6 +9997,103 @@ const controller = ({ strapi: strapi2 }) => ({
|
|
|
9868
9997
|
ctx.status = 500;
|
|
9869
9998
|
ctx.body = { data: null, error: "Failed to disable MFA" };
|
|
9870
9999
|
}
|
|
10000
|
+
},
|
|
10001
|
+
async enableEmail(ctx) {
|
|
10002
|
+
const user = ctx.state.user;
|
|
10003
|
+
const secretService = strapi2.service("plugin::strapi-identity.secret");
|
|
10004
|
+
const emailService = strapi2.service("plugin::strapi-identity.email");
|
|
10005
|
+
const configService = strapi2.service("plugin::strapi-identity.config");
|
|
10006
|
+
try {
|
|
10007
|
+
const config2 = await configService.getConfig();
|
|
10008
|
+
if (!config2.email_enabled) {
|
|
10009
|
+
ctx.status = 400;
|
|
10010
|
+
ctx.body = { data: null, error: "Email OTP is not configured" };
|
|
10011
|
+
return;
|
|
10012
|
+
}
|
|
10013
|
+
const existing = await strapi2.documents("plugin::strapi-identity.mfa-token").findFirst({ filters: { admin_user: { id: user.id }, enabled: true } });
|
|
10014
|
+
if (existing) {
|
|
10015
|
+
ctx.status = 400;
|
|
10016
|
+
ctx.body = { data: null, error: "MFA is already enabled. Disable it first." };
|
|
10017
|
+
return;
|
|
10018
|
+
}
|
|
10019
|
+
const otp = await secretService.generateEmailOTP(user.id, "setup");
|
|
10020
|
+
await emailService.send(user.email, otp);
|
|
10021
|
+
ctx.status = 200;
|
|
10022
|
+
ctx.body = { data: { message: "Verification email sent" }, error: null };
|
|
10023
|
+
} catch (error) {
|
|
10024
|
+
console.log("Error initiating email MFA setup:", error);
|
|
10025
|
+
ctx.status = 500;
|
|
10026
|
+
ctx.body = { data: null, error: "Failed to initiate email MFA setup" };
|
|
10027
|
+
}
|
|
10028
|
+
},
|
|
10029
|
+
async setupEmail(ctx) {
|
|
10030
|
+
const user = ctx.state.user;
|
|
10031
|
+
const body = ctx.request.body;
|
|
10032
|
+
const secretService = strapi2.service("plugin::strapi-identity.secret");
|
|
10033
|
+
try {
|
|
10034
|
+
const valid2 = await secretService.validateEmailOTP(user.id, body.code, "setup");
|
|
10035
|
+
if (!valid2) {
|
|
10036
|
+
ctx.status = 400;
|
|
10037
|
+
ctx.body = { data: null, error: "Invalid or expired verification code" };
|
|
10038
|
+
return;
|
|
10039
|
+
}
|
|
10040
|
+
await secretService.enableEmailMFA(user.id);
|
|
10041
|
+
ctx.status = 200;
|
|
10042
|
+
ctx.body = { data: { message: "Email OTP enabled" }, error: null };
|
|
10043
|
+
} catch (error) {
|
|
10044
|
+
console.log("Error completing email MFA setup:", error);
|
|
10045
|
+
ctx.status = 500;
|
|
10046
|
+
ctx.body = { data: null, error: "Failed to enable email MFA" };
|
|
10047
|
+
}
|
|
10048
|
+
},
|
|
10049
|
+
async requestDisableEmailOTP(ctx) {
|
|
10050
|
+
const user = ctx.state.user;
|
|
10051
|
+
const secretService = strapi2.service("plugin::strapi-identity.secret");
|
|
10052
|
+
const emailService = strapi2.service("plugin::strapi-identity.email");
|
|
10053
|
+
try {
|
|
10054
|
+
const info2 = await secretService.getMFAInfo(user.id);
|
|
10055
|
+
if (!info2 || info2.type !== "email") {
|
|
10056
|
+
ctx.status = 400;
|
|
10057
|
+
ctx.body = { data: null, error: "Email OTP is not enabled" };
|
|
10058
|
+
return;
|
|
10059
|
+
}
|
|
10060
|
+
const otp = await secretService.generateEmailOTP(user.id, "disable");
|
|
10061
|
+
await emailService.send(user.email, otp);
|
|
10062
|
+
ctx.status = 200;
|
|
10063
|
+
ctx.body = { data: { message: "Verification email sent" }, error: null };
|
|
10064
|
+
} catch (error) {
|
|
10065
|
+
console.log("Error sending disable email OTP:", error);
|
|
10066
|
+
ctx.status = 500;
|
|
10067
|
+
ctx.body = { data: null, error: "Failed to send verification email" };
|
|
10068
|
+
}
|
|
10069
|
+
},
|
|
10070
|
+
async resendVerifyOTP(ctx) {
|
|
10071
|
+
const secret2 = strapi2.config.get("admin.auth.secret");
|
|
10072
|
+
const token = ctx.cookies.get("strapi_admin_mfa");
|
|
10073
|
+
try {
|
|
10074
|
+
const payload = jwt.verify(token, secret2);
|
|
10075
|
+
if (payload.mfaType !== "email") {
|
|
10076
|
+
ctx.status = 400;
|
|
10077
|
+
ctx.body = { data: null, error: "Resend is only available for email OTP" };
|
|
10078
|
+
return;
|
|
10079
|
+
}
|
|
10080
|
+
const adminUser = await strapi2.db.query("admin::user").findOne({ where: { id: Number(payload.userId) }, select: ["email"] });
|
|
10081
|
+
if (!adminUser?.email) {
|
|
10082
|
+
ctx.status = 400;
|
|
10083
|
+
ctx.body = { data: null, error: "No email address found for this user" };
|
|
10084
|
+
return;
|
|
10085
|
+
}
|
|
10086
|
+
const secretService = strapi2.service("plugin::strapi-identity.secret");
|
|
10087
|
+
const emailService = strapi2.service("plugin::strapi-identity.email");
|
|
10088
|
+
const otp = await secretService.generateEmailOTP(payload.userId, "login");
|
|
10089
|
+
await emailService.send(adminUser.email, otp);
|
|
10090
|
+
ctx.status = 200;
|
|
10091
|
+
ctx.body = { data: { message: "Verification email resent" }, error: null };
|
|
10092
|
+
} catch (error) {
|
|
10093
|
+
console.log("Error resending login email OTP:", error);
|
|
10094
|
+
ctx.status = 500;
|
|
10095
|
+
ctx.body = { data: null, error: "Failed to resend verification email" };
|
|
10096
|
+
}
|
|
9871
10097
|
}
|
|
9872
10098
|
});
|
|
9873
10099
|
const controllers = {
|
|
@@ -9967,6 +10193,21 @@ const config$1 = [
|
|
|
9967
10193
|
]
|
|
9968
10194
|
}
|
|
9969
10195
|
},
|
|
10196
|
+
{
|
|
10197
|
+
method: "GET",
|
|
10198
|
+
path: "/config/email",
|
|
10199
|
+
handler: "config.getEmailStatus",
|
|
10200
|
+
info: {
|
|
10201
|
+
apiName: "getEmailStatus",
|
|
10202
|
+
pluginName: "strapi-identity",
|
|
10203
|
+
type: "content-api"
|
|
10204
|
+
},
|
|
10205
|
+
config: {
|
|
10206
|
+
policies: [
|
|
10207
|
+
"admin::isAuthenticatedAdmin"
|
|
10208
|
+
]
|
|
10209
|
+
}
|
|
10210
|
+
},
|
|
9970
10211
|
{
|
|
9971
10212
|
method: "PUT",
|
|
9972
10213
|
path: "/config",
|
|
@@ -10051,6 +10292,55 @@ const mfa = [
|
|
|
10051
10292
|
type: "content-api"
|
|
10052
10293
|
},
|
|
10053
10294
|
config: {}
|
|
10295
|
+
},
|
|
10296
|
+
{
|
|
10297
|
+
method: "POST",
|
|
10298
|
+
path: "/verify/resend",
|
|
10299
|
+
handler: "controller.resendVerifyOTP",
|
|
10300
|
+
info: {
|
|
10301
|
+
apiName: "resendVerifyOTP",
|
|
10302
|
+
pluginName: "strapi-identity",
|
|
10303
|
+
type: "content-api"
|
|
10304
|
+
},
|
|
10305
|
+
config: {
|
|
10306
|
+
auth: false,
|
|
10307
|
+
policies: [
|
|
10308
|
+
"has-mfa"
|
|
10309
|
+
]
|
|
10310
|
+
}
|
|
10311
|
+
},
|
|
10312
|
+
{
|
|
10313
|
+
method: "POST",
|
|
10314
|
+
path: "/enable-email",
|
|
10315
|
+
handler: "controller.enableEmail",
|
|
10316
|
+
info: {
|
|
10317
|
+
apiName: "enableEmail",
|
|
10318
|
+
pluginName: "strapi-identity",
|
|
10319
|
+
type: "content-api"
|
|
10320
|
+
},
|
|
10321
|
+
config: {}
|
|
10322
|
+
},
|
|
10323
|
+
{
|
|
10324
|
+
method: "POST",
|
|
10325
|
+
path: "/setup-email",
|
|
10326
|
+
handler: "controller.setupEmail",
|
|
10327
|
+
info: {
|
|
10328
|
+
apiName: "setupEmail",
|
|
10329
|
+
pluginName: "strapi-identity",
|
|
10330
|
+
type: "content-api"
|
|
10331
|
+
},
|
|
10332
|
+
config: {}
|
|
10333
|
+
},
|
|
10334
|
+
{
|
|
10335
|
+
method: "POST",
|
|
10336
|
+
path: "/disable-email/request",
|
|
10337
|
+
handler: "controller.requestDisableEmailOTP",
|
|
10338
|
+
info: {
|
|
10339
|
+
apiName: "requestDisableEmailOTP",
|
|
10340
|
+
pluginName: "strapi-identity",
|
|
10341
|
+
type: "content-api"
|
|
10342
|
+
},
|
|
10343
|
+
config: {}
|
|
10054
10344
|
}
|
|
10055
10345
|
];
|
|
10056
10346
|
const route = () => ({
|
|
@@ -10091,35 +10381,63 @@ const admin = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePropert
|
|
|
10091
10381
|
isEnabled: isEnabled$1,
|
|
10092
10382
|
reset
|
|
10093
10383
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
10384
|
+
const defaultConfig = {
|
|
10385
|
+
email_enabled: false,
|
|
10386
|
+
enabled: false,
|
|
10387
|
+
enforce: false,
|
|
10388
|
+
from_email: "",
|
|
10389
|
+
from_name: "",
|
|
10390
|
+
issuer: "",
|
|
10391
|
+
message: "",
|
|
10392
|
+
response_email: "",
|
|
10393
|
+
subject: "",
|
|
10394
|
+
text: ""
|
|
10395
|
+
};
|
|
10094
10396
|
const _config = (options2) => {
|
|
10095
|
-
return {
|
|
10096
|
-
enabled: options2?.enabled || false,
|
|
10097
|
-
enforce: options2?.enforce || false,
|
|
10098
|
-
issuer: options2?.issuer || ""
|
|
10099
|
-
};
|
|
10397
|
+
return Object.assign({}, defaultConfig, options2);
|
|
10100
10398
|
};
|
|
10399
|
+
const fields = Object.keys(defaultConfig);
|
|
10101
10400
|
const isEnabled = async () => {
|
|
10102
10401
|
const config2 = await getConfig();
|
|
10103
10402
|
return config2?.enabled || false;
|
|
10104
10403
|
};
|
|
10105
10404
|
const getConfig = async () => {
|
|
10106
10405
|
const configDocument = strapi.documents("plugin::strapi-identity.strapi-identity-config");
|
|
10107
|
-
return configDocument.findFirst({ fields
|
|
10406
|
+
return configDocument.findFirst({ fields }).then((config2) => _config(config2));
|
|
10108
10407
|
};
|
|
10109
10408
|
const updateConfig = async (data) => {
|
|
10110
10409
|
const configDocument = strapi.documents("plugin::strapi-identity.strapi-identity-config");
|
|
10111
10410
|
const existingConfig = await configDocument.findFirst();
|
|
10112
10411
|
if (!existingConfig) {
|
|
10113
|
-
return configDocument.create({ data, fields
|
|
10412
|
+
return configDocument.create({ data, fields }).then((created) => _config(created));
|
|
10114
10413
|
}
|
|
10115
|
-
if (existingConfig.enabled && !data.enabled)
|
|
10116
|
-
|
|
10414
|
+
if (existingConfig.enabled && !data.enabled) await disableMFAForAllUsers();
|
|
10415
|
+
if (existingConfig.email_enabled && data.email_enabled === false)
|
|
10416
|
+
await disableEmailMFAForAllUsers();
|
|
10417
|
+
return configDocument.update({ documentId: existingConfig.documentId, data: { ...existingConfig, ...data }, fields }).then((updated) => _config(updated));
|
|
10418
|
+
};
|
|
10419
|
+
const disableEmailMFAForAllUsers = async () => {
|
|
10420
|
+
const tokenDocument = strapi.documents("plugin::strapi-identity.mfa-token");
|
|
10421
|
+
const otpDocument = strapi.documents("plugin::strapi-identity.email-otp");
|
|
10422
|
+
try {
|
|
10423
|
+
const emailTokens = await tokenDocument.findMany({
|
|
10424
|
+
filters: { type: "email", enabled: true }
|
|
10425
|
+
});
|
|
10426
|
+
await Promise.all([
|
|
10427
|
+
...emailTokens.map(
|
|
10428
|
+
(token) => tokenDocument.update({
|
|
10429
|
+
documentId: token.documentId,
|
|
10430
|
+
data: { ...token, enabled: false }
|
|
10431
|
+
})
|
|
10432
|
+
),
|
|
10433
|
+
// Clean up any pending email OTPs
|
|
10434
|
+
otpDocument.findMany({}).then(
|
|
10435
|
+
(otps) => Promise.all(otps.map((otp) => otpDocument.delete({ documentId: otp.documentId })))
|
|
10436
|
+
)
|
|
10437
|
+
]);
|
|
10438
|
+
} catch (err) {
|
|
10439
|
+
console.log("Error disabling email MFA for all users:", err);
|
|
10117
10440
|
}
|
|
10118
|
-
return configDocument.update({
|
|
10119
|
-
documentId: existingConfig.documentId,
|
|
10120
|
-
data: { ...existingConfig, ...data },
|
|
10121
|
-
fields: ["enabled", "enforce", "issuer"]
|
|
10122
|
-
}).then((updated) => _config(updated));
|
|
10123
10441
|
};
|
|
10124
10442
|
const disableMFAForAllUsers = async () => {
|
|
10125
10443
|
const tokenDocument = strapi.documents("plugin::strapi-identity.mfa-token");
|
|
@@ -10143,6 +10461,39 @@ const config = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
|
|
|
10143
10461
|
isEnabled,
|
|
10144
10462
|
updateConfig
|
|
10145
10463
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
10464
|
+
const send = async (to, otp) => {
|
|
10465
|
+
const emailService = strapi.plugin("email").service("email");
|
|
10466
|
+
if (!emailService) return;
|
|
10467
|
+
if (!to) return;
|
|
10468
|
+
const config2 = await strapi.service("plugin::strapi-identity.config").getConfig();
|
|
10469
|
+
if (!config2.email_enabled) return;
|
|
10470
|
+
const sendConfig = {
|
|
10471
|
+
to,
|
|
10472
|
+
subject: config2.subject,
|
|
10473
|
+
text: replaceTemplateVariables(config2.text, { OTP: otp }),
|
|
10474
|
+
html: replaceTemplateVariables(config2.message, {
|
|
10475
|
+
OTP: otp,
|
|
10476
|
+
YEAR: (/* @__PURE__ */ new Date()).getFullYear().toString()
|
|
10477
|
+
})
|
|
10478
|
+
};
|
|
10479
|
+
if (config2.from_email) {
|
|
10480
|
+
sendConfig.from = config2.from_name ? `${config2.from_name} <${config2.from_email}>` : config2.from_email;
|
|
10481
|
+
}
|
|
10482
|
+
if (config2.response_email && config2.response_email !== config2.from_email) {
|
|
10483
|
+
sendConfig.replyTo = config2.response_email;
|
|
10484
|
+
}
|
|
10485
|
+
return emailService.send(sendConfig).catch((error) => {
|
|
10486
|
+
console.log("Error sending email:", error);
|
|
10487
|
+
});
|
|
10488
|
+
};
|
|
10489
|
+
const replaceTemplateVariables = (template, variables) => {
|
|
10490
|
+
return template.replace(/<%= (\w+) %>/g, (_, key) => variables[key] || "");
|
|
10491
|
+
};
|
|
10492
|
+
const email = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
10493
|
+
__proto__: null,
|
|
10494
|
+
replaceTemplateVariables,
|
|
10495
|
+
send
|
|
10496
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
10146
10497
|
function commonjsRequire(path) {
|
|
10147
10498
|
throw new Error('Could not dynamically require "' + path + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
|
|
10148
10499
|
}
|
|
@@ -11996,8 +12347,11 @@ const checkRecoveryCode = async (userId, code) => {
|
|
|
11996
12347
|
};
|
|
11997
12348
|
const validateLiveToken = async (userId, token) => {
|
|
11998
12349
|
const tokenDocument = strapi.documents("plugin::strapi-identity.mfa-token");
|
|
11999
|
-
const record = await tokenDocument.findFirst({
|
|
12000
|
-
|
|
12350
|
+
const record = await tokenDocument.findFirst({
|
|
12351
|
+
filters: { admin_user: { id: userId }, enabled: true }
|
|
12352
|
+
});
|
|
12353
|
+
if (!record) return false;
|
|
12354
|
+
if (record.type === "email") return false;
|
|
12001
12355
|
return validateToken(token, record.secret);
|
|
12002
12356
|
};
|
|
12003
12357
|
const validateTempToken = async (userId, token) => {
|
|
@@ -12043,12 +12397,13 @@ const setupFullSecret = async (userId) => {
|
|
|
12043
12397
|
if (existing) {
|
|
12044
12398
|
await tokenDocument.update({
|
|
12045
12399
|
documentId: existing.documentId,
|
|
12046
|
-
data: { ...existing, secret: tempField.secret, enabled: true, recovery_codes }
|
|
12400
|
+
data: { ...existing, type: "totp", secret: tempField.secret, enabled: true, recovery_codes }
|
|
12047
12401
|
});
|
|
12048
12402
|
} else {
|
|
12049
12403
|
await tokenDocument.create({
|
|
12050
12404
|
data: {
|
|
12051
12405
|
admin_user: { id: userId },
|
|
12406
|
+
type: "totp",
|
|
12052
12407
|
secret: tempField.secret,
|
|
12053
12408
|
enabled: true,
|
|
12054
12409
|
recovery_codes
|
|
@@ -12058,14 +12413,92 @@ const setupFullSecret = async (userId) => {
|
|
|
12058
12413
|
await tempDocument.delete({ documentId: tempField.documentId });
|
|
12059
12414
|
return codes;
|
|
12060
12415
|
};
|
|
12416
|
+
const EMAIL_OTP_EXPIRY_MINUTES = 10;
|
|
12417
|
+
const EMAIL_OTP_MAX_ATTEMPTS = 5;
|
|
12418
|
+
const generateNumericOTP = (length) => {
|
|
12419
|
+
const digits = "0123456789";
|
|
12420
|
+
const max = 256 - 256 % digits.length;
|
|
12421
|
+
const randomValues = new Uint8Array(length * 2);
|
|
12422
|
+
let result = "";
|
|
12423
|
+
while (result.length < length) {
|
|
12424
|
+
crypto.getRandomValues(randomValues);
|
|
12425
|
+
for (let i = 0; i < randomValues.length && result.length < length; i++) {
|
|
12426
|
+
if (randomValues[i] >= max) continue;
|
|
12427
|
+
result += digits[randomValues[i] % digits.length];
|
|
12428
|
+
}
|
|
12429
|
+
}
|
|
12430
|
+
return result;
|
|
12431
|
+
};
|
|
12432
|
+
const generateEmailOTP = async (userId, purpose = "login") => {
|
|
12433
|
+
const otpDocument = strapi.documents("plugin::strapi-identity.email-otp");
|
|
12434
|
+
const otp = generateNumericOTP(6);
|
|
12435
|
+
const code_hash = await bcrypt.hash(otp, 10);
|
|
12436
|
+
const expires_at = new Date(Date.now() + EMAIL_OTP_EXPIRY_MINUTES * 60 * 1e3).toISOString();
|
|
12437
|
+
const existing = await otpDocument.findFirst({
|
|
12438
|
+
filters: { admin_user: { id: userId }, purpose }
|
|
12439
|
+
});
|
|
12440
|
+
if (existing) await otpDocument.delete({ documentId: existing.documentId });
|
|
12441
|
+
await otpDocument.create({
|
|
12442
|
+
data: { admin_user: { id: userId }, code_hash, expires_at, purpose, attempts: 0 }
|
|
12443
|
+
});
|
|
12444
|
+
return otp;
|
|
12445
|
+
};
|
|
12446
|
+
const validateEmailOTP = async (userId, code, purpose = "login") => {
|
|
12447
|
+
const otpDocument = strapi.documents("plugin::strapi-identity.email-otp");
|
|
12448
|
+
const record = await otpDocument.findFirst({ filters: { admin_user: { id: userId }, purpose } });
|
|
12449
|
+
if (!record) return false;
|
|
12450
|
+
if (new Date(record.expires_at) < /* @__PURE__ */ new Date()) {
|
|
12451
|
+
await otpDocument.delete({ documentId: record.documentId });
|
|
12452
|
+
return false;
|
|
12453
|
+
}
|
|
12454
|
+
const attempts = record.attempts || 0;
|
|
12455
|
+
if (attempts >= EMAIL_OTP_MAX_ATTEMPTS) {
|
|
12456
|
+
await otpDocument.delete({ documentId: record.documentId });
|
|
12457
|
+
return false;
|
|
12458
|
+
}
|
|
12459
|
+
await otpDocument.update({
|
|
12460
|
+
documentId: record.documentId,
|
|
12461
|
+
data: { ...record, attempts: attempts + 1 }
|
|
12462
|
+
});
|
|
12463
|
+
const isValid = await bcrypt.compare(code, record.code_hash);
|
|
12464
|
+
if (isValid) await otpDocument.delete({ documentId: record.documentId });
|
|
12465
|
+
return isValid;
|
|
12466
|
+
};
|
|
12467
|
+
const enableEmailMFA = async (userId) => {
|
|
12468
|
+
const tokenDocument = strapi.documents("plugin::strapi-identity.mfa-token");
|
|
12469
|
+
const existing = await tokenDocument.findFirst({ filters: { admin_user: { id: userId } } });
|
|
12470
|
+
if (existing) {
|
|
12471
|
+
await tokenDocument.update({
|
|
12472
|
+
documentId: existing.documentId,
|
|
12473
|
+
data: { ...existing, type: "email", enabled: true, secret: "" }
|
|
12474
|
+
});
|
|
12475
|
+
} else {
|
|
12476
|
+
await tokenDocument.create({
|
|
12477
|
+
data: { admin_user: { id: userId }, type: "email", enabled: true, secret: "" }
|
|
12478
|
+
});
|
|
12479
|
+
}
|
|
12480
|
+
};
|
|
12481
|
+
const getMFAInfo = async (userId) => {
|
|
12482
|
+
const tokenDocument = strapi.documents("plugin::strapi-identity.mfa-token");
|
|
12483
|
+
const record = await tokenDocument.findFirst({
|
|
12484
|
+
filters: { admin_user: { id: userId }, enabled: true }
|
|
12485
|
+
});
|
|
12486
|
+
if (!record) return null;
|
|
12487
|
+
return { status: "full", type: record.type };
|
|
12488
|
+
};
|
|
12061
12489
|
const disableSecret = async (userId, code) => {
|
|
12062
|
-
const validToken = await validateTokenOrRecoveryCode(userId, code);
|
|
12063
|
-
if (!validToken) throw new Error("Invalid token or recovery code");
|
|
12064
12490
|
const tokenDocument = strapi.documents("plugin::strapi-identity.mfa-token");
|
|
12065
12491
|
const record = await tokenDocument.findFirst({
|
|
12066
12492
|
filters: { admin_user: { id: userId }, enabled: true }
|
|
12067
12493
|
});
|
|
12068
12494
|
if (!record) throw new Error("MFA is not enabled for this user");
|
|
12495
|
+
let valid2;
|
|
12496
|
+
if (record.type === "email") {
|
|
12497
|
+
valid2 = await validateEmailOTP(userId, code, "disable");
|
|
12498
|
+
} else {
|
|
12499
|
+
valid2 = await validateTokenOrRecoveryCode(userId, code);
|
|
12500
|
+
}
|
|
12501
|
+
if (!valid2) throw new Error("Invalid token or recovery code");
|
|
12069
12502
|
await tokenDocument.update({
|
|
12070
12503
|
documentId: record.documentId,
|
|
12071
12504
|
data: { ...record, enabled: false }
|
|
@@ -12108,14 +12541,18 @@ const secret = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
|
|
|
12108
12541
|
__proto__: null,
|
|
12109
12542
|
disableSecret,
|
|
12110
12543
|
disableTempSecret,
|
|
12544
|
+
enableEmailMFA,
|
|
12545
|
+
generateEmailOTP,
|
|
12111
12546
|
generateRecoveryCode,
|
|
12547
|
+
getMFAInfo,
|
|
12112
12548
|
isMFAEnabled,
|
|
12113
12549
|
setupFullSecret,
|
|
12114
12550
|
setupTemporarySecret,
|
|
12551
|
+
validateEmailOTP,
|
|
12115
12552
|
validateTempToken,
|
|
12116
12553
|
validateTokenOrRecoveryCode
|
|
12117
12554
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
12118
|
-
const services = { admin, config, secret };
|
|
12555
|
+
const services = { admin, config, email, secret };
|
|
12119
12556
|
const index = {
|
|
12120
12557
|
register,
|
|
12121
12558
|
bootstrap,
|