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.
package/README.md CHANGED
@@ -31,20 +31,16 @@ module.exports = ({ env }) => ({
31
31
  'strapi-plugin-oidc': {
32
32
  enabled: true,
33
33
  config: {
34
- // Required — find these in your provider's OIDC discovery document
34
+ // Required
35
+ OIDC_DISCOVERY_URL: env('OIDC_DISCOVERY_URL'), // https://your-provider/.well-known/openid-configuration
35
36
  OIDC_CLIENT_ID: env('OIDC_CLIENT_ID'),
36
37
  OIDC_CLIENT_SECRET: env('OIDC_CLIENT_SECRET'),
37
38
  OIDC_REDIRECT_URI: env('OIDC_REDIRECT_URI'), // https://your-strapi.com/strapi-plugin-oidc/oidc/callback
38
- OIDC_AUTHORIZATION_ENDPOINT: env('OIDC_AUTHORIZATION_ENDPOINT'),
39
- OIDC_TOKEN_ENDPOINT: env('OIDC_TOKEN_ENDPOINT'),
40
- OIDC_USERINFO_ENDPOINT: env('OIDC_USERINFO_ENDPOINT'),
41
39
 
42
40
  // Optional — defaults shown
43
- OIDC_SCOPE: 'openid profile email',
44
- OIDC_GRANT_TYPE: 'authorization_code',
41
+ OIDC_SCOPE: 'openid profile email', // space-separated scopes
45
42
  OIDC_FAMILY_NAME_FIELD: 'family_name',
46
43
  OIDC_GIVEN_NAME_FIELD: 'given_name',
47
- OIDC_END_SESSION_ENDPOINT: '', // Provider end-session URL (from discovery `end_session_endpoint`)
48
44
  OIDC_SSO_BUTTON_TEXT: 'Login via SSO',
49
45
  OIDC_ENFORCE: null, // null = use Admin UI toggle; true/false = override in config
50
46
  REMEMBER_ME: false, // Persist session across browser restarts
@@ -53,19 +49,17 @@ module.exports = ({ env }) => ({
53
49
  OIDC_GROUP_ROLE_MAP: '{}', // JSON map of group names to Strapi role names
54
50
  OIDC_REQUIRE_EMAIL_VERIFIED: true, // Reject logins when provider does not report email_verified=true (set false to disable)
55
51
  OIDC_TRUSTED_IP_HEADER: '', // Optional: 'cf-connecting-ip' for Cloudflare; read only when Strapi trusts the proxy
56
- OIDC_JWKS_URI: '', // Provider's JWKS URI (from discovery `jwks_uri`) — enables ID token signature verification
57
- OIDC_ISSUER: '', // Provider's issuer (from discovery `issuer`) — verified against ID token's `iss`
58
52
  OIDC_FORCE_SECURE_COOKIES: false, // Set true when behind a trusted HTTPS proxy that Strapi can't auto-detect
59
53
  },
60
54
  },
61
55
  });
62
56
  ```
63
57
 
64
- All OIDC values come from your provider's discovery document, typically available at `https://your-provider/.well-known/openid-configuration`.
58
+ `OIDC_DISCOVERY_URL` is the URL of your provider's OpenID Connect discovery document (`/.well-known/openid-configuration`). The plugin fetches it at startup and automatically configures all endpoints, JWKS URI, and issuer.
65
59
 
66
60
  ### Security features
67
61
 
68
- - **ID token verification** — `OIDC_JWKS_URI` and `OIDC_ISSUER` enable signature, issuer, audience, and expiry validation via [`jose`](https://github.com/panva/jose)
62
+ - **ID token verification** — Enabled automatically when the discovery document includes a `jwks_uri`. Validates signature, issuer, audience, and expiry via [`jose`](https://github.com/panva/jose)
69
63
  - **Email verification** — `OIDC_REQUIRE_EMAIL_VERIFIED: true` (default) rejects unverified emails
70
64
  - **CSRF protection** — OIDC state/nonce and POST-only logout endpoint
71
65
  - **Rate limiting** — 1 000 req/min per IP+UA (in-process; use a reverse-proxy-level limiter for multi-node)
@@ -83,7 +77,7 @@ Navigate to `/strapi-plugin-oidc/oidc` to start the OIDC flow, or click the **Lo
83
77
 
84
78
  ## Logout
85
79
 
86
- When `OIDC_END_SESSION_ENDPOINT` is set, clicking logout redirects to the provider's end-session URL (RP-initiated logout). If the provider session has already expired, Strapi skips the redirect and goes straight to the login page.
80
+ When the discovery document includes an `end_session_endpoint`, clicking logout redirects to the provider's end-session URL (RP-initiated logout). If the provider session has already expired, Strapi skips the redirect and goes straight to the login page.
87
81
 
88
82
  The logout endpoint is `POST /strapi-plugin-oidc/logout`. Using POST instead of GET prevents CSRF-forced-logout attacks.
89
83
 
@@ -1,4 +1,7 @@
1
- import { useRef, useEffect } from "react";
1
+ import React, { useRef, useEffect, useState } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import { jsx } from "react/jsx-runtime";
4
+ import { DesignSystemProvider, darkTheme, lightTheme, Loader } from "@strapi/design-system";
2
5
  const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
3
6
  const v = glob[path];
4
7
  if (v) {
@@ -32,6 +35,42 @@ function Initializer({ setPlugin }) {
32
35
  }, []);
33
36
  return null;
34
37
  }
38
+ const LOGOUT_EVENT = "strapi-oidc:logout";
39
+ function Overlay({ bg }) {
40
+ const [active, setActive] = useState(false);
41
+ useEffect(() => {
42
+ const handler = () => setActive(true);
43
+ window.addEventListener(LOGOUT_EVENT, handler);
44
+ return () => window.removeEventListener(LOGOUT_EVENT, handler);
45
+ }, []);
46
+ if (!active) return null;
47
+ return /* @__PURE__ */ jsx(
48
+ "div",
49
+ {
50
+ style: {
51
+ position: "fixed",
52
+ inset: 0,
53
+ zIndex: 1e4,
54
+ display: "flex",
55
+ alignItems: "center",
56
+ justifyContent: "center",
57
+ background: bg,
58
+ backdropFilter: "blur(2px)"
59
+ },
60
+ children: /* @__PURE__ */ jsx(Loader, {})
61
+ }
62
+ );
63
+ }
64
+ function resolveTheme() {
65
+ const stored = window.localStorage.getItem("STRAPI_THEME") ?? "system";
66
+ const isDark = stored === "dark" || stored === "system" && (window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false);
67
+ return isDark ? darkTheme : lightTheme;
68
+ }
69
+ function LogoutOverlay() {
70
+ const theme = resolveTheme();
71
+ const bg = theme === darkTheme ? "rgba(24, 24, 38, 0.85)" : "rgba(255, 255, 255, 0.85)";
72
+ return /* @__PURE__ */ jsx(DesignSystemProvider, { theme, children: /* @__PURE__ */ jsx(Overlay, { bg }) });
73
+ }
35
74
  const en = {
36
75
  "global.plugins.strapi-plugin-oidc": "OIDC Plugin",
37
76
  "page.title": "Configure OIDC default role(s) and access controls.",
@@ -94,7 +133,6 @@ const en = {
94
133
  "auditlog.table.ip": "IP",
95
134
  "auditlog.table.details": "Details",
96
135
  "auditlog.table.empty": "No audit log entries",
97
- "auditlog.loading": "Loading…",
98
136
  "auditlog.clear": "Clear Logs",
99
137
  "auditlog.clear.title": "Clear All Logs",
100
138
  "auditlog.clear.description": "This will permanently delete all {count, plural, one {# audit log entry} other {# audit log entries}}. This action cannot be undone.",
@@ -167,7 +205,7 @@ const index = {
167
205
  defaultMessage: "Configuration"
168
206
  },
169
207
  Component: async () => {
170
- return await import("./index-8YTLPV3h.mjs");
208
+ return await import("./index-BqWd-Iiq.mjs");
171
209
  },
172
210
  permissions: [{ action: "plugin::strapi-plugin-oidc.read", subject: null }]
173
211
  }
@@ -179,6 +217,9 @@ const index = {
179
217
  });
180
218
  },
181
219
  bootstrap() {
220
+ const overlayContainer = document.createElement("div");
221
+ document.body.appendChild(overlayContainer);
222
+ createRoot(overlayContainer).render(React.createElement(LogoutOverlay));
182
223
  const defaultButtonText = t("login.sso");
183
224
  const isAuthRoute = (path) => /\/auth\/(login|register|forgot-password|reset-password)/.test(path);
184
225
  let ssoButtonInjected = false;
@@ -238,6 +279,7 @@ const index = {
238
279
  if (!isAuthRoute(window.location.pathname)) return;
239
280
  injectSSOButton(buttonText);
240
281
  if (enforced) removeEnforcedElements();
282
+ if (ssoButtonInjected && !enforced) loginObserver?.disconnect();
241
283
  };
242
284
  tick();
243
285
  loginObserver = new MutationObserver(tick);
@@ -258,27 +300,27 @@ const index = {
258
300
  }
259
301
  };
260
302
  applySettings();
303
+ if (window.__strapiOidcFetchPatched) return;
304
+ window.__strapiOidcFetchPatched = true;
261
305
  const originalFetch = window.fetch;
262
306
  window.fetch = async (...args) => {
263
307
  const url = typeof args[0] === "string" ? args[0] : args[0].url;
264
308
  const isLogout = url?.endsWith("/admin/logout") && args[1]?.method?.toUpperCase() === "POST";
265
- const response = await originalFetch(...args);
266
- if (isLogout && response.ok) {
309
+ if (isLogout) {
310
+ window.dispatchEvent(new CustomEvent(LOGOUT_EVENT));
267
311
  window.localStorage.removeItem("jwtToken");
268
312
  window.localStorage.removeItem("isLoggedIn");
269
313
  window.sessionStorage.removeItem("jwtToken");
270
314
  window.sessionStorage.removeItem("isLoggedIn");
271
315
  document.cookie = "jwtToken=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/";
272
316
  document.cookie = "jwtToken=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/admin";
273
- const form = document.createElement("form");
274
- form.method = "POST";
275
- form.action = "/strapi-plugin-oidc/logout";
276
- document.body.appendChild(form);
277
- form.submit();
317
+ originalFetch(...args).catch(() => {
318
+ });
319
+ window.location.href = "/strapi-plugin-oidc/logout";
278
320
  return new Promise(() => {
279
321
  });
280
322
  }
281
- return response;
323
+ return originalFetch(...args);
282
324
  };
283
325
  },
284
326
  async registerTrads({ locales }) {
@@ -3,11 +3,11 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const reactRouterDom = require("react-router-dom");
5
5
  const admin = require("@strapi/strapi/admin");
6
- const react = require("react");
6
+ const React = require("react");
7
7
  const designSystem = require("@strapi/design-system");
8
8
  const icons = require("@strapi/icons");
9
9
  const reactIntl = require("react-intl");
10
- const index = require("./index-BSgVStns.js");
10
+ const index = require("./index-Dk6TYtio.js");
11
11
  const styled = require("styled-components");
12
12
  const lucideReact = require("lucide-react");
13
13
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
@@ -125,8 +125,8 @@ function TagChip({ label, onRemove }) {
125
125
  ] });
126
126
  }
