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.
@@ -12,6 +12,58 @@ const strapiUtils__default = /* @__PURE__ */ _interopDefault(strapiUtils);
12
12
  const generator__default = /* @__PURE__ */ _interopDefault(generator);
13
13
  function register$1() {
14
14
  }
15
+ const errorCodes = {
16
+ TOKEN_EXCHANGE_FAILED: "TOKEN_EXCHANGE_FAILED",
17
+ USERINFO_FETCH_FAILED: "USERINFO_FETCH_FAILED",
18
+ ID_TOKEN_PARSE_FAILED: "ID_TOKEN_PARSE_FAILED",
19
+ NONCE_MISMATCH: "NONCE_MISMATCH",
20
+ ROLE_UPDATE_FAILED: "ROLE_UPDATE_FAILED",
21
+ USER_CREATION_FAILED: "USER_CREATION_FAILED",
22
+ WHITELIST_CHECK_FAILED: "WHITELIST_CHECK_FAILED",
23
+ EMAIL_NOT_VERIFIED: "EMAIL_NOT_VERIFIED",
24
+ ID_TOKEN_INVALID: "ID_TOKEN_INVALID"
25
+ };
26
+ const ERROR_DETAIL_TEMPLATES = {
27
+ token_exchange_failed: "Token exchange failed with HTTP status {status}",
28
+ userinfo_fetch_failed: "UserInfo endpoint returned HTTP {status}",
29
+ role_update_failed: "Role update failed for user {userId}: {error}",
30
+ user_creation_failed: "User creation failed for {email}: {error}",
31
+ id_token_parse_failed: "ID token parse failed: {error}",
32
+ sign_in_unknown: "Unknown sign-in error: {error}",
33
+ invalid_email: "Invalid email address received from OIDC provider",
34
+ email_not_verified: "Email address has not been verified by the OIDC provider",
35
+ id_token_invalid: "ID token verification failed: {error}",
36
+ whitelist_not_present: "Email not present in whitelist",
37
+ session_manager_unsupported: "sessionManager is not supported. Please upgrade to Strapi v5.24.1 or later."
38
+ };
39
+ function interpolate$1(template, params) {
40
+ if (!params) return template;
41
+ return template.replace(/\{(\w+)\}/g, (_, key) => String(params[key] ?? `{${key}}`));
42
+ }
43
+ function getErrorDetail(key, params) {
44
+ const template = ERROR_DETAIL_TEMPLATES[key];
45
+ if (!template) return void 0;
46
+ return interpolate$1(template, params);
47
+ }
48
+ const errorMessages = {
49
+ TOKEN_EXCHANGE_FAILED: "Token exchange failed",
50
+ USERINFO_FETCH_FAILED: "Failed to fetch user info",
51
+ ID_TOKEN_PARSE_FAILED: "Failed to parse ID token",
52
+ NONCE_MISMATCH: "Nonce mismatch",
53
+ INVALID_EMAIL: "Invalid email address received from OIDC provider",
54
+ EMAIL_NOT_VERIFIED: "Email address has not been verified by the OIDC provider",
55
+ ID_TOKEN_INVALID: "ID token verification failed",
56
+ WHITELIST_NOT_PRESENT: "Not present in whitelist",
57
+ SESSION_MANAGER_UNSUPPORTED: "sessionManager is not supported. Please upgrade to Strapi v5.24.1 or later.",
58
+ 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.",
59
+ ENFORCE_MIDDLEWARE_ERROR: "Error checking OIDC enforcement in middleware:",
60
+ ENFORCE_SYNC_ERROR: "[strapi-plugin-oidc] Failed to sync OIDC_ENFORCE to database:",
61
+ DEFAULT_ROLE_INIT_ERROR: "Could not initialize default OIDC role:",
62
+ AUDIT_LOG_CLEANUP_ERROR: "[strapi-plugin-oidc] Audit log cleanup failed:",
63
+ AUDIT_LOG_EXPORT_ERROR: "NDJSON export stream failed",
64
+ DISCOVERY_FETCH_ERROR: (url, reason) => `[strapi-plugin-oidc] Failed to fetch OIDC discovery document from ${url}: ${reason}`,
65
+ MISSING_CONFIG: (keys) => `Missing required config keys: ${keys}`
66
+ };
15
67
  function getEnforceOIDCConfig(strapi2) {
16
68
  const config2 = strapi2.config.get("plugin::strapi-plugin-oidc");
17
69
  const val = config2.OIDC_ENFORCE;
@@ -44,8 +96,43 @@ const getRoleService = () => strapi.plugin(PLUGIN_NAME).service("role");
44
96
  const getWhitelistService = () => strapi.plugin(PLUGIN_NAME).service("whitelist");
45
97
  const getAuditLogService = () => strapi.plugin(PLUGIN_NAME).service("auditLog");
46
98
  const getAdminUserService = () => strapi.service("admin::user");
99
+ const DISCOVERY_TIMEOUT_MS = 5e3;
100
+ const FIELD_MAP = [
101
+ ["issuer", "OIDC_ISSUER"],
102
+ ["authorization_endpoint", "OIDC_AUTHORIZATION_ENDPOINT"],
103
+ ["token_endpoint", "OIDC_TOKEN_ENDPOINT"],
104
+ ["userinfo_endpoint", "OIDC_USERINFO_ENDPOINT"],
105
+ ["end_session_endpoint", "OIDC_END_SESSION_ENDPOINT"],
106
+ ["jwks_uri", "OIDC_JWKS_URI"]
107
+ ];
108
+ async function applyDiscovery(strapi2) {
109
+ const config2 = strapi2.config.get("plugin::strapi-plugin-oidc");
110
+ const discoveryUrl = config2.OIDC_DISCOVERY_URL;
111
+ if (!discoveryUrl) return;
112
+ let doc;
113
+ try {
114
+ const res = await fetch(discoveryUrl, { signal: AbortSignal.timeout(DISCOVERY_TIMEOUT_MS) });
115
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
116
+ doc = await res.json();
117
+ } catch (e) {
118
+ throw new Error(
119
+ errorMessages.DISCOVERY_FETCH_ERROR(discoveryUrl, e instanceof Error ? e.message : String(e))
120
+ );
121
+ }
122
+ const updates = {};
123
+ for (const [docField, configKey] of FIELD_MAP) {
124
+ if (doc[docField]) {
125
+ updates[configKey] = doc[docField];
126
+ }
127
+ }
128
+ if (Object.keys(updates).length > 0) {
129
+ strapi2.config.set("plugin::strapi-plugin-oidc", { ...config2, ...updates });
130
+ strapi2.log.info(`[strapi-plugin-oidc] Discovery applied: ${Object.keys(updates).join(", ")}`);
131
+ }
132
+ }
47
133
  const AUTH_ROUTES = ["login", "register", "register-admin", "forgot-password", "reset-password"];
48
134
  async function bootstrap({ strapi: strapi2 }) {
135
+ await applyDiscovery(strapi2);
49
136
  const adminUrl = strapi2.config.get("admin.url", "/admin");
50
137
  const tokenRefreshPath = `${adminUrl}/token/refresh`;
51
138
  const enforceOidcMiddleware = async (ctx, next) => {
@@ -85,7 +172,7 @@ async function bootstrap({ strapi: strapi2 }) {
85
172
  return;
86
173
  }
87
174
  } catch (err) {
88
- strapi2.log.error("Error checking OIDC enforcement in middleware:", err);
175
+ strapi2.log.error(errorMessages.ENFORCE_MIDDLEWARE_ERROR, err);
89
176
  }
90
177
  }
91
178
  await next();
@@ -122,7 +209,7 @@ async function bootstrap({ strapi: strapi2 }) {
122
209
  );
123
210
  }
