strapi-security-suite 0.3.1 → 0.3.3

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.
@@ -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,52 @@ 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
+ const configuredSecure = strapi.config.get("admin.auth.cookie.secure");
110
+ const isProduction = process.env.NODE_ENV === "production";
111
+ const jwtClearOpts = {
112
+ expires: /* @__PURE__ */ new Date(0),
113
+ httpOnly: false,
114
+ secure: typeof configuredSecure === "boolean" ? configuredSecure : isProduction,
115
+ domain: strapi.config.get("admin.auth.domain"),
116
+ overwrite: true
117
+ };
118
+ ctx.cookies.set(COOKIES.JWT_TOKEN, "", jwtClearOpts);
119
+ }
72
120
  async function trackActivity(ctx, next) {
73
121
  const adminUser = ctx.state[CTX_ADMIN_USER];
74
122
  let key = adminUser?.id ? `${adminUser.id}:${adminUser.email}` : null;
@@ -85,9 +133,7 @@ async function trackActivity(ctx, next) {
85
133
  return;
86
134
  }
87
135
  if (ctx.path.includes(LOGOUT_PATH)) {
88
- if (ctx.session !== void 0) {
89
- ctx.session = null;
90
- }
136
+ clearSessionCookies(ctx);
91
137
  key = null;
92
138
  }
93
139
  if (key) {
@@ -158,14 +204,7 @@ async function rejectRevokedTokens(ctx, next) {
158
204
  if (adminEmail && revokedTokenSet.has(adminEmail)) {
159
205
  ctx.set(HEADERS.ADMIN_TOKEN_SIGNAL, adminEmail);
160
206
  ctx.set(HEADERS.EXPOSE_HEADERS, HEADERS.ADMIN_TOKEN_SIGNAL);
161
- if (ctx.session !== void 0) {
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
- });
207
+ clearSessionCookies(ctx);
169
208
  const bearerToken = ctx.get("authorization")?.split("Bearer ")[1];
170
209
  if (bearerToken) {
171
210
  revokedConnectionTokens.set(bearerToken, Date.now());
@@ -199,9 +238,7 @@ async function interceptRenewToken(ctx, next) {
199
238
  if (bearerToken) {
200
239
  revokedConnectionTokens.set(bearerToken, Date.now());
201
240
  }
202
- if (ctx.session !== void 0) {
203
- ctx.session = null;
204
- }
241
+ clearSessionCookies(ctx);
205
242
  sessionActivityMap.delete(`${adminUser.id}:${adminUser.email}`);
206
243
  }
207
244
  await next();
@@ -381,29 +418,6 @@ const securitySettings = {
381
418
  const contentTypes = {
382
419
  "security-settings": securitySettings
383
420
  };
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
421
  const validateSettingsPayload = (body) => {
408
422
  if (!body || typeof body !== "object" || Array.isArray(body)) {
409
423
  throw new ValidationError(
@@ -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,52 @@ 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
+ const configuredSecure = strapi.config.get("admin.auth.cookie.secure");
107
+ const isProduction = process.env.NODE_ENV === "production";
108
+ const jwtClearOpts = {
109
+ expires: /* @__PURE__ */ new Date(0),
110
+ httpOnly: false,
111
+ secure: typeof configuredSecure === "boolean" ? configuredSecure : isProduction,
112
+ domain: strapi.config.get("admin.auth.domain"),
113
+ overwrite: true
114
+ };
115
+ ctx.cookies.set(COOKIES.JWT_TOKEN, "", jwtClearOpts);
116
+ }
69
117
  async function trackActivity(ctx, next) {
70
118
  const adminUser = ctx.state[CTX_ADMIN_USER];
71
119
  let key = adminUser?.id ? `${adminUser.id}:${adminUser.email}` : null;
@@ -82,9 +130,7 @@ async function trackActivity(ctx, next) {
82
130
  return;
83
131
  }
84
132
  if (ctx.path.includes(LOGOUT_PATH)) {
85
- if (ctx.session !== void 0) {
86
- ctx.session = null;
87
- }
133
+ clearSessionCookies(ctx);
88
134
  key = null;
89
135
  }
90
136
  if (key) {
@@ -155,14 +201,7 @@ async function rejectRevokedTokens(ctx, next) {
155
201
  if (adminEmail && revokedTokenSet.has(adminEmail)) {
156
202
  ctx.set(HEADERS.ADMIN_TOKEN_SIGNAL, adminEmail);
157
203
  ctx.set(HEADERS.EXPOSE_HEADERS, HEADERS.ADMIN_TOKEN_SIGNAL);
158
- if (ctx.session !== void 0) {
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
- });
204
+ clearSessionCookies(ctx);
166
205
  const bearerToken = ctx.get("authorization")?.split("Bearer ")[1];
167
206
  if (bearerToken) {
168
207
  revokedConnectionTokens.set(bearerToken, Date.now());
@@ -196,9 +235,7 @@ async function interceptRenewToken(ctx, next) {
196
235
  if (bearerToken) {
197
236
  revokedConnectionTokens.set(bearerToken, Date.now());
198
237
  }
199
- if (ctx.session !== void 0) {
200
- ctx.session = null;
201
- }
238
+ clearSessionCookies(ctx);
202
239
  sessionActivityMap.delete(`${adminUser.id}:${adminUser.email}`);
203
240
  }
204
241
  await next();
@@ -378,29 +415,6 @@ const securitySettings = {
378
415
  const contentTypes = {
379
416
  "security-settings": securitySettings
380
417
  };
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
418
  const validateSettingsPayload = (body) => {
405
419
  if (!body || typeof body !== "object" || Array.isArray(body)) {
406
420
  throw new ValidationError(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-security-suite",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
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>",