strapi-identity 0.1.2 → 0.3.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-CCGgw-0k.mjs} +2 -2
- package/dist/admin/{AdminReset-C0QibZXW.js → AdminReset-QClQP2Il.js} +2 -2
- package/dist/admin/{ProfileToggle-BFmIWCrN.js → ProfileToggle-H31GHNDA.js} +299 -39
- package/dist/admin/{ProfileToggle-DQeXCx34.mjs → ProfileToggle-gPuH6dGP.mjs} +299 -39
- package/dist/admin/{SettingsPage-OwMik_IK.mjs → SettingsPage-DgPikS3m.mjs} +344 -102
- package/dist/admin/{SettingsPage-DyF7YbsX.js → SettingsPage-DulEjadb.js} +343 -101
- 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-BXZI8nMZ.js → index-C9K2h5UC.js} +65 -12
- package/dist/admin/{index-D45I6rWF.mjs → index-C_4USMnn.mjs} +65 -12
- 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/{tokenHelpers-jtoRu0q5.js → tokenHelpers-B54WRTn1.js} +4 -10
- package/dist/admin/{tokenHelpers-DagDzpso.mjs → tokenHelpers-CKVyT1sz.mjs} +4 -10
- 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 +5 -4
- 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.mjs
CHANGED
|
@@ -3,12 +3,71 @@ import require$$3 from "stream";
|
|
|
3
3
|
import require$$5 from "util";
|
|
4
4
|
import require$$1 from "crypto";
|
|
5
5
|
import { TOTP, Secret } from "otpauth";
|
|
6
|
+
const defaultConfig$1 = {
|
|
7
|
+
enabled: false,
|
|
8
|
+
enforce: false,
|
|
9
|
+
issuer: "Strapi",
|
|
10
|
+
from_email: "",
|
|
11
|
+
from_name: "",
|
|
12
|
+
response_email: "",
|
|
13
|
+
subject: "Your One-Time Password",
|
|
14
|
+
text: "Your one-time password is: <%= OTP %>",
|
|
15
|
+
message: `<div style="margin: 0; padding: 0;">
|
|
16
|
+
<table border="0" cellpadding="0" cellspacing="0" width="100%" bgcolor="#f9f9f9">
|
|
17
|
+
<tr>
|
|
18
|
+
<td align="center" style="padding: 40px 10px;">
|
|
19
|
+
<table border="0" cellpadding="0" cellspacing="0" width="600" bgcolor="#ffffff" style="border: 1px solid #dddddd;">
|
|
20
|
+
<tr>
|
|
21
|
+
<td align="center" style="padding: 40px 40px 20px 40px;">
|
|
22
|
+
<font face="Arial, sans-serif" size="5" color="#333333">
|
|
23
|
+
<strong>Verify Your Account</strong>
|
|
24
|
+
</font>
|
|
25
|
+
</td>
|
|
26
|
+
</tr>
|
|
27
|
+
<tr>
|
|
28
|
+
<td align="center" style="padding: 0 40px 20px 40px;">
|
|
29
|
+
<font face="Arial, sans-serif" size="3" color="#555555" style="line-height: 24px;">
|
|
30
|
+
Please use the following one-time password to complete your registration. This code will expire in 10 minutes.
|
|
31
|
+
</font>
|
|
32
|
+
</td>
|
|
33
|
+
</tr>
|
|
34
|
+
<tr>
|
|
35
|
+
<td align="center" style="padding: 20px 40px;">
|
|
36
|
+
<table border="0" cellpadding="0" cellspacing="0">
|
|
37
|
+
<tr>
|
|
38
|
+
<td align="center" bgcolor="#f4f4f4" style="padding: 20px 30px; font-family: Courier, monospace; font-size: 36px; color: #2d3436;">
|
|
39
|
+
<strong><%= OTP %></strong>
|
|
40
|
+
</td>
|
|
41
|
+
</tr>
|
|
42
|
+
</table>
|
|
43
|
+
</td>
|
|
44
|
+
</tr>
|
|
45
|
+
<tr>
|
|
46
|
+
<td align="center" style="padding: 20px 40px 40px 40px;">
|
|
47
|
+
<font face="Arial, sans-serif" size="2" color="#888888">
|
|
48
|
+
If you did not request this code, please ignore this email.
|
|
49
|
+
</font>
|
|
50
|
+
</td>
|
|
51
|
+
</tr>
|
|
52
|
+
</table>
|
|
53
|
+
<table border="0" cellpadding="0" cellspacing="0" width="600">
|
|
54
|
+
<tr>
|
|
55
|
+
<td align="center" style="padding: 20px;">
|
|
56
|
+
<font face="Arial, sans-serif" size="1" color="#aaaaaa">
|
|
57
|
+
© <%= YEAR %> Your Company Name
|
|
58
|
+
</font>
|
|
59
|
+
</td>
|
|
60
|
+
</tr>
|
|
61
|
+
</table>
|
|
62
|
+
</td>
|
|
63
|
+
</tr>
|
|
64
|
+
</table>
|
|
65
|
+
</div>`
|
|
66
|
+
};
|
|
6
67
|
const bootstrap = async () => {
|
|
7
68
|
const config2 = strapi.documents("plugin::strapi-identity.strapi-identity-config");
|
|
8
69
|
const existingConfig = await config2.count({});
|
|
9
|
-
if (!existingConfig) {
|
|
10
|
-
await config2.create({ data: { enabled: false, enforce: false, issuer: "Strapi" } });
|
|
11
|
-
}
|
|
70
|
+
if (!existingConfig) await config2.create({ data: defaultConfig$1 });
|
|
12
71
|
strapi.admin.services.permission.actionProvider.registerMany([
|
|
13
72
|
{
|
|
14
73
|
uid: "settings.read",
|
|
@@ -3821,7 +3880,7 @@ function requireLodash() {
|
|
|
3821
3880
|
(function(module, exports$1) {
|
|
3822
3881
|
(function() {
|
|
3823
3882
|
var undefined$1;
|
|
3824
|
-
var VERSION = "4.17.
|
|
3883
|
+
var VERSION = "4.17.23";
|
|
3825
3884
|
var LARGE_ARRAY_SIZE = 200;
|
|
3826
3885
|
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`";
|
|
3827
3886
|
var HASH_UNDEFINED = "__lodash_hash_undefined__";
|
|
@@ -5749,8 +5808,28 @@ function requireLodash() {
|
|
|
5749
5808
|
}
|
|
5750
5809
|
function baseUnset(object, path) {
|
|
5751
5810
|
path = castPath(path, object);
|
|
5752
|
-
|
|
5753
|
-
|
|
5811
|
+
var index2 = -1, length = path.length;
|
|
5812
|
+
if (!length) {
|
|
5813
|
+
return true;
|
|
5814
|
+
}
|
|
5815
|
+
var isRootPrimitive = object == null || typeof object !== "object" && typeof object !== "function";
|
|
5816
|
+
while (++index2 < length) {
|
|
5817
|
+
var key = path[index2];
|
|
5818
|
+
if (typeof key !== "string") {
|
|
5819
|
+
continue;
|
|
5820
|
+
}
|
|
5821
|
+
if (key === "__proto__" && !hasOwnProperty.call(object, "__proto__")) {
|
|
5822
|
+
return false;
|
|
5823
|
+
}
|
|
5824
|
+
if (key === "constructor" && index2 + 1 < length && typeof path[index2 + 1] === "string" && path[index2 + 1] === "prototype") {
|
|
5825
|
+
if (isRootPrimitive && index2 === 0) {
|
|
5826
|
+
continue;
|
|
5827
|
+
}
|
|
5828
|
+
return false;
|
|
5829
|
+
}
|
|
5830
|
+
}
|
|
5831
|
+
var obj = parent(object, path);
|
|
5832
|
+
return obj == null || delete obj[toKey(last(path))];
|
|
5754
5833
|
}
|
|
5755
5834
|
function baseUpdate(object, path, updater, customizer) {
|
|
5756
5835
|
return baseSet(object, path, updater(baseGet(object, path)), customizer);
|
|
@@ -9533,14 +9612,14 @@ function requireJsonwebtoken() {
|
|
|
9533
9612
|
var jsonwebtokenExports = requireJsonwebtoken();
|
|
9534
9613
|
const jwt = /* @__PURE__ */ getDefaultExportFromCjs(jsonwebtokenExports);
|
|
9535
9614
|
const register = ({ strapi: strapi2 }) => {
|
|
9536
|
-
const
|
|
9537
|
-
const secret2 =
|
|
9538
|
-
const domain =
|
|
9539
|
-
const loginRoute =
|
|
9615
|
+
const { admin: admin2, config: config2, server } = strapi2;
|
|
9616
|
+
const secret2 = config2.get("admin.auth.secret");
|
|
9617
|
+
const domain = config2.get("admin.auth.domain");
|
|
9618
|
+
const loginRoute = admin2.routes.admin.routes.find(
|
|
9540
9619
|
({ method, path }) => method === "POST" && path === "/login"
|
|
9541
9620
|
);
|
|
9542
9621
|
if (loginRoute) replaceLogin(loginRoute, secret2, domain);
|
|
9543
|
-
|
|
9622
|
+
server.use(async (ctx, next) => {
|
|
9544
9623
|
const mfaCookie = ctx.cookies.get("strapi_admin_mfa");
|
|
9545
9624
|
if (mfaCookie && ctx.path.startsWith("/admin/auth")) {
|
|
9546
9625
|
ctx.cookies.set("jwtToken", null, { expires: /* @__PURE__ */ new Date(0) });
|
|
@@ -9568,8 +9647,25 @@ const replaceLogin = (route2, secret2, domain) => {
|
|
|
9568
9647
|
filters: { admin_user: { id: payload.userId }, enabled: true }
|
|
9569
9648
|
});
|
|
9570
9649
|
if (!exists) return;
|
|
9650
|
+
if (exists.type === "email") {
|
|
9651
|
+
try {
|
|
9652
|
+
const adminUser = await strapi.db.query("admin::user").findOne({ where: { id: Number(payload.userId) }, select: ["email"] });
|
|
9653
|
+
if (adminUser?.email) {
|
|
9654
|
+
const otp = await strapi.service("plugin::strapi-identity.secret").generateEmailOTP(payload.userId, "login");
|
|
9655
|
+
await strapi.service("plugin::strapi-identity.email").send(adminUser.email, otp);
|
|
9656
|
+
}
|
|
9657
|
+
} catch (err) {
|
|
9658
|
+
console.log("Error sending login email OTP:", err);
|
|
9659
|
+
}
|
|
9660
|
+
}
|
|
9571
9661
|
ctx.res.removeHeader("set-cookie");
|
|
9572
|
-
const newPayload = {
|
|
9662
|
+
const newPayload = {
|
|
9663
|
+
userId: payload.userId,
|
|
9664
|
+
deviceId,
|
|
9665
|
+
rememberMe,
|
|
9666
|
+
type: "mfa",
|
|
9667
|
+
mfaType: exists.type
|
|
9668
|
+
};
|
|
9573
9669
|
const newToken = jwt.sign(newPayload, secret2, { expiresIn: "5m" });
|
|
9574
9670
|
const expires = new Date(Date.now() + 5 * 60 * 1e3);
|
|
9575
9671
|
const opt = { domain, httpOnly: false, overwrite: true, secure: ctx.request.secure, expires };
|
|
@@ -9582,12 +9678,27 @@ const config$4 = {
|
|
|
9582
9678
|
validator() {
|
|
9583
9679
|
}
|
|
9584
9680
|
};
|
|
9681
|
+
const kind$3 = "collectionType";
|
|
9682
|
+
const collectionName$3 = "mfa-tokens";
|
|
9683
|
+
const info$3 = { "singularName": "mfa-token", "pluralName": "mfa-tokens", "displayName": "MFA Token" };
|
|
9684
|
+
const options$3 = { "draftAndPublish": false };
|
|
9685
|
+
const pluginOptions$3 = { "content-manager": { "visible": false }, "content-type-builder": { "visible": false } };
|
|
9686
|
+
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 } };
|
|
9687
|
+
const schema$3 = {
|
|
9688
|
+
kind: kind$3,
|
|
9689
|
+
collectionName: collectionName$3,
|
|
9690
|
+
info: info$3,
|
|
9691
|
+
options: options$3,
|
|
9692
|
+
pluginOptions: pluginOptions$3,
|
|
9693
|
+
attributes: attributes$3
|
|
9694
|
+
};
|
|
9695
|
+
const mfaToken = { schema: schema$3 };
|
|
9585
9696
|
const kind$2 = "collectionType";
|
|
9586
|
-
const collectionName$2 = "mfa-
|
|
9587
|
-
const info$2 = { "singularName": "mfa-
|
|
9697
|
+
const collectionName$2 = "mfa-temps";
|
|
9698
|
+
const info$2 = { "singularName": "mfa-temp", "pluralName": "mfa-temps", "displayName": "MFA Temp" };
|
|
9588
9699
|
const options$2 = { "draftAndPublish": false };
|
|
9589
9700
|
const pluginOptions$2 = { "content-manager": { "visible": false }, "content-type-builder": { "visible": false } };
|
|
9590
|
-
const attributes$2 = { "admin_user": { "type": "relation", "relation": "oneToOne", "target": "admin::user" }, "
|
|
9701
|
+
const attributes$2 = { "admin_user": { "type": "relation", "relation": "oneToOne", "target": "admin::user" }, "secret": { "type": "string", "required": false, "private": true } };
|
|
9591
9702
|
const schema$2 = {
|
|
9592
9703
|
kind: kind$2,
|
|
9593
9704
|
collectionName: collectionName$2,
|
|
@@ -9596,13 +9707,13 @@ const schema$2 = {
|
|
|
9596
9707
|
pluginOptions: pluginOptions$2,
|
|
9597
9708
|
attributes: attributes$2
|
|
9598
9709
|
};
|
|
9599
|
-
const
|
|
9600
|
-
const kind$1 = "
|
|
9601
|
-
const collectionName$1 = "
|
|
9602
|
-
const info$1 = { "singularName": "
|
|
9710
|
+
const mfaTemp = { schema: schema$2 };
|
|
9711
|
+
const kind$1 = "singleType";
|
|
9712
|
+
const collectionName$1 = "strapi-identity-config";
|
|
9713
|
+
const info$1 = { "singularName": "strapi-identity-config", "pluralName": "strapi-identity-configs", "displayName": "Strapi Identity Config" };
|
|
9603
9714
|
const options$1 = { "draftAndPublish": false };
|
|
9604
9715
|
const pluginOptions$1 = { "content-manager": { "visible": false }, "content-type-builder": { "visible": false } };
|
|
9605
|
-
const attributes$1 = { "
|
|
9716
|
+
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": "" } };
|
|
9606
9717
|
const schema$1 = {
|
|
9607
9718
|
kind: kind$1,
|
|
9608
9719
|
collectionName: collectionName$1,
|
|
@@ -9611,13 +9722,13 @@ const schema$1 = {
|
|
|
9611
9722
|
pluginOptions: pluginOptions$1,
|
|
9612
9723
|
attributes: attributes$1
|
|
9613
9724
|
};
|
|
9614
|
-
const
|
|
9615
|
-
const kind = "
|
|
9616
|
-
const collectionName = "
|
|
9617
|
-
const info = { "singularName": "
|
|
9725
|
+
const config$3 = { schema: schema$1 };
|
|
9726
|
+
const kind = "collectionType";
|
|
9727
|
+
const collectionName = "email-otps";
|
|
9728
|
+
const info = { "singularName": "email-otp", "pluralName": "email-otps", "displayName": "Email OTP" };
|
|
9618
9729
|
const options = { "draftAndPublish": false };
|
|
9619
9730
|
const pluginOptions = { "content-manager": { "visible": false }, "content-type-builder": { "visible": false } };
|
|
9620
|
-
const attributes = { "
|
|
9731
|
+
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" } };
|
|
9621
9732
|
const schema = {
|
|
9622
9733
|
kind,
|
|
9623
9734
|
collectionName,
|
|
@@ -9626,11 +9737,12 @@ const schema = {
|
|
|
9626
9737
|
pluginOptions,
|
|
9627
9738
|
attributes
|
|
9628
9739
|
};
|
|
9629
|
-
const
|
|
9740
|
+
const emailOtp = { schema };
|
|
9630
9741
|
const contentTypes = {
|
|
9631
9742
|
"mfa-token": mfaToken,
|
|
9632
9743
|
"mfa-temp": mfaTemp,
|
|
9633
|
-
"strapi-identity-config": config$3
|
|
9744
|
+
"strapi-identity-config": config$3,
|
|
9745
|
+
"email-otp": emailOtp
|
|
9634
9746
|
};
|
|
9635
9747
|
const admin$2 = ({ strapi: strapi2 }) => ({
|
|
9636
9748
|
async isEnabled(ctx) {
|
|
@@ -9691,6 +9803,17 @@ const config$2 = ({ strapi: strapi2 }) => ({
|
|
|
9691
9803
|
ctx.body = { data: null, error: "Server Error" };
|
|
9692
9804
|
}
|
|
9693
9805
|
},
|
|
9806
|
+
async getEmailStatus(ctx) {
|
|
9807
|
+
try {
|
|
9808
|
+
const emailService = strapi2.config.get("plugin::email");
|
|
9809
|
+
ctx.status = 200;
|
|
9810
|
+
ctx.body = { data: emailService, error: null };
|
|
9811
|
+
} catch (error) {
|
|
9812
|
+
console.log("Error getting email status:", error);
|
|
9813
|
+
ctx.status = 500;
|
|
9814
|
+
ctx.body = { data: null, error: "Server Error" };
|
|
9815
|
+
}
|
|
9816
|
+
},
|
|
9694
9817
|
async updateConfig(ctx) {
|
|
9695
9818
|
const body = ctx.request.body;
|
|
9696
9819
|
try {
|
|
@@ -9757,7 +9880,13 @@ const controller = ({ strapi: strapi2 }) => ({
|
|
|
9757
9880
|
const payload = jwt.verify(token, secret2);
|
|
9758
9881
|
const body = ctx.request.body;
|
|
9759
9882
|
try {
|
|
9760
|
-
const
|
|
9883
|
+
const mfaRecord = await strapi2.documents("plugin::strapi-identity.mfa-token").findFirst({ filters: { admin_user: { id: payload.userId }, enabled: true } });
|
|
9884
|
+
let valid2;
|
|
9885
|
+
if (mfaRecord?.type === "email") {
|
|
9886
|
+
valid2 = await strapi2.service("plugin::strapi-identity.secret").validateEmailOTP(payload.userId, body.code, "login");
|
|
9887
|
+
} else {
|
|
9888
|
+
valid2 = await strapi2.service("plugin::strapi-identity.secret").validateTokenOrRecoveryCode(payload.userId, body.code);
|
|
9889
|
+
}
|
|
9761
9890
|
if (!valid2) {
|
|
9762
9891
|
ctx.status = 400;
|
|
9763
9892
|
ctx.body = { data: null, error: "Invalid MFA code" };
|
|
@@ -9840,9 +9969,9 @@ const controller = ({ strapi: strapi2 }) => ({
|
|
|
9840
9969
|
const user = ctx.state.user;
|
|
9841
9970
|
const secretService = strapi2.service("plugin::strapi-identity.secret");
|
|
9842
9971
|
try {
|
|
9843
|
-
const
|
|
9972
|
+
const info2 = await secretService.getMFAInfo(user.id);
|
|
9844
9973
|
ctx.status = 200;
|
|
9845
|
-
ctx.body = { data: { status:
|
|
9974
|
+
ctx.body = { data: { status: info2?.status || null, type: info2?.type || null }, error: null };
|
|
9846
9975
|
} catch (error) {
|
|
9847
9976
|
ctx.status = 500;
|
|
9848
9977
|
ctx.body = { data: null, error: "Failed to check MFA status" };
|
|
@@ -9861,6 +9990,103 @@ const controller = ({ strapi: strapi2 }) => ({
|
|
|
9861
9990
|
ctx.status = 500;
|
|
9862
9991
|
ctx.body = { data: null, error: "Failed to disable MFA" };
|
|
9863
9992
|
}
|
|
9993
|
+
},
|
|
9994
|
+
async enableEmail(ctx) {
|
|
9995
|
+
const user = ctx.state.user;
|
|
9996
|
+
const secretService = strapi2.service("plugin::strapi-identity.secret");
|
|
9997
|
+
const emailService = strapi2.service("plugin::strapi-identity.email");
|
|
9998
|
+
const configService = strapi2.service("plugin::strapi-identity.config");
|
|
9999
|
+
try {
|
|
10000
|
+
const config2 = await configService.getConfig();
|
|
10001
|
+
if (!config2.email_enabled) {
|
|
10002
|
+
ctx.status = 400;
|
|
10003
|
+
ctx.body = { data: null, error: "Email OTP is not configured" };
|
|
10004
|
+
return;
|
|
10005
|
+
}
|
|
10006
|
+
const existing = await strapi2.documents("plugin::strapi-identity.mfa-token").findFirst({ filters: { admin_user: { id: user.id }, enabled: true } });
|
|
10007
|
+
if (existing) {
|
|
10008
|
+
ctx.status = 400;
|
|
10009
|
+
ctx.body = { data: null, error: "MFA is already enabled. Disable it first." };
|
|
10010
|
+
return;
|
|
10011
|
+
}
|
|
10012
|
+
const otp = await secretService.generateEmailOTP(user.id, "setup");
|
|
10013
|
+
await emailService.send(user.email, otp);
|
|
10014
|
+
ctx.status = 200;
|
|
10015
|
+
ctx.body = { data: { message: "Verification email sent" }, error: null };
|
|
10016
|
+
} catch (error) {
|
|
10017
|
+
console.log("Error initiating email MFA setup:", error);
|
|
10018
|
+
ctx.status = 500;
|
|
10019
|
+
ctx.body = { data: null, error: "Failed to initiate email MFA setup" };
|
|
10020
|
+
}
|
|
10021
|
+
},
|
|
10022
|
+
async setupEmail(ctx) {
|
|
10023
|
+
const user = ctx.state.user;
|
|
10024
|
+
const body = ctx.request.body;
|
|
10025
|
+
const secretService = strapi2.service("plugin::strapi-identity.secret");
|
|
10026
|
+
try {
|
|
10027
|
+
const valid2 = await secretService.validateEmailOTP(user.id, body.code, "setup");
|
|
10028
|
+
if (!valid2) {
|
|
10029
|
+
ctx.status = 400;
|
|
10030
|
+
ctx.body = { data: null, error: "Invalid or expired verification code" };
|
|
10031
|
+
return;
|
|
10032
|
+
}
|
|
10033
|
+
await secretService.enableEmailMFA(user.id);
|
|
10034
|
+
ctx.status = 200;
|
|
10035
|
+
ctx.body = { data: { message: "Email OTP enabled" }, error: null };
|
|
10036
|
+
} catch (error) {
|
|
10037
|
+
console.log("Error completing email MFA setup:", error);
|
|
10038
|
+
ctx.status = 500;
|
|
10039
|
+
ctx.body = { data: null, error: "Failed to enable email MFA" };
|
|
10040
|
+
}
|
|
10041
|
+
},
|
|
10042
|
+
async requestDisableEmailOTP(ctx) {
|
|
10043
|
+
const user = ctx.state.user;
|
|
10044
|
+
const secretService = strapi2.service("plugin::strapi-identity.secret");
|
|
10045
|
+
const emailService = strapi2.service("plugin::strapi-identity.email");
|
|
10046
|
+
try {
|
|
10047
|
+
const info2 = await secretService.getMFAInfo(user.id);
|
|
10048
|
+
if (!info2 || info2.type !== "email") {
|
|
10049
|
+
ctx.status = 400;
|
|
10050
|
+
ctx.body = { data: null, error: "Email OTP is not enabled" };
|
|
10051
|
+
return;
|
|
10052
|
+
}
|
|
10053
|
+
const otp = await secretService.generateEmailOTP(user.id, "disable");
|
|
10054
|
+
await emailService.send(user.email, otp);
|
|
10055
|
+
ctx.status = 200;
|
|
10056
|
+
ctx.body = { data: { message: "Verification email sent" }, error: null };
|
|
10057
|
+
} catch (error) {
|
|
10058
|
+
console.log("Error sending disable email OTP:", error);
|
|
10059
|
+
ctx.status = 500;
|
|
10060
|
+
ctx.body = { data: null, error: "Failed to send verification email" };
|
|
10061
|
+
}
|
|
10062
|
+
},
|
|
10063
|
+
async resendVerifyOTP(ctx) {
|
|
10064
|
+
const secret2 = strapi2.config.get("admin.auth.secret");
|
|
10065
|
+
const token = ctx.cookies.get("strapi_admin_mfa");
|
|
10066
|
+
try {
|
|
10067
|
+
const payload = jwt.verify(token, secret2);
|
|
10068
|
+
if (payload.mfaType !== "email") {
|
|
10069
|
+
ctx.status = 400;
|
|
10070
|
+
ctx.body = { data: null, error: "Resend is only available for email OTP" };
|
|
10071
|
+
return;
|
|
10072
|
+
}
|
|
10073
|
+
const adminUser = await strapi2.db.query("admin::user").findOne({ where: { id: Number(payload.userId) }, select: ["email"] });
|
|
10074
|
+
if (!adminUser?.email) {
|
|
10075
|
+
ctx.status = 400;
|
|
10076
|
+
ctx.body = { data: null, error: "No email address found for this user" };
|
|
10077
|
+
return;
|
|
10078
|
+
}
|
|
10079
|
+
const secretService = strapi2.service("plugin::strapi-identity.secret");
|
|
10080
|
+
const emailService = strapi2.service("plugin::strapi-identity.email");
|
|
10081
|
+
const otp = await secretService.generateEmailOTP(payload.userId, "login");
|
|
10082
|
+
await emailService.send(adminUser.email, otp);
|
|
10083
|
+
ctx.status = 200;
|
|
10084
|
+
ctx.body = { data: { message: "Verification email resent" }, error: null };
|
|
10085
|
+
} catch (error) {
|
|
10086
|
+
console.log("Error resending login email OTP:", error);
|
|
10087
|
+
ctx.status = 500;
|
|
10088
|
+
ctx.body = { data: null, error: "Failed to resend verification email" };
|
|
10089
|
+
}
|
|
9864
10090
|
}
|
|
9865
10091
|
});
|
|
9866
10092
|
const controllers = {
|
|
@@ -9960,6 +10186,21 @@ const config$1 = [
|
|
|
9960
10186
|
]
|
|
9961
10187
|
}
|
|
9962
10188
|
},
|
|
10189
|
+
{
|
|
10190
|
+
method: "GET",
|
|
10191
|
+
path: "/config/email",
|
|
10192
|
+
handler: "config.getEmailStatus",
|
|
10193
|
+
info: {
|
|
10194
|
+
apiName: "getEmailStatus",
|
|
10195
|
+
pluginName: "strapi-identity",
|
|
10196
|
+
type: "content-api"
|
|
10197
|
+
},
|
|
10198
|
+
config: {
|
|
10199
|
+
policies: [
|
|
10200
|
+
"admin::isAuthenticatedAdmin"
|
|
10201
|
+
]
|
|
10202
|
+
}
|
|
10203
|
+
},
|
|
9963
10204
|
{
|
|
9964
10205
|
method: "PUT",
|
|
9965
10206
|
path: "/config",
|
|
@@ -10044,6 +10285,55 @@ const mfa = [
|
|
|
10044
10285
|
type: "content-api"
|
|
10045
10286
|
},
|
|
10046
10287
|
config: {}
|
|
10288
|
+
},
|
|
10289
|
+
{
|
|
10290
|
+
method: "POST",
|
|
10291
|
+
path: "/verify/resend",
|
|
10292
|
+
handler: "controller.resendVerifyOTP",
|
|
10293
|
+
info: {
|
|
10294
|
+
apiName: "resendVerifyOTP",
|
|
10295
|
+
pluginName: "strapi-identity",
|
|
10296
|
+
type: "content-api"
|
|
10297
|
+
},
|
|
10298
|
+
config: {
|
|
10299
|
+
auth: false,
|
|
10300
|
+
policies: [
|
|
10301
|
+
"has-mfa"
|
|
10302
|
+
]
|
|
10303
|
+
}
|
|
10304
|
+
},
|
|
10305
|
+
{
|
|
10306
|
+
method: "POST",
|
|
10307
|
+
path: "/enable-email",
|
|
10308
|
+
handler: "controller.enableEmail",
|
|
10309
|
+
info: {
|
|
10310
|
+
apiName: "enableEmail",
|
|
10311
|
+
pluginName: "strapi-identity",
|
|
10312
|
+
type: "content-api"
|
|
10313
|
+
},
|
|
10314
|
+
config: {}
|
|
10315
|
+
},
|
|
10316
|
+
{
|
|
10317
|
+
method: "POST",
|
|
10318
|
+
path: "/setup-email",
|
|
10319
|
+
handler: "controller.setupEmail",
|
|
10320
|
+
info: {
|
|
10321
|
+
apiName: "setupEmail",
|
|
10322
|
+
pluginName: "strapi-identity",
|
|
10323
|
+
type: "content-api"
|
|
10324
|
+
},
|
|
10325
|
+
config: {}
|
|
10326
|
+
},
|
|
10327
|
+
{
|
|
10328
|
+
method: "POST",
|
|
10329
|
+
path: "/disable-email/request",
|
|
10330
|
+
handler: "controller.requestDisableEmailOTP",
|
|
10331
|
+
info: {
|
|
10332
|
+
apiName: "requestDisableEmailOTP",
|
|
10333
|
+
pluginName: "strapi-identity",
|
|
10334
|
+
type: "content-api"
|
|
10335
|
+
},
|
|
10336
|
+
config: {}
|
|
10047
10337
|
}
|
|
10048
10338
|
];
|
|
10049
10339
|
const route = () => ({
|
|
@@ -10084,35 +10374,63 @@ const admin = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePropert
|
|
|
10084
10374
|
isEnabled: isEnabled$1,
|
|
10085
10375
|
reset
|
|
10086
10376
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
10377
|
+
const defaultConfig = {
|
|
10378
|
+
email_enabled: false,
|
|
10379
|
+
enabled: false,
|
|
10380
|
+
enforce: false,
|
|
10381
|
+
from_email: "",
|
|
10382
|
+
from_name: "",
|
|
10383
|
+
issuer: "",
|
|
10384
|
+
message: "",
|
|
10385
|
+
response_email: "",
|
|
10386
|
+
subject: "",
|
|
10387
|
+
text: ""
|
|
10388
|
+
};
|
|
10087
10389
|
const _config = (options2) => {
|
|
10088
|
-
return {
|
|
10089
|
-
enabled: options2?.enabled || false,
|
|
10090
|
-
enforce: options2?.enforce || false,
|
|
10091
|
-
issuer: options2?.issuer || ""
|
|
10092
|
-
};
|
|
10390
|
+
return Object.assign({}, defaultConfig, options2);
|
|
10093
10391
|
};
|
|
10392
|
+
const fields = Object.keys(defaultConfig);
|
|
10094
10393
|
const isEnabled = async () => {
|
|
10095
10394
|
const config2 = await getConfig();
|
|
10096
10395
|
return config2?.enabled || false;
|
|
10097
10396
|
};
|
|
10098
10397
|
const getConfig = async () => {
|
|
10099
10398
|
const configDocument = strapi.documents("plugin::strapi-identity.strapi-identity-config");
|
|
10100
|
-
return configDocument.findFirst({ fields
|
|
10399
|
+
return configDocument.findFirst({ fields }).then((config2) => _config(config2));
|
|
10101
10400
|
};
|
|
10102
10401
|
const updateConfig = async (data) => {
|
|
10103
10402
|
const configDocument = strapi.documents("plugin::strapi-identity.strapi-identity-config");
|
|
10104
10403
|
const existingConfig = await configDocument.findFirst();
|
|
10105
10404
|
if (!existingConfig) {
|
|
10106
|
-
return configDocument.create({ data, fields
|
|
10405
|
+
return configDocument.create({ data, fields }).then((created) => _config(created));
|
|
10107
10406
|
}
|
|
10108
|
-
if (existingConfig.enabled && !data.enabled)
|
|
10109
|
-
|
|
10407
|
+
if (existingConfig.enabled && !data.enabled) await disableMFAForAllUsers();
|
|
10408
|
+
if (existingConfig.email_enabled && data.email_enabled === false)
|
|
10409
|
+
await disableEmailMFAForAllUsers();
|
|
10410
|
+
return configDocument.update({ documentId: existingConfig.documentId, data: { ...existingConfig, ...data }, fields }).then((updated) => _config(updated));
|
|
10411
|
+
};
|
|
10412
|
+
const disableEmailMFAForAllUsers = async () => {
|
|
10413
|
+
const tokenDocument = strapi.documents("plugin::strapi-identity.mfa-token");
|
|
10414
|
+
const otpDocument = strapi.documents("plugin::strapi-identity.email-otp");
|
|
10415
|
+
try {
|
|
10416
|
+
const emailTokens = await tokenDocument.findMany({
|
|
10417
|
+
filters: { type: "email", enabled: true }
|
|
10418
|
+
});
|
|
10419
|
+
await Promise.all([
|
|
10420
|
+
...emailTokens.map(
|
|
10421
|
+
(token) => tokenDocument.update({
|
|
10422
|
+
documentId: token.documentId,
|
|
10423
|
+
data: { ...token, enabled: false }
|
|
10424
|
+
})
|
|
10425
|
+
),
|
|
10426
|
+
// Clean up any pending email OTPs
|
|
10427
|
+
otpDocument.findMany({}).then(
|
|
10428
|
+
(otps) => Promise.all(otps.map((otp) => otpDocument.delete({ documentId: otp.documentId })))
|
|
10429
|
+
)
|
|
10430
|
+
]);
|
|
10431
|
+
} catch (err) {
|
|
10432
|
+
console.log("Error disabling email MFA for all users:", err);
|
|
10110
10433
|
}
|
|
10111
|
-
return configDocument.update({
|
|
10112
|
-
documentId: existingConfig.documentId,
|
|
10113
|
-
data: { ...existingConfig, ...data },
|
|
10114
|
-
fields: ["enabled", "enforce", "issuer"]
|
|
10115
|
-
}).then((updated) => _config(updated));
|
|
10116
10434
|
};
|
|
10117
10435
|
const disableMFAForAllUsers = async () => {
|
|
10118
10436
|
const tokenDocument = strapi.documents("plugin::strapi-identity.mfa-token");
|
|
@@ -10136,6 +10454,39 @@ const config = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
|
|
|
10136
10454
|
isEnabled,
|
|
10137
10455
|
updateConfig
|
|
10138
10456
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
10457
|
+
const send = async (to, otp) => {
|
|
10458
|
+
const emailService = strapi.plugin("email").service("email");
|
|
10459
|
+
if (!emailService) return;
|
|
10460
|
+
if (!to) return;
|
|
10461
|
+
const config2 = await strapi.service("plugin::strapi-identity.config").getConfig();
|
|
10462
|
+
if (!config2.email_enabled) return;
|
|
10463
|
+
const sendConfig = {
|
|
10464
|
+
to,
|
|
10465
|
+
subject: config2.subject,
|
|
10466
|
+
text: replaceTemplateVariables(config2.text, { OTP: otp }),
|
|
10467
|
+
html: replaceTemplateVariables(config2.message, {
|
|
10468
|
+
OTP: otp,
|
|
10469
|
+
YEAR: (/* @__PURE__ */ new Date()).getFullYear().toString()
|
|
10470
|
+
})
|
|
10471
|
+
};
|
|
10472
|
+
if (config2.from_email) {
|
|
10473
|
+
sendConfig.from = config2.from_name ? `${config2.from_name} <${config2.from_email}>` : config2.from_email;
|
|
10474
|
+
}
|
|
10475
|
+
if (config2.response_email && config2.response_email !== config2.from_email) {
|
|
10476
|
+
sendConfig.replyTo = config2.response_email;
|
|
10477
|
+
}
|
|
10478
|
+
return emailService.send(sendConfig).catch((error) => {
|
|
10479
|
+
console.log("Error sending email:", error);
|
|
10480
|
+
});
|
|
10481
|
+
};
|
|
10482
|
+
const replaceTemplateVariables = (template, variables) => {
|
|
10483
|
+
return template.replace(/<%= (\w+) %>/g, (_, key) => variables[key] || "");
|
|
10484
|
+
};
|
|
10485
|
+
const email = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
10486
|
+
__proto__: null,
|
|
10487
|
+
replaceTemplateVariables,
|
|
10488
|
+
send
|
|
10489
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
10139
10490
|
function commonjsRequire(path) {
|
|
10140
10491
|
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.');
|
|
10141
10492
|
}
|
|
@@ -11989,8 +12340,11 @@ const checkRecoveryCode = async (userId, code) => {
|
|
|
11989
12340
|
};
|
|
11990
12341
|
const validateLiveToken = async (userId, token) => {
|
|
11991
12342
|
const tokenDocument = strapi.documents("plugin::strapi-identity.mfa-token");
|
|
11992
|
-
const record = await tokenDocument.findFirst({
|
|
11993
|
-
|
|
12343
|
+
const record = await tokenDocument.findFirst({
|
|
12344
|
+
filters: { admin_user: { id: userId }, enabled: true }
|
|
12345
|
+
});
|
|
12346
|
+
if (!record) return false;
|
|
12347
|
+
if (record.type === "email") return false;
|
|
11994
12348
|
return validateToken(token, record.secret);
|
|
11995
12349
|
};
|
|
11996
12350
|
const validateTempToken = async (userId, token) => {
|
|
@@ -12036,12 +12390,13 @@ const setupFullSecret = async (userId) => {
|
|
|
12036
12390
|
if (existing) {
|
|
12037
12391
|
await tokenDocument.update({
|
|
12038
12392
|
documentId: existing.documentId,
|
|
12039
|
-
data: { ...existing, secret: tempField.secret, enabled: true, recovery_codes }
|
|
12393
|
+
data: { ...existing, type: "totp", secret: tempField.secret, enabled: true, recovery_codes }
|
|
12040
12394
|
});
|
|
12041
12395
|
} else {
|
|
12042
12396
|
await tokenDocument.create({
|
|
12043
12397
|
data: {
|
|
12044
12398
|
admin_user: { id: userId },
|
|
12399
|
+
type: "totp",
|
|
12045
12400
|
secret: tempField.secret,
|
|
12046
12401
|
enabled: true,
|
|
12047
12402
|
recovery_codes
|
|
@@ -12051,14 +12406,92 @@ const setupFullSecret = async (userId) => {
|
|
|
12051
12406
|
await tempDocument.delete({ documentId: tempField.documentId });
|
|
12052
12407
|
return codes;
|
|
12053
12408
|
};
|
|
12409
|
+
const EMAIL_OTP_EXPIRY_MINUTES = 10;
|
|
12410
|
+
const EMAIL_OTP_MAX_ATTEMPTS = 5;
|
|
12411
|
+
const generateNumericOTP = (length) => {
|
|
12412
|
+
const digits = "0123456789";
|
|
12413
|
+
const max = 256 - 256 % digits.length;
|
|
12414
|
+
const randomValues = new Uint8Array(length * 2);
|
|
12415
|
+
let result = "";
|
|
12416
|
+
while (result.length < length) {
|
|
12417
|
+
crypto.getRandomValues(randomValues);
|
|
12418
|
+
for (let i = 0; i < randomValues.length && result.length < length; i++) {
|
|
12419
|
+
if (randomValues[i] >= max) continue;
|
|
12420
|
+
result += digits[randomValues[i] % digits.length];
|
|
12421
|
+
}
|
|
12422
|
+
}
|
|
12423
|
+
return result;
|
|
12424
|
+
};
|
|
12425
|
+
const generateEmailOTP = async (userId, purpose = "login") => {
|
|
12426
|
+
const otpDocument = strapi.documents("plugin::strapi-identity.email-otp");
|
|
12427
|
+
const otp = generateNumericOTP(6);
|
|
12428
|
+
const code_hash = await bcrypt.hash(otp, 10);
|
|
12429
|
+
const expires_at = new Date(Date.now() + EMAIL_OTP_EXPIRY_MINUTES * 60 * 1e3).toISOString();
|
|
12430
|
+
const existing = await otpDocument.findFirst({
|
|
12431
|
+
filters: { admin_user: { id: userId }, purpose }
|
|
12432
|
+
});
|
|
12433
|
+
if (existing) await otpDocument.delete({ documentId: existing.documentId });
|
|
12434
|
+
await otpDocument.create({
|
|
12435
|
+
data: { admin_user: { id: userId }, code_hash, expires_at, purpose, attempts: 0 }
|
|
12436
|
+
});
|
|
12437
|
+
return otp;
|
|
12438
|
+
};
|
|
12439
|
+
const validateEmailOTP = async (userId, code, purpose = "login") => {
|
|
12440
|
+
const otpDocument = strapi.documents("plugin::strapi-identity.email-otp");
|
|
12441
|
+
const record = await otpDocument.findFirst({ filters: { admin_user: { id: userId }, purpose } });
|
|
12442
|
+
if (!record) return false;
|
|
12443
|
+
if (new Date(record.expires_at) < /* @__PURE__ */ new Date()) {
|
|
12444
|
+
await otpDocument.delete({ documentId: record.documentId });
|
|
12445
|
+
return false;
|
|
12446
|
+
}
|
|
12447
|
+
const attempts = record.attempts || 0;
|
|
12448
|
+
if (attempts >= EMAIL_OTP_MAX_ATTEMPTS) {
|
|
12449
|
+
await otpDocument.delete({ documentId: record.documentId });
|
|
12450
|
+
return false;
|
|
12451
|
+
}
|
|
12452
|
+
await otpDocument.update({
|
|
12453
|
+
documentId: record.documentId,
|
|
12454
|
+
data: { ...record, attempts: attempts + 1 }
|
|
12455
|
+
});
|
|
12456
|
+
const isValid = await bcrypt.compare(code, record.code_hash);
|
|
12457
|
+
if (isValid) await otpDocument.delete({ documentId: record.documentId });
|
|
12458
|
+
return isValid;
|
|
12459
|
+
};
|
|
12460
|
+
const enableEmailMFA = async (userId) => {
|
|
12461
|
+
const tokenDocument = strapi.documents("plugin::strapi-identity.mfa-token");
|
|
12462
|
+
const existing = await tokenDocument.findFirst({ filters: { admin_user: { id: userId } } });
|
|
12463
|
+
if (existing) {
|
|
12464
|
+
await tokenDocument.update({
|
|
12465
|
+
documentId: existing.documentId,
|
|
12466
|
+
data: { ...existing, type: "email", enabled: true, secret: "" }
|
|
12467
|
+
});
|
|
12468
|
+
} else {
|
|
12469
|
+
await tokenDocument.create({
|
|
12470
|
+
data: { admin_user: { id: userId }, type: "email", enabled: true, secret: "" }
|
|
12471
|
+
});
|
|
12472
|
+
}
|
|
12473
|
+
};
|
|
12474
|
+
const getMFAInfo = async (userId) => {
|
|
12475
|
+
const tokenDocument = strapi.documents("plugin::strapi-identity.mfa-token");
|
|
12476
|
+
const record = await tokenDocument.findFirst({
|
|
12477
|
+
filters: { admin_user: { id: userId }, enabled: true }
|
|
12478
|
+
});
|
|
12479
|
+
if (!record) return null;
|
|
12480
|
+
return { status: "full", type: record.type };
|
|
12481
|
+
};
|
|
12054
12482
|
const disableSecret = async (userId, code) => {
|
|
12055
|
-
const validToken = await validateTokenOrRecoveryCode(userId, code);
|
|
12056
|
-
if (!validToken) throw new Error("Invalid token or recovery code");
|
|
12057
12483
|
const tokenDocument = strapi.documents("plugin::strapi-identity.mfa-token");
|
|
12058
12484
|
const record = await tokenDocument.findFirst({
|
|
12059
12485
|
filters: { admin_user: { id: userId }, enabled: true }
|
|
12060
12486
|
});
|
|
12061
12487
|
if (!record) throw new Error("MFA is not enabled for this user");
|
|
12488
|
+
let valid2;
|
|
12489
|
+
if (record.type === "email") {
|
|
12490
|
+
valid2 = await validateEmailOTP(userId, code, "disable");
|
|
12491
|
+
} else {
|
|
12492
|
+
valid2 = await validateTokenOrRecoveryCode(userId, code);
|
|
12493
|
+
}
|
|
12494
|
+
if (!valid2) throw new Error("Invalid token or recovery code");
|
|
12062
12495
|
await tokenDocument.update({
|
|
12063
12496
|
documentId: record.documentId,
|
|
12064
12497
|
data: { ...record, enabled: false }
|
|
@@ -12101,14 +12534,18 @@ const secret = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
|
|
|
12101
12534
|
__proto__: null,
|
|
12102
12535
|
disableSecret,
|
|
12103
12536
|
disableTempSecret,
|
|
12537
|
+
enableEmailMFA,
|
|
12538
|
+
generateEmailOTP,
|
|
12104
12539
|
generateRecoveryCode,
|
|
12540
|
+
getMFAInfo,
|
|
12105
12541
|
isMFAEnabled,
|
|
12106
12542
|
setupFullSecret,
|
|
12107
12543
|
setupTemporarySecret,
|
|
12544
|
+
validateEmailOTP,
|
|
12108
12545
|
validateTempToken,
|
|
12109
12546
|
validateTokenOrRecoveryCode
|
|
12110
12547
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
12111
|
-
const services = { admin, config, secret };
|
|
12548
|
+
const services = { admin, config, email, secret };
|
|
12112
12549
|
const index = {
|
|
12113
12550
|
register,
|
|
12114
12551
|
bootstrap,
|