strapi-plugin-magic-sessionmanager 3.0.2 → 3.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 +121 -1
- package/admin/src/pages/Settings.jsx +146 -0
- package/dist/_chunks/{Analytics-Bp1cPJx1.mjs → Analytics-CwyLwdOZ.mjs} +2 -2
- package/dist/_chunks/{Analytics-CjqdGXnQ.js → Analytics-DRzCKaDF.js} +2 -2
- package/dist/_chunks/{App-DONYhluL.mjs → App-Zhs_vt59.mjs} +2 -2
- package/dist/_chunks/{App-DtfZMVae.js → App-nGu2Eb87.js} +2 -2
- package/dist/_chunks/{License-lcVK7rtT.mjs → License-CPI0p_W8.mjs} +1 -1
- package/dist/_chunks/{License-IWH6ClOx.js → License-k5vvhgKr.js} +1 -1
- package/dist/_chunks/{Settings-JaiqQ_7r.mjs → Settings-CL2im8M3.mjs} +154 -6
- package/dist/_chunks/{Settings-JixgQiB_.js → Settings-Lkmxisuv.js} +152 -4
- package/dist/_chunks/{index--JzOiQNw.mjs → index-B-0VPfeF.mjs} +4 -4
- package/dist/_chunks/{index-DqtQaEBL.js → index-W_QbTAYU.js} +4 -4
- package/dist/_chunks/{useLicense-DA-averf.js → useLicense-C_Rneohy.js} +1 -1
- package/dist/_chunks/{useLicense-W1cxUaca.mjs → useLicense-DUGjNbQ9.mjs} +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +103 -14
- package/dist/server/index.mjs +103 -14
- package/package.json +1 -1
- package/server/src/bootstrap.js +18 -7
- package/server/src/content-types/session/schema.json +5 -0
- package/server/src/controllers/session.js +17 -5
- package/server/src/services/session.js +13 -2
- package/server/src/utils/encryption.js +121 -0
|
@@ -6,16 +6,16 @@ const designSystem = require("@strapi/design-system");
|
|
|
6
6
|
const admin = require("@strapi/strapi/admin");
|
|
7
7
|
const icons = require("@strapi/icons");
|
|
8
8
|
const styled = require("styled-components");
|
|
9
|
-
const index = require("./index-
|
|
10
|
-
const useLicense = require("./useLicense-
|
|
9
|
+
const index = require("./index-W_QbTAYU.js");
|
|
10
|
+
const useLicense = require("./useLicense-C_Rneohy.js");
|
|
11
11
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
12
12
|
const styled__default = /* @__PURE__ */ _interopDefault(styled);
|
|
13
13
|
const theme = {
|
|
14
14
|
colors: {
|
|
15
|
-
primary: { 600: "#0284C7", 700: "#075985" },
|
|
15
|
+
primary: { 600: "#0284C7", 700: "#075985", 100: "#E0F2FE", 50: "#F0F9FF" },
|
|
16
16
|
success: { 600: "#16A34A", 700: "#15803D" },
|
|
17
17
|
danger: { 600: "#DC2626" },
|
|
18
|
-
neutral: { 200: "#E5E7EB", 400: "#9CA3AF", 700: "#374151" }
|
|
18
|
+
neutral: { 0: "#FFFFFF", 200: "#E5E7EB", 400: "#9CA3AF", 700: "#374151" }
|
|
19
19
|
},
|
|
20
20
|
shadows: { sm: "0 1px 3px rgba(0,0,0,0.1)" },
|
|
21
21
|
borderRadius: { md: "8px", lg: "12px" }
|
|
@@ -326,6 +326,16 @@ Login Details:
|
|
|
326
326
|
- VPN: {{reason.isVpn}}, Proxy: {{reason.isProxy}}`
|
|
327
327
|
}
|
|
328
328
|
});
|
|
329
|
+
const generateSecureKey = () => {
|
|
330
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?";
|
|
331
|
+
let key = "";
|
|
332
|
+
const array = new Uint8Array(32);
|
|
333
|
+
crypto.getRandomValues(array);
|
|
334
|
+
for (let i = 0; i < 32; i++) {
|
|
335
|
+
key += chars[array[i] % chars.length];
|
|
336
|
+
}
|
|
337
|
+
return key;
|
|
338
|
+
};
|
|
329
339
|
const SettingsPage = () => {
|
|
330
340
|
const { get, post, put } = admin.useFetchClient();
|
|
331
341
|
const { toggleNotification } = admin.useNotification();
|
|
@@ -335,6 +345,8 @@ const SettingsPage = () => {
|
|
|
335
345
|
const [hasChanges, setHasChanges] = react.useState(false);
|
|
336
346
|
const [cleaning, setCleaning] = react.useState(false);
|
|
337
347
|
const [activeTemplateTab, setActiveTemplateTab] = react.useState("suspiciousLogin");
|
|
348
|
+
const [encryptionKey, setEncryptionKey] = react.useState("");
|
|
349
|
+
const [showEncryptionKey, setShowEncryptionKey] = react.useState(false);
|
|
338
350
|
const [settings, setSettings] = react.useState({
|
|
339
351
|
inactivityTimeout: 15,
|
|
340
352
|
cleanupInterval: 30,
|
|
@@ -662,6 +674,142 @@ const SettingsPage = () => {
|
|
|
662
674
|
) }),
|
|
663
675
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Content, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 6, children: [
|
|
664
676
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", fontWeight: "bold", style: { marginBottom: "16px", display: "block", color: theme.colors.neutral[700] }, children: "🔒 SECURITY OPTIONS" }),
|
|
677
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
678
|
+
designSystem.Box,
|
|
679
|
+
{
|
|
680
|
+
background: "neutral0",
|
|
681
|
+
padding: 6,
|
|
682
|
+
style: {
|
|
683
|
+
borderRadius: theme.borderRadius.lg,
|
|
684
|
+
marginBottom: "32px",
|
|
685
|
+
border: `2px solid ${theme.colors.primary[100]}`,
|
|
686
|
+
background: `linear-gradient(135deg, ${theme.colors.neutral[0]} 0%, ${theme.colors.primary[50]} 100%)`
|
|
687
|
+
},
|
|
688
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 4, children: [
|
|
689
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 3, children: [
|
|
690
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.Shield, { style: { width: 24, height: 24, color: theme.colors.primary[600] } }),
|
|
691
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", fontWeight: "bold", children: "JWT Encryption Key Generator" })
|
|
692
|
+
] }),
|
|
693
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", style: { lineHeight: 1.6 }, children: "Generate a secure 32-character encryption key for JWT token storage. This key is used to encrypt tokens before saving them to the database." }),
|
|
694
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
695
|
+
designSystem.Alert,
|
|
696
|
+
{
|
|
697
|
+
variant: "default",
|
|
698
|
+
title: "Important",
|
|
699
|
+
style: { marginTop: 8 },
|
|
700
|
+
children: [
|
|
701
|
+
"Add this key to your ",
|
|
702
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { children: ".env" }),
|
|
703
|
+
" file as ",
|
|
704
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: "SESSION_ENCRYPTION_KEY" }),
|
|
705
|
+
" for production."
|
|
706
|
+
]
|
|
707
|
+
}
|
|
708
|
+
),
|
|
709
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 3, alignItems: "flex-end", children: [
|
|
710
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1 }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
711
|
+
designSystem.TextInput,
|
|
712
|
+
{
|
|
713
|
+
label: "Generated Encryption Key",
|
|
714
|
+
value: encryptionKey,
|
|
715
|
+
onChange: (e) => setEncryptionKey(e.target.value),
|
|
716
|
+
placeholder: "Click 'Generate Key' to create a secure key",
|
|
717
|
+
type: showEncryptionKey ? "text" : "password"
|
|
718
|
+
}
|
|
719
|
+
) }),
|
|
720
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
721
|
+
designSystem.Button,
|
|
722
|
+
{
|
|
723
|
+
variant: "secondary",
|
|
724
|
+
onClick: () => setShowEncryptionKey(!showEncryptionKey),
|
|
725
|
+
size: "L",
|
|
726
|
+
children: showEncryptionKey ? "Hide" : "Show"
|
|
727
|
+
}
|
|
728
|
+
)
|
|
729
|
+
] }),
|
|
730
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 3, children: [
|
|
731
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
732
|
+
designSystem.Button,
|
|
733
|
+
{
|
|
734
|
+
variant: "default",
|
|
735
|
+
startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Code, {}),
|
|
736
|
+
onClick: () => {
|
|
737
|
+
const key = generateSecureKey();
|
|
738
|
+
setEncryptionKey(key);
|
|
739
|
+
setShowEncryptionKey(true);
|
|
740
|
+
toggleNotification({
|
|
741
|
+
type: "success",
|
|
742
|
+
message: "32-character encryption key generated!"
|
|
743
|
+
});
|
|
744
|
+
},
|
|
745
|
+
size: "L",
|
|
746
|
+
children: "Generate Key"
|
|
747
|
+
}
|
|
748
|
+
),
|
|
749
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
750
|
+
designSystem.Button,
|
|
751
|
+
{
|
|
752
|
+
variant: "tertiary",
|
|
753
|
+
startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Duplicate, {}),
|
|
754
|
+
onClick: () => {
|
|
755
|
+
if (encryptionKey) {
|
|
756
|
+
navigator.clipboard.writeText(encryptionKey);
|
|
757
|
+
toggleNotification({
|
|
758
|
+
type: "success",
|
|
759
|
+
message: "Encryption key copied to clipboard!"
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
},
|
|
763
|
+
disabled: !encryptionKey,
|
|
764
|
+
size: "L",
|
|
765
|
+
children: "Copy to Clipboard"
|
|
766
|
+
}
|
|
767
|
+
),
|
|
768
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
769
|
+
designSystem.Button,
|
|
770
|
+
{
|
|
771
|
+
variant: "tertiary",
|
|
772
|
+
startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Duplicate, {}),
|
|
773
|
+
onClick: () => {
|
|
774
|
+
if (encryptionKey) {
|
|
775
|
+
const envLine = `SESSION_ENCRYPTION_KEY=${encryptionKey}`;
|
|
776
|
+
navigator.clipboard.writeText(envLine);
|
|
777
|
+
toggleNotification({
|
|
778
|
+
type: "success",
|
|
779
|
+
message: "Copied as .env format!"
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
},
|
|
783
|
+
disabled: !encryptionKey,
|
|
784
|
+
size: "L",
|
|
785
|
+
children: "Copy for .env"
|
|
786
|
+
}
|
|
787
|
+
)
|
|
788
|
+
] }),
|
|
789
|
+
encryptionKey && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
790
|
+
designSystem.Box,
|
|
791
|
+
{
|
|
792
|
+
padding: 4,
|
|
793
|
+
background: "neutral100",
|
|
794
|
+
style: {
|
|
795
|
+
borderRadius: theme.borderRadius.md,
|
|
796
|
+
border: "1px solid " + theme.colors.neutral[200],
|
|
797
|
+
fontFamily: "monospace",
|
|
798
|
+
fontSize: "12px",
|
|
799
|
+
wordBreak: "break-all"
|
|
800
|
+
},
|
|
801
|
+
children: [
|
|
802
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", style: { marginBottom: 8, display: "block" }, children: "Add to .env file:" }),
|
|
803
|
+
/* @__PURE__ */ jsxRuntime.jsxs("code", { style: { color: theme.colors.primary[700] }, children: [
|
|
804
|
+
"SESSION_ENCRYPTION_KEY=",
|
|
805
|
+
encryptionKey
|
|
806
|
+
] })
|
|
807
|
+
]
|
|
808
|
+
}
|
|
809
|
+
)
|
|
810
|
+
] })
|
|
811
|
+
}
|
|
812
|
+
),
|
|
665
813
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { background: "neutral100", padding: 5, style: { borderRadius: theme.borderRadius.md, marginBottom: "32px" }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Grid.Root, { gap: 4, children: [
|
|
666
814
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 6, s: 12, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
667
815
|
ToggleCard,
|
|
@@ -403,7 +403,7 @@ const index = {
|
|
|
403
403
|
id: `${pluginId}.plugin.name`,
|
|
404
404
|
defaultMessage: pluginPkg.strapi.displayName
|
|
405
405
|
},
|
|
406
|
-
Component: () => import("./App-
|
|
406
|
+
Component: () => import("./App-Zhs_vt59.mjs")
|
|
407
407
|
});
|
|
408
408
|
app.createSettingSection(
|
|
409
409
|
{
|
|
@@ -419,7 +419,7 @@ const index = {
|
|
|
419
419
|
},
|
|
420
420
|
id: "general",
|
|
421
421
|
to: `/settings/${pluginId}/general`,
|
|
422
|
-
Component: () => import("./Settings-
|
|
422
|
+
Component: () => import("./Settings-CL2im8M3.mjs")
|
|
423
423
|
},
|
|
424
424
|
{
|
|
425
425
|
intlLabel: {
|
|
@@ -428,7 +428,7 @@ const index = {
|
|
|
428
428
|
},
|
|
429
429
|
id: "analytics",
|
|
430
430
|
to: `/settings/${pluginId}/analytics`,
|
|
431
|
-
Component: () => import("./Analytics-
|
|
431
|
+
Component: () => import("./Analytics-CwyLwdOZ.mjs")
|
|
432
432
|
},
|
|
433
433
|
{
|
|
434
434
|
intlLabel: {
|
|
@@ -437,7 +437,7 @@ const index = {
|
|
|
437
437
|
},
|
|
438
438
|
id: "license",
|
|
439
439
|
to: `/settings/${pluginId}/license`,
|
|
440
|
-
Component: () => import("./License-
|
|
440
|
+
Component: () => import("./License-CPI0p_W8.mjs")
|
|
441
441
|
}
|
|
442
442
|
]
|
|
443
443
|
);
|
|
@@ -404,7 +404,7 @@ const index = {
|
|
|
404
404
|
id: `${pluginId}.plugin.name`,
|
|
405
405
|
defaultMessage: pluginPkg.strapi.displayName
|
|
406
406
|
},
|
|
407
|
-
Component: () => Promise.resolve().then(() => require("./App-
|
|
407
|
+
Component: () => Promise.resolve().then(() => require("./App-nGu2Eb87.js"))
|
|
408
408
|
});
|
|
409
409
|
app.createSettingSection(
|
|
410
410
|
{
|
|
@@ -420,7 +420,7 @@ const index = {
|
|
|
420
420
|
},
|
|
421
421
|
id: "general",
|
|
422
422
|
to: `/settings/${pluginId}/general`,
|
|
423
|
-
Component: () => Promise.resolve().then(() => require("./Settings-
|
|
423
|
+
Component: () => Promise.resolve().then(() => require("./Settings-Lkmxisuv.js"))
|
|
424
424
|
},
|
|
425
425
|
{
|
|
426
426
|
intlLabel: {
|
|
@@ -429,7 +429,7 @@ const index = {
|
|
|
429
429
|
},
|
|
430
430
|
id: "analytics",
|
|
431
431
|
to: `/settings/${pluginId}/analytics`,
|
|
432
|
-
Component: () => Promise.resolve().then(() => require("./Analytics-
|
|
432
|
+
Component: () => Promise.resolve().then(() => require("./Analytics-DRzCKaDF.js"))
|
|
433
433
|
},
|
|
434
434
|
{
|
|
435
435
|
intlLabel: {
|
|
@@ -438,7 +438,7 @@ const index = {
|
|
|
438
438
|
},
|
|
439
439
|
id: "license",
|
|
440
440
|
to: `/settings/${pluginId}/license`,
|
|
441
|
-
Component: () => Promise.resolve().then(() => require("./License-
|
|
441
|
+
Component: () => Promise.resolve().then(() => require("./License-k5vvhgKr.js"))
|
|
442
442
|
}
|
|
443
443
|
]
|
|
444
444
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const react = require("react");
|
|
3
3
|
const admin = require("@strapi/strapi/admin");
|
|
4
|
-
const index = require("./index-
|
|
4
|
+
const index = require("./index-W_QbTAYU.js");
|
|
5
5
|
const useLicense = () => {
|
|
6
6
|
const { get } = admin.useFetchClient();
|
|
7
7
|
const [isPremium, setIsPremium] = react.useState(false);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState, useEffect } from "react";
|
|
2
2
|
import { useFetchClient } from "@strapi/strapi/admin";
|
|
3
|
-
import { a as pluginId } from "./index
|
|
3
|
+
import { a as pluginId } from "./index-B-0VPfeF.mjs";
|
|
4
4
|
const useLicense = () => {
|
|
5
5
|
const { get } = useFetchClient();
|
|
6
6
|
const [isPremium, setIsPremium] = useState(false);
|
package/dist/admin/index.js
CHANGED
package/dist/admin/index.mjs
CHANGED
package/dist/server/index.js
CHANGED
|
@@ -87,6 +87,68 @@ const isPrivateIp = (ip) => {
|
|
|
87
87
|
return false;
|
|
88
88
|
};
|
|
89
89
|
var getClientIp_1 = getClientIp$1;
|
|
90
|
+
const crypto$1 = require$$0__default.default;
|
|
91
|
+
const ALGORITHM = "aes-256-gcm";
|
|
92
|
+
const IV_LENGTH = 16;
|
|
93
|
+
function getEncryptionKey() {
|
|
94
|
+
const envKey = process.env.SESSION_ENCRYPTION_KEY;
|
|
95
|
+
if (envKey) {
|
|
96
|
+
const key2 = crypto$1.createHash("sha256").update(envKey).digest();
|
|
97
|
+
return key2;
|
|
98
|
+
}
|
|
99
|
+
const strapiKeys = process.env.APP_KEYS || process.env.API_TOKEN_SALT || "default-insecure-key";
|
|
100
|
+
const key = crypto$1.createHash("sha256").update(strapiKeys).digest();
|
|
101
|
+
console.warn("[magic-sessionmanager/encryption] ⚠️ No SESSION_ENCRYPTION_KEY found. Using fallback (not recommended for production).");
|
|
102
|
+
console.warn("[magic-sessionmanager/encryption] Set SESSION_ENCRYPTION_KEY in .env for better security.");
|
|
103
|
+
return key;
|
|
104
|
+
}
|
|
105
|
+
function encryptToken$2(token) {
|
|
106
|
+
if (!token) return null;
|
|
107
|
+
try {
|
|
108
|
+
const key = getEncryptionKey();
|
|
109
|
+
const iv = crypto$1.randomBytes(IV_LENGTH);
|
|
110
|
+
const cipher = crypto$1.createCipheriv(ALGORITHM, key, iv);
|
|
111
|
+
let encrypted = cipher.update(token, "utf8", "hex");
|
|
112
|
+
encrypted += cipher.final("hex");
|
|
113
|
+
const authTag = cipher.getAuthTag();
|
|
114
|
+
return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`;
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.error("[magic-sessionmanager/encryption] Encryption failed:", err);
|
|
117
|
+
throw new Error("Failed to encrypt token");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function decryptToken$3(encryptedToken) {
|
|
121
|
+
if (!encryptedToken) return null;
|
|
122
|
+
try {
|
|
123
|
+
const key = getEncryptionKey();
|
|
124
|
+
const parts = encryptedToken.split(":");
|
|
125
|
+
if (parts.length !== 3) {
|
|
126
|
+
throw new Error("Invalid encrypted token format");
|
|
127
|
+
}
|
|
128
|
+
const iv = Buffer.from(parts[0], "hex");
|
|
129
|
+
const authTag = Buffer.from(parts[1], "hex");
|
|
130
|
+
const encrypted = parts[2];
|
|
131
|
+
const decipher = crypto$1.createDecipheriv(ALGORITHM, key, iv);
|
|
132
|
+
decipher.setAuthTag(authTag);
|
|
133
|
+
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
134
|
+
decrypted += decipher.final("utf8");
|
|
135
|
+
return decrypted;
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error("[magic-sessionmanager/encryption] Decryption failed:", err);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function generateSessionId$1(userId) {
|
|
142
|
+
const timestamp = Date.now().toString(36);
|
|
143
|
+
const randomBytes = crypto$1.randomBytes(8).toString("hex");
|
|
144
|
+
const userHash = crypto$1.createHash("sha256").update(userId.toString()).digest("hex").substring(0, 8);
|
|
145
|
+
return `sess_${timestamp}_${userHash}_${randomBytes}`;
|
|
146
|
+
}
|
|
147
|
+
var encryption = {
|
|
148
|
+
encryptToken: encryptToken$2,
|
|
149
|
+
decryptToken: decryptToken$3,
|
|
150
|
+
generateSessionId: generateSessionId$1
|
|
151
|
+
};
|
|
90
152
|
var lastSeen = ({ strapi: strapi2, sessionService }) => {
|
|
91
153
|
return async (ctx, next) => {
|
|
92
154
|
if (ctx.state.user && ctx.state.user.id) {
|
|
@@ -123,6 +185,7 @@ var lastSeen = ({ strapi: strapi2, sessionService }) => {
|
|
|
123
185
|
};
|
|
124
186
|
};
|
|
125
187
|
const getClientIp = getClientIp_1;
|
|
188
|
+
const { encryptToken: encryptToken$1, decryptToken: decryptToken$2 } = encryption;
|
|
126
189
|
var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
127
190
|
strapi2.log.info("[magic-sessionmanager] 🚀 Bootstrap starting...");
|
|
128
191
|
try {
|
|
@@ -190,16 +253,23 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
190
253
|
ctx.body = { message: "Logged out successfully" };
|
|
191
254
|
return;
|
|
192
255
|
}
|
|
193
|
-
const
|
|
256
|
+
const allSessions = await strapi2.entityService.findMany("plugin::magic-sessionmanager.session", {
|
|
194
257
|
filters: {
|
|
195
|
-
token,
|
|
196
258
|
isActive: true
|
|
197
|
-
}
|
|
198
|
-
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
const matchingSession = allSessions.find((session2) => {
|
|
262
|
+
if (!session2.token) return false;
|
|
263
|
+
try {
|
|
264
|
+
const decrypted = decryptToken$2(session2.token);
|
|
265
|
+
return decrypted === token;
|
|
266
|
+
} catch (err) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
199
269
|
});
|
|
200
|
-
if (
|
|
201
|
-
await sessionService.terminateSession({ sessionId:
|
|
202
|
-
strapi2.log.info(`[magic-sessionmanager] 🚪 Logout via /api/auth/logout - Session ${
|
|
270
|
+
if (matchingSession) {
|
|
271
|
+
await sessionService.terminateSession({ sessionId: matchingSession.id });
|
|
272
|
+
strapi2.log.info(`[magic-sessionmanager] 🚪 Logout via /api/auth/logout - Session ${matchingSession.id} terminated`);
|
|
203
273
|
}
|
|
204
274
|
ctx.status = 200;
|
|
205
275
|
ctx.body = { message: "Logged out successfully" };
|
|
@@ -387,6 +457,11 @@ const pluginOptions = {
|
|
|
387
457
|
}
|
|
388
458
|
};
|
|
389
459
|
const attributes = {
|
|
460
|
+
sessionId: {
|
|
461
|
+
type: "string",
|
|
462
|
+
unique: true,
|
|
463
|
+
required: true
|
|
464
|
+
},
|
|
390
465
|
user: {
|
|
391
466
|
type: "relation",
|
|
392
467
|
relation: "manyToOne",
|
|
@@ -643,6 +718,7 @@ var routes$1 = {
|
|
|
643
718
|
admin,
|
|
644
719
|
"content-api": contentApi
|
|
645
720
|
};
|
|
721
|
+
const { decryptToken: decryptToken$1 } = encryption;
|
|
646
722
|
var session$3 = {
|
|
647
723
|
/**
|
|
648
724
|
* Get ALL sessions (active + inactive) - Admin only
|
|
@@ -722,13 +798,21 @@ var session$3 = {
|
|
|
722
798
|
const sessions = await strapi.entityService.findMany("plugin::magic-sessionmanager.session", {
|
|
723
799
|
filters: {
|
|
724
800
|
user: { id: userId },
|
|
725
|
-
token,
|
|
726
801
|
isActive: true
|
|
727
802
|
}
|
|
728
803
|
});
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
804
|
+
const matchingSession = sessions.find((session2) => {
|
|
805
|
+
if (!session2.token) return false;
|
|
806
|
+
try {
|
|
807
|
+
const decrypted = decryptToken$1(session2.token);
|
|
808
|
+
return decrypted === token;
|
|
809
|
+
} catch (err) {
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
if (matchingSession) {
|
|
814
|
+
await sessionService.terminateSession({ sessionId: matchingSession.id });
|
|
815
|
+
strapi.log.info(`[magic-sessionmanager] User ${userId} logged out (session ${matchingSession.id})`);
|
|
732
816
|
}
|
|
733
817
|
ctx.body = {
|
|
734
818
|
message: "Logged out successfully"
|
|
@@ -1227,6 +1311,7 @@ var controllers$1 = {
|
|
|
1227
1311
|
license,
|
|
1228
1312
|
settings
|
|
1229
1313
|
};
|
|
1314
|
+
const { encryptToken, decryptToken, generateSessionId } = encryption;
|
|
1230
1315
|
var session$1 = ({ strapi: strapi2 }) => ({
|
|
1231
1316
|
/**
|
|
1232
1317
|
* Create a new session record
|
|
@@ -1236,6 +1321,8 @@ var session$1 = ({ strapi: strapi2 }) => ({
|
|
|
1236
1321
|
async createSession({ userId, ip = "unknown", userAgent = "unknown", token }) {
|
|
1237
1322
|
try {
|
|
1238
1323
|
const now = /* @__PURE__ */ new Date();
|
|
1324
|
+
const sessionId = generateSessionId(userId);
|
|
1325
|
+
const encryptedToken = token ? encryptToken(token) : null;
|
|
1239
1326
|
const session2 = await strapi2.entityService.create("plugin::magic-sessionmanager.session", {
|
|
1240
1327
|
data: {
|
|
1241
1328
|
user: userId,
|
|
@@ -1244,11 +1331,13 @@ var session$1 = ({ strapi: strapi2 }) => ({
|
|
|
1244
1331
|
loginTime: now,
|
|
1245
1332
|
lastActive: now,
|
|
1246
1333
|
isActive: true,
|
|
1247
|
-
token
|
|
1248
|
-
//
|
|
1334
|
+
token: encryptedToken,
|
|
1335
|
+
// ✅ Encrypted JWT for security
|
|
1336
|
+
sessionId
|
|
1337
|
+
// ✅ Unique identifier
|
|
1249
1338
|
}
|
|
1250
1339
|
});
|
|
1251
|
-
strapi2.log.info(`[magic-sessionmanager] ✅ Session ${session2.id} created for user ${userId}`);
|
|
1340
|
+
strapi2.log.info(`[magic-sessionmanager] ✅ Session ${session2.id} (${sessionId}) created for user ${userId}`);
|
|
1252
1341
|
return session2;
|
|
1253
1342
|
} catch (err) {
|
|
1254
1343
|
strapi2.log.error("[magic-sessionmanager] Error creating session:", err);
|
package/dist/server/index.mjs
CHANGED
|
@@ -83,6 +83,68 @@ const isPrivateIp = (ip) => {
|
|
|
83
83
|
return false;
|
|
84
84
|
};
|
|
85
85
|
var getClientIp_1 = getClientIp$1;
|
|
86
|
+
const crypto$1 = require$$0$1;
|
|
87
|
+
const ALGORITHM = "aes-256-gcm";
|
|
88
|
+
const IV_LENGTH = 16;
|
|
89
|
+
function getEncryptionKey() {
|
|
90
|
+
const envKey = process.env.SESSION_ENCRYPTION_KEY;
|
|
91
|
+
if (envKey) {
|
|
92
|
+
const key2 = crypto$1.createHash("sha256").update(envKey).digest();
|
|
93
|
+
return key2;
|
|
94
|
+
}
|
|
95
|
+
const strapiKeys = process.env.APP_KEYS || process.env.API_TOKEN_SALT || "default-insecure-key";
|
|
96
|
+
const key = crypto$1.createHash("sha256").update(strapiKeys).digest();
|
|
97
|
+
console.warn("[magic-sessionmanager/encryption] ⚠️ No SESSION_ENCRYPTION_KEY found. Using fallback (not recommended for production).");
|
|
98
|
+
console.warn("[magic-sessionmanager/encryption] Set SESSION_ENCRYPTION_KEY in .env for better security.");
|
|
99
|
+
return key;
|
|
100
|
+
}
|
|
101
|
+
function encryptToken$2(token) {
|
|
102
|
+
if (!token) return null;
|
|
103
|
+
try {
|
|
104
|
+
const key = getEncryptionKey();
|
|
105
|
+
const iv = crypto$1.randomBytes(IV_LENGTH);
|
|
106
|
+
const cipher = crypto$1.createCipheriv(ALGORITHM, key, iv);
|
|
107
|
+
let encrypted = cipher.update(token, "utf8", "hex");
|
|
108
|
+
encrypted += cipher.final("hex");
|
|
109
|
+
const authTag = cipher.getAuthTag();
|
|
110
|
+
return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`;
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.error("[magic-sessionmanager/encryption] Encryption failed:", err);
|
|
113
|
+
throw new Error("Failed to encrypt token");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function decryptToken$3(encryptedToken) {
|
|
117
|
+
if (!encryptedToken) return null;
|
|
118
|
+
try {
|
|
119
|
+
const key = getEncryptionKey();
|
|
120
|
+
const parts = encryptedToken.split(":");
|
|
121
|
+
if (parts.length !== 3) {
|
|
122
|
+
throw new Error("Invalid encrypted token format");
|
|
123
|
+
}
|
|
124
|
+
const iv = Buffer.from(parts[0], "hex");
|
|
125
|
+
const authTag = Buffer.from(parts[1], "hex");
|
|
126
|
+
const encrypted = parts[2];
|
|
127
|
+
const decipher = crypto$1.createDecipheriv(ALGORITHM, key, iv);
|
|
128
|
+
decipher.setAuthTag(authTag);
|
|
129
|
+
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
130
|
+
decrypted += decipher.final("utf8");
|
|
131
|
+
return decrypted;
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.error("[magic-sessionmanager/encryption] Decryption failed:", err);
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function generateSessionId$1(userId) {
|
|
138
|
+
const timestamp = Date.now().toString(36);
|
|
139
|
+
const randomBytes = crypto$1.randomBytes(8).toString("hex");
|
|
140
|
+
const userHash = crypto$1.createHash("sha256").update(userId.toString()).digest("hex").substring(0, 8);
|
|
141
|
+
return `sess_${timestamp}_${userHash}_${randomBytes}`;
|
|
142
|
+
}
|
|
143
|
+
var encryption = {
|
|
144
|
+
encryptToken: encryptToken$2,
|
|
145
|
+
decryptToken: decryptToken$3,
|
|
146
|
+
generateSessionId: generateSessionId$1
|
|
147
|
+
};
|
|
86
148
|
var lastSeen = ({ strapi: strapi2, sessionService }) => {
|
|
87
149
|
return async (ctx, next) => {
|
|
88
150
|
if (ctx.state.user && ctx.state.user.id) {
|
|
@@ -119,6 +181,7 @@ var lastSeen = ({ strapi: strapi2, sessionService }) => {
|
|
|
119
181
|
};
|
|
120
182
|
};
|
|
121
183
|
const getClientIp = getClientIp_1;
|
|
184
|
+
const { encryptToken: encryptToken$1, decryptToken: decryptToken$2 } = encryption;
|
|
122
185
|
var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
123
186
|
strapi2.log.info("[magic-sessionmanager] 🚀 Bootstrap starting...");
|
|
124
187
|
try {
|
|
@@ -186,16 +249,23 @@ var bootstrap$1 = async ({ strapi: strapi2 }) => {
|
|
|
186
249
|
ctx.body = { message: "Logged out successfully" };
|
|
187
250
|
return;
|
|
188
251
|
}
|
|
189
|
-
const
|
|
252
|
+
const allSessions = await strapi2.entityService.findMany("plugin::magic-sessionmanager.session", {
|
|
190
253
|
filters: {
|
|
191
|
-
token,
|
|
192
254
|
isActive: true
|
|
193
|
-
}
|
|
194
|
-
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
const matchingSession = allSessions.find((session2) => {
|
|
258
|
+
if (!session2.token) return false;
|
|
259
|
+
try {
|
|
260
|
+
const decrypted = decryptToken$2(session2.token);
|
|
261
|
+
return decrypted === token;
|
|
262
|
+
} catch (err) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
195
265
|
});
|
|
196
|
-
if (
|
|
197
|
-
await sessionService.terminateSession({ sessionId:
|
|
198
|
-
strapi2.log.info(`[magic-sessionmanager] 🚪 Logout via /api/auth/logout - Session ${
|
|
266
|
+
if (matchingSession) {
|
|
267
|
+
await sessionService.terminateSession({ sessionId: matchingSession.id });
|
|
268
|
+
strapi2.log.info(`[magic-sessionmanager] 🚪 Logout via /api/auth/logout - Session ${matchingSession.id} terminated`);
|
|
199
269
|
}
|
|
200
270
|
ctx.status = 200;
|
|
201
271
|
ctx.body = { message: "Logged out successfully" };
|
|
@@ -383,6 +453,11 @@ const pluginOptions = {
|
|
|
383
453
|
}
|
|
384
454
|
};
|
|
385
455
|
const attributes = {
|
|
456
|
+
sessionId: {
|
|
457
|
+
type: "string",
|
|
458
|
+
unique: true,
|
|
459
|
+
required: true
|
|
460
|
+
},
|
|
386
461
|
user: {
|
|
387
462
|
type: "relation",
|
|
388
463
|
relation: "manyToOne",
|
|
@@ -639,6 +714,7 @@ var routes$1 = {
|
|
|
639
714
|
admin,
|
|
640
715
|
"content-api": contentApi
|
|
641
716
|
};
|
|
717
|
+
const { decryptToken: decryptToken$1 } = encryption;
|
|
642
718
|
var session$3 = {
|
|
643
719
|
/**
|
|
644
720
|
* Get ALL sessions (active + inactive) - Admin only
|
|
@@ -718,13 +794,21 @@ var session$3 = {
|
|
|
718
794
|
const sessions = await strapi.entityService.findMany("plugin::magic-sessionmanager.session", {
|
|
719
795
|
filters: {
|
|
720
796
|
user: { id: userId },
|
|
721
|
-
token,
|
|
722
797
|
isActive: true
|
|
723
798
|
}
|
|
724
799
|
});
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
800
|
+
const matchingSession = sessions.find((session2) => {
|
|
801
|
+
if (!session2.token) return false;
|
|
802
|
+
try {
|
|
803
|
+
const decrypted = decryptToken$1(session2.token);
|
|
804
|
+
return decrypted === token;
|
|
805
|
+
} catch (err) {
|
|
806
|
+
return false;
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
if (matchingSession) {
|
|
810
|
+
await sessionService.terminateSession({ sessionId: matchingSession.id });
|
|
811
|
+
strapi.log.info(`[magic-sessionmanager] User ${userId} logged out (session ${matchingSession.id})`);
|
|
728
812
|
}
|
|
729
813
|
ctx.body = {
|
|
730
814
|
message: "Logged out successfully"
|
|
@@ -1223,6 +1307,7 @@ var controllers$1 = {
|
|
|
1223
1307
|
license,
|
|
1224
1308
|
settings
|
|
1225
1309
|
};
|
|
1310
|
+
const { encryptToken, decryptToken, generateSessionId } = encryption;
|
|
1226
1311
|
var session$1 = ({ strapi: strapi2 }) => ({
|
|
1227
1312
|
/**
|
|
1228
1313
|
* Create a new session record
|
|
@@ -1232,6 +1317,8 @@ var session$1 = ({ strapi: strapi2 }) => ({
|
|
|
1232
1317
|
async createSession({ userId, ip = "unknown", userAgent = "unknown", token }) {
|
|
1233
1318
|
try {
|
|
1234
1319
|
const now = /* @__PURE__ */ new Date();
|
|
1320
|
+
const sessionId = generateSessionId(userId);
|
|
1321
|
+
const encryptedToken = token ? encryptToken(token) : null;
|
|
1235
1322
|
const session2 = await strapi2.entityService.create("plugin::magic-sessionmanager.session", {
|
|
1236
1323
|
data: {
|
|
1237
1324
|
user: userId,
|
|
@@ -1240,11 +1327,13 @@ var session$1 = ({ strapi: strapi2 }) => ({
|
|
|
1240
1327
|
loginTime: now,
|
|
1241
1328
|
lastActive: now,
|
|
1242
1329
|
isActive: true,
|
|
1243
|
-
token
|
|
1244
|
-
//
|
|
1330
|
+
token: encryptedToken,
|
|
1331
|
+
// ✅ Encrypted JWT for security
|
|
1332
|
+
sessionId
|
|
1333
|
+
// ✅ Unique identifier
|
|
1245
1334
|
}
|
|
1246
1335
|
});
|
|
1247
|
-
strapi2.log.info(`[magic-sessionmanager] ✅ Session ${session2.id} created for user ${userId}`);
|
|
1336
|
+
strapi2.log.info(`[magic-sessionmanager] ✅ Session ${session2.id} (${sessionId}) created for user ${userId}`);
|
|
1248
1337
|
return session2;
|
|
1249
1338
|
} catch (err) {
|
|
1250
1339
|
strapi2.log.error("[magic-sessionmanager] Error creating session:", err);
|
package/package.json
CHANGED