strapi-security-suite 0.3.0 → 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.
@@ -3,7 +3,7 @@ import { useFetchClient, Page } from "@strapi/strapi/admin";
3
3
  import { Routes, Route } from "react-router-dom";
4
4
  import { useState, useEffect } from "react";
5
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-CAEB836L.mjs";
6
+ import { A as API_BASE_PATH, S as SUCCESS_ALERT_DURATION } from "./index-ZKJuPZEH.mjs";
7
7
  const HomePage = () => {
8
8
  const client = useFetchClient();
9
9
  const [config, setConfig] = useState({
@@ -5,7 +5,7 @@ const admin = require("@strapi/strapi/admin");
5
5
  const reactRouterDom = require("react-router-dom");
6
6
  const react = require("react");
7
7
  const designSystem = require("@strapi/design-system");
8
- const index = require("./index-ub4Bl9QF.js");
8
+ const index = require("./index-BGBd43He.js");
9
9
  const HomePage = () => {
10
10
  const client = admin.useFetchClient();
11
11
  const [config, setConfig] = react.useState({
@@ -49,9 +49,8 @@ const index = {
49
49
  const response = await originalFetch(...args);
50
50
  const captured = response.headers.get(ADMIN_TOKEN_HEADER);
51
51
  if (captured) {
52
- window.stop();
53
52
  window.location.reload();
54
- return;
53
+ return new Response(null, { status: 401 });
55
54
  }
56
55
  return response;
57
56
  };
@@ -71,7 +70,7 @@ const index = {
71
70
  id: getTrad("settings.title"),
72
71
  defaultMessage: "Security Suite"
73
72
  },
74
- Component: () => Promise.resolve().then(() => require("./App-CfPg1Thn.js")),
73
+ Component: () => Promise.resolve().then(() => require("./App-ConqHB2Q.js")),
75
74
  permissions: [
76
75
  {
77
76
  action: "plugin::strapi-security-suite.access",
@@ -48,9 +48,8 @@ const index = {
48
48
  const response = await originalFetch(...args);
49
49
  const captured = response.headers.get(ADMIN_TOKEN_HEADER);
50
50
  if (captured) {
51
- window.stop();
52
51
  window.location.reload();
53
- return;
52
+ return new Response(null, { status: 401 });
54
53
  }
55
54
  return response;
56
55
  };
@@ -70,7 +69,7 @@ const index = {
70
69
  id: getTrad("settings.title"),
71
70
  defaultMessage: "Security Suite"
72
71
  },
73
- Component: () => import("./App-t96Cfein.mjs"),
72
+ Component: () => import("./App-CBOxzfqu.mjs"),
74
73
  permissions: [
75
74
  {
76
75
  action: "plugin::strapi-security-suite.access",
@@ -1,3 +1,3 @@
1
1
  "use strict";
2
- const index = require("../_chunks/index-ub4Bl9QF.js");
2
+ const index = require("../_chunks/index-BGBd43He.js");
3
3
  module.exports = index.index;
@@ -1,4 +1,4 @@
1
- import { i } from "../_chunks/index-CAEB836L.mjs";
1
+ import { i } from "../_chunks/index-ZKJuPZEH.mjs";
2
2
  export {
3
3
  i as default
4
4
  };
@@ -3,7 +3,16 @@ const jwt = require("jsonwebtoken");
3
3
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
4
4
  const jwt__default = /* @__PURE__ */ _interopDefault(jwt);
5
5
  const revokedTokenSet = /* @__PURE__ */ new Set();
6
- const revokedConnectionTokens = /* @__PURE__ */ new Set();
6
+ const revokedConnectionTokens = /* @__PURE__ */ new Map();
7
+ const TOKEN_TTL = 30 * 60 * 1e3;
8
+ function pruneExpiredTokens() {
9
+ const cutoff = Date.now() - TOKEN_TTL;
10
+ for (const [token, revokedAt] of revokedConnectionTokens) {
11
+ if (revokedAt < cutoff) {
12
+ revokedConnectionTokens.delete(token);
13
+ }
14
+ }
15
+ }
7
16
  const sessionActivityMap = /* @__PURE__ */ new Map();
8
17
  const PLUGIN_ID = "strapi-security-suite";
9
18
  const CONTENT_TYPES = {
@@ -12,6 +21,7 @@ const CONTENT_TYPES = {
12
21
  const SERVICES = {
13
22
  AUTO_LOGOUT_CHECKER: "autoLogoutChecker"
14
23
  };
24
+ const CTX_ADMIN_USER = Symbol.for("security-suite:adminUser");
15
25
  const CHECK_INTERVAL = 5e3;
16
26
  const DEFAULT_AUTOLOGOUT_TIME = 30;
17
27
  const MS_PER_MINUTE = 6e4;
@@ -37,7 +47,6 @@ const HEADERS = {
37
47
  /** Required so the browser exposes custom headers in fetch responses. */
38
48
  EXPOSE_HEADERS: "Access-Control-Expose-Headers"
39
49
  };
40
- const ADMIN_TOKEN_FALLBACK = "email.admin";
41
50
  const ERROR_MESSAGES = {
42
51
  SETTINGS_NOT_FOUND: "Security settings not found.",
43
52
  INSUFFICIENT_PERMISSIONS: "Insufficient permissions.",
@@ -61,7 +70,7 @@ const DEFAULT_SETTINGS = {
61
70
  };
62
71
  const VALID_SETTINGS_KEYS = new Set(Object.keys(DEFAULT_SETTINGS));
63
72
  async function trackActivity(ctx, next) {
64
- const adminUser = ctx.session?.user;
73
+ const adminUser = ctx.state[CTX_ADMIN_USER];
65
74
  let key = adminUser?.id ? `${adminUser.id}:${adminUser.email}` : null;
66
75
  const bearerToken = ctx.get("authorization")?.split("Bearer ")[1];
67
76
  if (bearerToken && revokedConnectionTokens.has(bearerToken)) {
@@ -76,7 +85,9 @@ async function trackActivity(ctx, next) {
76
85
  return;
77
86
  }
78
87
  if (ctx.path.includes(LOGOUT_PATH)) {
79
- ctx.session = null;
88
+ if (ctx.session !== void 0) {
89
+ ctx.session = null;
90
+ }
80
91
  key = null;
81
92
  }
82
93
  if (key) {
@@ -86,14 +97,22 @@ async function trackActivity(ctx, next) {
86
97
  await next();
87
98
  }
88
99
  const loginLocks = /* @__PURE__ */ new Set();
100
+ function cleanupLoginState(ctx) {
101
+ const email = ctx.request.body?.email;
102
+ loginLocks.delete(email);
103
+ if (email && ctx.status < 400) {
104
+ revokedTokenSet.delete(email);
105
+ }
106
+ }
89
107
  async function preventMultipleSessions(ctx, next) {
90
108
  const isLoginPost = ctx.path === LOGIN_PATH && ctx.method === "POST";
91
- const alreadyAdmin = ctx.session?.user;
92
109
  if (!isLoginPost) {
93
110
  return await next();
94
111
  }
95
- if (alreadyAdmin) {
96
- strapi.log.debug(`[${PLUGIN_ID}] Skipping session lock. ${JSON.stringify(alreadyAdmin)}`);
112
+ if (ctx.state[CTX_ADMIN_USER]) {
113
+ strapi.log.debug(
114
+ `[${PLUGIN_ID}] Skipping session lock. ${JSON.stringify(ctx.state[CTX_ADMIN_USER])}`
115
+ );
97
116
  return await next();
98
117
  }
99
118
  try {
@@ -127,22 +146,21 @@ async function preventMultipleSessions(ctx, next) {
127
146
  try {
128
147
  await next();
129
148
  } finally {
130
- if (ctx.path === LOGIN_PATH && ctx.method === "POST") {
131
- const email = ctx.request.body?.email;
132
- loginLocks.delete(email);
133
- }
149
+ cleanupLoginState(ctx);
134
150
  }
135
151
  }
136
152
  async function rejectRevokedTokens(ctx, next) {
137
- const sessionUser = ctx.session?.user;
138
- if (!sessionUser?.email) return await next();
139
- const { id, email: adminEmail } = sessionUser;
153
+ const adminUser = ctx.state[CTX_ADMIN_USER];
154
+ if (!adminUser?.email) return await next();
155
+ const { id, email: adminEmail } = adminUser;
140
156
  const key = id && adminEmail ? `${id}:${adminEmail}` : null;
141
157
  try {
142
158
  if (adminEmail && revokedTokenSet.has(adminEmail)) {
143
159
  ctx.set(HEADERS.ADMIN_TOKEN_SIGNAL, adminEmail);
144
160
  ctx.set(HEADERS.EXPOSE_HEADERS, HEADERS.ADMIN_TOKEN_SIGNAL);
145
- ctx.session = null;
161
+ if (ctx.session !== void 0) {
162
+ ctx.session = null;
163
+ }
146
164
  ctx.cookies.set(COOKIES.REFRESH_TOKEN, "", {
147
165
  expires: /* @__PURE__ */ new Date(0),
148
166
  path: "/admin",
@@ -150,7 +168,7 @@ async function rejectRevokedTokens(ctx, next) {
150
168
  });
151
169
  const bearerToken = ctx.get("authorization")?.split("Bearer ")[1];
152
170
  if (bearerToken) {
153
- revokedConnectionTokens.add(bearerToken);
171
+ revokedConnectionTokens.set(bearerToken, Date.now());
154
172
  }
155
173
  sessionActivityMap.delete(key);
156
174
  revokedTokenSet.delete(adminEmail);
@@ -173,29 +191,27 @@ async function rejectRevokedTokens(ctx, next) {
173
191
  }
174
192
  async function interceptRenewToken(ctx, next) {
175
193
  if (ctx.path.includes(LOGOUT_PATH)) {
176
- const adminUser = ctx.session?.user;
194
+ const adminUser = ctx.state[CTX_ADMIN_USER];
177
195
  strapi.log.debug(`[${PLUGIN_ID}] Logout captured: ${JSON.stringify(adminUser)}`);
178
196
  if (adminUser?.id) {
179
197
  strapi.plugin(PLUGIN_ID).service(SERVICES.AUTO_LOGOUT_CHECKER).clearSessionActivity(adminUser.id, adminUser.email);
180
198
  const bearerToken = ctx.get("authorization")?.split("Bearer ")[1];
181
199
  if (bearerToken) {
182
- revokedConnectionTokens.add(bearerToken);
200
+ revokedConnectionTokens.set(bearerToken, Date.now());
201
+ }
202
+ if (ctx.session !== void 0) {
203
+ ctx.session = null;
183
204
  }
184
- ctx.session = null;
185
205
  sessionActivityMap.delete(`${adminUser.id}:${adminUser.email}`);
186
206
  }
187
207
  await next();
188
208
  return;
189
209
  }
190
210
  if (ctx.path.includes(ACCESS_TOKEN_PATH) || ctx.path.includes(CONTENT_PATH)) {
191
- const { email } = ctx.session?.user || {};
192
- if (!email) {
193
- ctx.set(HEADERS.ADMIN_TOKEN_SIGNAL, ADMIN_TOKEN_FALLBACK);
194
- ctx.set(HEADERS.EXPOSE_HEADERS, HEADERS.ADMIN_TOKEN_SIGNAL);
195
- await next();
196
- return;
211
+ const adminUser = ctx.state[CTX_ADMIN_USER];
212
+ if (adminUser?.email) {
213
+ strapi.log.debug(`[${PLUGIN_ID}] Token renewal intercepted for ${adminUser.email}`);
197
214
  }
198
- strapi.log.debug(`[${PLUGIN_ID}] Token renewal intercepted for ${email}`);
199
215
  }
200
216
  await next();
201
217
  }
@@ -208,9 +224,8 @@ async function seedUserInfos(ctx, next) {
208
224
  const token = authHeader.split("Bearer ")[1];
209
225
  if (!token) return await next();
210
226
  const decodedToken = jwt__default.default.decode(token);
211
- const session = ctx.session?.user ?? null;
212
227
  const adminId = decodedToken?.userId;
213
- if (!adminId || session?.id) {
228
+ if (!adminId || ctx.state[CTX_ADMIN_USER]?.id) {
214
229
  return await next();
215
230
  }
216
231
  const adminUser = await strapi.db.query("admin::user").findOne({
@@ -221,25 +236,18 @@ async function seedUserInfos(ctx, next) {
221
236
  strapi.log.debug(`[${PLUGIN_ID}] No admin user found with ID ${adminId}`);
222
237
  return await next();
223
238
  }
224
- if (revokedTokenSet.has(adminUser.email)) {
225
- strapi.log.debug(
226
- `[${PLUGIN_ID}] Admin ${adminUser.email} is in revoked set — skipping hydration`
227
- );
228
- return await next();
229
- }
230
- const userInfos = {
239
+ ctx.state[CTX_ADMIN_USER] = {
231
240
  id: adminUser.id,
232
241
  email: adminUser.email,
233
242
  firstname: adminUser.firstname,
234
243
  lastname: adminUser.lastname,
235
244
  roles: adminUser.roles
236
245
  };
237
- ctx.session.user = userInfos;
238
246
  const key = `${adminUser.id}:${adminUser.email}`;
239
247
  if (!sessionActivityMap.has(key)) {
240
248
  sessionActivityMap.set(key, Date.now());
241
249
  }
242
- strapi.log.debug(`[${PLUGIN_ID}] Session hydrated for admin ${adminUser.email}`);
250
+ strapi.log.debug(`[${PLUGIN_ID}] Admin identified: ${adminUser.email}`);
243
251
  return await next();
244
252
  } catch (error) {
245
253
  strapi.log.error(`[${PLUGIN_ID}] Failed to decode or hydrate admin token:`, error);
@@ -557,6 +565,7 @@ const autoLogoutChecker = ({ strapi: strapi2 }) => ({
557
565
  }
558
566
  interval = setInterval(async () => {
559
567
  try {
568
+ pruneExpiredTokens();
560
569
  const settings = await strapi2.documents(CONTENT_TYPES.SECURITY_SETTINGS).findFirst({});
561
570
  const autoLogoutTime = (settings?.autoLogoutTime ?? DEFAULT_AUTOLOGOUT_TIME) * MS_PER_MINUTE;
562
571
  const now = Date.now();
@@ -1,6 +1,15 @@
1
1
  import jwt from "jsonwebtoken";
2
2
  const revokedTokenSet = /* @__PURE__ */ new Set();
3
- const revokedConnectionTokens = /* @__PURE__ */ new Set();
3
+ const revokedConnectionTokens = /* @__PURE__ */ new Map();
4
+ const TOKEN_TTL = 30 * 60 * 1e3;
5
+ function pruneExpiredTokens() {
6
+ const cutoff = Date.now() - TOKEN_TTL;
7
+ for (const [token, revokedAt] of revokedConnectionTokens) {
8
+ if (revokedAt < cutoff) {
9
+ revokedConnectionTokens.delete(token);
10
+ }
11
+ }
12
+ }
4
13
  const sessionActivityMap = /* @__PURE__ */ new Map();
5
14
  const PLUGIN_ID = "strapi-security-suite";
6
15
  const CONTENT_TYPES = {
@@ -9,6 +18,7 @@ const CONTENT_TYPES = {
9
18
  const SERVICES = {
10
19
  AUTO_LOGOUT_CHECKER: "autoLogoutChecker"
11
20
  };
21
+ const CTX_ADMIN_USER = Symbol.for("security-suite:adminUser");
12
22
  const CHECK_INTERVAL = 5e3;
13
23
  const DEFAULT_AUTOLOGOUT_TIME = 30;
14
24
  const MS_PER_MINUTE = 6e4;
@@ -34,7 +44,6 @@ const HEADERS = {
34
44
  /** Required so the browser exposes custom headers in fetch responses. */
35
45
  EXPOSE_HEADERS: "Access-Control-Expose-Headers"
36
46
  };
37
- const ADMIN_TOKEN_FALLBACK = "email.admin";
38
47
  const ERROR_MESSAGES = {
39
48
  SETTINGS_NOT_FOUND: "Security settings not found.",
40
49
  INSUFFICIENT_PERMISSIONS: "Insufficient permissions.",
@@ -58,7 +67,7 @@ const DEFAULT_SETTINGS = {
58
67
  };
59
68
  const VALID_SETTINGS_KEYS = new Set(Object.keys(DEFAULT_SETTINGS));
60
69
  async function trackActivity(ctx, next) {
61
- const adminUser = ctx.session?.user;
70
+ const adminUser = ctx.state[CTX_ADMIN_USER];
62
71
  let key = adminUser?.id ? `${adminUser.id}:${adminUser.email}` : null;
63
72
  const bearerToken = ctx.get("authorization")?.split("Bearer ")[1];
64
73
  if (bearerToken && revokedConnectionTokens.has(bearerToken)) {
@@ -73,7 +82,9 @@ async function trackActivity(ctx, next) {
73
82
  return;
74
83
  }
75
84
  if (ctx.path.includes(LOGOUT_PATH)) {
76
- ctx.session = null;
85
+ if (ctx.session !== void 0) {
86
+ ctx.session = null;
87
+ }
77
88
  key = null;
78
89
  }
79
90
  if (key) {
@@ -83,14 +94,22 @@ async function trackActivity(ctx, next) {
83
94
  await next();
84
95
  }
85
96
  const loginLocks = /* @__PURE__ */ new Set();
97
+ function cleanupLoginState(ctx) {
98
+ const email = ctx.request.body?.email;
99
+ loginLocks.delete(email);
100
+ if (email && ctx.status < 400) {
101
+ revokedTokenSet.delete(email);
102
+ }
103
+ }
86
104
  async function preventMultipleSessions(ctx, next) {
87
105
  const isLoginPost = ctx.path === LOGIN_PATH && ctx.method === "POST";
88
- const alreadyAdmin = ctx.session?.user;
89
106
  if (!isLoginPost) {
90
107
  return await next();
91
108
  }
92
- if (alreadyAdmin) {
93
- strapi.log.debug(`[${PLUGIN_ID}] Skipping session lock. ${JSON.stringify(alreadyAdmin)}`);
109
+ if (ctx.state[CTX_ADMIN_USER]) {
110
+ strapi.log.debug(
111
+ `[${PLUGIN_ID}] Skipping session lock. ${JSON.stringify(ctx.state[CTX_ADMIN_USER])}`
112
+ );
94
113
  return await next();
95
114
  }
96
115
  try {
@@ -124,22 +143,21 @@ async function preventMultipleSessions(ctx, next) {
124
143
  try {
125
144
  await next();
126
145
  } finally {
127
- if (ctx.path === LOGIN_PATH && ctx.method === "POST") {
128
- const email = ctx.request.body?.email;
129
- loginLocks.delete(email);
130
- }
146
+ cleanupLoginState(ctx);
131
147
  }
132
148
  }
133
149
  async function rejectRevokedTokens(ctx, next) {
134
- const sessionUser = ctx.session?.user;
135
- if (!sessionUser?.email) return await next();
136
- const { id, email: adminEmail } = sessionUser;
150
+ const adminUser = ctx.state[CTX_ADMIN_USER];
151
+ if (!adminUser?.email) return await next();
152
+ const { id, email: adminEmail } = adminUser;
137
153
  const key = id && adminEmail ? `${id}:${adminEmail}` : null;
138
154
  try {
139
155
  if (adminEmail && revokedTokenSet.has(adminEmail)) {
140
156
  ctx.set(HEADERS.ADMIN_TOKEN_SIGNAL, adminEmail);
141
157
  ctx.set(HEADERS.EXPOSE_HEADERS, HEADERS.ADMIN_TOKEN_SIGNAL);
142
- ctx.session = null;
158
+ if (ctx.session !== void 0) {
159
+ ctx.session = null;
160
+ }
143
161
  ctx.cookies.set(COOKIES.REFRESH_TOKEN, "", {
144
162
  expires: /* @__PURE__ */ new Date(0),
145
163
  path: "/admin",
@@ -147,7 +165,7 @@ async function rejectRevokedTokens(ctx, next) {
147
165
  });
148
166
  const bearerToken = ctx.get("authorization")?.split("Bearer ")[1];
149
167
  if (bearerToken) {
150
- revokedConnectionTokens.add(bearerToken);
168
+ revokedConnectionTokens.set(bearerToken, Date.now());
151
169
  }
152
170
  sessionActivityMap.delete(key);
153
171
  revokedTokenSet.delete(adminEmail);
@@ -170,29 +188,27 @@ async function rejectRevokedTokens(ctx, next) {
170
188
  }
171
189
  async function interceptRenewToken(ctx, next) {
172
190
  if (ctx.path.includes(LOGOUT_PATH)) {
173
- const adminUser = ctx.session?.user;
191
+ const adminUser = ctx.state[CTX_ADMIN_USER];
174
192
  strapi.log.debug(`[${PLUGIN_ID}] Logout captured: ${JSON.stringify(adminUser)}`);
175
193
  if (adminUser?.id) {
176
194
  strapi.plugin(PLUGIN_ID).service(SERVICES.AUTO_LOGOUT_CHECKER).clearSessionActivity(adminUser.id, adminUser.email);
177
195
  const bearerToken = ctx.get("authorization")?.split("Bearer ")[1];
178
196
  if (bearerToken) {
179
- revokedConnectionTokens.add(bearerToken);
197
+ revokedConnectionTokens.set(bearerToken, Date.now());
198
+ }
199
+ if (ctx.session !== void 0) {
200
+ ctx.session = null;
180
201
  }
181
- ctx.session = null;
182
202
  sessionActivityMap.delete(`${adminUser.id}:${adminUser.email}`);
183
203
  }
184
204
  await next();
185
205
  return;
186
206
  }
187
207
  if (ctx.path.includes(ACCESS_TOKEN_PATH) || ctx.path.includes(CONTENT_PATH)) {
188
- const { email } = ctx.session?.user || {};
189
- if (!email) {
190
- ctx.set(HEADERS.ADMIN_TOKEN_SIGNAL, ADMIN_TOKEN_FALLBACK);
191
- ctx.set(HEADERS.EXPOSE_HEADERS, HEADERS.ADMIN_TOKEN_SIGNAL);
192
- await next();
193
- return;
208
+ const adminUser = ctx.state[CTX_ADMIN_USER];
209
+ if (adminUser?.email) {
210
+ strapi.log.debug(`[${PLUGIN_ID}] Token renewal intercepted for ${adminUser.email}`);
194
211
  }
195
- strapi.log.debug(`[${PLUGIN_ID}] Token renewal intercepted for ${email}`);
196
212
  }
197
213
  await next();
198
214
  }
@@ -205,9 +221,8 @@ async function seedUserInfos(ctx, next) {
205
221
  const token = authHeader.split("Bearer ")[1];
206
222
  if (!token) return await next();
207
223
  const decodedToken = jwt.decode(token);
208
- const session = ctx.session?.user ?? null;
209
224
  const adminId = decodedToken?.userId;
210
- if (!adminId || session?.id) {
225
+ if (!adminId || ctx.state[CTX_ADMIN_USER]?.id) {
211
226
  return await next();
212
227
  }
213
228
  const adminUser = await strapi.db.query("admin::user").findOne({
@@ -218,25 +233,18 @@ async function seedUserInfos(ctx, next) {
218
233
  strapi.log.debug(`[${PLUGIN_ID}] No admin user found with ID ${adminId}`);
219
234
  return await next();
220
235
  }
221
- if (revokedTokenSet.has(adminUser.email)) {
222
- strapi.log.debug(
223
- `[${PLUGIN_ID}] Admin ${adminUser.email} is in revoked set — skipping hydration`
224
- );
225
- return await next();
226
- }
227
- const userInfos = {
236
+ ctx.state[CTX_ADMIN_USER] = {
228
237
  id: adminUser.id,
229
238
  email: adminUser.email,
230
239
  firstname: adminUser.firstname,
231
240
  lastname: adminUser.lastname,
232
241
  roles: adminUser.roles
233
242
  };
234
- ctx.session.user = userInfos;
235
243
  const key = `${adminUser.id}:${adminUser.email}`;
236
244
  if (!sessionActivityMap.has(key)) {
237
245
  sessionActivityMap.set(key, Date.now());
238
246
  }
239
- strapi.log.debug(`[${PLUGIN_ID}] Session hydrated for admin ${adminUser.email}`);
247
+ strapi.log.debug(`[${PLUGIN_ID}] Admin identified: ${adminUser.email}`);
240
248
  return await next();
241
249
  } catch (error) {
242
250
  strapi.log.error(`[${PLUGIN_ID}] Failed to decode or hydrate admin token:`, error);
@@ -554,6 +562,7 @@ const autoLogoutChecker = ({ strapi: strapi2 }) => ({
554
562
  }
555
563
  interval = setInterval(async () => {
556
564
  try {
565
+ pruneExpiredTokens();
557
566
  const settings = await strapi2.documents(CONTENT_TYPES.SECURITY_SETTINGS).findFirst({});
558
567
  const autoLogoutTime = (settings?.autoLogoutTime ?? DEFAULT_AUTOLOGOUT_TIME) * MS_PER_MINUTE;
559
568
  const now = Date.now();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-security-suite",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "All-in-one authentication and session security plugin for Strapi v5",
5
5
  "license": "MIT",
6
6
  "author": "(LPIX-11) <mohamed.johnson@orange-sonatel.com>",