124
211
  } catch (err) {
125
- strapi2.log.error("[strapi-plugin-oidc] Failed to sync OIDC_ENFORCE to database:", err);
212
+ strapi2.log.error(errorMessages.ENFORCE_SYNC_ERROR, err);
126
213
  }
127
214
  }
128
215
  try {
@@ -136,7 +223,7 @@ async function bootstrap({ strapi: strapi2 }) {
136
223
  }
137
224
  }
138
225
  } catch (err) {
139
- strapi2.log.warn("Could not initialize default OIDC role:", err.message);
226
+ strapi2.log.warn(errorMessages.DEFAULT_ROLE_INIT_ERROR, err.message);
140
227
  }
141
228
  strapi2.cron.add({
142
229
  "strapi-plugin-oidc-audit-log-cleanup": {
@@ -145,7 +232,7 @@ async function bootstrap({ strapi: strapi2 }) {
145
232
  const retentionDays = getRetentionDays();
146
233
  await getAuditLogService().cleanup(retentionDays);
147
234
  } catch (err) {
148
- strapi2.log.warn("[strapi-plugin-oidc] Audit log cleanup failed:", err.message);
235
+ strapi2.log.warn(errorMessages.AUDIT_LOG_CLEANUP_ERROR, err.message);
149
236
  }
150
237
  },
151
238
  options: { rule: "0 0 * * *" }
@@ -157,17 +244,13 @@ function destroy() {
157
244
  const config = {
158
245
  default: {
159
246
  REMEMBER_ME: false,
247
+ OIDC_DISCOVERY_URL: "",
160
248
  OIDC_REDIRECT_URI: "http://localhost:1337/strapi-plugin-oidc/oidc/callback",
161
249
  OIDC_CLIENT_ID: "",
162
250
  OIDC_CLIENT_SECRET: "",
163
251
  OIDC_SCOPE: "openid profile email",
164
- OIDC_AUTHORIZATION_ENDPOINT: "",
165
- OIDC_TOKEN_ENDPOINT: "",
166
- OIDC_USERINFO_ENDPOINT: "",
167
- OIDC_GRANT_TYPE: "authorization_code",
168
252
  OIDC_FAMILY_NAME_FIELD: "family_name",
169
253
  OIDC_GIVEN_NAME_FIELD: "given_name",
170
- OIDC_END_SESSION_ENDPOINT: "",
171
254
  OIDC_SSO_BUTTON_TEXT: "Login via SSO",
172
255
  OIDC_ENFORCE: null,
173
256
  // null = use DB setting; true/false = override DB (useful for lockout recovery)
@@ -176,9 +259,14 @@ const config = {
176
259
  OIDC_GROUP_ROLE_MAP: "{}",
177
260
  OIDC_REQUIRE_EMAIL_VERIFIED: true,
178
261
  OIDC_TRUSTED_IP_HEADER: "",
262
+ OIDC_FORCE_SECURE_COOKIES: false,
263
+ // Populated at bootstrap from OIDC_DISCOVERY_URL — not user-configurable directly
264
+ OIDC_AUTHORIZATION_ENDPOINT: "",
265
+ OIDC_TOKEN_ENDPOINT: "",
266
+ OIDC_USERINFO_ENDPOINT: "",
267
+ OIDC_END_SESSION_ENDPOINT: "",
179
268
  OIDC_JWKS_URI: "",
180
- OIDC_ISSUER: "",
181
- OIDC_FORCE_SECURE_COOKIES: false
269
+ OIDC_ISSUER: ""
182
270
  },
183
271
  validator() {
184
272
  }
@@ -234,8 +322,7 @@ function shouldMarkSecure(strapi2, ctx) {
234
322
  if (config2.OIDC_FORCE_SECURE_COOKIES === true) return true;
235
323
  if (ctx.request.secure) return true;
236
324
  const proxyTrusted = ctx.app?.proxy === true;
237
- if (proxyTrusted && typeof ctx.get === "function" && ctx.get("x-forwarded-proto") === "https")
238
- return true;
325
+ if (proxyTrusted && ctx.get("x-forwarded-proto") === "https") return true;
239
326
  return false;
240
327
  }
241
328
  function getExpiredCookieOptions(strapi2, ctx) {
@@ -261,52 +348,6 @@ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
261
348
  function isValidEmail(email) {
262
349
  return EMAIL_REGEX.test(email);
263
350
  }
264
- const errorCodes = {
265
- TOKEN_EXCHANGE_FAILED: "TOKEN_EXCHANGE_FAILED",
266
- USERINFO_FETCH_FAILED: "USERINFO_FETCH_FAILED",
267
- ID_TOKEN_PARSE_FAILED: "ID_TOKEN_PARSE_FAILED",
268
- NONCE_MISMATCH: "NONCE_MISMATCH",
269
- ROLE_UPDATE_FAILED: "ROLE_UPDATE_FAILED",
270
- USER_CREATION_FAILED: "USER_CREATION_FAILED",
271
- WHITELIST_CHECK_FAILED: "WHITELIST_CHECK_FAILED",
272
- EMAIL_NOT_VERIFIED: "EMAIL_NOT_VERIFIED",
273
- ID_TOKEN_INVALID: "ID_TOKEN_INVALID"
274
- };
275
- const ERROR_DETAIL_TEMPLATES = {
276
- token_exchange_failed: "Token exchange failed with HTTP status {status}",
277
- userinfo_fetch_failed: "UserInfo endpoint returned HTTP {status}",
278
- role_update_failed: "Role update failed for user {userId}: {error}",
279
- user_creation_failed: "User creation failed for {email}: {error}",
280
- id_token_parse_failed: "ID token parse failed: {error}",
281
- sign_in_unknown: "Unknown sign-in error: {error}",
282
- invalid_email: "Invalid email address received from OIDC provider",
283
- email_not_verified: "Email address has not been verified by the OIDC provider",
284
- id_token_invalid: "ID token verification failed: {error}",
285
- whitelist_not_present: "Email not present in whitelist",
286
- session_manager_unsupported: "sessionManager is not supported. Please upgrade to Strapi v5.24.1 or later.",
287
- missing_config: "Missing required config keys: {keys}"
288
- };
289
- function interpolate$1(template, params) {
290
- if (!params) return template;
291
- return template.replace(/\{(\w+)\}/g, (_, key) => String(params[key] ?? `{${key}}`));
292
- }
293
- function getErrorDetail(key, params) {
294
- const template = ERROR_DETAIL_TEMPLATES[key];
295
- if (!template) return void 0;
296
- return interpolate$1(template, params);
297
- }
298
- const errorMessages = {
299
- TOKEN_EXCHANGE_FAILED: "Token exchange failed",
300
- USERINFO_FETCH_FAILED: "Failed to fetch user info",
301
- ID_TOKEN_PARSE_FAILED: "Failed to parse ID token",
302
- NONCE_MISMATCH: "Nonce mismatch",
303
- INVALID_EMAIL: "Invalid email address received from OIDC provider",
304
- EMAIL_NOT_VERIFIED: "Email address has not been verified by the OIDC provider",
305
- ID_TOKEN_INVALID: "ID token verification failed",
306
- WHITELIST_NOT_PRESENT: "Not present in whitelist",
307
- SESSION_MANAGER_UNSUPPORTED: "sessionManager is not supported. Please upgrade to Strapi v5.24.1 or later.",
308
- MISSING_CONFIG: (keys) => `Missing required config keys: ${keys}`
309
- };
310
351
  const en = {
311
352
  "global.plugins.strapi-plugin-oidc": "OIDC Plugin",
312
353
  "page.title": "Configure OIDC default role(s) and access controls.",
@@ -369,7 +410,6 @@ const en = {
369
410
  "auditlog.table.ip": "IP",
370
411
  "auditlog.table.details": "Details",
371
412
  "auditlog.table.empty": "No audit log entries",
372
- "auditlog.loading": "Loading…",
373
413
  "auditlog.clear": "Clear Logs",
374
414
  "auditlog.clear.title": "Clear All Logs",
375
415
  "auditlog.clear.description": "This will permanently delete all {count, plural, one {# audit log entry} other {# audit log entries}}. This action cannot be undone.",
@@ -424,7 +464,6 @@ const locales = Object.fromEntries(
424
464
  return [code ?? "", mod.default];
425
465
  })
426
466
  );
427
- Object.keys(locales).filter(Boolean);
428
467
  const DEFAULT_LOCALE = "en";
429
468
  function parseAcceptLanguage(header) {
430
469
  return header.split(",").map((part) => {
@@ -527,13 +566,13 @@ const OIDC_ERROR_DISPATCH = {
527
566
  key: "sign_in_unknown"
528
567
  }
529
568
  };
530
- const TRUSTED_HEADER_WHITELIST = /* @__PURE__ */ new Set(["cf-connecting-ip"]);
569
+ const TRUSTED_IP_HEADER = "cf-connecting-ip";
531
570
  function getTrustedHeaderName() {
532
571
  const config2 = strapi.config.get("plugin::strapi-plugin-oidc") ?? {};
533
572
  const raw = config2.OIDC_TRUSTED_IP_HEADER;
534
573
  if (typeof raw !== "string" || !raw) return void 0;
535
574
  const normalized = raw.trim().toLowerCase();
536
- return TRUSTED_HEADER_WHITELIST.has(normalized) ? normalized : void 0;
575
+ return normalized === TRUSTED_IP_HEADER ? normalized : void 0;
537
576
  }
538
577
  function getClientIp(ctx) {
539
578
  const proxyTrusted = ctx.app?.proxy === true;
@@ -550,19 +589,23 @@ function getClientIp(ctx) {
550
589
  }
551
590
  return ctx.ip;
552
591
  }
592
+ function toMessage(e) {
593
+ return e instanceof Error ? e.message : String(e);
594
+ }
553
595
  const REQUIRED_CONFIG_KEYS = [
596
+ "OIDC_DISCOVERY_URL",
554
597
  "OIDC_CLIENT_ID",
555
598
  "OIDC_CLIENT_SECRET",
556
599
  "OIDC_REDIRECT_URI",
557
600
  "OIDC_SCOPE",
558
- "OIDC_TOKEN_ENDPOINT",
559
- "OIDC_USERINFO_ENDPOINT",
560
- "OIDC_GRANT_TYPE",
561
601
  "OIDC_FAMILY_NAME_FIELD",
562
602
  "OIDC_GIVEN_NAME_FIELD",
603
+ // Populated at bootstrap from OIDC_DISCOVERY_URL — checked here as a runtime safety net
604
+ "OIDC_TOKEN_ENDPOINT",
605
+ "OIDC_USERINFO_ENDPOINT",
563
606
  "OIDC_AUTHORIZATION_ENDPOINT"
564
607
  ];
565
- const LOGOUT_USERINFO_TIMEOUT_MS = 3e3;
608
+ const LOGOUT_USERINFO_TIMEOUT_MS = 1500;
566
609
  const jwksCache = /* @__PURE__ */ new Map();
567
610
  let jwksDisabledWarned = false;
568
611
  function getJwks(uri) {
@@ -579,9 +622,7 @@ async function verifyIdToken(idToken, config2) {
579
622
  if (!jwksUri) {
580
623
  if (!jwksDisabledWarned) {
581
624
  jwksDisabledWarned = true;
582
- strapi.log.warn(
583
- "[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."
584
- );
625
+ strapi.log.warn(errorMessages.JWKS_URI_NOT_CONFIGURED);
585
626
  }
586
627
  return null;
587
628
  }
@@ -594,7 +635,7 @@ async function verifyIdToken(idToken, config2) {
594
635
  return payload;
595
636
  } catch (e) {
596
637
  if (e instanceof jose.errors.JWTClaimValidationFailed || e instanceof jose.errors.JWSSignatureVerificationFailed || e instanceof jose.errors.JWTExpired || e instanceof jose.errors.JWTInvalid || e instanceof jose.errors.JWSInvalid) {
597
- const msg = e instanceof Error ? e.message : String(e);
638
+ const msg = toMessage(e);
598
639
  throw new OidcError("id_token_invalid", msg, e);
599
640
  }
600
641
  throw e;
@@ -785,7 +826,7 @@ async function ensureUser(userService, oauthService2, email, userResponseData, c
785
826
  );
786
827
  return { user, userCreated: true, rolesUpdated: true };
787
828
  } catch (e) {
788
- const msg = e instanceof Error ? e.message : String(e);
829
+ const msg = toMessage(e);
789
830
  throw new OidcError("user_creation_failed", msg, e);
790
831
  }
791
832
  }
@@ -835,7 +876,7 @@ async function handleUserAuthentication(userService, oauthService2, roleService2
835
876
  function classifyOidcError(e, userInfo) {
836
877
  const kind = e instanceof OidcError ? e.kind : "unknown";
837
878
  const dispatch = OIDC_ERROR_DISPATCH[kind];
838
- const msg = e instanceof Error ? e.message : String(e);
879
+ const msg = toMessage(e);
839
880
  let params;
840
881
  if (kind === "id_token_parse_failed" || kind === "id_token_invalid" || kind === "unknown") {
841
882
  params = { error: msg };
@@ -884,7 +925,7 @@ async function logSuccessfulAuth(auditLog2, ctx, user, userCreated, rolesUpdated
884
925
  }
885
926
  async function handleCallbackError(e, userInfo, auditLog2, oauthService2, ctx) {
886
927
  const errorInfo = classifyOidcError(e, userInfo);
887
- const message = e instanceof Error ? e.message : String(e);
928
+ const message = toMessage(e);
888
929
  await auditLog2.log({
889
930
  action: errorInfo.action,
890
931
  email: userInfo?.email,
@@ -925,7 +966,7 @@ async function oidcSignInCallback(ctx) {
925
966
  client_id: config2.OIDC_CLIENT_ID,
926
967
  client_secret: config2.OIDC_CLIENT_SECRET,
927
968
  redirect_uri: config2.OIDC_REDIRECT_URI,
928
- grant_type: config2.OIDC_GRANT_TYPE,
969
+ grant_type: "authorization_code",
929
970
  code_verifier: codeVerifier ?? ""
930
971
  });
931
972
  let userInfo;
@@ -969,13 +1010,13 @@ async function oidcSignInCallback(ctx) {
969
1010
  await handleCallbackError(e, userInfo, auditLog2, oauthService2, ctx);
970
1011
  }
971
1012
  }
972
- async function isProviderSessionActive(userinfoEndpoint, accessToken) {
1013
+ async function isProviderSessionExpired(userinfoEndpoint, accessToken) {
973
1014
  try {
974
1015
  const response = await fetch(userinfoEndpoint, {
975
1016
  headers: { Authorization: `Bearer ${accessToken}` },
976
1017
  signal: AbortSignal.timeout(LOGOUT_USERINFO_TIMEOUT_MS)
977
1018
  });
978
- return response.ok;
1019
+ return !response.ok;
979
1020
  } catch {
980
1021
  return false;
981
1022
  }
@@ -995,14 +1036,14 @@ async function logout(ctx) {
995
1036
  }
996
1037
  const logAudit = (action) => userEmail ? auditLog2.log({ action, email: userEmail, ip: getClientIp(ctx) }) : Promise.resolve();
997
1038
  if (logoutUrl && accessToken) {
998
- const active = await isProviderSessionActive(config2.OIDC_USERINFO_ENDPOINT, accessToken);
999
- if (active) {
1000
- logAudit("logout").catch(() => {
1001
- });
1002
- return ctx.redirect(logoutUrl);
1039
+ const expired = await isProviderSessionExpired(config2.OIDC_USERINFO_ENDPOINT, accessToken);
1040
+ if (expired) {
1041
+ await logAudit("session_expired");
1042
+ return ctx.redirect(loginUrl);
1003
1043
  }
1004
- await logAudit("session_expired");
1005
- return ctx.redirect(loginUrl);
1044
+ logAudit("logout").catch(() => {
1045
+ });
1046
+ return ctx.redirect(logoutUrl);
1006
1047
  }
1007
1048
  await logAudit("logout");
1008
1049
  ctx.redirect(logoutUrl || loginUrl);
@@ -1160,13 +1201,9 @@ async function importUsers(ctx) {
1160
1201
  const whitelistService2 = getWhitelistService();
1161
1202
  const existing = await whitelistService2.getUsers();
1162
1203
  const existingEmails = new Set(existing.map((u) => u.email));
1163
- let importedCount = 0;
1164
- for (const email of deduped) {
1165
- if (existingEmails.has(email)) continue;
1166
- await whitelistService2.registerUser(email);
1167
- importedCount++;
1168
- }
1169
- ctx.body = { importedCount };
1204
+ const toImport = deduped.filter((email) => !existingEmails.has(email));
1205
+ await Promise.all(toImport.map((email) => whitelistService2.registerUser(email)));
1206
+ ctx.body = { importedCount: toImport.length };
1170
1207
  }
1171
1208
  async function syncUsers(ctx) {
1172
1209
  const { users: rawUsers } = ctx.request.body;
@@ -1175,16 +1212,10 @@ async function syncUsers(ctx) {
1175
1212
  const currentUsers = await whitelistService2.getUsers();
1176
1213
  const syncEmailSet = new Set(emails);
1177
1214
  const currentUsersByEmail = new Map(currentUsers.map((u) => [u.email, u]));
1178
- for (const currUser of currentUsers) {
1179
- if (!syncEmailSet.has(currUser.email)) {
1180
- await whitelistService2.removeUser(currUser.email);
1181
- }
1182
- }
1183
- for (const email of emails) {
1184
- if (!currentUsersByEmail.has(email)) {
1185
- await whitelistService2.registerUser(email);
1186
- }
1187
- }
1215
+ await Promise.all([
1216
+ ...currentUsers.filter((u) => !syncEmailSet.has(u.email)).map((u) => whitelistService2.removeUser(u.email)),
1217
+ ...emails.filter((email) => !currentUsersByEmail.has(email)).map((email) => whitelistService2.registerUser(email))
1218
+ ]);
1188
1219
  ctx.body = {};
1189
1220
  }
1190
1221
  const whitelist = {
@@ -1363,7 +1394,7 @@ function errorAwareNdjsonStream(strapi2, service, filters) {
1363
1394
  const gen = ndjsonRowStream(service, filters);
1364
1395
  const readable = node_stream.Readable.from(gen);
1365
1396
  readable.on("error", (err) => {
1366
- strapi2.log.error({ phase: "audit_log_export", err }, "NDJSON export stream failed");
1397
+ strapi2.log.error({ phase: "audit_log_export", err }, errorMessages.AUDIT_LOG_EXPORT_ERROR);
1367
1398
  });
1368
1399
  return readable;
1369
1400
  }
@@ -1488,6 +1519,12 @@ const routes = {
1488
1519
  handler: "oidc.oidcSignInCallback",
1489
1520
  config: { auth: false, middlewares: [rateLimitMiddleware] }
1490
1521
  },
1522
+ {
1523
+ method: "GET",
1524
+ path: "/logout",
1525
+ handler: "oidc.logout",
1526
+ config: { auth: false }
1527
+ },
1491
1528
  {
1492
1529
  method: "POST",
1493
1530
  path: "/logout",
@@ -1796,9 +1833,10 @@ function oauthService({ strapi: strapi2 }) {
1796
1833
  }
1797
1834
  const modelDef = strapi2.getModel("admin::user");
1798
1835
  const sanitizedEntity = await strapiUtils__default.default.sanitize.sanitizers.defaultSanitizeOutput(
1799
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1800
- { schema: modelDef, getModel: (uid2) => strapi2.getModel(uid2) },
1801
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1836
+ {
1837
+ schema: modelDef,
1838
+ getModel: (uid2) => strapi2.getModel(uid2)
1839
+ },
1802
1840
  user
1803
1841
  );
1804
1842
  eventHub?.emit(ENTRY_CREATE ?? "entry.create", {