strapi-plugin-oidc 1.8.0 → 1.8.1

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.
@@ -2,10 +2,10 @@ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
2
  import { useBlocker, Routes, Route } from "react-router-dom";
3
3
  import { useNotification, useFetchClient, Page, Layouts } from "@strapi/strapi/admin";
4
4
  import { useState, useRef, useId, useEffect, useCallback, useReducer, useMemo, memo } from "react";
5
- import { Typography, Flex, Box, MultiSelect, MultiSelectOption, Button, Dialog, Table, Pagination, PreviousLink, NextLink, PageLink, Field, Divider, Thead, Tr, Th, Tbody, Td, IconButton, Tooltip, Alert } from "@strapi/design-system";
5
+ import { Typography, Flex, Box, MultiSelect, MultiSelectOption, Button, Dialog, Table, Pagination, PreviousLink, NextLink, PageLink, Field, Divider, Thead, Tr, Th, Tbody, Td, IconButton, Loader, Tooltip, Alert } from "@strapi/design-system";
6
6
  import { Cross, WarningCircle, Plus, Download, Upload, Trash, Calendar, Mail, Information } from "@strapi/icons";
7
7
  import { useIntl } from "react-intl";
8
- import { g as getTrad } from "./index-B-K4X_N9.mjs";
8
+ import { g as getTrad } from "./index-Bb9-aYb4.mjs";
9
9
  import styled from "styled-components";
10
10
  import { Filter, ClipboardList, Server } from "lucide-react";
11
11
  function Role({ oidcRoles, roles, onChangeRole }) {
@@ -1014,7 +1014,7 @@ function Whitelist({
1014
1014
  /* @__PURE__ */ jsx(Th, { children: formatMessage(getTrad("whitelist.table.created")) }),
1015
1015
  /* @__PURE__ */ jsx(Th, { style: { paddingRight: 0 }, children: " " })
1016
1016
  ] }) }),