127
127
  function useTagState({ value, onChange }) {
128
- const [inputValue, setInputValue] = react.useState("");
129
- const inputRef = react.useRef(null);
128
+ const [inputValue, setInputValue] = React.useState("");
129
+ const inputRef = React.useRef(null);
130
130
  const addTag = (tag, predicate) => {
131
131
  const trimmed = tag.trim();
132
132
  if (trimmed && !value.includes(trimmed) && (!predicate || predicate(trimmed))) {
@@ -261,11 +261,11 @@ function ComboboxTagInput({ value, onChange, placeholder, startIcon, options })
261
261
  value,
262
262
  onChange
263
263
  });
264
- const [showDropdown, setShowDropdown] = react.useState(false);
265
- const [activeIndex, setActiveIndex] = react.useState(-1);
266
- const wrapperRef = react.useRef(null);
267
- const listboxId = react.useId();
268
- const optionIdPrefix = react.useId();
264
+ const [showDropdown, setShowDropdown] = React.useState(false);
265
+ const [activeIndex, setActiveIndex] = React.useState(-1);
266
+ const wrapperRef = React.useRef(null);
267
+ const listboxId = React.useId();
268
+ const optionIdPrefix = React.useId();
269
269
  const filteredOptions = options.filter(
270
270
  (opt) => !value.includes(opt) && opt.toLowerCase().includes(inputValue.toLowerCase())
271
271
  );
@@ -333,7 +333,7 @@ function ComboboxTagInput({ value, onChange, placeholder, startIcon, options })
333
333
  setShowDropdown(true);
334
334
  setActiveIndex(-1);
335
335
  };
