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.
- package/dist/_chunks/{App-t96Cfein.mjs → App-CBOxzfqu.mjs} +1 -1
- package/dist/_chunks/{App-CfPg1Thn.js → App-ConqHB2Q.js} +1 -1
- package/dist/_chunks/{index-ub4Bl9QF.js → index-BGBd43He.js} +2 -3
- package/dist/_chunks/{index-CAEB836L.mjs → index-ZKJuPZEH.mjs} +2 -3
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +46 -37
- package/dist/server/index.mjs +46 -37
- package/package.json +1 -1
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
72
|
+
Component: () => import("./App-CBOxzfqu.mjs"),
|
|
74
73
|
permissions: [
|
|
75
74
|
{
|
|
76
75
|
action: "plugin::strapi-security-suite.access",
|
package/dist/admin/index.js
CHANGED
package/dist/admin/index.mjs
CHANGED
package/dist/server/index.js
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
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 (
|
|
96
|
-
strapi.log.debug(
|
|
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
|
-
|
|
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
|
|
138
|
-
if (!
|
|
139
|
-
const { id, email: adminEmail } =
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
192
|
-
if (
|
|
193
|
-
|
|
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 ||
|
|
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
|
-
|
|
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}]
|
|
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();
|
package/dist/server/index.mjs
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import jwt from "jsonwebtoken";
|
|
2
2
|
const revokedTokenSet = /* @__PURE__ */ new Set();
|
|
3
|
-
const revokedConnectionTokens = /* @__PURE__ */ new
|
|
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.
|
|
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
|
|
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 (
|
|
93
|
-
strapi.log.debug(
|
|
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
|
-
|
|
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
|
|
135
|
-
if (!
|
|
136
|
-
const { id, email: adminEmail } =
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
189
|
-
if (
|
|
190
|
-
|
|
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 ||
|
|
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
|
-
|
|
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}]
|
|
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