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.
@@ -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
- if (ctx.session !== void 0) {
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
- 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
- });
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
- if (ctx.session !== void 0) {
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(
@@ -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
- if (ctx.session !== void 0) {
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
- 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
- });
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
- if (ctx.session !== void 0) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-security-suite",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
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>",