strapi-security-suite 0.2.3 → 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 +314 -92
- package/dist/_chunks/App-CfPg1Thn.js +162 -0
- package/dist/_chunks/App-t96Cfein.mjs +162 -0
- package/dist/_chunks/en-B0wBsCyw.mjs +6 -0
- package/dist/_chunks/{en-B4KWt_jN.js → en-BBlRv7nL.js} +3 -1
- package/dist/_chunks/index-CAEB836L.mjs +108 -0
- package/dist/_chunks/index-ub4Bl9QF.js +107 -0
- package/dist/admin/index.js +2 -84
- package/dist/admin/index.mjs +2 -84
- package/dist/server/index.js +318 -164
- package/dist/server/index.mjs +318 -164
- package/package.json +36 -24
- package/dist/_chunks/App-_xsdnv0p.js +0 -191
- package/dist/_chunks/App-h9NYFBrR.mjs +0 -191
- package/dist/_chunks/en-Byx4XI2L.mjs +0 -4
package/README.md
CHANGED
|
@@ -1,58 +1,247 @@
|
|
|
1
|
-
# 🛡️ Strapi Security Suite
|
|
1
|
+
# 🛡️ Strapi Security Suite
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
### The admin security plugin that takes your sessions _personally_.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
> **One plugin. Auto-logout. Single-session enforcement. Token revocation. Password policy.**
|
|
6
|
+
> Built for **Strapi v5**. Fueled by in-memory Maps and zero tolerance for stale tokens.
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## 🤔 What Is This?
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
Imagine a bouncer at a nightclub. But the nightclub is your **Strapi admin panel**, and the bouncer has a _perfect memory_, never sleeps, and will physically escort your idle admins out the door after 30 minutes of doing nothing.
|
|
13
|
+
|
|
14
|
+
**That's this plugin.**
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
🔐 Admin logs in
|
|
18
|
+
|
|
|
19
|
+
| 👀 Every request tracked (timestamp saved in memory)
|
|
20
|
+
|
|
|
21
|
+
| 😴 Admin goes idle...
|
|
22
|
+
|
|
|
23
|
+
| ⏰ 30 minutes pass...
|
|
24
|
+
|
|
|
25
|
+
🚪 BOOM. Logged out. Cookies cleared. Token dead.
|
|
26
|
+
No arguments. No appeals. Just security.
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## ✨ Features at a Glance
|
|
32
|
+
|
|
33
|
+
| Feature | What It Does | Vibe |
|
|
34
|
+
| -------------------------- | ---------------------------------------------- | -------------------- |
|
|
35
|
+
| ⏰ **Auto-Logout** | Kicks idle admins after configurable minutes | "Use it or lose it" |
|
|
36
|
+
| 🚫 **Single-Session Lock** | One admin = one session. Period. | "No shadow clones" |
|
|
37
|
+
| 💀 **Token Revocation** | Dead tokens stay dead. Instantly. | "Ghosts get ghosted" |
|
|
38
|
+
| 🔑 **Password Policy** | Expiry + non-reusable passwords (configurable) | "Rotate or regret" |
|
|
39
|
+
| ⚙️ **Admin UI** | Settings panel right inside Strapi | "Click, don't code" |
|
|
40
|
+
| 🛡️ **Input Validation** | Server-side validation on every settings save | "Trust nobody" |
|
|
15
41
|
|
|
16
42
|
---
|
|
17
43
|
|
|
18
|
-
##
|
|
44
|
+
## 🚀 Quick Start
|
|
45
|
+
|
|
46
|
+
### Step 1: Install it
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
yarn add strapi-security-suite
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Step 2: Enable it
|
|
53
|
+
|
|
54
|
+
Add this to your `config/plugins.js` (or `.ts`):
|
|
19
55
|
|
|
20
|
-
|
|
56
|
+
```javascript
|
|
57
|
+
module.exports = ({ env }) => ({
|
|
58
|
+
'strapi-security-suite': {
|
|
59
|
+
enabled: true,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
```
|
|
21
63
|
|
|
22
|
-
|
|
64
|
+
### Step 3: Restart Strapi
|
|
23
65
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- 💨 Triggers soft or *nuclear* logout depending on your vibe
|
|
28
|
-
- 💾 Graceful 440s, JS responses, and gentle redirects
|
|
66
|
+
```bash
|
|
67
|
+
yarn develop
|
|
68
|
+
```
|
|
29
69
|
|
|
30
|
-
###
|
|
70
|
+
### Step 4: Find it
|
|
31
71
|
|
|
32
|
-
|
|
72
|
+
Go to **Settings** → **Global** → **Security Suite**
|
|
33
73
|
|
|
34
|
-
|
|
35
|
-
- 🧹 Cleans old sessions like a digital janitor
|
|
74
|
+
That's it. You're done. Go get a coffee. ☕
|
|
36
75
|
|
|
37
|
-
|
|
76
|
+
---
|
|
38
77
|
|
|
39
|
-
|
|
40
|
-
Even if Strapi tries to pretend they’re still cute.
|
|
78
|
+
## 🖼️ The Admin Panel
|
|
41
79
|
|
|
42
|
-
|
|
43
|
-
- 🪦 Session cookie wipeout
|
|
44
|
-
- 📩 Headers set for frontend rejections
|
|
45
|
-
- 🗑️ `isLoggedIn` purged with prejudice
|
|
80
|
+
Once installed, you get a beautiful settings page with two panels:
|
|
46
81
|
|
|
47
|
-
|
|
82
|
+
```
|
|
83
|
+
┌───────────────────────────────────────────────────────┐
|
|
84
|
+
│ 🛡️ Security & Session Settings │
|
|
85
|
+
├───────────────────────────┬───────────────────────────┤
|
|
86
|
+
│ │ │
|
|
87
|
+
│ 🕐 SESSION MANAGEMENT │ 🔑 PASSWORD MANAGEMENT │
|
|
88
|
+
│ │ │
|
|
89
|
+
│ Auto Logout Time: [30] │ Password Control: [ON] │
|
|
90
|
+
│ (minutes) │ │
|
|
91
|
+
│ │ Expiry Days: [30] │
|
|
92
|
+
│ Multi-Session │ │
|
|
93
|
+
│ Control: [ON] │ Non-Reusable: [ON] │
|
|
94
|
+
│ │ │
|
|
95
|
+
├───────────────────────────┴───────────────────────────┤
|
|
96
|
+
│ [ 💾 Save Settings ] │
|
|
97
|
+
└───────────────────────────────────────────────────────┘
|
|
98
|
+
```
|
|
48
99
|
|
|
49
|
-
|
|
50
|
-
- `rejectRevokedTokens`: Blocks dead sessions like a haunted firewall
|
|
51
|
-
- `interceptRenewToken`: Stops Strapi’s clingy `/renew-token` requests from reviving zombies
|
|
100
|
+
**Everything is stored in the database.** Change a value, hit save, it takes effect immediately. No restarts. No config files. No drama.
|
|
52
101
|
|
|
53
102
|
---
|
|
54
103
|
|
|
55
|
-
##
|
|
104
|
+
## 🧠 How It Actually Works
|
|
105
|
+
|
|
106
|
+
Here's the whole flow, explained like you're five (but a very smart five):
|
|
107
|
+
|
|
108
|
+
### 🔗 The Middleware Pipeline
|
|
109
|
+
|
|
110
|
+
When any request hits your Strapi server, it passes through **5 security checkpoints** (middlewares), in this exact order:
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
🌐 Incoming Request
|
|
114
|
+
│
|
|
115
|
+
▼
|
|
116
|
+
1. 🐣 seedUserInfos
|
|
117
|
+
│ "Who are you? Let me check your JWT and load your profile"
|
|
118
|
+
│
|
|
119
|
+
▼
|
|
120
|
+
2. 🔍 interceptRenewToken
|
|
121
|
+
│ "Trying to renew your token? Let me make sure you're still welcome"
|
|
122
|
+
│
|
|
123
|
+
▼
|
|
124
|
+
3. 👣 trackActivity
|
|
125
|
+
│ "OK you're legit. I'm writing down the time. Don't be idle."
|
|
126
|
+
│
|
|
127
|
+
▼
|
|
128
|
+
4. ☠️ rejectRevokedTokens
|
|
129
|
+
│ "Wait... is your session revoked? GET OUT. Cookies deleted. Token dead."
|
|
130
|
+
│
|
|
131
|
+
▼
|
|
132
|
+
5. 🚫 preventMultipleSessions (on login only)
|
|
133
|
+
"Already logged in somewhere else? 409 Conflict. One session only."
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### ⏱️ The Auto-Logout Watcher
|
|
137
|
+
|
|
138
|
+
Running in the background, checking every 5 seconds:
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
🔄 Every 5 seconds:
|
|
142
|
+
│
|
|
143
|
+
│ 🔍 Check each active session in memory
|
|
144
|
+
│ 🕐 Compare last activity timestamp vs. configured timeout
|
|
145
|
+
│
|
|
146
|
+
│ 😴 Idle too long?
|
|
147
|
+
│ │
|
|
148
|
+
│ YES → Add email to revoked set
|
|
149
|
+
│ │ → Delete session from activity map
|
|
150
|
+
│ │ → Log it: "Auto-logged out admin X after Y seconds"
|
|
151
|
+
│ │
|
|
152
|
+
│ NO → Carry on, you're fine 👍
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 🖥️ The Frontend Interceptor
|
|
156
|
+
|
|
157
|
+
On the admin panel side, `window.fetch` is patched to watch for a special header:
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
🌐 Admin makes any API call
|
|
161
|
+
│
|
|
162
|
+
▼
|
|
163
|
+
👀 Check response headers for 'app.admin.tk'
|
|
164
|
+
│
|
|
165
|
+
YES → 🚨 FORCED LOGOUT 🚨
|
|
166
|
+
│ window.stop()
|
|
167
|
+
│ window.location.reload()
|
|
168
|
+
│ (Session is over. Go home.)
|
|
169
|
+
│
|
|
170
|
+
NO → ✅ Normal response. Continue working.
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 📂 Project Structure
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
strapi-security-suite/
|
|
179
|
+
📁 admin/src/ ← Admin panel (React)
|
|
180
|
+
│ 📄 index.js Plugin entry + fetch interceptor
|
|
181
|
+
│ 📄 constants.js API paths, header names
|
|
182
|
+
│ 📄 pluginId.js Plugin ID constant
|
|
183
|
+
│ 📁 components/
|
|
184
|
+
│ │ 📄 Initializer.jsx Plugin lifecycle init
|
|
185
|
+
│ 📁 pages/
|
|
186
|
+
│ │ 📄 App.jsx Router
|
|
187
|
+
│ │ 📄 HomePage.jsx Settings UI (the pretty one)
|
|
188
|
+
│ 📁 translations/
|
|
189
|
+
│ 📄 en.json i18n strings
|
|
190
|
+
│
|
|
191
|
+
📁 server/src/ ← Server-side (Node.js)
|
|
192
|
+
│ 📄 index.js Plugin entry point
|
|
193
|
+
│ 📄 register.js Middleware registration phase
|
|
194
|
+
│ 📄 bootstrap.js Permissions + settings seeding
|
|
195
|
+
│ 📄 destroy.js Cleanup on shutdown
|
|
196
|
+
│ 📄 constants.js ⭐ ALL magic values live here
|
|
197
|
+
│ │
|
|
198
|
+
│ 📁 controllers/
|
|
199
|
+
│ │ 📄 adminSecurityController.js GET/POST settings (with validation!)
|
|
200
|
+
│ │
|
|
201
|
+
│ 📁 services/
|
|
202
|
+
│ │ 📄 autoLogoutChecker.js Background watcher (setInterval)
|
|
203
|
+
│ │
|
|
204
|
+
│ 📁 middlewares/
|
|
205
|
+
│ │ 📄 seedUserInfos.js Hydrate session from JWT
|
|
206
|
+
│ │ 📄 interceptRenewToken.js Block renewal for dead sessions
|
|
207
|
+
│ │ 📄 trackActivity.js Record last activity timestamp
|
|
208
|
+
│ │ 📄 rejectRevokedTokens.js Nuke revoked sessions
|
|
209
|
+
│ │ 📄 preventMultipleSessions.js One-session-per-admin gate
|
|
210
|
+
│ │
|
|
211
|
+
│ 📁 policies/
|
|
212
|
+
│ │ 📄 has-admin-permission.js Route-level permission check
|
|
213
|
+
│ │
|
|
214
|
+
│ 📁 globals/ ← In-memory state (the "brain")
|
|
215
|
+
│ │ 📄 sessionActivityMap.js Map<"id:email", timestamp>
|
|
216
|
+
│ │ 📄 revokedTokenSet.js Set<email> of revoked sessions
|
|
217
|
+
│ │ 📄 loginLocks.js Set<email> login race-condition guard
|
|
218
|
+
│ │
|
|
219
|
+
│ 📁 utils/
|
|
220
|
+
│ │ 📄 errors.js PluginError, ValidationError, AuthorizationError
|
|
221
|
+
│ │ 📄 force-expire-admin.js Issue a 1-second JWT to kill client token
|
|
222
|
+
│ │
|
|
223
|
+
│ 📁 content-types/
|
|
224
|
+
│ │ 📁 security-settings/
|
|
225
|
+
│ │ 📄 schema.json DB schema (singleType)
|
|
226
|
+
│ │
|
|
227
|
+
│ 📁 routes/
|
|
228
|
+
│ │ 📄 index.js Admin-typed routes with policies
|
|
229
|
+
│ │
|
|
230
|
+
│ 📁 types/
|
|
231
|
+
│ 📄 typedefs.js JSDoc type definitions
|
|
232
|
+
│
|
|
233
|
+
📄 eslint.config.mjs ESLint v9 flat config
|
|
234
|
+
📄 .prettierrc Prettier config
|
|
235
|
+
📄 package.json Scripts, deps, lint-staged
|
|
236
|
+
📁 .husky/
|
|
237
|
+
📄 pre-commit Runs lint-staged before every commit
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## 🔧 Configuration Schema
|
|
243
|
+
|
|
244
|
+
All settings live in a **single-type** content-type in the database:
|
|
56
245
|
|
|
57
246
|
```json
|
|
58
247
|
{
|
|
@@ -64,103 +253,136 @@ Even if Strapi tries to pretend they’re still cute.
|
|
|
64
253
|
}
|
|
65
254
|
```
|
|
66
255
|
|
|
67
|
-
|
|
68
|
-
|
|
256
|
+
| Field | Type | Default | What It Does |
|
|
257
|
+
| -------------------------- | --------- | ------- | ---------------------------------------- |
|
|
258
|
+
| `autoLogoutTime` | `integer` | `30` | Minutes of inactivity before auto-logout |
|
|
259
|
+
| `multipleSessionsControl` | `boolean` | `true` | Block concurrent sessions for same admin |
|
|
260
|
+
| `passwordExpiryDays` | `integer` | `30` | Days before password must be changed |
|
|
261
|
+
| `nonReusablePassword` | `boolean` | `true` | Prevent reuse of previous passwords |
|
|
262
|
+
| `enablePasswordManagement` | `boolean` | `true` | Master switch for password features |
|
|
69
263
|
|
|
70
264
|
---
|
|
71
265
|
|
|
72
|
-
##
|
|
266
|
+
## 🧪 API Endpoints
|
|
73
267
|
|
|
74
|
-
|
|
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
|
|
268
|
+
All routes are **admin-typed** (Strapi handles auth automatically):
|
|
78
269
|
|
|
79
|
-
|
|
270
|
+
| Method | Path | Auth | Permission | Description |
|
|
271
|
+
| ------ | --------------------------------------- | -------- | ---------------- | --------------- |
|
|
272
|
+
| `GET` | `/strapi-security-suite/health` | 🔓 None | — | Health check |
|
|
273
|
+
| `GET` | `/strapi-security-suite/admin/settings` | 🔒 Admin | `view-configs` | Read settings |
|
|
274
|
+
| `POST` | `/strapi-security-suite/admin/settings` | 🔒 Admin | `manage-configs` | Update settings |
|
|
275
|
+
|
|
276
|
+
### 🔐 Permissions
|
|
80
277
|
|
|
81
|
-
|
|
278
|
+
The plugin registers three permission actions:
|
|
82
279
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
-
|
|
280
|
+
| Permission | What It Allows |
|
|
281
|
+
| ---------------------------------------------- | ------------------------ |
|
|
282
|
+
| `plugin::strapi-security-suite.access` | Access the settings page |
|
|
283
|
+
| `plugin::strapi-security-suite.view-configs` | Read security settings |
|
|
284
|
+
| `plugin::strapi-security-suite.manage-configs` | Modify security settings |
|
|
86
285
|
|
|
87
286
|
---
|
|
88
287
|
|
|
89
|
-
##
|
|
288
|
+
## ✅ Engineering Standards Compliance
|
|
90
289
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
290
|
+
This plugin follows the **STANDARDS.md** operating protocol:
|
|
291
|
+
|
|
292
|
+
| Standard | Status | Details |
|
|
293
|
+
| ----------------------------- | ------ | --------------------------------------------------------------------- |
|
|
294
|
+
| 💬 **ES6+ JavaScript** | ✅ | Arrow functions, destructuring, template literals, native ESM |
|
|
295
|
+
| 📄 **Full JSDoc** | ✅ | Every export documented with `@param`, `@returns`, `@module` |
|
|
296
|
+
| 🔢 **No Magic Values** | ✅ | All values in `constants.js` (status codes, cookies, headers, timing) |
|
|
297
|
+
| 🚨 **Custom Error Hierarchy** | ✅ | `PluginError` → `ValidationError`, `AuthorizationError` |
|
|
298
|
+
| 🛡️ **Input Validation** | ✅ | `saveSettings` validates keys, types, and shape |
|
|
299
|
+
| 🔍 **ESLint** | ✅ | v9 flat config with jsdoc plugin + Prettier compat |
|
|
300
|
+
| 🎨 **Prettier** | ✅ | Single quotes, 100 print width, trailing commas |
|
|
301
|
+
| 🪝 **Husky + lint-staged** | ✅ | Pre-commit: ESLint fix + Prettier on staged files |
|
|
302
|
+
| 🏗️ **Architecture** | ✅ | Routes → Controllers → Services → Data (proper layering) |
|
|
303
|
+
| 🔐 **Security-First** | ✅ | Auth/AuthZ separated, input validated, no secrets in logs |
|
|
95
304
|
|
|
96
305
|
---
|
|
97
306
|
|
|
98
|
-
##
|
|
307
|
+
## 🛠️ Development
|
|
99
308
|
|
|
100
309
|
```bash
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
or
|
|
104
|
-
```bash
|
|
105
|
-
npm install strapi-security-suite
|
|
106
|
-
```
|
|
310
|
+
# Install dependencies
|
|
311
|
+
yarn install
|
|
107
312
|
|
|
108
|
-
|
|
109
|
-
|
|
313
|
+
# Build the plugin
|
|
314
|
+
yarn build
|
|
110
315
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
316
|
+
# Watch mode (auto-rebuild on changes)
|
|
317
|
+
yarn watch
|
|
318
|
+
|
|
319
|
+
# Lint everything
|
|
320
|
+
yarn lint
|
|
321
|
+
|
|
322
|
+
# Lint + auto-fix
|
|
323
|
+
yarn lint:fix
|
|
324
|
+
|
|
325
|
+
# Check formatting
|
|
326
|
+
yarn format:check
|
|
327
|
+
|
|
328
|
+
# Auto-format everything
|
|
329
|
+
yarn format
|
|
330
|
+
|
|
331
|
+
# Verify plugin exports
|
|
332
|
+
yarn verify
|
|
117
333
|
```
|
|
118
334
|
|
|
119
335
|
---
|
|
120
336
|
|
|
121
|
-
## 🔮
|
|
122
|
-
|
|
123
|
-
| Feature
|
|
124
|
-
|
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
|
|
|
128
|
-
|
|
|
129
|
-
|
|
|
130
|
-
|
|
|
337
|
+
## 🔮 Roadmap
|
|
338
|
+
|
|
339
|
+
| Feature | Status |
|
|
340
|
+
| ----------------------------- | ----------------- |
|
|
341
|
+
| ⏰ Auto-Logout | ✅ Shipped |
|
|
342
|
+
| 🚫 Single-Session Enforcement | ✅ Shipped |
|
|
343
|
+
| 💀 Token Revocation Pipeline | ✅ Shipped |
|
|
344
|
+
| ⚙️ Admin Settings UI | ✅ Shipped |
|
|
345
|
+
| 🔑 Password Expiry | 🚧 In Development |
|
|
346
|
+
| 🔄 Non-Reusable Passwords | 🚧 In Development |
|
|
347
|
+
| 📝 Admin Activity Logs | 🔜 Planned |
|
|
348
|
+
| 📊 Security Dashboard | 🔜 Planned |
|
|
349
|
+
| 👊 Brute Force Detection | 🔜 Planned |
|
|
350
|
+
| 👁️ Real-time Session Viewer | 🔜 Planned |
|
|
131
351
|
|
|
132
352
|
---
|
|
133
353
|
|
|
134
|
-
##
|
|
354
|
+
## 🗣️ Real Talk
|
|
355
|
+
|
|
356
|
+
> "We installed this and now our interns can't share logins anymore."
|
|
357
|
+
> — A CTO, probably
|
|
135
358
|
|
|
136
|
-
>
|
|
137
|
-
> —
|
|
359
|
+
> "Our admin panel feels like it _judges_ us now. I love it."
|
|
360
|
+
> — That one developer who actually cares
|
|
138
361
|
|
|
139
|
-
>
|
|
140
|
-
> —
|
|
362
|
+
> "I left my desk for coffee and came back logged out. Respect."
|
|
363
|
+
> — Someone who now understands security
|
|
141
364
|
|
|
142
365
|
---
|
|
143
366
|
|
|
144
|
-
##
|
|
367
|
+
## 👥 Author
|
|
145
368
|
|
|
146
|
-
[LPIX-11](mohamed.johnson@orange-sonatel.com)
|
|
369
|
+
**[LPIX-11](mailto:mohamed.johnson@orange-sonatel.com)** — Orange / Sonatel
|
|
147
370
|
|
|
148
371
|
---
|
|
149
372
|
|
|
150
|
-
##
|
|
373
|
+
## ⚖️ License
|
|
151
374
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
- Fast
|
|
155
|
-
- Unforgiving
|
|
156
|
-
- Elegant
|
|
157
|
-
- **Mildly judgmental**
|
|
375
|
+
**MIT** — Do whatever you want. Just don't blame us if you turn off all the features and get breached. That's on you.
|
|
158
376
|
|
|
159
377
|
---
|
|
160
378
|
|
|
161
|
-
##
|
|
379
|
+
## 💡 Philosophy
|
|
380
|
+
|
|
381
|
+
Security should be:
|
|
162
382
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
383
|
+
- **Fast** — In-memory. No database lookups on every request.
|
|
384
|
+
- **Unforgiving** — Idle? Gone. Revoked? Dead. Duplicated? Blocked.
|
|
385
|
+
- **Elegant** — Clean constants, typed errors, layered architecture.
|
|
386
|
+
- **Mildly judgmental** — This plugin _will_ side-eye your stale sessions.
|
|
166
387
|
|
|
388
|
+
> _"The meta-principle: make the right thing the default thing. Discipline compounds. Shortcuts compound too, just in the wrong direction."_
|
|
@@ -0,0 +1,162 @@
|
|
|
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
|
+
const index = require("./index-ub4Bl9QF.js");
|
|
9
|
+
const HomePage = () => {
|
|
10
|
+
const client = admin.useFetchClient();
|
|
11
|
+
const [config, setConfig] = react.useState({
|
|
12
|
+
autoLogoutTime: 30,
|
|
13
|
+
multipleSessionsControl: false,
|
|
14
|
+
passwordExpiryDays: 365,
|
|
15
|
+
nonReusablePassword: false,
|
|
16
|
+
enablePasswordManagement: false
|
|
17
|
+
});
|
|
18
|
+
const [success, setSuccess] = react.useState(false);
|
|
19
|
+
const [error, setError] = react.useState(null);
|
|
20
|
+
react.useEffect(() => {
|
|
21
|
+
const fetchConfig = async () => {
|
|
22
|
+
try {
|
|
23
|
+
const { data } = await client.get(`${index.API_BASE_PATH}/admin/settings`);
|
|
24
|
+
if (data?.data) {
|
|
25
|
+
setConfig(data.data);
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
setError("Failed to load settings. Please refresh the page.");
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
fetchConfig();
|
|
32
|
+
}, [client]);
|
|
33
|
+
const handleChange = (key, value) => {
|
|
34
|
+
setConfig((prev) => ({ ...prev, [key]: value }));
|
|
35
|
+
};
|
|
36
|
+
const saveConfig = async () => {
|
|
37
|
+
setError(null);
|
|
38
|
+
try {
|
|
39
|
+
await client.post(`${index.API_BASE_PATH}/admin/settings`, config);
|
|
40
|
+
setSuccess(true);
|
|
41
|
+
setTimeout(() => setSuccess(false), index.SUCCESS_ALERT_DURATION);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
setError(err.response?.data?.error?.message ?? "Failed to save settings.");
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 10, background: "neutral0", shadow: "filterShadow", borderRadius: "12px", children: [
|
|
47
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", as: "h1", fontWeight: "bold", children: "Security & Session Settings" }),
|
|
48
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "epsilon", textColor: "neutral600", paddingTop: 2, paddingBottom: 4, children: "Configure session duration, password policies, and security settings." }),
|
|
49
|
+
success && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Alert, { title: "Success", variant: "success", marginBottom: 4, children: "Configuration saved successfully!" }),
|
|
50
|
+
error && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Alert, { title: "Error", variant: "danger", marginBottom: 4, onClose: () => setError(null), children: error }),
|
|
51
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
52
|
+
designSystem.Flex,
|
|
53
|
+
{
|
|
54
|
+
alignItems: "flex-start",
|
|
55
|
+
justifyContent: "space-between",
|
|
56
|
+
gap: 6,
|
|
57
|
+
paddingTop: 6,
|
|
58
|
+
wrap: "wrap",
|
|
59
|
+
children: [
|
|
60
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
61
|
+
designSystem.Box,
|
|
62
|
+
{
|
|
63
|
+
flex: "1",
|
|
64
|
+
minWidth: "400px",
|
|
65
|
+
padding: 6,
|
|
66
|
+
background: "neutral100",
|
|
67
|
+
borderRadius: "8px",
|
|
68
|
+
shadow: "tableShadow",
|
|
69
|
+
children: [
|
|
70
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", fontWeight: "semiBold", children: "Session Management" }),
|
|
71
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, { marginTop: 2, marginBottom: 4 }),
|
|
72
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", gap: 4, children: [
|
|
73
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "row", gap: 4, children: [
|
|
74
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: "Auto Logout Time (minutes)" }),
|
|
75
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
76
|
+
designSystem.NumberInput,
|
|
77
|
+
{
|
|
78
|
+
minValue: 1,
|
|
79
|
+
value: config.autoLogoutTime,
|
|
80
|
+
onValueChange: (value) => handleChange("autoLogoutTime", value)
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
] }),
|
|
84
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, children: [
|
|
85
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: "Activate multiple sessions control?" }),
|
|
86
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
87
|
+
designSystem.Switch,
|
|
88
|
+
{
|
|
89
|
+
checked: config.multipleSessionsControl,
|
|
90
|
+
onCheckedChange: () => handleChange("multipleSessionsControl", !config.multipleSessionsControl)
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
] })
|
|
94
|
+
] })
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
),
|
|
98
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
99
|
+
designSystem.Box,
|
|
100
|
+
{
|
|
101
|
+
flex: "1",
|
|
102
|
+
minWidth: "400px",
|
|
103
|
+
minHeight: "173px",
|
|
104
|
+
padding: 6,
|
|
105
|
+
background: "neutral100",
|
|
106
|
+
borderRadius: "8px",
|
|
107
|
+
shadow: "tableShadow",
|
|
108
|
+
children: [
|
|
109
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", fontWeight: "semiBold", children: "Password Management" }),
|
|
110
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, { marginTop: 2, marginBottom: 4 }),
|
|
111
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "flex-start", direction: "column", gap: 4, children: [
|
|
112
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, children: [
|
|
113
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: "Activate Password Control?" }),
|
|
114
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
115
|
+
designSystem.Switch,
|
|
116
|
+
{
|
|
117
|
+
checked: config.enablePasswordManagement,
|
|
118
|
+
onCheckedChange: () => handleChange("enablePasswordManagement", !config.enablePasswordManagement)
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
] }),
|
|
122
|
+
config.enablePasswordManagement && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "flex-start", direction: "column", gap: 5, children: [
|
|
123
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "flex-start", direction: "column", gap: 3, children: [
|
|
124
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: "Password Expiry (days)?" }),
|
|
125
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
126
|
+
designSystem.NumberInput,
|
|
127
|
+
{
|
|
128
|
+
minValue: 1,
|
|
129
|
+
value: config.passwordExpiryDays,
|
|
130
|
+
onValueChange: (value) => handleChange("passwordExpiryDays", value)
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
] }),
|
|
134
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 3, children: [
|
|
135
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: "Activate Non-Reusable Password?" }),
|
|
136
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
137
|
+
designSystem.Switch,
|
|
138
|
+
{
|
|
139
|
+
checked: config.nonReusablePassword,
|
|
140
|
+
onCheckedChange: () => handleChange("nonReusablePassword", !config.nonReusablePassword)
|
|
141
|
+
}
|
|
142
|
+
)
|
|
143
|
+
] })
|
|
144
|
+
] })
|
|
145
|
+
] })
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
),
|
|
152
|
+
/* @__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" }) })
|
|
153
|
+
] });
|
|
154
|
+
};
|
|
155
|
+
const App = () => {
|
|
156
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Routes, { children: [
|
|
157
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { index: true, element: /* @__PURE__ */ jsxRuntime.jsx(HomePage, {}) }),
|
|
158
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "*", element: /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Error, {}) })
|
|
159
|
+
] });
|
|
160
|
+
};
|
|
161
|
+
exports.App = App;
|
|
162
|
+
exports.default = App;
|