strapi-security-suite 0.2.4 → 0.3.1

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 CHANGED
@@ -1,58 +1,247 @@
1
- # 🛡️ Strapi Security Suite (Beta)
1
+ # 🛡️ Strapi Security Suite
2
2
 
3
- ## **The Last Plugin You’ll Ever Need to Sleep at Night**
3
+ ### The admin security plugin that takes your sessions _personally_.
4
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.**
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
- ## Why This Exists
10
+ ## 🤔 What Is This?
11
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.
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
- ## ⚔️ Features That Slap
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
- ### 🔒 Auto Logout (with taste)
56
+ ```javascript
57
+ module.exports = ({ env }) => ({
58
+ 'strapi-security-suite': {
59
+ enabled: true,
60
+ },
61
+ });
62
+ ```
21
63
 
22
- Kick idle admins like it’s office closing time.
64
+ ### Step 3: Restart Strapi
23
65
 
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
66
+ ```bash
67
+ yarn develop
68
+ ```
29
69
 
30
- ### 🚷 Multi-Session Lock
70
+ ### Step 4: Find it
31
71
 
32
- One admin = one session. No shadow clones allowed.
72
+ Go to **Settings** **Global** **Security Suite**
33
73
 
34
- - 💥 First login wins, others are denied
35
- - 🧹 Cleans old sessions like a digital janitor
74
+ That's it. You're done. Go get a coffee. ☕
36
75
 
37
- ### 🧄 Session Exorcism Layer™
76
+ ---
38
77
 
39
- Revoked tokens get ghosted *instantly*.\
40
- Even if Strapi tries to pretend they’re still cute.
78
+ ## 🖼️ The Admin Panel
41
79
 
42
- - 🔪 Middleware blocks
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
- ### 🧠 Smart Middleware Stack
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
- - `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
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
- ## 🧪 Configuration Schema
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
- Defined in the content-type:\
68
- `plugin::strapi-security-suite.security_settings`
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
- ## 🧠 Architecture You’ll Brag About
266
+ ## 🧪 API Endpoints
73
267
 
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
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
- ## ⚙️ Admin Panel UI
278
+ The plugin registers three permission actions:
82
279
 
83
- - 🎛️ Control timeouts, session logic, and password rules
84
- - 📜 Planned audit logs, charts, and drama
85
- - 🌌 Future dashboard: all your infra sins visualized
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
- ## 🔐 Frontend Catch Logic
288
+ ## Engineering Standards Compliance
90
289
 
91
- - Fetch wrapper intercepts `440`
92
- - Purges local/session storage
93
- - Sends you crying to `/session-expired`
94
- - Optionally calls `/admin/logout` for drama
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
- ## 📦 Installation
307
+ ## 🛠️ Development
99
308
 
100
309
  ```bash