336
- react.useEffect(() => {
336
+ React.useEffect(() => {
337
337
  const handleClickOutside = (e) => {
338
338
  if (wrapperRef.current && !wrapperRef.current.contains(e.target)) {
339
339
  setShowDropdown(false);
@@ -546,13 +546,13 @@ const DAY_NAMES = Array.from(
546
546
  );
547
547
  function TagDateInput({ value = [], onChange, placeholder, startIcon }) {
548
548
  const { formatMessage } = reactIntl.useIntl();
549
- const [isOpen, setIsOpen] = react.useState(false);
550
- const [viewDate, setViewDate] = react.useState(() => /* @__PURE__ */ new Date());
551
- const [pendingDates, setPendingDates] = react.useState([]);
549
+ const [isOpen, setIsOpen] = React.useState(false);
550
+ const [viewDate, setViewDate] = React.useState(() => /* @__PURE__ */ new Date());
551
+ const [pendingDates, setPendingDates] = React.useState([]);
552
552
  const today = /* @__PURE__ */ new Date();
553
- const wrapperRef = react.useRef(null);
554
- const dialogId = react.useId();
555
- const dialogLabelId = react.useId();
553
+ const wrapperRef = React.useRef(null);
554
+ const dialogId = React.useId();
555
+ const dialogLabelId = React.useId();
556
556
  const year = viewDate.getFullYear();
557
557
  const month = viewDate.getMonth();
558
558
  const daysInMonth = getDaysInMonth(year, month);
@@ -594,7 +594,7 @@ function TagDateInput({ value = [], onChange, placeholder, startIcon }) {
594
594
  const removeTag = (indexToRemove) => {
595
595
  onChange(value.filter((_, i) => i !== indexToRemove));
596
596
  };
597
- react.useEffect(() => {
597
+ React.useEffect(() => {
598
598
  const handleClickOutside = (e) => {
599
599
  if (wrapperRef.current && !wrapperRef.current.contains(e.target)) {
600
600
  setIsOpen(false);
@@ -863,14 +863,14 @@ function Whitelist({
863
863
  onImport,
864
864
  onExport
865
865
  }) {
866
- const [email, setEmail] = react.useState("");
867
- const [page, setPage] = react.useState(1);
866
+ const [email, setEmail] = React.useState("");
867
+ const [page, setPage] = React.useState(1);
868
868
  const { formatMessage } = reactIntl.useIntl();
869
869
  const { toggleNotification } = admin.useNotification();
870
- const fileInputRef = react.useRef(null);
870
+ const fileInputRef = React.useRef(null);
871
871
  const pageCount = Math.ceil(users.length / PAGE_SIZE$1) || 1;
872
872
  const paginatedUsers = users.slice((page - 1) * PAGE_SIZE$1, page * PAGE_SIZE$1);
873
- const onSaveEmail = react.useCallback(() => {
873
+ const onSaveEmail = React.useCallback(() => {
874
874
  const emailText = email.trim();
875
875
  if (users.some((user) => user.email === emailText)) {
876
876
  toggleNotification({
@@ -882,7 +882,7 @@ function Whitelist({
882
882
  setEmail("");
883
883
  }
884
884
  }, [email, users, onSave, formatMessage, toggleNotification]);
885
- const handleImport = react.useCallback(
885
+ const handleImport = React.useCallback(
886
886
  async (e) => {
887
887
  const file = e.target.files?.[0];
888
888
  if (!fileInputRef.current || !file) return;
@@ -1018,7 +1018,7 @@ function Whitelist({
1018
1018
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: formatMessage(index.getTrad("whitelist.table.created")) }),
1019
1019
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { style: { paddingRight: 0 }, children: " " })
1020
1020
  ] }) }),
1021
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: users.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tr, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { colSpan: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: formatMessage(index.getTrad("whitelist.table.empty")) }) }) }) }) : paginatedUsers.map((user, index$1) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
1021
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: users.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tr, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { colSpan: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", alignItems: "center", style: { minHeight: "80px" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: formatMessage(index.getTrad("whitelist.table.empty")) }) }) }) }) : paginatedUsers.map((user, index$1) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
1022
1022
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: index$1 + 1 + (page - 1) * PAGE_SIZE$1 }),
1023
1023
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: user.email }),
1024
1024
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(LocalizedDate, { date: user.createdAt, options: { second: "2-digit" } }) }),
@@ -3622,6 +3622,7 @@ const AUDIT_ACTIONS = [
3622
3622
  "user_created"
3623
3623
  ];
3624
3624
  const PAGE_SIZE = 10;
3625
+ const MIN_SPINNER_MS = 400;
3625
3626
  const DETAILS_TEXT_STYLE = {
3626
3627
  display: "block",
3627
3628
  overflow: "hidden",
@@ -3652,8 +3653,8 @@ function buildQueryString(params) {
3652
3653
  );
3653
3654
  }
3654
3655
  function useDebounced(value, delay = 300) {
3655
- const [debounced, setDebounced] = react.useState(value);
3656
- react.useEffect(() => {
3656
+ const [debounced, setDebounced] = React.useState(value);
3657
+ React.useEffect(() => {
3657
3658
  const id = setTimeout(() => setDebounced(value), delay);
3658
3659
  return () => clearTimeout(id);
3659
3660
  }, [value, delay]);
@@ -3663,40 +3664,47 @@ function AuditLog({ title } = {}) {
3663
3664
  const { formatMessage } = reactIntl.useIntl();
3664
3665
  const { get: get2, del } = admin.useFetchClient();
3665
3666
  const { toggleNotification } = admin.useNotification();
3666
- const [records, setRecords] = react.useState([]);
3667
- const [pagination, setPagination] = react.useState({
3667
+ const [records, setRecords] = React.useState([]);
3668
+ const [pagination, setPagination] = React.useState({
3668
3669
  page: 1,
3669
3670
  pageSize: PAGE_SIZE,
3670
3671
  total: 0,
3671
3672
  pageCount: 1
3672
3673
  });
3673
- const [page, setPage] = react.useState(1);
3674
- const [loading, setLoading] = react.useState(false);
3675
- const [filters, setFilters] = react.useState({});
3674
+ const [page, setPage] = React.useState(1);
3675
+ const [loading, setLoading] = React.useState(true);
3676
+ const [filters, setFilters] = React.useState({});
3677
+ const fetchGenRef = React.useRef(0);
3676
3678
  const debouncedFilters = useDebounced(filters);
3677
- const fetchLogs = react.useCallback(
3679
+ const fetchLogs = React.useCallback(
3678
3680
  async (p, f) => {
3681
+ const gen = ++fetchGenRef.current;
3679
3682
  setLoading(true);
3683
+ const startTime = Date.now();
3684
+ let newRecords = [];
3685
+ let newPagination = { page: p, pageSize: PAGE_SIZE, total: 0, pageCount: 1 };
3680
3686
  try {
3681
- const queryString = buildQueryString({
3682
- filters: f,
3683
- page: p,
3684
- pageSize: PAGE_SIZE
3685
- });
3687
+ const queryString = buildQueryString({ filters: f, page: p, pageSize: PAGE_SIZE });
3686
3688
  const response = await get2(`/strapi-plugin-oidc/audit-logs?${queryString}`);
3687
- setRecords(response.data.results ?? []);
3688
- setPagination(
3689
- response.data.pagination ?? { page: p, pageSize: PAGE_SIZE, total: 0, pageCount: 1 }
3690
- );
3689
+ newRecords = response.data.results ?? [];
3690
+ newPagination = response.data.pagination ?? {
3691
+ page: p,
3692
+ pageSize: PAGE_SIZE,
3693
+ total: 0,
3694
+ pageCount: 1
3695
+ };
3691
3696
  } catch {
3692
- setRecords([]);
3693
- } finally {
3694
- setLoading(false);
3695
3697
  }
3698
+ const remaining = MIN_SPINNER_MS - (Date.now() - startTime);
3699
+ if (remaining > 0) await new Promise((r) => setTimeout(r, remaining));
3700
+ if (gen !== fetchGenRef.current) return;
3701
+ setRecords(newRecords);
3702
+ setPagination(newPagination);
3703
+ setLoading(false);
3696
3704
  },
3697
3705
  [get2]
3698
3706
  );
3699
- react.useEffect(() => {
3707
+ React.useEffect(() => {
3700
3708
  fetchLogs(page, debouncedFilters);
3701
3709
  }, [fetchLogs, page, debouncedFilters]);
3702
3710
  const handleClearAll = async () => {
@@ -3869,37 +3877,54 @@ function AuditLog({ title } = {}) {
3869
3877
  ]
3870
3878
  }
3871
3879
  ),
3872
- /* @__PURE__ */ jsxRuntime.jsxs(CustomTable, { colCount: 5, rowCount: records.length, children: [
3873
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
3874
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: formatMessage(index.getTrad("auditlog.table.timestamp")) }),
3875
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: formatMessage(index.getTrad("auditlog.table.action")) }),
3876
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: formatMessage(index.getTrad("auditlog.table.email")) }),
3877
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: formatMessage(index.getTrad("auditlog.table.ip")) }),
3878
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: formatMessage(index.getTrad("auditlog.table.details")) })
3879
- ] }) }),
3880
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tbody, { children: [
3881
- loading && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tr, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { colSpan: 5, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: formatMessage(index.getTrad("auditlog.loading")) }) }) }) }),
3882
- !loading && records.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tr, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { colSpan: 5, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: hasActiveFilters ? formatMessage(index.getTrad("auditlog.filters.empty")) : formatMessage(index.getTrad("auditlog.table.empty")) }) }) }) }),
3883
- !loading && records.map((record) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
3884
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", children: /* @__PURE__ */ jsxRuntime.jsx(LocalizedDate, { date: record.createdAt, options: { second: "2-digit" } }) }) }),
3885
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
3886
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", children: record.action }),
3887
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tooltip, { label: formatMessage(index.getTrad(`auditlog.action.${record.action}`)), children: /* @__PURE__ */ jsxRuntime.jsx(
3888
- icons.Information,
3889
- {
3890
- "aria-hidden": true,
3891
- style: { cursor: "help" },
3892
- width: "1.4rem",
3893
- height: "1.4rem",
3894
- fill: "primary600"
3895
- }
3896
- ) })
3897
- ] }) }),
3898
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", children: record.email ?? "—" }) }),
3899
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", children: record.ip ?? "—" }) }),
3900
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { style: { maxWidth: "200px" }, children: record.details ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tooltip, { label: record.details, side: "top", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", style: DETAILS_TEXT_STYLE, children: record.details }) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: "—" }) })
3901
- ] }, record.id))
3902
- ] })
3880
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", width: "100%" }, children: [
3881
+ /* @__PURE__ */ jsxRuntime.jsxs(CustomTable, { colCount: 5, rowCount: records.length, children: [
3882
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
3883
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: formatMessage(index.getTrad("auditlog.table.timestamp")) }),
3884
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: formatMessage(index.getTrad("auditlog.table.action")) }),
3885
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: formatMessage(index.getTrad("auditlog.table.email")) }),
3886
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: formatMessage(index.getTrad("auditlog.table.ip")) }),
3887
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: formatMessage(index.getTrad("auditlog.table.details")) })
3888
+ ] }) }),
3889
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tbody, { children: [
3890
+ records.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tr, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { colSpan: 5, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", alignItems: "center", style: { minHeight: "80px" }, children: loading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { small: true }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: hasActiveFilters ? formatMessage(index.getTrad("auditlog.filters.empty")) : formatMessage(index.getTrad("auditlog.table.empty")) }) }) }) }),
3891
+ records.map((record) => /* @__PURE__ */ jsxRuntime.jsxs(
3892
+ designSystem.Tr,
3893
+ {
3894
+ style: { opacity: loading ? 0.4 : 1, transition: "opacity 0.15s" },
3895
+ children: [
3896
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", children: /* @__PURE__ */ jsxRuntime.jsx(LocalizedDate, { date: record.createdAt, options: { second: "2-digit" } }) }) }),
3897
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
3898
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", children: record.action }),
3899
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tooltip, { label: formatMessage(index.getTrad(`auditlog.action.${record.action}`)), children: /* @__PURE__ */ jsxRuntime.jsx(
3900
+ icons.Information,
3901
+ {
3902
+ "aria-hidden": true,
3903
+ style: { cursor: "help" },
3904
+ width: "1.4rem",
3905
+ height: "1.4rem",
3906
+ fill: "primary600"
3907
+ }
3908
+ ) })
3909
+ ] }) }),
3910
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", children: record.email ?? "—" }) }),
3911
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", children: record.ip ?? "—" }) }),
3912
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { style: { maxWidth: "200px" }, children: record.details ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tooltip, { label: record.details, side: "top", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", style: DETAILS_TEXT_STYLE, children: record.details }) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: "—" }) })
3913
+ ]
3914
+ },
3915
+ record.id
3916
+ ))
3917
+ ] })
3918
+ ] }),
3919
+ loading && records.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
3920
+ designSystem.Flex,
3921
+ {
3922
+ justifyContent: "center",
3923
+ alignItems: "center",
3924
+ style: { position: "absolute", inset: 0, pointerEvents: "none" },
3925
+ children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { small: true })
3926
+ }
3927
+ )
3903
3928
  ] }),
