strapi-plugin-oidc 1.8.0 → 1.8.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.
@@ -6,6 +6,58 @@ import strapiUtils from "@strapi/utils";
6
6
  import generator from "generate-password";
7
7
  function register$1() {
8
8
  }
9
+ const errorCodes = {
10
+ TOKEN_EXCHANGE_FAILED: "TOKEN_EXCHANGE_FAILED",
11
+ USERINFO_FETCH_FAILED: "USERINFO_FETCH_FAILED",
12
+ ID_TOKEN_PARSE_FAILED: "ID_TOKEN_PARSE_FAILED",
13
+ NONCE_MISMATCH: "NONCE_MISMATCH",
14
+ ROLE_UPDATE_FAILED: "ROLE_UPDATE_FAILED",
15
+ USER_CREATION_FAILED: "USER_CREATION_FAILED",
16
+ WHITELIST_CHECK_FAILED: "WHITELIST_CHECK_FAILED",
17
+ EMAIL_NOT_VERIFIED: "EMAIL_NOT_VERIFIED",
18
+ ID_TOKEN_INVALID: "ID_TOKEN_INVALID"
19
+ };
20
+ const ERROR_DETAIL_TEMPLATES = {
21
+ token_exchange_failed: "Token exchange failed with HTTP status {status}",
22
+ userinfo_fetch_failed: "UserInfo endpoint returned HTTP {status}",
23
+ role_update_failed: "Role update failed for user {userId}: {error}",
24
+ user_creation_failed: "User creation failed for {email}: {error}",
25
+ id_token_parse_failed: "ID token parse failed: {error}",
26
+ sign_in_unknown: "Unknown sign-in error: {error}",
27
+ invalid_email: "Invalid email address received from OIDC provider",
28
+ email_not_verified: "Email address has not been verified by the OIDC provider",
29
+ id_token_invalid: "ID token verification failed: {error}",
30
+ whitelist_not_present: "Email not present in whitelist",
31
+ session_manager_unsupported: "sessionManager is not supported. Please upgrade to Strapi v5.24.1 or later."
32
+ };
33
+ function interpolate$1(template, params) {
34
+ if (!params) return template;
35
+ return template.replace(/\{(\w+)\}/g, (_, key) => String(params[key] ?? `{${key}}`));
36
+ }
37
+ function getErrorDetail(key, params) {
38
+ const template = ERROR_DETAIL_TEMPLATES[key];
39
+ if (!template) return void 0;
40
+ return interpolate$1(template, params);
41
+ }
42
+ const errorMessages = {
43
+ TOKEN_EXCHANGE_FAILED: "Token exchange failed",
44
+ USERINFO_FETCH_FAILED: "Failed to fetch user info",
45
+ ID_TOKEN_PARSE_FAILED: "Failed to parse ID token",
46
+ NONCE_MISMATCH: "Nonce mismatch",
47
+ INVALID_EMAIL: "Invalid email address received from OIDC provider",
48
+ EMAIL_NOT_VERIFIED: "Email address has not been verified by the OIDC provider",
49
+ ID_TOKEN_INVALID: "ID token verification failed",
50
+ WHITELIST_NOT_PRESENT: "Not present in whitelist",
51
+ SESSION_MANAGER_UNSUPPORTED: "sessionManager is not supported. Please upgrade to Strapi v5.24.1 or later.",
52
+ JWKS_URI_NOT_CONFIGURED: "[OIDC] OIDC_JWKS_URI is not configured — ID token signature verification is disabled. Set OIDC_JWKS_URI and OIDC_ISSUER from your provider's discovery document.",
53
+ ENFORCE_MIDDLEWARE_ERROR: "Error checking OIDC enforcement in middleware:",
54
+ ENFORCE_SYNC_ERROR: "[strapi-plugin-oidc] Failed to sync OIDC_ENFORCE to database:",
55
+ DEFAULT_ROLE_INIT_ERROR: "Could not initialize default OIDC role:",
56
+ AUDIT_LOG_CLEANUP_ERROR: "[strapi-plugin-oidc] Audit log cleanup failed:",
57
+ AUDIT_LOG_EXPORT_ERROR: "NDJSON export stream failed",
58
+ DISCOVERY_FETCH_ERROR: (url, reason) => `[strapi-plugin-oidc] Failed to fetch OIDC discovery document from ${url}: ${reason}`,
59
+ MISSING_CONFIG: (keys) => `Missing required config keys: ${keys}`
60
+ };
9
61
  function getEnforceOIDCConfig(strapi2) {
10
62
  const config2 = strapi2.config.get("plugin::strapi-plugin-oidc");
11
63
  const val = config2.OIDC_ENFORCE;
@@ -38,8 +90,43 @@ const getRoleService = () => strapi.plugin(PLUGIN_NAME).service("role");
38
90
  const getWhitelistService = () => strapi.plugin(PLUGIN_NAME).service("whitelist");
39
91
  const getAuditLogService = () => strapi.plugin(PLUGIN_NAME).service("auditLog");
40
92
  const getAdminUserService = () => strapi.service("admin::user");
93
+ const DISCOVERY_TIMEOUT_MS = 5e3;
94
+ const FIELD_MAP = [
95
+ ["issuer", "OIDC_ISSUER"],
96
+ ["authorization_endpoint", "OIDC_AUTHORIZATION_ENDPOINT"],
97
+ ["token_endpoint", "OIDC_TOKEN_ENDPOINT"],
98
+ ["userinfo_endpoint", "OIDC_USERINFO_ENDPOINT"],
99
+ ["end_session_endpoint", "OIDC_END_SESSION_ENDPOINT"],
100
+ ["jwks_uri", "OIDC_JWKS_URI"]
101
+ ];
102
+ async function applyDiscovery(strapi2) {
103
+ const config2 = strapi2.config.get("plugin::strapi-plugin-oidc");
104
+ const discoveryUrl = config2.OIDC_DISCOVERY_URL;
105
+ if (!discoveryUrl) return;
106
+ let doc;
107
+ try {
108
+ const res = await fetch(discoveryUrl, { signal: AbortSignal.timeout(DISCOVERY_TIMEOUT_MS) });
109
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
110
+ doc = await res.json();
111
+ } catch (e) {
112
+ throw new Error(
113
+ errorMessages.DISCOVERY_FETCH_ERROR(discoveryUrl, e instanceof Error ? e.message : String(e))
114
+ );
115
+ }
116
+ const updates = {};
117
+ for (const [docField, configKey] of FIELD_MAP) {
118
+ if (doc[docField]) {
119
+ updates[configKey] = doc[docField];
120
+ }
121
+ }
122
+ if (Object.keys(updates).length > 0) {
123
+ strapi2.config.set("plugin::strapi-plugin-oidc", { ...config2, ...updates });
124
+ strapi2.log.info(`[strapi-plugin-oidc] Discovery applied: ${Object.keys(updates).join(", ")}`);
125
+ }
126
+ }
41
127
  const AUTH_ROUTES = ["login", "register", "register-admin", "forgot-password", "reset-password"];
42
128
  async function bootstrap({ strapi: strapi2 }) {
129
+ await applyDiscovery(strapi2);
43
130
  const adminUrl = strapi2.config.get("admin.url", "/admin");
44
131
  const tokenRefreshPath = `${adminUrl}/token/refresh`;
45
132
  const enforceOidcMiddleware = async (ctx, next) => {
@@ -79,7 +166,7 @@ async function bootstrap({ strapi: strapi2 }) {
79
166
  return;
80
167
  }
81
168
  } catch (err) {
82
- strapi2.log.error("Error checking OIDC enforcement in middleware:", err);
169
+ strapi2.log.error(errorMessages.ENFORCE_MIDDLEWARE_ERROR, err);
83
170
  }
84
171
  }
85
172
  await next();
@@ -116,7 +203,7 @@ async function bootstrap({ strapi: strapi2 }) {
116
203
  );
117
204
  }
