strapi-security-suite 0.1.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 ADDED
@@ -0,0 +1,166 @@
1
+ # 🛡️ Strapi Security Suite (Beta)
2
+
3
+ ## **The Last Plugin You’ll Ever Need to Sleep at Night**
4
+
5
+ A high-performance, in-memory security enhancement plugin for **Strapi v5**, Session-obsessed. Built for the **chaotic genius admin** who refuses to get breached by a stale token.\
6
+ Powered by **rage, memory maps, and accountability.**
7
+
8
+ ---
9
+
10
+ ## ✨ Why This Exists
11
+
12
+ Because “just trusting sessions” is how *breaches happen*.\
13
+ Because the admin panel deserves better.\
14
+ Because your team deserves **a real security layer**, not a checkbox.
15
+
16
+ ---
17
+
18
+ ## ⚔️ Features That Slap
19
+
20
+ ### 🔒 Auto Logout (with taste)
21
+
22
+ Kick idle admins like it’s office closing time.
23
+
24
+ - 🔍 Tracks every request
25
+ - ⏲️ Custom inactivity timeout from DB
26
+ - 🧠 Memory-first with `sessionActivityMap`
27
+ - 💨 Triggers soft or *nuclear* logout depending on your vibe
28
+ - 💾 Graceful 440s, JS responses, and gentle redirects
29
+
30
+ ### 🚷 Multi-Session Lock
31
+
32
+ One admin = one session. No shadow clones allowed.
33
+
34
+ - 💥 First login wins, others are denied
35
+ - 🧹 Cleans old sessions like a digital janitor
36
+
37
+ ### 🧄 Session Exorcism Layer™
38
+
39
+ Revoked tokens get ghosted *instantly*.\
40
+ Even if Strapi tries to pretend they’re still cute.
41
+
42
+ - 🔪 Middleware blocks
43
+ - 🪦 Session cookie wipeout
44
+ - 📩 Headers set for frontend rejections
45
+ - 🗑️ `isLoggedIn` purged with prejudice
46
+
47
+ ### 🧠 Smart Middleware Stack
48
+
49
+ - `trackActivity`: Updates timestamps on every move
50
+ - `rejectRevokedTokens`: Blocks dead sessions like a haunted firewall
51
+ - `interceptRenewToken`: Stops Strapi’s clingy `/renew-token` requests from reviving zombies
52
+
53
+ ---
54
+
55
+ ## 🧪 Configuration Schema
56
+
57
+ ```json
58
+ {
59
+ "autoLogoutTime": 30,
60
+ "multipleSessionsControl": true,
61
+ "passwordExpiryDays": 30,
62
+ "nonReusablePassword": true,
63
+ "enablePasswordManagement": true
64
+ }
65
+ ```
66
+
67
+ Defined in the content-type:\
68
+ `plugin::strapi-security-suite.security_settings`
69
+
70
+ ---
71
+
72
+ ## 🧠 Architecture You’ll Brag About
73
+
74
+ - 🧬 In-memory tracking via `Map()`
75
+ - ⏱️ `startAutoLogoutWatcher()` with 5s intervals
76
+ - 🔄 Frontend fetch interceptor for 440s
77
+ - 🧹 JS logout payload injected server-side to destroy sessions, cookies, and self-respect
78
+
79
+ ---
80
+
81
+ ## ⚙️ Admin Panel UI
82
+
83
+ - 🎛️ Control timeouts, session logic, and password rules
84
+ - 📜 Planned audit logs, charts, and drama
85
+ - 🌌 Future dashboard: all your infra sins visualized
86
+
87
+ ---
88
+
89
+ ## 🔐 Frontend Catch Logic
90
+
91
+ - Fetch wrapper intercepts `440`
92
+ - Purges local/session storage
93
+ - Sends you crying to `/session-expired`
94
+ - Optionally calls `/admin/logout` for drama
95
+
96
+ ---
97
+
98
+ ## 📦 Installation
99
+
100
+ ```bash
101
+ yarn add strapi-security-suite
102
+ ```
103
+ or
104
+ ```bash
105
+ npm install strapi-security-suite
106
+ ```
107
+
108
+ ### 🔹 `config/plugins.js`
109
+ Add the following entry inside your `config/plugins.js` file:
110
+
111
+ ```javascript
112
+ module.exports = ({ env }) => ({
113
+ 'strapi-security-suite': {
114
+ enabled: true,
115
+ },
116
+ });
117
+ ```
118
+
119
+ ---
120
+
121
+ ## 🔮 Upcoming
122
+
123
+ | Feature | Status |
124
+ | ------------------------------- | -------------- |
125
+ | Password Expiry | 🛠️ In Dev |
126
+ | Non-Reusable Passwords | 🛠️ In Dev |
127
+ | Admin Activity Logs | 🔜 |
128
+ | Security Dashboard | 🔜 |
129
+ | Brute Force Detection | 🔜 |
130
+ | Real-time Session Visualization | 🔜 (and spicy) |
131
+
132
+ ---
133
+
134
+ ## 💥 Real-World Impact
135
+
136
+ > “We installed this and now our interns can’t share logins anymore.”\
137
+ > — CTO, probably
138
+
139
+ > “Our admin panel feels like it judges us now. I love it.”\
140
+ > — That one developer who cares
141
+
142
+ ---
143
+
144
+ ## 🧑‍💻 Author
145
+
146
+ [LPIX-11](mohamed.johnson@orange-sonatel.com)
147
+
148
+ ---
149
+
150
+ ## 💡 Philosophy
151
+
152
+ Security should be:
153
+
154
+ - Fast
155
+ - Unforgiving
156
+ - Elegant
157
+ - **Mildly judgmental**
158
+
159
+ ---
160
+
161
+ ## ⚠️ Legal Drama
162
+
163
+ > This plugin is in **Beta**.\
164
+ > You break it, it breaks you back, but we’ll still love you.\
165
+ > Not liable for insecure vibes.
166
+
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const admin = require("@strapi/strapi/admin");
5
+ const reactRouterDom = require("react-router-dom");
6
+ const react = require("react");
7
+ const designSystem = require("@strapi/design-system");
8
+ function IsLoggedInDebugger() {
9
+ const [value, setValue] = react.useState(localStorage.getItem("isLoggedIn"));
10
+ react.useEffect(() => {
11
+ const originalSetItem = Storage.prototype.setItem;
12
+ Storage.prototype.setItem = function(key, val) {
13
+ if (key === "isLoggedIn") {
14
+ console.log(`🕵️ isLoggedIn changed to:`, val);
15
+ setValue(val);
16
+ }
17
+ return originalSetItem.apply(this, arguments);
18
+ };
19
+ const interval = setInterval(() => {
20
+ const current = localStorage.getItem("isLoggedIn");
21
+ setValue(current);
22
+ }, 1e3);
23
+ return () => {
24
+ clearInterval(interval);
25
+ Storage.prototype.setItem = originalSetItem;
26
+ };
27
+ }, []);
28
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
29
+ position: "fixed",
30
+ bottom: "10px",
31
+ right: "10px",
32
+ backgroundColor: "rgba(0,0,0,0.7)",
33
+ color: "lime",
34
+ padding: "10px",
35
+ borderRadius: "10px",
36
+ fontSize: "14px",
37
+ zIndex: 9999,
38
+ fontFamily: "monospace"
39
+ }, children: [
40
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: 'localStorage["isLoggedIn"]:' }),
41
+ " ",
42
+ String(value)
43
+ ] });
44
+ }
45
+ const HomePage = () => {
46
+ const [config, setConfig] = react.useState({
47
+ autoLogoutTime: 30,
48
+ multipleSessionsControl: false,
49
+ passwordExpiryDays: 365,
50
+ nonReusablePassword: false,
51
+ enablePasswordManagement: false
52
+ });
53
+ const [success, setSuccess] = react.useState(false);
54
+ react.useEffect(() => {
55
+ const fetchConfig = async () => {
56
+ try {
57
+ const res = await fetch("/strapi-security-suite/admin/settings");
58
+ const json = await res.json();
59
+ setConfig(json);
60
+ } catch (e) {
61
+ console.error("❌ Failed to fetch config", e);
62
+ }
63
+ };
64
+ fetchConfig();
65
+ }, []);
66
+ const handleChange = (key, value) => {
67
+ setConfig((prev) => ({ ...prev, [key]: value }));
68
+ };
69
+ const saveConfig = async () => {
70
+ try {
71
+ const res = await fetch("/strapi-security-suite/admin/settings", {
72
+ method: "POST",
73
+ headers: {
74
+ "Content-Type": "application/json"
75
+ },
76
+ body: JSON.stringify(config)
77
+ });
78
+ if (res.ok) {
79
+ setSuccess(true);
80
+ setTimeout(() => setSuccess(false), 3e3);
81
+ }
82
+ } catch (err) {
83
+ console.error("❌ Save failed:", err);
84
+ }
85
+ };
86
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 10, background: "neutral0", shadow: "filterShadow", borderRadius: "12px", children: [
87
+ /* @__PURE__ */ jsxRuntime.jsx(IsLoggedInDebugger, {}),
88
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", as: "h1", fontWeight: "bold", children: "Security & Session Settings" }),
89
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "epsilon", textColor: "neutral600", paddingTop: 2, paddingBottom: 4, children: "Configure session duration, password policies, and security settings." }),
90
+ success && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Alert, { title: "Success", variant: "success", marginBottom: 4, children: "Configuration saved successfully!" }),
91
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "flex-start", justifyContent: "space-between", gap: 6, paddingTop: 6, wrap: "wrap", children: [
92
+ /* @__PURE__ */ jsxRuntime.jsxs(
93
+ designSystem.Box,
94
+ {
95
+ flex: "1",
96
+ minWidth: "400px",
97
+ padding: 6,
98
+ background: "neutral100",
99
+ borderRadius: "8px",
100
+ shadow: "tableShadow",
101
+ children: [
102
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", fontWeight: "semiBold", children: "Session Management" }),
103
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, { marginTop: 2, marginBottom: 4 }),
104
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-star", gap: 4, children: [
105
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "row", gap: 4, children: [
106
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: "Auto Logout Time (minutes)" }),
107
+ /* @__PURE__ */ jsxRuntime.jsx(
108
+ designSystem.NumberInput,
109
+ {
110
+ minValue: 1,
111
+ value: config.autoLogoutTime,
112
+ onValueChange: (value) => handleChange("autoLogoutTime", value)
113
+ }
114
+ )
115
+ ] }),
116
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, children: [
117
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: "Activate multiple sessions control?" }),
118
+ /* @__PURE__ */ jsxRuntime.jsx(
119
+ designSystem.Switch,
120
+ {
121
+ checked: config.multipleSessionsControl,
122
+ onCheckedChange: () => handleChange("multipleSessionsControl", !config.multipleSessionsControl)
123
+ }
124
+ )
125
+ ] })
126
+ ] })
127
+ ]
128
+ }
129
+ ),
130
+ /* @__PURE__ */ jsxRuntime.jsxs(
131
+ designSystem.Box,
132
+ {
133
+ flex: "1",
134
+ minWidth: "400px",
135
+ minHeight: "173px",
136
+ padding: 6,
137
+ background: "neutral100",
138
+ borderRadius: "8px",
139
+ shadow: "tableShadow",
140
+ children: [
141
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", fontWeight: "semiBold", children: "Password Management" }),
142
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, { marginTop: 2, marginBottom: 4 }),
143
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "flex-start", direction: "column", gap: 4, children: [
144
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, children: [
145
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: "Activate Password Control?" }),
146
+ /* @__PURE__ */ jsxRuntime.jsx(
147
+ designSystem.Switch,
148
+ {
149
+ checked: config.enablePasswordManagement,
150
+ onCheckedChange: () => handleChange("enablePasswordManagement", !config.enablePasswordManagement)
151
+ }
152
+ )
153
+ ] }),
154
+ config.enablePasswordManagement && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "flex-start", direction: "column", gap: 5, children: [
155
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "flex-start", direction: "column", gap: 3, children: [
156
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: "Password Expiry (days)?" }),
157
+ /* @__PURE__ */ jsxRuntime.jsx(
158
+ designSystem.NumberInput,
159
+ {
160
+ minValue: 1,
161
+ value: config.passwordExpiryDays,
162
+ onValueChange: (value) => handleChange("passwordExpiryDays", value)
163
+ }
164
+ )
165
+ ] }),
166
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 3, children: [
167
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: "Activate Non-Reusable Password?" }),
168
+ /* @__PURE__ */ jsxRuntime.jsx(
169
+ designSystem.Switch,
170
+ {
171
+ checked: config.nonReusablePassword,
172
+ onCheckedChange: () => handleChange("nonReusablePassword", !config.nonReusablePassword)
173
+ }
174
+ )
175
+ ] })
176
+ ] })
177
+ ] })
178
+ ]
179
+ }
180
+ )
181
+ ] }),
182
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "flex-end", paddingTop: 6, paddingBottom: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: saveConfig, variant: "secondary", size: "L", shadow: "buttonShadow", children: "Save Settings" }) })
183
+ ] });
184
+ };
185
+ const App = () => {
186
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Routes, { children: [
187
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { index: true, element: /* @__PURE__ */ jsxRuntime.jsx(HomePage, {}) }),
188
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "*", element: /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Error, {}) })
189
+ ] });
190
+ };
191
+ exports.App = App;
@@ -0,0 +1,191 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { Page } from "@strapi/strapi/admin";
3
+ import { Routes, Route } from "react-router-dom";
4
+ import { useState, useEffect } from "react";
5
+ import { Box, Typography, Alert, Flex, Divider, NumberInput, Switch, Button } from "@strapi/design-system";
6
+ function IsLoggedInDebugger() {
7
+ const [value, setValue] = useState(localStorage.getItem("isLoggedIn"));
8
+ useEffect(() => {
9
+ const originalSetItem = Storage.prototype.setItem;
10
+ Storage.prototype.setItem = function(key, val) {
11
+ if (key === "isLoggedIn") {
12
+ console.log(`🕵️ isLoggedIn changed to:`, val);
13
+ setValue(val);
14
+ }
15
+ return originalSetItem.apply(this, arguments);
16
+ };
17
+ const interval = setInterval(() => {
18
+ const current = localStorage.getItem("isLoggedIn");
19
+ setValue(current);
20
+ }, 1e3);
21
+ return () => {
22
+ clearInterval(interval);
23
+ Storage.prototype.setItem = originalSetItem;
24
+ };
25
+ }, []);
26
+ return /* @__PURE__ */ jsxs("div", { style: {
27
+ position: "fixed",
28
+ bottom: "10px",
29
+ right: "10px",
30
+ backgroundColor: "rgba(0,0,0,0.7)",
31
+ color: "lime",
32
+ padding: "10px",
33
+ borderRadius: "10px",
34
+ fontSize: "14px",
35
+ zIndex: 9999,
36
+ fontFamily: "monospace"
37
+ }, children: [
38
+ /* @__PURE__ */ jsx("strong", { children: 'localStorage["isLoggedIn"]:' }),
39
+ " ",
40
+ String(value)
41
+ ] });
42
+ }
43
+ const HomePage = () => {
44
+ const [config, setConfig] = useState({
45
+ autoLogoutTime: 30,
46
+ multipleSessionsControl: false,
47
+ passwordExpiryDays: 365,
48
+ nonReusablePassword: false,
49
+ enablePasswordManagement: false
50
+ });
51
+ const [success, setSuccess] = useState(false);
52
+ useEffect(() => {
53
+ const fetchConfig = async () => {
54
+ try {
55
+ const res = await fetch("/strapi-security-suite/admin/settings");
56
+ const json = await res.json();
57
+ setConfig(json);
58
+ } catch (e) {
59
+ console.error("❌ Failed to fetch config", e);
60
+ }
61
+ };
62
+ fetchConfig();
63
+ }, []);
64
+ const handleChange = (key, value) => {
65
+ setConfig((prev) => ({ ...prev, [key]: value }));
66
+ };
67
+ const saveConfig = async () => {
68
+ try {
69
+ const res = await fetch("/strapi-security-suite/admin/settings", {
70
+ method: "POST",
71
+ headers: {
72
+ "Content-Type": "application/json"
73
+ },
74
+ body: JSON.stringify(config)
75
+ });
76
+ if (res.ok) {
77
+ setSuccess(true);
78
+ setTimeout(() => setSuccess(false), 3e3);
79
+ }
80
+ } catch (err) {
81
+ console.error("❌ Save failed:", err);
82
+ }
83
+ };
84
+ return /* @__PURE__ */ jsxs(Box, { padding: 10, background: "neutral0", shadow: "filterShadow", borderRadius: "12px", children: [
85
+ /* @__PURE__ */ jsx(IsLoggedInDebugger, {}),
86
+ /* @__PURE__ */ jsx(Typography, { variant: "alpha", as: "h1", fontWeight: "bold", children: "Security & Session Settings" }),
87
+ /* @__PURE__ */ jsx(Typography, { variant: "epsilon", textColor: "neutral600", paddingTop: 2, paddingBottom: 4, children: "Configure session duration, password policies, and security settings." }),
88
+ success && /* @__PURE__ */ jsx(Alert, { title: "Success", variant: "success", marginBottom: 4, children: "Configuration saved successfully!" }),
89
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "flex-start", justifyContent: "space-between", gap: 6, paddingTop: 6, wrap: "wrap", children: [
90
+ /* @__PURE__ */ jsxs(
91
+ Box,
92
+ {
93
+ flex: "1",
94
+ minWidth: "400px",
95
+ padding: 6,
96
+ background: "neutral100",
97
+ borderRadius: "8px",
98
+ shadow: "tableShadow",
99
+ children: [
100
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "semiBold", children: "Session Management" }),
101
+ /* @__PURE__ */ jsx(Divider, { marginTop: 2, marginBottom: 4 }),
102
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-star", gap: 4, children: [
103
+ /* @__PURE__ */ jsxs(Flex, { direction: "row", gap: 4, children: [
104
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", children: "Auto Logout Time (minutes)" }),
105
+ /* @__PURE__ */ jsx(
106
+ NumberInput,
107
+ {
108
+ minValue: 1,
109
+ value: config.autoLogoutTime,
110
+ onValueChange: (value) => handleChange("autoLogoutTime", value)
111
+ }
112
+ )
113
+ ] }),
114
+ /* @__PURE__ */ jsxs(Flex, { gap: 4, children: [
115
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", children: "Activate multiple sessions control?" }),
116
+ /* @__PURE__ */ jsx(
117
+ Switch,
118
+ {
119
+ checked: config.multipleSessionsControl,
120
+ onCheckedChange: () => handleChange("multipleSessionsControl", !config.multipleSessionsControl)
121
+ }
122
+ )
123
+ ] })
124
+ ] })
125
+ ]
126
+ }
127
+ ),
128
+ /* @__PURE__ */ jsxs(
129
+ Box,
130
+ {
131
+ flex: "1",
132
+ minWidth: "400px",
133
+ minHeight: "173px",
134
+ padding: 6,
135
+ background: "neutral100",
136
+ borderRadius: "8px",
137
+ shadow: "tableShadow",
138
+ children: [
139
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "semiBold", children: "Password Management" }),
140
+ /* @__PURE__ */ jsx(Divider, { marginTop: 2, marginBottom: 4 }),
141
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "flex-start", direction: "column", gap: 4, children: [
142
+ /* @__PURE__ */ jsxs(Flex, { gap: 4, children: [
143
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", children: "Activate Password Control?" }),
144
+ /* @__PURE__ */ jsx(
145
+ Switch,
146
+ {
147
+ checked: config.enablePasswordManagement,
148
+ onCheckedChange: () => handleChange("enablePasswordManagement", !config.enablePasswordManagement)
149
+ }
150
+ )
151
+ ] }),
152
+ config.enablePasswordManagement && /* @__PURE__ */ jsxs(Flex, { alignItems: "flex-start", direction: "column", gap: 5, children: [
153
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "flex-start", direction: "column", gap: 3, children: [
154
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", children: "Password Expiry (days)?" }),
155
+ /* @__PURE__ */ jsx(
156
+ NumberInput,
157
+ {
158
+ minValue: 1,
159
+ value: config.passwordExpiryDays,
160
+ onValueChange: (value) => handleChange("passwordExpiryDays", value)
161
+ }
162
+ )
163
+ ] }),
164
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 3, children: [
165
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", children: "Activate Non-Reusable Password?" }),
166
+ /* @__PURE__ */ jsx(
167
+ Switch,
168
+ {
169
+ checked: config.nonReusablePassword,
170
+ onCheckedChange: () => handleChange("nonReusablePassword", !config.nonReusablePassword)
171
+ }
172
+ )
173
+ ] })
174
+ ] })
175
+ ] })
176
+ ]
177
+ }
178
+ )
179
+ ] }),
180
+ /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", paddingTop: 6, paddingBottom: 2, children: /* @__PURE__ */ jsx(Button, { onClick: saveConfig, variant: "secondary", size: "L", shadow: "buttonShadow", children: "Save Settings" }) })
181
+ ] });
182
+ };
183
+ const App = () => {
184
+ return /* @__PURE__ */ jsxs(Routes, { children: [
185
+ /* @__PURE__ */ jsx(Route, { index: true, element: /* @__PURE__ */ jsx(HomePage, {}) }),
186
+ /* @__PURE__ */ jsx(Route, { path: "*", element: /* @__PURE__ */ jsx(Page.Error, {}) })
187
+ ] });
188
+ };
189
+ export {
190
+ App
191
+ };
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const en = {};
4
+ exports.default = en;
@@ -0,0 +1,4 @@
1
+ const en = {};
2
+ export {
3
+ en as default
4
+ };
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ const react = require("react");
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const icons = require("@strapi/icons");
5
+ const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
6
+ const v = glob[path];
7
+ if (v) {
8
+ return typeof v === "function" ? v() : Promise.resolve(v);
9
+ }
10
+ return new Promise((_, reject) => {
11
+ (typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(
12
+ reject.bind(
13
+ null,
14
+ new Error(
15
+ "Unknown variable dynamic import: " + path + (path.split("/").length !== segs ? ". Note that variables only represent file names one level deep." : "")
16
+ )
17
+ )
18
+ );
19
+ });
20
+ };
21
+ const PLUGIN_ID = "strapi-security-suite";
22
+ const Initializer = ({ setPlugin }) => {
23
+ const ref = react.useRef(setPlugin);
24
+ react.useEffect(() => {
25
+ ref.current(PLUGIN_ID);
26
+ }, []);
27
+ return null;
28
+ };
29
+ const PluginIcon = () => /* @__PURE__ */ jsxRuntime.jsx(icons.User, {});
30
+ const autologout = () => {
31
+ console.warn("💥 Intercepted module import failure! Executing backup plan.");
32
+ localStorage.setItem("isLoggedIn", "false");
33
+ sessionStorage.clear();
34
+ document.cookie = "koa.sess=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT;";
35
+ document.cookie = "koa.sess.sig=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT;";
36
+ document.cookie = "jwtToken=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT;";
37
+ setTimeout(() => {
38
+ window.location.href = "/admin/auth/login?t=" + Date.now();
39
+ }, 50);
40
+ };
41
+ const index = {
42
+ register(app) {
43
+ app.addMenuLink({
44
+ to: `plugins/${PLUGIN_ID}`,
45
+ icon: PluginIcon,
46
+ intlLabel: {
47
+ id: `${PLUGIN_ID}.plugin.name`,
48
+ defaultMessage: PLUGIN_ID
49
+ },
50
+ Component: async () => {
51
+ const { App } = await Promise.resolve().then(() => require("../_chunks/App-_xsdnv0p.js"));
52
+ return App;
53
+ },
54
+ permissions: [
55
+ {
56
+ action: "plugin::strapi-security-suite.access",
57
+ subject: null
58
+ }
59
+ ]
60
+ });
61
+ app.registerPlugin({
62
+ id: PLUGIN_ID,
63
+ initializer: Initializer,
64
+ isReady: false,
65
+ name: PLUGIN_ID
66
+ });
67
+ const originalSetItem = Storage.prototype.setItem;
68
+ Storage.prototype.setItem = function(key, value) {
69
+ console.log(`🕵️ Intercepted localStorage.setItem:`, key, value);
70
+ return originalSetItem.apply(this, arguments);
71
+ };
72
+ if (!window.__fetchPatchedForSession) {
73
+ window.addEventListener("unhandledrejection", function(event) {
74
+ if (event.reason?.message?.includes("Importing a module script failed")) {
75
+ autologout();
76
+ }
77
+ });
78
+ window.addEventListener("error", function(event) {
79
+ const message = event.message || "";
80
+ if (message.includes("Importing a module script failed")) {
81
+ autologout();
82
+ }
83
+ });
84
+ const originalFetch = window.fetch;
85
+ window.fetch = async (...args) => {
86
+ try {
87
+ const response = await originalFetch(...args);
88
+ if ([400, 440].includes(response.status)) {
89
+ if (window.stop) window.stop();
90
+ if (document.execCommand) document.execCommand("Stop");
91
+ window.location.reload();
92
+ return;
93
+ }
94
+ return response;
95
+ } catch (err) {
96
+ console.log(err);
97
+ if (err?.message?.includes("MIME")) {
98
+ window.location.reload();
99
+ return;
100
+ }
101
+ }
102
+ };
103
+ window.__fetchPatchedForSession = true;
104
+ }
105
+ },
106
+ async registerTrads({ locales }) {
107
+ return Promise.all(
108
+ locales.map(async (locale) => {
109
+ try {
110
+ const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => Promise.resolve().then(() => require("../_chunks/en-B4KWt_jN.js")) }), `./translations/${locale}.json`, 3);
111
+ return { data, locale };
112
+ } catch {
113
+ return { data: {}, locale };
114
+ }
115
+ })
116
+ );
117
+ }
118
+ };
119
+ module.exports = index;