1017
- /* @__PURE__ */ jsx(Tbody, { children: users.length === 0 ? /* @__PURE__ */ jsx(Tr, { children: /* @__PURE__ */ jsx(Td, { colSpan: 4, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: formatMessage(getTrad("whitelist.table.empty")) }) }) }) }) : paginatedUsers.map((user, index) => /* @__PURE__ */ jsxs(Tr, { children: [
1017
+ /* @__PURE__ */ jsx(Tbody, { children: users.length === 0 ? /* @__PURE__ */ jsx(Tr, { children: /* @__PURE__ */ jsx(Td, { colSpan: 4, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", alignItems: "center", style: { minHeight: "80px" }, children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: formatMessage(getTrad("whitelist.table.empty")) }) }) }) }) : paginatedUsers.map((user, index) => /* @__PURE__ */ jsxs(Tr, { children: [
1018
1018
  /* @__PURE__ */ jsx(Td, { children: index + 1 + (page - 1) * PAGE_SIZE$1 }),
1019
1019
  /* @__PURE__ */ jsx(Td, { children: user.email }),
1020
1020
  /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(LocalizedDate, { date: user.createdAt, options: { second: "2-digit" } }) }),
@@ -3618,6 +3618,7 @@ const AUDIT_ACTIONS = [
3618
3618
  "user_created"
3619
3619
  ];
3620
3620
  const PAGE_SIZE = 10;
3621
+ const MIN_SPINNER_MS = 400;
3621
3622
  const DETAILS_TEXT_STYLE = {
3622
3623
  display: "block",
3623
3624
  overflow: "hidden",
@@ -3667,28 +3668,35 @@ function AuditLog({ title } = {}) {
3667
3668
  pageCount: 1
3668
3669
  });
3669
3670
  const [page, setPage] = useState(1);
3670
- const [loading, setLoading] = useState(false);
3671
+ const [loading, setLoading] = useState(true);
3671
3672
  const [filters, setFilters] = useState({});
3673
+ const fetchGenRef = useRef(0);
3672
3674
  const debouncedFilters = useDebounced(filters);
3673
3675
  const fetchLogs = useCallback(
3674
3676
  async (p, f) => {
3677
+ const gen = ++fetchGenRef.current;
3675
3678
  setLoading(true);
3679
+ const startTime = Date.now();
3680
+ let newRecords = [];
3681
+ let newPagination = { page: p, pageSize: PAGE_SIZE, total: 0, pageCount: 1 };
3676
3682
  try {
3677
- const queryString = buildQueryString({
3678
- filters: f,
3679
- page: p,
3680
- pageSize: PAGE_SIZE
3681
- });
3683
+ const queryString = buildQueryString({ filters: f, page: p, pageSize: PAGE_SIZE });
3682
3684
  const response = await get2(`/strapi-plugin-oidc/audit-logs?${queryString}`);
3683
- setRecords(response.data.results ?? []);
3684
- setPagination(
3685
- response.data.pagination ?? { page: p, pageSize: PAGE_SIZE, total: 0, pageCount: 1 }
3686
- );
3685
+ newRecords = response.data.results ?? [];
3686
+ newPagination = response.data.pagination ?? {
3687
+ page: p,
3688
+ pageSize: PAGE_SIZE,
3689
+ total: 0,
3690
+ pageCount: 1
3691
+ };
3687
3692
  } catch {
3688
- setRecords([]);
3689
- } finally {
3690
- setLoading(false);
3691
3693
  }
3694
+ const remaining = MIN_SPINNER_MS - (Date.now() - startTime);
3695
+ if (remaining > 0) await new Promise((r) => setTimeout(r, remaining));
3696
+ if (gen !== fetchGenRef.current) return;
3697
+ setRecords(newRecords);
3698
+ setPagination(newPagination);
3699
+ setLoading(false);
3692
3700
  },
3693
3701
  [get2]
3694
3702
  );
@@ -3865,37 +3873,54 @@ function AuditLog({ title } = {}) {
3865
3873
  ]
3866
3874
  }
3867
3875
  ),
3868
- /* @__PURE__ */ jsxs(CustomTable, { colCount: 5, rowCount: records.length, children: [
3869
- /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
3870
- /* @__PURE__ */ jsx(Th, { children: formatMessage(getTrad("auditlog.table.timestamp")) }),
3871
- /* @__PURE__ */ jsx(Th, { children: formatMessage(getTrad("auditlog.table.action")) }),
3872
- /* @__PURE__ */ jsx(Th, { children: formatMessage(getTrad("auditlog.table.email")) }),
3873
- /* @__PURE__ */ jsx(Th, { children: formatMessage(getTrad("auditlog.table.ip")) }),
3874
- /* @__PURE__ */ jsx(Th, { children: formatMessage(getTrad("auditlog.table.details")) })
3875
- ] }) }),
3876
- /* @__PURE__ */ jsxs(Tbody, { children: [
3877
- loading && /* @__PURE__ */ jsx(Tr, { children: /* @__PURE__ */ jsx(Td, { colSpan: 5, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: formatMessage(getTrad("auditlog.loading")) }) }) }) }),
3878
- !loading && records.length === 0 && /* @__PURE__ */ jsx(Tr, { children: /* @__PURE__ */ jsx(Td, { colSpan: 5, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: hasActiveFilters ? formatMessage(getTrad("auditlog.filters.empty")) : formatMessage(getTrad("auditlog.table.empty")) }) }) }) }),
3879
- !loading && records.map((record) => /* @__PURE__ */ jsxs(Tr, { children: [
3880
- /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: /* @__PURE__ */ jsx(LocalizedDate, { date: record.createdAt, options: { second: "2-digit" } }) }) }),
3881
- /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
3882
- /* @__PURE__ */ jsx(Typography, { variant: "omega", children: record.action }),
3883
- /* @__PURE__ */ jsx(Tooltip, { label: formatMessage(getTrad(`auditlog.action.${record.action}`)), children: /* @__PURE__ */ jsx(
3884
- Information,
3885
- {
3886
- "aria-hidden": true,
3887
- style: { cursor: "help" },
3888
- width: "1.4rem",
3889
- height: "1.4rem",
3890
- fill: "primary600"
3891
- }
3892
- ) })
3893
- ] }) }),
3894
- /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: record.email ?? "—" }) }),
3895
- /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: record.ip ?? "—" }) }),
3896
- /* @__PURE__ */ jsx(Td, { style: { maxWidth: "200px" }, children: record.details ? /* @__PURE__ */ jsx(Tooltip, { label: record.details, side: "top", children: /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", style: DETAILS_TEXT_STYLE, children: record.details }) }) : /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", children: "—" }) })
3897
- ] }, record.id))
3898
- ] })
3876
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: "100%" }, children: [
3877
+ /* @__PURE__ */ jsxs(CustomTable, { colCount: 5, rowCount: records.length, children: [
3878
+ /* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
3879
+ /* @__PURE__ */ jsx(Th, { children: formatMessage(getTrad("auditlog.table.timestamp")) }),
3880
+ /* @__PURE__ */ jsx(Th, { children: formatMessage(getTrad("auditlog.table.action")) }),
3881
+ /* @__PURE__ */ jsx(Th, { children: formatMessage(getTrad("auditlog.table.email")) }),
3882
+ /* @__PURE__ */ jsx(Th, { children: formatMessage(getTrad("auditlog.table.ip")) }),
3883
+ /* @__PURE__ */ jsx(Th, { children: formatMessage(getTrad("auditlog.table.details")) })
3884
+ ] }) }),
3885
+ /* @__PURE__ */ jsxs(Tbody, { children: [
3886
+ records.length === 0 && /* @__PURE__ */ jsx(Tr, { children: /* @__PURE__ */ jsx(Td, { colSpan: 5, children: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", alignItems: "center", style: { minHeight: "80px" }, children: loading ? /* @__PURE__ */ jsx(Loader, { small: true }) : /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: hasActiveFilters ? formatMessage(getTrad("auditlog.filters.empty")) : formatMessage(getTrad("auditlog.table.empty")) }) }) }) }),
3887
+ records.map((record) => /* @__PURE__ */ jsxs(
3888
+ Tr,
3889
+ {
3890
+ style: { opacity: loading ? 0.4 : 1, transition: "opacity 0.15s" },
3891
+ children: [
3892
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: /* @__PURE__ */ jsx(LocalizedDate, { date: record.createdAt, options: { second: "2-digit" } }) }) }),
3893
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
3894
+ /* @__PURE__ */ jsx(Typography, { variant: "omega", children: record.action }),
3895
+ /* @__PURE__ */ jsx(Tooltip, { label: formatMessage(getTrad(`auditlog.action.${record.action}`)), children: /* @__PURE__ */ jsx(
3896
+ Information,
3897
+ {
3898
+ "aria-hidden": true,
3899
+ style: { cursor: "help" },
3900
+ width: "1.4rem",
3901
+ height: "1.4rem",
3902
+ fill: "primary600"
3903
+ }
3904
+ ) })
3905
+ ] }) }),
3906
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: record.email ?? "—" }) }),
3907
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "omega", children: record.ip ?? "—" }) }),
3908
+ /* @__PURE__ */ jsx(Td, { style: { maxWidth: "200px" }, children: record.details ? /* @__PURE__ */ jsx(Tooltip, { label: record.details, side: "top", children: /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", style: DETAILS_TEXT_STYLE, children: record.details }) }) : /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral600", children: "—" }) })
3909
+ ]
3910
+ },
3911
+ record.id
3912
+ ))
3913
+ ] })
3914
+ ] }),
3915
+ loading && records.length > 0 && /* @__PURE__ */ jsx(
3916
+ Flex,
3917
+ {
3918
+ justifyContent: "center",
3919
+ alignItems: "center",
3920
+ style: { position: "absolute", inset: 0, pointerEvents: "none" },
3921
+ children: /* @__PURE__ */ jsx(Loader, { small: true })
3922
+ }
3923
+ )
3899
3924
  ] }),