118
205
  } catch (err) {
119
- strapi2.log.error("[strapi-plugin-oidc] Failed to sync OIDC_ENFORCE to database:", err);
206
+ strapi2.log.error(errorMessages.ENFORCE_SYNC_ERROR, err);
120
207
  }
121
208
  }
122
209
  try {
@@ -130,7 +217,7 @@ async function bootstrap({ strapi: strapi2 }) {
130
217
  }
131
218
  }
132
219
  } catch (err) {
133
- strapi2.log.warn("Could not initialize default OIDC role:", err.message);
220
+ strapi2.log.warn(errorMessages.DEFAULT_ROLE_INIT_ERROR, err.message);
134
221
  }
135
222
  strapi2.cron.add({
136
223
  "strapi-plugin-oidc-audit-log-cleanup": {
@@ -139,7 +226,7 @@ async function bootstrap({ strapi: strapi2 }) {
139
226
  const retentionDays = getRetentionDays();
140
227
  await getAuditLogService().cleanup(retentionDays);
141
228
  } catch (err) {
142
- strapi2.log.warn("[strapi-plugin-oidc] Audit log cleanup failed:", err.message);
229
+ strapi2.log.warn(errorMessages.AUDIT_LOG_CLEANUP_ERROR, err.message);
143
230
  }
144
231
  },
145
232
  options: { rule: "0 0 * * *" }
@@ -151,17 +238,13 @@ function destroy() {
151
238
  const config = {
152
239
  default: {
153
240
  REMEMBER_ME: false,
241
+ OIDC_DISCOVERY_URL: "",
154
242
  OIDC_REDIRECT_URI: "http://localhost:1337/strapi-plugin-oidc/oidc/callback",
155
243
  OIDC_CLIENT_ID: "",
156
244
  OIDC_CLIENT_SECRET: "",
157
245
  OIDC_SCOPE: "openid profile email",
158
- OIDC_AUTHORIZATION_ENDPOINT: "",
159
- OIDC_TOKEN_ENDPOINT: "",
160
- OIDC_USERINFO_ENDPOINT: "",
161
- OIDC_GRANT_TYPE: "authorization_code",
162
246
  OIDC_FAMILY_NAME_FIELD: "family_name",
163
247
  OIDC_GIVEN_NAME_FIELD: "given_name",
164
- OIDC_END_SESSION_ENDPOINT: "",
165
248
  OIDC_SSO_BUTTON_TEXT: "Login via SSO",
166
249
  OIDC_ENFORCE: null,
167
250
  // null = use DB setting; true/false = override DB (useful for lockout recovery)
@@ -170,9 +253,14 @@ const config = {
170
253
  OIDC_GROUP_ROLE_MAP: "{}",
171
254
  OIDC_REQUIRE_EMAIL_VERIFIED: true,
172
255
  OIDC_TRUSTED_IP_HEADER: "",
256
+ OIDC_FORCE_SECURE_COOKIES: false,
257
+ // Populated at bootstrap from OIDC_DISCOVERY_URL — not user-configurable directly
258
+ OIDC_AUTHORIZATION_ENDPOINT: "",
259
+ OIDC_TOKEN_ENDPOINT: "",
260
+ OIDC_USERINFO_ENDPOINT: "",
261
+ OIDC_END_SESSION_ENDPOINT: "",
173
262
  OIDC_JWKS_URI: "",
174
- OIDC_ISSUER: "",
175
- OIDC_FORCE_SECURE_COOKIES: false
263
+ OIDC_ISSUER: ""
176
264
  },
177
265
  validator() {
178
266
  }
@@ -228,8 +316,7 @@ function shouldMarkSecure(strapi2, ctx) {
228
316
  if (config2.OIDC_FORCE_SECURE_COOKIES === true) return true;
229
317
  if (ctx.request.secure) return true;
230
318
  const proxyTrusted = ctx.app?.proxy === true;
231
- if (proxyTrusted && typeof ctx.get === "function" && ctx.get("x-forwarded-proto") === "https")
232
- return true;
319
+ if (proxyTrusted && ctx.get("x-forwarded-proto") === "https") return true;
233
320
  return false;
234
321
  }
235
322
  function getExpiredCookieOptions(strapi2, ctx) {
@@ -255,52 +342,6 @@ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
255
342
  function isValidEmail(email) {
256
343
  return EMAIL_REGEX.test(email);
257
344
  }
258
- const errorCodes = {
259
- TOKEN_EXCHANGE_FAILED: "TOKEN_EXCHANGE_FAILED",
260
- USERINFO_FETCH_FAILED: "USERINFO_FETCH_FAILED",
261
- ID_TOKEN_PARSE_FAILED: "ID_TOKEN_PARSE_FAILED",
262
- NONCE_MISMATCH: "NONCE_MISMATCH",
263
- ROLE_UPDATE_FAILED: "ROLE_UPDATE_FAILED",
264
- USER_CREATION_FAILED: "USER_CREATION_FAILED",
265
- WHITELIST_CHECK_FAILED: "WHITELIST_CHECK_FAILED",
266
- EMAIL_NOT_VERIFIED: "EMAIL_NOT_VERIFIED",
267
- ID_TOKEN_INVALID: "ID_TOKEN_INVALID"
268
- };
269
- const ERROR_DETAIL_TEMPLATES = {
270
- token_exchange_failed: "Token exchange failed with HTTP status {status}",
271
- userinfo_fetch_failed: "UserInfo endpoint returned HTTP {status}",
272
- role_update_failed: "Role update failed for user {userId}: {error}",
273
- user_creation_failed: "User creation failed for {email}: {error}",
274
- id_token_parse_failed: "ID token parse failed: {error}",
275
- sign_in_unknown: "Unknown sign-in error: {error}",
276
- invalid_email: "Invalid email address received from OIDC provider",
277
- email_not_verified: "Email address has not been verified by the OIDC provider",
278
- id_token_invalid: "ID token verification failed: {error}",
279
- whitelist_not_present: "Email not present in whitelist",
280
- session_manager_unsupported: "sessionManager is not supported. Please upgrade to Strapi v5.24.1 or later.",
281
- missing_config: "Missing required config keys: {keys}"
282
- };
283
- function interpolate$1(template, params) {
284
- if (!params) return template;
285
- return template.replace(/\{(\w+)\}/g, (_, key) => String(params[key] ?? `{${key}}`));
286
- }
287
- function getErrorDetail(key, params) {
288
- const template = ERROR_DETAIL_TEMPLATES[key];
289
- if (!template) return void 0;
290
- return interpolate$1(template, params);
291
- }
292
- const errorMessages = {
293
- TOKEN_EXCHANGE_FAILED: "Token exchange failed",
294
- USERINFO_FETCH_FAILED: "Failed to fetch user info",
295
- ID_TOKEN_PARSE_FAILED: "Failed to parse ID token",
296
- NONCE_MISMATCH: "Nonce mismatch",
297
- INVALID_EMAIL: "Invalid email address received from OIDC provider",
298
- EMAIL_NOT_VERIFIED: "Email address has not been verified by the OIDC provider",
299
- ID_TOKEN_INVALID: "ID token verification failed",
300
- WHITELIST_NOT_PRESENT: "Not present in whitelist",
301
- SESSION_MANAGER_UNSUPPORTED: "sessionManager is not supported. Please upgrade to Strapi v5.24.1 or later.",
302
- MISSING_CONFIG: (keys) => `Missing required config keys: ${keys}`
303
- };
304
345
  const en = {
305
346
  "global.plugins.strapi-plugin-oidc": "OIDC Plugin",
306
347
  "page.title": "Configure OIDC default role(s) and access controls.",
@@ -363,7 +404,6 @@ const en = {
363
404
  "auditlog.table.ip": "IP",
364
405
  "auditlog.table.details": "Details",
365
406
  "auditlog.table.empty": "No audit log entries",
366
- "auditlog.loading": "Loading…",
367
407
  "auditlog.clear": "Clear Logs",
368
408
  "auditlog.clear.title": "Clear All Logs",
369
409
  "auditlog.clear.description": "This will permanently delete all {count, plural, one {# audit log entry} other {# audit log entries}}. This action cannot be undone.",
@@ -418,7 +458,6 @@ const locales = Object.fromEntries(
418
458
  return [code ?? "", mod.default];
419
459
  })
420
460
  );
421
- Object.keys(locales).filter(Boolean);
422
461
  const DEFAULT_LOCALE = "en";
423
462
  function parseAcceptLanguage(header) {
424
463
  return header.split(",").map((part) => {
@@ -521,13 +560,13 @@ const OIDC_ERROR_DISPATCH = {
521
560
  key: "sign_in_unknown"
522
561
  }
523
562
  };
524
- const TRUSTED_HEADER_WHITELIST = /* @__PURE__ */ new Set(["cf-connecting-ip"]);
563
+ const TRUSTED_IP_HEADER = "cf-connecting-ip";
525
564
  function getTrustedHeaderName() {
526
565
  const config2 = strapi.config.get("plugin::strapi-plugin-oidc") ?? {};
527
566
  const raw = config2.OIDC_TRUSTED_IP_HEADER;
528
567
  if (typeof raw !== "string" || !raw) return void 0;
529
568
  const normalized = raw.trim().toLowerCase();
530
- return TRUSTED_HEADER_WHITELIST.has(normalized) ? normalized : void 0;
569
+ return normalized === TRUSTED_IP_HEADER ? normalized : void 0;
531
570
  }
532
571
  function getClientIp(ctx) {
533
572
  const proxyTrusted = ctx.app?.proxy === true;
@@ -544,19 +583,23 @@ function getClientIp(ctx) {
544
583
  }
545
584
  return ctx.ip;
546
585
  }
586
+ function toMessage(e) {
587
+ return e instanceof Error ? e.message : String(e);
588
+ }
547
589
  const REQUIRED_CONFIG_KEYS = [
590
+ "OIDC_DISCOVERY_URL",
548
591
  "OIDC_CLIENT_ID",
549
592
  "OIDC_CLIENT_SECRET",
550
593
  "OIDC_REDIRECT_URI",
551
594
  "OIDC_SCOPE",
552
- "OIDC_TOKEN_ENDPOINT",
553
- "OIDC_USERINFO_ENDPOINT",
554
- "OIDC_GRANT_TYPE",
555
595
  "OIDC_FAMILY_NAME_FIELD",
556
596
  "OIDC_GIVEN_NAME_FIELD",
597
+ // Populated at bootstrap from OIDC_DISCOVERY_URL — checked here as a runtime safety net
598
+ "OIDC_TOKEN_ENDPOINT",
599
+ "OIDC_USERINFO_ENDPOINT",
557
600
  "OIDC_AUTHORIZATION_ENDPOINT"
558
601
  ];
559
- const LOGOUT_USERINFO_TIMEOUT_MS = 3e3;
602
+ const LOGOUT_USERINFO_TIMEOUT_MS = 1500;
560
603
  const jwksCache = /* @__PURE__ */ new Map();
561
604
  let jwksDisabledWarned = false;
562
605
  function getJwks(uri) {
@@ -573,9 +616,7 @@ async function verifyIdToken(idToken, config2) {
573
616
  if (!jwksUri) {
574
617
  if (!jwksDisabledWarned) {
575
618
  jwksDisabledWarned = true;
576
- strapi.log.warn(
577
- "[OIDC] OIDC_JWKS_URI is not configured — ID token signature verification is disabled. Set OIDC_JWKS_URI and OIDC_ISSUER from your provider's discovery document."
578
- );
619
+ strapi.log.warn(errorMessages.JWKS_URI_NOT_CONFIGURED);
579
620
  }
580
621
  return null;
581
622
  }
@@ -588,7 +629,7 @@ async function verifyIdToken(idToken, config2) {
588
629
  return payload;
589
630
  } catch (e) {
590
631
  if (e instanceof errors.JWTClaimValidationFailed || e instanceof errors.JWSSignatureVerificationFailed || e instanceof errors.JWTExpired || e instanceof errors.JWTInvalid || e instanceof errors.JWSInvalid) {
591
- const msg = e instanceof Error ? e.message : String(e);
632
+ const msg = toMessage(e);
592
633
  throw new OidcError("id_token_invalid", msg, e);
593
634
  }
594
635
  throw e;
@@ -779,7 +820,7 @@ async function ensureUser(userService, oauthService2, email, userResponseData, c
779
820
  );
780
821
  return { user, userCreated: true, rolesUpdated: true };
781
822
  } catch (e) {
782
- const msg = e instanceof Error ? e.message : String(e);
823
+ const msg = toMessage(e);
783
824
  throw new OidcError("user_creation_failed", msg, e);
784
825
  }
785
826
  }
@@ -829,7 +870,7 @@ async function handleUserAuthentication(userService, oauthService2, roleService2
829
870
  function classifyOidcError(e, userInfo) {
830
871
  const kind = e instanceof OidcError ? e.kind : "unknown";
831
872
  const dispatch = OIDC_ERROR_DISPATCH[kind];
832
- const msg = e instanceof Error ? e.message : String(e);
873
+ const msg = toMessage(e);
833
874
  let params;
834
875
  if (kind === "id_token_parse_failed" || kind === "id_token_invalid" || kind === "unknown") {
835
876
  params = { error: msg };
@@ -878,7 +919,7 @@ async function logSuccessfulAuth(auditLog2, ctx, user, userCreated, rolesUpdated
878
919
  }
879
920
  async function handleCallbackError(e, userInfo, auditLog2, oauthService2, ctx) {
880
921
  const errorInfo = classifyOidcError(e, userInfo);
881
- const message = e instanceof Error ? e.message : String(e);
922
+ const message = toMessage(e);
882
923
  await auditLog2.log({
883
924
  action: errorInfo.action,
884
925
  email: userInfo?.email,
@@ -919,7 +960,7 @@ async function oidcSignInCallback(ctx) {
919
960
  client_id: config2.OIDC_CLIENT_ID,
920
961
  client_secret: config2.OIDC_CLIENT_SECRET,
921
962
  redirect_uri: config2.OIDC_REDIRECT_URI,
922
- grant_type: config2.OIDC_GRANT_TYPE,
963
+ grant_type: "authorization_code",
923
964
  code_verifier: codeVerifier ?? ""
924
965
  });
925
966
  let userInfo;
@@ -963,13 +1004,13 @@ async function oidcSignInCallback(ctx) {
963
1004
  await handleCallbackError(e, userInfo, auditLog2, oauthService2, ctx);
964
1005
  }
965
1006
  }
966
- async function isProviderSessionActive(userinfoEndpoint, accessToken) {
1007
+ async function isProviderSessionExpired(userinfoEndpoint, accessToken) {
967
1008
  try {
968
1009
  const response = await fetch(userinfoEndpoint, {
969
1010
  headers: { Authorization: `Bearer ${accessToken}` },
970
1011
  signal: AbortSignal.timeout(LOGOUT_USERINFO_TIMEOUT_MS)
971
1012
  });
972
- return response.ok;
1013
+ return !response.ok;
973
1014
  } catch {
974
1015
  return false;
975
1016
  }
@@ -989,14 +1030,14 @@ async function logout(ctx) {
989
1030
  }
990
1031
  const logAudit = (action) => userEmail ? auditLog2.log({ action, email: userEmail, ip: getClientIp(ctx) }) : Promise.resolve();
991
1032
  if (logoutUrl && accessToken) {
992
- const active = await isProviderSessionActive(config2.OIDC_USERINFO_ENDPOINT, accessToken);
993
- if (active) {
994
- logAudit("logout").catch(() => {
995
- });
996
- return ctx.redirect(logoutUrl);
1033
+ const expired = await isProviderSessionExpired(config2.OIDC_USERINFO_ENDPOINT, accessToken);
1034
+ if (expired) {
1035
+ await logAudit("session_expired");
1036
+ return ctx.redirect(loginUrl);
997
1037
  }
998
- await logAudit("session_expired");
999
- return ctx.redirect(loginUrl);
1038
+ logAudit("logout").catch(() => {
1039
+ });
1040
+ return ctx.redirect(logoutUrl);
1000
1041
  }
1001
1042
  await logAudit("logout");
1002
1043
  ctx.redirect(logoutUrl || loginUrl);
@@ -1154,13 +1195,9 @@ async function importUsers(ctx) {
1154
1195
  const whitelistService2 = getWhitelistService();
1155
1196
  const existing = await whitelistService2.getUsers();
1156
1197
  const existingEmails = new Set(existing.map((u) => u.email));
1157
- let importedCount = 0;
1158
- for (const email of deduped) {
1159
- if (existingEmails.has(email)) continue;
1160
- await whitelistService2.registerUser(email);
1161
- importedCount++;
1162
- }
1163
- ctx.body = { importedCount };
1198
+ const toImport = deduped.filter((email) => !existingEmails.has(email));
1199
+ await Promise.all(toImport.map((email) => whitelistService2.registerUser(email)));
1200
+ ctx.body = { importedCount: toImport.length };
1164
1201
  }
1165
1202
  async function syncUsers(ctx) {
1166
1203
  const { users: rawUsers } = ctx.request.body;
@@ -1169,16 +1206,10 @@ async function syncUsers(ctx) {
1169
1206
  const currentUsers = await whitelistService2.getUsers();
1170
1207
  const syncEmailSet = new Set(emails);
1171
1208
  const currentUsersByEmail = new Map(currentUsers.map((u) => [u.email, u]));
1172
- for (const currUser of currentUsers) {
1173
- if (!syncEmailSet.has(currUser.email)) {
1174
- await whitelistService2.removeUser(currUser.email);
1175
- }
1176
- }
1177
- for (const email of emails) {
1178
- if (!currentUsersByEmail.has(email)) {
1179
- await whitelistService2.registerUser(email);
1180
- }
1181
- }
1209
+ await Promise.all([
1210
+ ...currentUsers.filter((u) => !syncEmailSet.has(u.email)).map((u) => whitelistService2.removeUser(u.email)),
1211
+ ...emails.filter((email) => !currentUsersByEmail.has(email)).map((email) => whitelistService2.registerUser(email))
1212
+ ]);
1182
1213
  ctx.body = {};
1183
1214
  }
1184
1215
  const whitelist = {
@@ -1357,7 +1388,7 @@ function errorAwareNdjsonStream(strapi2, service, filters) {
1357
1388
  const gen = ndjsonRowStream(service, filters);
1358
1389
  const readable = Readable.from(gen);
1359
1390
  readable.on("error", (err) => {
1360
- strapi2.log.error({ phase: "audit_log_export", err }, "NDJSON export stream failed");
1391
+ strapi2.log.error({ phase: "audit_log_export", err }, errorMessages.AUDIT_LOG_EXPORT_ERROR);
1361
1392
  });
1362
1393
  return readable;
1363
1394
  }
@@ -1482,6 +1513,12 @@ const routes = {
1482
1513
  handler: "oidc.oidcSignInCallback",
1483
1514
  config: { auth: false, middlewares: [rateLimitMiddleware] }
1484
1515
  },
1516
+ {
1517
+ method: "GET",
1518
+ path: "/logout",
1519
+ handler: "oidc.logout",
1520
+ config: { auth: false }
1521
+ },
1485
1522
  {
1486
1523
  method: "POST",
1487
1524
  path: "/logout",
@@ -1790,9 +1827,10 @@ function oauthService({ strapi: strapi2 }) {
1790
1827
  }
1791
1828
  const modelDef = strapi2.getModel("admin::user");
1792
1829
  const sanitizedEntity = await strapiUtils.sanitize.sanitizers.defaultSanitizeOutput(
1793
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1794
- { schema: modelDef, getModel: (uid2) => strapi2.getModel(uid2) },
1795
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1830
+ {
1831
+ schema: modelDef,
1832
+ getModel: (uid2) => strapi2.getModel(uid2)
1833
+ },
1796
1834
  user
1797
1835
  );
1798
1836
  eventHub?.emit(ENTRY_CREATE ?? "entry.create", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-oidc",
3
- "version": "1.8.0",
3
+ "version": "1.8.2",
4
4
  "description": "A Strapi plugin that provides OpenID Connect (OIDC) authentication functionality for the Strapi Admin Panel.",
5
5
  "strapi": {
6
6
  "displayName": "OIDC Plugin",