101
- yarn add strapi-security-suite
102
- ```
103
- or
104
- ```bash
105
- npm install strapi-security-suite
106
- ```
310
+ # Install dependencies
311
+ yarn install
107
312
 
108
- ### 🔹 `config/plugins.js`
109
- Add the following entry inside your `config/plugins.js` file:
313
+ # Build the plugin
314
+ yarn build
110
315
 
111
- ```javascript
112
- module.exports = ({ env }) => ({
113
- 'strapi-security-suite': {
114
- enabled: true,
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
- ## 🔮 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) |
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
- ## 💥 Real-World Impact
354
+ ## 🗣️ Real Talk
355
+
356
+ > "We installed this and now our interns can't share logins anymore."
357
+ > — A CTO, probably
135
358
 
136
- > “We installed this and now our interns can’t share logins anymore.”\
137
- > — CTO, probably
359
+ > "Our admin panel feels like it _judges_ us now. I love it."
360
+ > — That one developer who actually cares
138
361
 
139
- > “Our admin panel feels like it judges us now. I love it.”\
140
- > — That one developer who cares
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
- ## 🧑‍💻 Author
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
- ## 💡 Philosophy
373
+ ## ⚖️ License
151
374
 
152
- Security should be:
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
- ## ⚠️ Legal Drama
379
+ ## 💡 Philosophy
380
+
381
+ Security should be:
162
382
 
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.
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
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import { useFetchClient, 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
+ import { A as API_BASE_PATH, S as SUCCESS_ALERT_DURATION } from "./index-ZKJuPZEH.mjs";
7
+ const HomePage = () => {
8
+ const client = useFetchClient();
9
+ const [config, setConfig] = useState({
10
+ autoLogoutTime: 30,
11
+ multipleSessionsControl: false,
12
+ passwordExpiryDays: 365,
13
+ nonReusablePassword: false,
14
+ enablePasswordManagement: false
15
+ });
16
+ const [success, setSuccess] = useState(false);
17
+ const [error, setError] = useState(null);
18
+ useEffect(() => {
19
+ const fetchConfig = async () => {
20
+ try {
21
+ const { data } = await client.get(`${API_BASE_PATH}/admin/settings`);
22
+ if (data?.data) {
23
+ setConfig(data.data);
24
+ }
25
+ } catch {
26
+ setError("Failed to load settings. Please refresh the page.");
27
+ }
28
+ };
29
+ fetchConfig();
30
+ }, [client]);
31
+ const handleChange = (key, value) => {
32
+ setConfig((prev) => ({ ...prev, [key]: value }));
33
+ };
34
+ const saveConfig = async () => {
35
+ setError(null);
36
+ try {
37
+ await client.post(`${API_BASE_PATH}/admin/settings`, config);
38
+ setSuccess(true);
39
+ setTimeout(() => setSuccess(false), SUCCESS_ALERT_DURATION);
40
+ } catch (err) {
41
+ setError(err.response?.data?.error?.message ?? "Failed to save settings.");
42
+ }
43
+ };
44
+ return /* @__PURE__ */ jsxs(Box, { padding: 10, background: "neutral0", shadow: "filterShadow", borderRadius: "12px", children: [
45
+ /* @__PURE__ */ jsx(Typography, { variant: "alpha", as: "h1", fontWeight: "bold", children: "Security & Session Settings" }),
46
+ /* @__PURE__ */ jsx(Typography, { variant: "epsilon", textColor: "neutral600", paddingTop: 2, paddingBottom: 4, children: "Configure session duration, password policies, and security settings." }),
47
+ success && /* @__PURE__ */ jsx(Alert, { title: "Success", variant: "success", marginBottom: 4, children: "Configuration saved successfully!" }),
48
+ error && /* @__PURE__ */ jsx(Alert, { title: "Error", variant: "danger", marginBottom: 4, onClose: () => setError(null), children: error }),
49
+ /* @__PURE__ */ jsxs(
50
+ Flex,
51
+ {
52
+ alignItems: "flex-start",
53
+ justifyContent: "space-between",
54
+ gap: 6,
55
+ paddingTop: 6,
56
+ wrap: "wrap",
57
+ children: [
58
+ /* @__PURE__ */ jsxs(
59
+ Box,
60
+ {
61
+ flex: "1",
62
+ minWidth: "400px",
63
+ padding: 6,
64
+ background: "neutral100",
65
+ borderRadius: "8px",
66
+ shadow: "tableShadow",
67
+ children: [
68
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "semiBold", children: "Session Management" }),
69
+ /* @__PURE__ */ jsx(Divider, { marginTop: 2, marginBottom: 4 }),
70
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 4, children: [
71
+ /* @__PURE__ */ jsxs(Flex, { direction: "row", gap: 4, children: [
72
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", children: "Auto Logout Time (minutes)" }),
73
+ /* @__PURE__ */ jsx(
74
+ NumberInput,
75
+ {
76
+ minValue: 1,
77
+ value: config.autoLogoutTime,
78
+ onValueChange: (value) => handleChange("autoLogoutTime", value)
79
+ }
80
+ )
81
+ ] }),
82
+ /* @__PURE__ */ jsxs(Flex, { gap: 4, children: [
83
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", children: "Activate multiple sessions control?" }),
84
+ /* @__PURE__ */ jsx(
85
+ Switch,
86
+ {
87
+ checked: config.multipleSessionsControl,
88
+ onCheckedChange: () => handleChange("multipleSessionsControl", !config.multipleSessionsControl)
89
+ }
90
+ )
91
+ ] })
92
+ ] })
93
+ ]
94
+ }
95
+ ),
96
+ /* @__PURE__ */ jsxs(
97
+ Box,
98
+ {
99
+ flex: "1",
100
+ minWidth: "400px",
101
+ minHeight: "173px",
102
+ padding: 6,
103
+ background: "neutral100",
104
+ borderRadius: "8px",
105
+ shadow: "tableShadow",
106
+ children: [
107
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", fontWeight: "semiBold", children: "Password Management" }),
108
+ /* @__PURE__ */ jsx(Divider, { marginTop: 2, marginBottom: 4 }),
109
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "flex-start", direction: "column", gap: 4, children: [
110
+ /* @__PURE__ */ jsxs(Flex, { gap: 4, children: [
111
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", children: "Activate Password Control?" }),
112
+ /* @__PURE__ */ jsx(
113
+ Switch,
114
+ {
115
+ checked: config.enablePasswordManagement,
116
+ onCheckedChange: () => handleChange("enablePasswordManagement", !config.enablePasswordManagement)
117
+ }
118
+ )
119
+ ] }),
120
+ config.enablePasswordManagement && /* @__PURE__ */ jsxs(Flex, { alignItems: "flex-start", direction: "column", gap: 5, children: [
121
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "flex-start", direction: "column", gap: 3, children: [
122
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", children: "Password Expiry (days)?" }),
123
+ /* @__PURE__ */ jsx(
124
+ NumberInput,
125
+ {
126
+ minValue: 1,
127
+ value: config.passwordExpiryDays,
128
+ onValueChange: (value) => handleChange("passwordExpiryDays", value)
129
+ }
130
+ )
131
+ ] }),
132
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 3, children: [
133
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", children: "Activate Non-Reusable Password?" }),
134
+ /* @__PURE__ */ jsx(
135
+ Switch,
136
+ {
137
+ checked: config.nonReusablePassword,
138
+ onCheckedChange: () => handleChange("nonReusablePassword", !config.nonReusablePassword)
139
+ }
140
+ )
141
+ ] })
142
+ ] })
143
+ ] })
144
+ ]
145
+ }
146
+ )
147
+ ]
148
+ }
149
+ ),
150
+ /* @__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" }) })
151
+ ] });
152
+ };
153
+ const App = () => {
154
+ return /* @__PURE__ */ jsxs(Routes, { children: [
155
+ /* @__PURE__ */ jsx(Route, { index: true, element: /* @__PURE__ */ jsx(HomePage, {}) }),
156
+ /* @__PURE__ */ jsx(Route, { path: "*", element: /* @__PURE__ */ jsx(Page.Error, {}) })
157
+ ] });
158
+ };
159
+ export {
160
+ App,
161
+ App as default
162
+ };