strapi-security-suite 0.3.1 → 0.3.2
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/server/index.js +47 -38
- package/dist/server/index.mjs +47 -38
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -39,7 +39,9 @@ const COOKIES = {
|
|
|
39
39
|
SESSION: "koa.sess",
|
|
40
40
|
SESSION_SIG: "koa.sess.sig",
|
|
41
41
|
/** Strapi v5 admin refresh-token cookie (managed by session manager). */
|
|
42
|
-
REFRESH_TOKEN: "strapi_admin_refresh"
|
|
42
|
+
REFRESH_TOKEN: "strapi_admin_refresh",
|
|
43
|
+
/** JWT access-token cookie set by Strapi EE SSO authentication flow. */
|
|
44
|
+
JWT_TOKEN: "jwtToken"
|
|
43
45
|
};
|
|
44
46
|
const HEADERS = {
|
|
45
47
|
/** Header that signals the frontend to force-reload (session revoked). */
|
|
@@ -69,6 +71,47 @@ const DEFAULT_SETTINGS = {
|
|
|
69
71
|
enablePasswordManagement: true
|
|
70
72
|
};
|
|
71
73
|
const VALID_SETTINGS_KEYS = new Set(Object.keys(DEFAULT_SETTINGS));
|
|
74
|
+
class PluginError extends Error {
|
|
75
|
+
/**
|
|
76
|
+
* @param {string} message - Internal message (for logs)
|
|
77
|
+
* @param {string} sanitizedMessage - Safe message for the client
|
|
78
|
+
* @param {number} [statusCode=400] - HTTP status code
|
|
79
|
+
*/
|
|
80
|
+
constructor(message, sanitizedMessage, statusCode = HTTP_STATUS.BAD_REQUEST) {
|
|
81
|
+
super(message);
|
|
82
|
+
this.name = "PluginError";
|
|
83
|
+
this.sanitizedMessage = sanitizedMessage;
|
|
84
|
+
this.statusCode = statusCode;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
class ValidationError extends PluginError {
|
|
88
|
+
/**
|
|
89
|
+
* @param {string} message - Internal message
|
|
90
|
+
* @param {string} [sanitizedMessage='Validation failed.'] - Client-safe message
|
|
91
|
+
*/
|
|
92
|
+
constructor(message, sanitizedMessage = "Validation failed.") {
|
|
93
|
+
super(message, sanitizedMessage, HTTP_STATUS.BAD_REQUEST);
|
|
94
|
+
this.name = "ValidationError";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function clearSessionCookies(ctx) {
|
|
98
|
+
if (ctx.session !== void 0) {
|
|
99
|
+
ctx.session = null;
|
|
100
|
+
}
|
|
101
|
+
const expireOpts = { expires: /* @__PURE__ */ new Date(0), path: "/", httpOnly: true };
|
|
102
|
+
ctx.cookies.set(COOKIES.SESSION, "", expireOpts);
|
|
103
|
+
ctx.cookies.set(COOKIES.SESSION_SIG, "", expireOpts);
|
|
104
|
+
ctx.cookies.set(COOKIES.REFRESH_TOKEN, "", {
|
|
105
|
+
expires: /* @__PURE__ */ new Date(0),
|
|
106
|
+
path: "/admin",
|
|
107
|
+
httpOnly: true
|
|
108
|
+
});
|
|
109
|
+
ctx.cookies.set(COOKIES.JWT_TOKEN, "", {
|
|
110
|
+
expires: /* @__PURE__ */ new Date(0),
|
|
111
|
+
path: "/",
|
|
112
|
+
httpOnly: false
|
|
113
|
+
});
|
|
114
|
+
}
|
|
72
115
|
async function trackActivity(ctx, next) {
|
|
73
116
|
const adminUser = ctx.state[CTX_ADMIN_USER];
|
|
74
117
|
let key = adminUser?.id ? `${adminUser.id}:${adminUser.email}` : null;
|
|
@@ -85,9 +128,7 @@ async function trackActivity(ctx, next) {
|
|
|
85
128
|
return;
|
|
86
129
|
}
|
|
87
130
|
if (ctx.path.includes(LOGOUT_PATH)) {
|
|
88
|
-
|
|
89
|
-
ctx.session = null;
|
|
90
|
-
}
|
|
131
|
+
clearSessionCookies(ctx);
|
|
91
132
|
key = null;
|
|
92
133
|
}
|
|
93
134
|
if (key) {
|
|
@@ -158,14 +199,7 @@ async function rejectRevokedTokens(ctx, next) {
|
|
|
158
199
|
if (adminEmail && revokedTokenSet.has(adminEmail)) {
|
|
159
200
|
ctx.set(HEADERS.ADMIN_TOKEN_SIGNAL, adminEmail);
|
|
160
201
|
ctx.set(HEADERS.EXPOSE_HEADERS, HEADERS.ADMIN_TOKEN_SIGNAL);
|
|
161
|
-
|
|
162
|
-
ctx.session = null;
|
|
163
|
-
}
|
|
164
|
-
ctx.cookies.set(COOKIES.REFRESH_TOKEN, "", {
|
|
165
|
-
expires: /* @__PURE__ */ new Date(0),
|
|
166
|
-
path: "/admin",
|
|
167
|
-
httpOnly: true
|
|
168
|
-
});
|
|
202
|
+
clearSessionCookies(ctx);
|
|
169
203
|
const bearerToken = ctx.get("authorization")?.split("Bearer ")[1];
|
|
170
204
|
if (bearerToken) {
|
|
171
205
|
revokedConnectionTokens.set(bearerToken, Date.now());
|
|
@@ -199,9 +233,7 @@ async function interceptRenewToken(ctx, next) {
|
|
|
199
233
|
if (bearerToken) {
|
|
200
234
|
revokedConnectionTokens.set(bearerToken, Date.now());
|
|
201
235
|
}
|
|
202
|
-
|
|
203
|
-
ctx.session = null;
|
|
204
|
-
}
|
|
236
|
+
clearSessionCookies(ctx);
|
|
205
237
|
sessionActivityMap.delete(`${adminUser.id}:${adminUser.email}`);
|
|
206
238
|
}
|
|
207
239
|
await next();
|
|
@@ -381,29 +413,6 @@ const securitySettings = {
|
|
|
381
413
|
const contentTypes = {
|
|
382
414
|
"security-settings": securitySettings
|
|
383
415
|
};
|
|
384
|
-
class PluginError extends Error {
|
|
385
|
-
/**
|
|
386
|
-
* @param {string} message - Internal message (for logs)
|
|
387
|
-
* @param {string} sanitizedMessage - Safe message for the client
|
|
388
|
-
* @param {number} [statusCode=400] - HTTP status code
|
|
389
|
-
*/
|
|
390
|
-
constructor(message, sanitizedMessage, statusCode = HTTP_STATUS.BAD_REQUEST) {
|
|
391
|
-
super(message);
|
|
392
|
-
this.name = "PluginError";
|
|
393
|
-
this.sanitizedMessage = sanitizedMessage;
|
|
394
|
-
this.statusCode = statusCode;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
class ValidationError extends PluginError {
|
|
398
|
-
/**
|
|
399
|
-
* @param {string} message - Internal message
|
|
400
|
-
* @param {string} [sanitizedMessage='Validation failed.'] - Client-safe message
|
|
401
|
-
*/
|
|
402
|
-
constructor(message, sanitizedMessage = "Validation failed.") {
|
|
403
|
-
super(message, sanitizedMessage, HTTP_STATUS.BAD_REQUEST);
|
|
404
|
-
this.name = "ValidationError";
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
416
|
const validateSettingsPayload = (body) => {
|
|
408
417
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
409
418
|
throw new ValidationError(
|
package/dist/server/index.mjs
CHANGED
|
@@ -36,7 +36,9 @@ const COOKIES = {
|
|
|
36
36
|
SESSION: "koa.sess",
|
|
37
37
|
SESSION_SIG: "koa.sess.sig",
|
|
38
38
|
/** Strapi v5 admin refresh-token cookie (managed by session manager). */
|
|
39
|
-
REFRESH_TOKEN: "strapi_admin_refresh"
|
|
39
|
+
REFRESH_TOKEN: "strapi_admin_refresh",
|
|
40
|
+
/** JWT access-token cookie set by Strapi EE SSO authentication flow. */
|
|
41
|
+
JWT_TOKEN: "jwtToken"
|
|
40
42
|
};
|
|
41
43
|
const HEADERS = {
|
|
42
44
|
/** Header that signals the frontend to force-reload (session revoked). */
|
|
@@ -66,6 +68,47 @@ const DEFAULT_SETTINGS = {
|
|
|
66
68
|
enablePasswordManagement: true
|
|
67
69
|
};
|
|
68
70
|
const VALID_SETTINGS_KEYS = new Set(Object.keys(DEFAULT_SETTINGS));
|
|
71
|
+
class PluginError extends Error {
|
|
72
|
+
/**
|
|
73
|
+
* @param {string} message - Internal message (for logs)
|
|
74
|
+
* @param {string} sanitizedMessage - Safe message for the client
|
|
75
|
+
* @param {number} [statusCode=400] - HTTP status code
|
|
76
|
+
*/
|
|
77
|
+
constructor(message, sanitizedMessage, statusCode = HTTP_STATUS.BAD_REQUEST) {
|
|
78
|
+
super(message);
|
|
79
|
+
this.name = "PluginError";
|
|
80
|
+
this.sanitizedMessage = sanitizedMessage;
|
|
81
|
+
this.statusCode = statusCode;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
class ValidationError extends PluginError {
|
|
85
|
+
/**
|
|
86
|
+
* @param {string} message - Internal message
|
|
87
|
+
* @param {string} [sanitizedMessage='Validation failed.'] - Client-safe message
|
|
88
|
+
*/
|
|
89
|
+
constructor(message, sanitizedMessage = "Validation failed.") {
|
|
90
|
+
super(message, sanitizedMessage, HTTP_STATUS.BAD_REQUEST);
|
|
91
|
+
this.name = "ValidationError";
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function clearSessionCookies(ctx) {
|
|
95
|
+
if (ctx.session !== void 0) {
|
|
96
|
+
ctx.session = null;
|
|
97
|
+
}
|
|
98
|
+
const expireOpts = { expires: /* @__PURE__ */ new Date(0), path: "/", httpOnly: true };
|
|
99
|
+
ctx.cookies.set(COOKIES.SESSION, "", expireOpts);
|
|
100
|
+
ctx.cookies.set(COOKIES.SESSION_SIG, "", expireOpts);
|
|
101
|
+
ctx.cookies.set(COOKIES.REFRESH_TOKEN, "", {
|
|
102
|
+
expires: /* @__PURE__ */ new Date(0),
|
|
103
|
+
path: "/admin",
|
|
104
|
+
httpOnly: true
|
|
105
|
+
});
|
|
106
|
+
ctx.cookies.set(COOKIES.JWT_TOKEN, "", {
|
|
107
|
+
expires: /* @__PURE__ */ new Date(0),
|
|
108
|
+
path: "/",
|
|
109
|
+
httpOnly: false
|
|
110
|
+
});
|
|
111
|
+
}
|
|
69
112
|
async function trackActivity(ctx, next) {
|
|
70
113
|
const adminUser = ctx.state[CTX_ADMIN_USER];
|
|
71
114
|
let key = adminUser?.id ? `${adminUser.id}:${adminUser.email}` : null;
|
|
@@ -82,9 +125,7 @@ async function trackActivity(ctx, next) {
|
|
|
82
125
|
return;
|
|
83
126
|
}
|
|
84
127
|
if (ctx.path.includes(LOGOUT_PATH)) {
|
|
85
|
-
|
|
86
|
-
ctx.session = null;
|
|
87
|
-
}
|
|
128
|
+
clearSessionCookies(ctx);
|
|
88
129
|
key = null;
|
|
89
130
|
}
|
|
90
131
|
if (key) {
|
|
@@ -155,14 +196,7 @@ async function rejectRevokedTokens(ctx, next) {
|
|
|
155
196
|
if (adminEmail && revokedTokenSet.has(adminEmail)) {
|
|
156
197
|
ctx.set(HEADERS.ADMIN_TOKEN_SIGNAL, adminEmail);
|
|
157
198
|
ctx.set(HEADERS.EXPOSE_HEADERS, HEADERS.ADMIN_TOKEN_SIGNAL);
|
|
158
|
-
|
|
159
|
-
ctx.session = null;
|
|
160
|
-
}
|
|
161
|
-
ctx.cookies.set(COOKIES.REFRESH_TOKEN, "", {
|
|
162
|
-
expires: /* @__PURE__ */ new Date(0),
|
|
163
|
-
path: "/admin",
|
|
164
|
-
httpOnly: true
|
|
165
|
-
});
|
|
199
|
+
clearSessionCookies(ctx);
|
|
166
200
|
const bearerToken = ctx.get("authorization")?.split("Bearer ")[1];
|
|
167
201
|
if (bearerToken) {
|
|
168
202
|
revokedConnectionTokens.set(bearerToken, Date.now());
|
|
@@ -196,9 +230,7 @@ async function interceptRenewToken(ctx, next) {
|
|
|
196
230
|
if (bearerToken) {
|
|
197
231
|
revokedConnectionTokens.set(bearerToken, Date.now());
|
|
198
232
|
}
|
|
199
|
-
|
|
200
|
-
ctx.session = null;
|
|
201
|
-
}
|
|
233
|
+
clearSessionCookies(ctx);
|
|
202
234
|
sessionActivityMap.delete(`${adminUser.id}:${adminUser.email}`);
|
|
203
235
|
}
|
|
204
236
|
await next();
|
|
@@ -378,29 +410,6 @@ const securitySettings = {
|
|
|
378
410
|
const contentTypes = {
|
|
379
411
|
"security-settings": securitySettings
|
|
380
412
|
};
|
|
381
|
-
class PluginError extends Error {
|
|
382
|
-
/**
|
|
383
|
-
* @param {string} message - Internal message (for logs)
|
|
384
|
-
* @param {string} sanitizedMessage - Safe message for the client
|
|
385
|
-
* @param {number} [statusCode=400] - HTTP status code
|
|
386
|
-
*/
|
|
387
|
-
constructor(message, sanitizedMessage, statusCode = HTTP_STATUS.BAD_REQUEST) {
|
|
388
|
-
super(message);
|
|
389
|
-
this.name = "PluginError";
|
|
390
|
-
this.sanitizedMessage = sanitizedMessage;
|
|
391
|
-
this.statusCode = statusCode;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
class ValidationError extends PluginError {
|
|
395
|
-
/**
|
|
396
|
-
* @param {string} message - Internal message
|
|
397
|
-
* @param {string} [sanitizedMessage='Validation failed.'] - Client-safe message
|
|
398
|
-
*/
|
|
399
|
-
constructor(message, sanitizedMessage = "Validation failed.") {
|
|
400
|
-
super(message, sanitizedMessage, HTTP_STATUS.BAD_REQUEST);
|
|
401
|
-
this.name = "ValidationError";
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
413
|
const validateSettingsPayload = (body) => {
|
|
405
414
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
406
415
|
throw new ValidationError(
|
package/package.json
CHANGED