3904
3929
  /* @__PURE__ */ jsxRuntime.jsx(
3905
3930
  TablePagination,
@@ -4173,8 +4198,8 @@ function isDirtyArray(a, b) {
4173
4198
  }
4174
4199
  function useOidcSettings() {
4175
4200
  const { get: get2, put, post } = admin.useFetchClient();
4176
- const [state, dispatch] = react.useReducer(reducer, initialState);
4177
- react.useEffect(() => {
4201
+ const [state, dispatch] = React.useReducer(reducer, initialState);
4202
+ React.useEffect(() => {
4178
4203
  get2(`/strapi-plugin-oidc/oidc-roles`).then((response) => {
4179
4204
  dispatch({ type: "hydrate/oidcRoles", oidcRoles: response.data });
4180
4205
  });
@@ -4195,19 +4220,19 @@ function useOidcSettings() {
4195
4220
  });
4196
4221
  });
4197
4222
  }, [get2]);
4198
- const onChangeRole = react.useCallback((values, oidcId) => {
4223
+ const onChangeRole = React.useCallback((values, oidcId) => {
4199
4224
  dispatch({ type: "patch/oidcRole", oidcId, values });
4200
4225
  }, []);
4201
- const onRegisterWhitelist = react.useCallback((email) => {
4226
+ const onRegisterWhitelist = React.useCallback((email) => {
4202
4227
  dispatch({ type: "user/add", email });
4203
4228
  }, []);
4204
- const onDeleteWhitelist = react.useCallback((email) => {
4229
+ const onDeleteWhitelist = React.useCallback((email) => {
4205
4230
  dispatch({ type: "user/delete", email });
4206
4231
  }, []);
4207
- const onDeleteAll = react.useCallback(() => {
4232
+ const onDeleteAll = React.useCallback(() => {
4208
4233
  dispatch({ type: "users/clear" });
4209
4234
  }, []);
4210
- const onImport = react.useCallback(
4235
+ const onImport = React.useCallback(
4211
4236
  async (emails) => {
4212
4237
  const response = await post("/strapi-plugin-oidc/whitelist/import", {
4213
4238
  users: emails.map((e) => ({ email: e }))
@@ -4219,22 +4244,22 @@ function useOidcSettings() {
4219
4244
  },
4220
4245
  [post, get2]
4221
4246
  );
4222
- const onExport = react.useCallback(async () => {
4247
+ const onExport = React.useCallback(async () => {
4223
4248
  const response = await get2("/strapi-plugin-oidc/whitelist/export");
4224
4249
  const data = response.data;
4225
4250
  downloadJson("strapi-oidc-whitelist", data);
4226
4251
  }, [get2]);
4227
- const onToggleWhitelist = react.useCallback((e) => {
4252
+ const onToggleWhitelist = React.useCallback((e) => {
4228
4253
  dispatch({ type: "toggle/useWhitelist", value: e.target.checked });
4229
4254
  }, []);
4230
- const onToggleEnforce = react.useCallback((e) => {
4255
+ const onToggleEnforce = React.useCallback((e) => {
4231
4256
  dispatch({ type: "toggle/enforceOIDC", value: e.target.checked });
4232
4257
  }, []);
4233
- const isDirty = react.useMemo(
4258
+ const isDirty = React.useMemo(
4234
4259
  () => isDirtyPrimitive(state.current.useWhitelist, state.initial.useWhitelist) || isDirtyPrimitive(state.current.enforceOIDC, state.initial.enforceOIDC) || isDirtyArray(state.current.oidcRoles, state.initial.oidcRoles) || isDirtyArray(state.current.users, state.initial.users),
4235
4260
  [state.current, state.initial]
4236
4261
  );
4237
- const onSaveAll = react.useCallback(async () => {
4262
+ const onSaveAll = React.useCallback(async () => {
4238
4263
  dispatch({ type: "loading", value: true });
4239
4264
  try {
4240
4265
  await Promise.all([
@@ -4409,7 +4434,7 @@ function HomePage$1() {
4409
4434
  ] }) })
4410
4435
  ] });
4411
4436
  }
4412
- const HomePage = react.memo(HomePage$1);
4437
+ const HomePage = React.memo(HomePage$1);
4413
4438
  function App() {
4414
4439
  return /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Routes, { children: [
4415
4440
  /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { index: true, element: /* @__PURE__ */ jsxRuntime.jsx(HomePage, {}) }),