3900
3925
  /* @__PURE__ */ jsx(
3901
3926
  TablePagination,
@@ -1,5 +1,10 @@
1
1
  "use strict";
2
- const react = require("react");
2
+ const React = require("react");
3
+ const client = require("react-dom/client");
4
+ const jsxRuntime = require("react/jsx-runtime");
5
+ const designSystem = require("@strapi/design-system");
6
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
7
+ const React__default = /* @__PURE__ */ _interopDefault(React);
3
8
  const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
4
9
  const v = glob[path];
5
10
  if (v) {
@@ -24,15 +29,51 @@ const pluginPkg = {
24
29
  };
25
30
  const pluginId = pluginPkg.name.replace(/^@strapi\/plugin-/i, "");
26
31
  function Initializer({ setPlugin }) {
27
- const ref = react.useRef();
32
+ const ref = React.useRef();
28
33
  ref.current = setPlugin;
29
- react.useEffect(() => {
34
+ React.useEffect(() => {
30
35
  if (ref.current) {
31
36
  ref.current(pluginId);
32
37
  }
33
38
  }, []);
34
39
  return null;
35
40
  }
41
+ const LOGOUT_EVENT = "strapi-oidc:logout";
42
+ function Overlay({ bg }) {
43
+ const [active, setActive] = React.useState(false);
44
+ React.useEffect(() => {
45
+ const handler = () => setActive(true);
46
+ window.addEventListener(LOGOUT_EVENT, handler);
47
+ return () => window.removeEventListener(LOGOUT_EVENT, handler);
48
+ }, []);
49
+ if (!active) return null;
50
+ return /* @__PURE__ */ jsxRuntime.jsx(
51
+ "div",
52
+ {
53
+ style: {
54
+ position: "fixed",
55
+ inset: 0,
56
+ zIndex: 1e4,
57
+ display: "flex",
58
+ alignItems: "center",
59
+ justifyContent: "center",
60
+ background: bg,
61
+ backdropFilter: "blur(2px)"
62
+ },
63
+ children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, {})
64
+ }
65
+ );
66
+ }
67
+ function resolveTheme() {
68
+ const stored = window.localStorage.getItem("STRAPI_THEME") ?? "system";
69
+ const isDark = stored === "dark" || stored === "system" && (window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false);
70
+ return isDark ? designSystem.darkTheme : designSystem.lightTheme;
71
+ }
72
+ function LogoutOverlay() {
73
+ const theme = resolveTheme();
74
+ const bg = theme === designSystem.darkTheme ? "rgba(24, 24, 38, 0.85)" : "rgba(255, 255, 255, 0.85)";
75
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.DesignSystemProvider, { theme, children: /* @__PURE__ */ jsxRuntime.jsx(Overlay, { bg }) });
76
+ }
36
77
  const en = {
37
78
  "global.plugins.strapi-plugin-oidc": "OIDC Plugin",
38
79
  "page.title": "Configure OIDC default role(s) and access controls.",
@@ -95,7 +136,6 @@ const en = {
95
136
  "auditlog.table.ip": "IP",
96
137
  "auditlog.table.details": "Details",
97
138
  "auditlog.table.empty": "No audit log entries",
98
- "auditlog.loading": "Loading…",
99
139
  "auditlog.clear": "Clear Logs",
100
140
  "auditlog.clear.title": "Clear All Logs",
101
141
  "auditlog.clear.description": "This will permanently delete all {count, plural, one {# audit log entry} other {# audit log entries}}. This action cannot be undone.",
@@ -168,7 +208,7 @@ const index = {
168
208
  defaultMessage: "Configuration"
169
209
  },
170
210
  Component: async () => {
171
- return await Promise.resolve().then(() => require("./index-CgG_mHzZ.js"));
211
+ return await Promise.resolve().then(() => require("./index-Bmg4eTYb.js"));
172
212
  },
173
213
  permissions: [{ action: "plugin::strapi-plugin-oidc.read", subject: null }]
174
214
  }
@@ -180,6 +220,9 @@ const index = {
180
220
  });
181
221
  },
182
222
  bootstrap() {
223
+ const overlayContainer = document.createElement("div");
224
+ document.body.appendChild(overlayContainer);
225
+ client.createRoot(overlayContainer).render(React__default.default.createElement(LogoutOverlay));
183
226
  const defaultButtonText = t("login.sso");
184
227
  const isAuthRoute = (path) => /\/auth\/(login|register|forgot-password|reset-password)/.test(path);
185
228
  let ssoButtonInjected = false;
@@ -239,6 +282,7 @@ const index = {
239
282
  if (!isAuthRoute(window.location.pathname)) return;
240
283
  injectSSOButton(buttonText);
241
284
  if (enforced) removeEnforcedElements();
285
+ if (ssoButtonInjected && !enforced) loginObserver?.disconnect();
242
286
  };
243
287
  tick();
244
288
  loginObserver = new MutationObserver(tick);
@@ -259,27 +303,27 @@ const index = {
259
303
  }
260
304
  };
261
305
  applySettings();
306
+ if (window.__strapiOidcFetchPatched) return;
307
+ window.__strapiOidcFetchPatched = true;
262
308
  const originalFetch = window.fetch;
263
309
  window.fetch = async (...args) => {
264
310
  const url = typeof args[0] === "string" ? args[0] : args[0].url;
265
311
  const isLogout = url?.endsWith("/admin/logout") && args[1]?.method?.toUpperCase() === "POST";
266
- const response = await originalFetch(...args);
267
- if (isLogout && response.ok) {
312
+ if (isLogout) {
313
+ window.dispatchEvent(new CustomEvent(LOGOUT_EVENT));
268
314
  window.localStorage.removeItem("jwtToken");
269
315
  window.localStorage.removeItem("isLoggedIn");
270
316
  window.sessionStorage.removeItem("jwtToken");
271
317
  window.sessionStorage.removeItem("isLoggedIn");
272
318
  document.cookie = "jwtToken=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/";
273
319
  document.cookie = "jwtToken=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/admin";
274
- const form = document.createElement("form");
275
- form.method = "POST";
276
- form.action = "/strapi-plugin-oidc/logout";
277
- document.body.appendChild(form);
278
- form.submit();
320
+ originalFetch(...args).catch(() => {
321
+ });
322
+ window.location.href = "/strapi-plugin-oidc/logout";
279
323
  return new Promise(() => {
280
324
  });
281
325
  }
282
- return response;
326
+ return originalFetch(...args);
283
327
  };
284
328
  },
285
329
  async registerTrads({ locales }) {
@@ -1,4 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
- const index = require("./index-BSgVStns.js");
3
+ const index = require("./index-Dk6TYtio.js");
4
+ require("react");
5
+ require("react-dom/client");
4
6
  exports.default = index.index;
@@ -1,4 +1,6 @@
1
- import { i } from "./index-B-K4X_N9.mjs";
1
+ import { i } from "./index-Bb9-aYb4.mjs";
2
+ import "react";
3
+ import "react-dom/client";
2
4
  export {
3
5
  i as default
4
6
  };
@@ -44,8 +44,44 @@ const getRoleService = () => strapi.plugin(PLUGIN_NAME).service("role");
44
44
  const getWhitelistService = () => strapi.plugin(PLUGIN_NAME).service("whitelist");
45
45
  const getAuditLogService = () => strapi.plugin(PLUGIN_NAME).service("auditLog");
46
46
  const getAdminUserService = () => strapi.service("admin::user");
47
+ const DISCOVERY_TIMEOUT_MS = 5e3;
48
+ const FIELD_MAP = [
49
+ ["issuer", "OIDC_ISSUER"],
50
+ ["authorization_endpoint", "OIDC_AUTHORIZATION_ENDPOINT"],
51
+ ["token_endpoint", "OIDC_TOKEN_ENDPOINT"],
52
+ ["userinfo_endpoint", "OIDC_USERINFO_ENDPOINT"],
53
+ ["end_session_endpoint", "OIDC_END_SESSION_ENDPOINT"],
54
+ ["jwks_uri", "OIDC_JWKS_URI"]
55
+ ];
56
+ async function applyDiscovery(strapi2) {
57
+ const config2 = strapi2.config.get("plugin::strapi-plugin-oidc");
58
+ const discoveryUrl = config2.OIDC_DISCOVERY_URL;
59
+ if (!discoveryUrl) return;
60
+ let doc;
61
+ try {
62
+ const res = await fetch(discoveryUrl, { signal: AbortSignal.timeout(DISCOVERY_TIMEOUT_MS) });
63
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
64
+ doc = await res.json();
65
+ } catch (e) {
66
+ strapi2.log.error(
67
+ `[strapi-plugin-oidc] Failed to fetch OIDC discovery document from ${discoveryUrl}: ${e instanceof Error ? e.message : String(e)}`
68
+ );
69
+ return;
70
+ }
71
+ const updates = {};
72
+ for (const [docField, configKey] of FIELD_MAP) {
73
+ if (doc[docField]) {
74
+ updates[configKey] = doc[docField];
75
+ }
76
+ }
77
+ if (Object.keys(updates).length > 0) {
78
+ strapi2.config.set("plugin::strapi-plugin-oidc", { ...config2, ...updates });
79
+ strapi2.log.info(`[strapi-plugin-oidc] Discovery applied: ${Object.keys(updates).join(", ")}`);
80
+ }
81
+ }
47
82
  const AUTH_ROUTES = ["login", "register", "register-admin", "forgot-password", "reset-password"];
48
83
  async function bootstrap({ strapi: strapi2 }) {
84
+ await applyDiscovery(strapi2);
49
85
  const adminUrl = strapi2.config.get("admin.url", "/admin");
50
86
  const tokenRefreshPath = `${adminUrl}/token/refresh`;
51
87
  const enforceOidcMiddleware = async (ctx, next) => {
@@ -157,17 +193,13 @@ function destroy() {
157
193
  const config = {
158
194
  default: {
159
195
  REMEMBER_ME: false,
196
+ OIDC_DISCOVERY_URL: "",
160
197
  OIDC_REDIRECT_URI: "http://localhost:1337/strapi-plugin-oidc/oidc/callback",
161
198
  OIDC_CLIENT_ID: "",
162
199
  OIDC_CLIENT_SECRET: "",
163
200
  OIDC_SCOPE: "openid profile email",
164
- OIDC_AUTHORIZATION_ENDPOINT: "",
165
- OIDC_TOKEN_ENDPOINT: "",
166
- OIDC_USERINFO_ENDPOINT: "",
167
- OIDC_GRANT_TYPE: "authorization_code",
168
201
  OIDC_FAMILY_NAME_FIELD: "family_name",
169
202
  OIDC_GIVEN_NAME_FIELD: "given_name",
170
- OIDC_END_SESSION_ENDPOINT: "",
171
203
  OIDC_SSO_BUTTON_TEXT: "Login via SSO",
172
204
  OIDC_ENFORCE: null,
173
205
  // null = use DB setting; true/false = override DB (useful for lockout recovery)
@@ -176,9 +208,14 @@ const config = {
176
208
  OIDC_GROUP_ROLE_MAP: "{}",
177
209
  OIDC_REQUIRE_EMAIL_VERIFIED: true,
178
210
  OIDC_TRUSTED_IP_HEADER: "",
211
+ OIDC_FORCE_SECURE_COOKIES: false,
212
+ // Populated at bootstrap from OIDC_DISCOVERY_URL — not user-configurable directly
213
+ OIDC_AUTHORIZATION_ENDPOINT: "",
214
+ OIDC_TOKEN_ENDPOINT: "",
215
+ OIDC_USERINFO_ENDPOINT: "",
216
+ OIDC_END_SESSION_ENDPOINT: "",
179
217
  OIDC_JWKS_URI: "",
180
- OIDC_ISSUER: "",
181
- OIDC_FORCE_SECURE_COOKIES: false
218
+ OIDC_ISSUER: ""
182
219
  },
183
220
  validator() {
184
221
  }
@@ -234,8 +271,7 @@ function shouldMarkSecure(strapi2, ctx) {
234
271
  if (config2.OIDC_FORCE_SECURE_COOKIES === true) return true;
235
272
  if (ctx.request.secure) return true;
236
273
  const proxyTrusted = ctx.app?.proxy === true;
237
- if (proxyTrusted && typeof ctx.get === "function" && ctx.get("x-forwarded-proto") === "https")
238
- return true;
274
+ if (proxyTrusted && ctx.get("x-forwarded-proto") === "https") return true;
239
275
  return false;
240
276
  }
241
277
  function getExpiredCookieOptions(strapi2, ctx) {
@@ -369,7 +405,6 @@ const en = {
369
405
  "auditlog.table.ip": "IP",
370
406
  "auditlog.table.details": "Details",
371
407
  "auditlog.table.empty": "No audit log entries",
372
- "auditlog.loading": "Loading…",
373
408
  "auditlog.clear": "Clear Logs",
374
409
  "auditlog.clear.title": "Clear All Logs",
375
410
  "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 +459,6 @@ const locales = Object.fromEntries(
424
459
  return [code ?? "", mod.default];
425
460
  })
426
461
  );
427
- Object.keys(locales).filter(Boolean);
428
462
  const DEFAULT_LOCALE = "en";
429
463
  function parseAcceptLanguage(header) {
430
464
  return header.split(",").map((part) => {
@@ -527,13 +561,13 @@ const OIDC_ERROR_DISPATCH = {
527
561
  key: "sign_in_unknown"
528
562
  }
529
563
  };
530
- const TRUSTED_HEADER_WHITELIST = /* @__PURE__ */ new Set(["cf-connecting-ip"]);
564
+ const TRUSTED_IP_HEADER = "cf-connecting-ip";
531
565
  function getTrustedHeaderName() {
532
566
  const config2 = strapi.config.get("plugin::strapi-plugin-oidc") ?? {};
533
567
  const raw = config2.OIDC_TRUSTED_IP_HEADER;
534
568
  if (typeof raw !== "string" || !raw) return void 0;
535
569
  const normalized = raw.trim().toLowerCase();
536
- return TRUSTED_HEADER_WHITELIST.has(normalized) ? normalized : void 0;
570
+ return normalized === TRUSTED_IP_HEADER ? normalized : void 0;
537
571
  }
538
572
  function getClientIp(ctx) {
539
573
  const proxyTrusted = ctx.app?.proxy === true;
@@ -550,19 +584,23 @@ function getClientIp(ctx) {
550
584
  }
551
585
  return ctx.ip;
552
586
  }
587
+ function toMessage(e) {
588
+ return e instanceof Error ? e.message : String(e);
589
+ }
553
590
  const REQUIRED_CONFIG_KEYS = [
591
+ "OIDC_DISCOVERY_URL",
554
592
  "OIDC_CLIENT_ID",
555
593
  "OIDC_CLIENT_SECRET",
556
594
  "OIDC_REDIRECT_URI",
557
595
  "OIDC_SCOPE",
558
- "OIDC_TOKEN_ENDPOINT",
559
- "OIDC_USERINFO_ENDPOINT",
560
- "OIDC_GRANT_TYPE",
561
596
  "OIDC_FAMILY_NAME_FIELD",
562
597
  "OIDC_GIVEN_NAME_FIELD",
598
+ // Populated at bootstrap from OIDC_DISCOVERY_URL — checked here as a runtime safety net
599
+ "OIDC_TOKEN_ENDPOINT",
600
+ "OIDC_USERINFO_ENDPOINT",
563
601
  "OIDC_AUTHORIZATION_ENDPOINT"
564
602
  ];
565
- const LOGOUT_USERINFO_TIMEOUT_MS = 3e3;
603
+ const LOGOUT_USERINFO_TIMEOUT_MS = 1500;
566
604
  const jwksCache = /* @__PURE__ */ new Map();
567
605
  let jwksDisabledWarned = false;
568
606
  function getJwks(uri) {
@@ -594,7 +632,7 @@ async function verifyIdToken(idToken, config2) {
594
632
  return payload;
595
633
  } catch (e) {
596
634
  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);
635
+ const msg = toMessage(e);
598
636
  throw new OidcError("id_token_invalid", msg, e);
599
637
  }
600
638
  throw e;
@@ -785,7 +823,7 @@ async function ensureUser(userService, oauthService2, email, userResponseData, c
785
823
  );
786
824
  return { user, userCreated: true, rolesUpdated: true };
787
825
  } catch (e) {
788
- const msg = e instanceof Error ? e.message : String(e);
826
+ const msg = toMessage(e);
789
827
  throw new OidcError("user_creation_failed", msg, e);
790
828
  }
791
829
  }
@@ -835,7 +873,7 @@ async function handleUserAuthentication(userService, oauthService2, roleService2
835
873
  function classifyOidcError(e, userInfo) {
836
874
  const kind = e instanceof OidcError ? e.kind : "unknown";
837
875
  const dispatch = OIDC_ERROR_DISPATCH[kind];
838
- const msg = e instanceof Error ? e.message : String(e);
876
+ const msg = toMessage(e);
839
877
  let params;
840
878
  if (kind === "id_token_parse_failed" || kind === "id_token_invalid" || kind === "unknown") {
841
879
  params = { error: msg };
@@ -884,7 +922,7 @@ async function logSuccessfulAuth(auditLog2, ctx, user, userCreated, rolesUpdated
884
922
  }
885
923
  async function handleCallbackError(e, userInfo, auditLog2, oauthService2, ctx) {
886
924
  const errorInfo = classifyOidcError(e, userInfo);
887
- const message = e instanceof Error ? e.message : String(e);
925
+ const message = toMessage(e);
888
926
  await auditLog2.log({
889
927
  action: errorInfo.action,
890
928
  email: userInfo?.email,
@@ -925,7 +963,7 @@ async function oidcSignInCallback(ctx) {
925
963
  client_id: config2.OIDC_CLIENT_ID,
926
964
  client_secret: config2.OIDC_CLIENT_SECRET,
927
965
  redirect_uri: config2.OIDC_REDIRECT_URI,
928
- grant_type: config2.OIDC_GRANT_TYPE,
966
+ grant_type: "authorization_code",
929
967
  code_verifier: codeVerifier ?? ""
930
968
  });
931
969
  let userInfo;
@@ -969,13 +1007,13 @@ async function oidcSignInCallback(ctx) {
969
1007
  await handleCallbackError(e, userInfo, auditLog2, oauthService2, ctx);
970
1008
  }
971
1009
  }
972
- async function isProviderSessionActive(userinfoEndpoint, accessToken) {
1010
+ async function isProviderSessionExpired(userinfoEndpoint, accessToken) {
973
1011
  try {
974
1012
  const response = await fetch(userinfoEndpoint, {
975
1013
  headers: { Authorization: `Bearer ${accessToken}` },
976
1014
  signal: AbortSignal.timeout(LOGOUT_USERINFO_TIMEOUT_MS)
977
1015
  });
978
- return response.ok;
1016
+ return !response.ok;
979
1017
  } catch {
980
1018
  return false;
981
1019
  }
@@ -995,14 +1033,14 @@ async function logout(ctx) {
995
1033
  }
996
1034
  const logAudit = (action) => userEmail ? auditLog2.log({ action, email: userEmail, ip: getClientIp(ctx) }) : Promise.resolve();
997
1035
  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);
1036
+ const expired = await isProviderSessionExpired(config2.OIDC_USERINFO_ENDPOINT, accessToken);
1037
+ if (expired) {
1038
+ await logAudit("session_expired");
1039
+ return ctx.redirect(loginUrl);
1003
1040
  }
1004
- await logAudit("session_expired");
1005
- return ctx.redirect(loginUrl);
1041
+ logAudit("logout").catch(() => {
1042
+ });
1043
+ return ctx.redirect(logoutUrl);
1006
1044
  }
1007
1045
  await logAudit("logout");
1008
1046
  ctx.redirect(logoutUrl || loginUrl);
@@ -1160,13 +1198,9 @@ async function importUsers(ctx) {
1160
1198
  const whitelistService2 = getWhitelistService();
1161
1199
  const existing = await whitelistService2.getUsers();
1162
1200
  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 };
1201
+ const toImport = deduped.filter((email) => !existingEmails.has(email));
1202
+ await Promise.all(toImport.map((email) => whitelistService2.registerUser(email)));
1203
+ ctx.body = { importedCount: toImport.length };
1170
1204
  }
1171
1205
  async function syncUsers(ctx) {
1172
1206
  const { users: rawUsers } = ctx.request.body;
@@ -1175,16 +1209,10 @@ async function syncUsers(ctx) {
1175
1209
  const currentUsers = await whitelistService2.getUsers();
1176
1210
  const syncEmailSet = new Set(emails);
1177
1211
  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
- }
1212
+ await Promise.all([
1213
+ ...currentUsers.filter((u) => !syncEmailSet.has(u.email)).map((u) => whitelistService2.removeUser(u.email)),
1214
+ ...emails.filter((email) => !currentUsersByEmail.has(email)).map((email) => whitelistService2.registerUser(email))
1215
+ ]);
1188
1216
  ctx.body = {};
1189
1217
  }
1190
1218
  const whitelist = {
@@ -1488,6 +1516,12 @@ const routes = {
1488
1516
  handler: "oidc.oidcSignInCallback",
1489
1517
  config: { auth: false, middlewares: [rateLimitMiddleware] }
1490
1518
  },
1519
+ {
1520
+ method: "GET",
1521
+ path: "/logout",
1522
+ handler: "oidc.logout",
1523
+ config: { auth: false }
1524
+ },
1491
1525
  {
1492
1526
  method: "POST",
1493
1527
  path: "/logout",
@@ -1796,9 +1830,10 @@ function oauthService({ strapi: strapi2 }) {
1796
1830
  }
1797
1831
  const modelDef = strapi2.getModel("admin::user");
1798
1832
  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
1833
+ {
1834
+ schema: modelDef,
1835
+ getModel: (uid2) => strapi2.getModel(uid2)
1836
+ },
1802
1837
  user
1803
1838
  );
1804
1839
  eventHub?.emit(ENTRY_CREATE ?? "entry.create", {