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 +6 -12
- package/dist/admin/{index-B-K4X_N9.mjs → index-Bb9-aYb4.mjs} +53 -11
- package/dist/admin/{index-CgG_mHzZ.js → index-Bmg4eTYb.js} +113 -88
- package/dist/admin/{index-8YTLPV3h.mjs → index-BqWd-Iiq.mjs} +72 -47
- package/dist/admin/{index-BSgVStns.js → index-Dk6TYtio.js} +57 -13
- package/dist/admin/index.js +3 -1
- package/dist/admin/index.mjs +3 -1
- package/dist/server/index.js +143 -105
- package/dist/server/index.mjs +143 -105
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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** —
|
|
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
|
|
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-
|
|
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
|
-
|
|
266
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
|
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
|
|
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-
|
|
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] =
|
|
129
|
-
const inputRef =
|
|
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] =
|
|
265
|
-
const [activeIndex, setActiveIndex] =
|
|
266
|
-
const wrapperRef =
|
|
267
|
-
const listboxId =
|
|
268
|
-
const optionIdPrefix =
|
|
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
|
-
|
|
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] =
|
|
550
|
-
const [viewDate, setViewDate] =
|
|
551
|
-
const [pendingDates, setPendingDates] =
|
|
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 =
|
|
554
|
-
const dialogId =
|
|
555
|
-
const dialogLabelId =
|
|
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
|
-
|
|
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] =
|
|
867
|
-
const [page, setPage] =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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",
|
|
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] =
|
|
3656
|
-
|
|
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] =
|
|
3667
|
-
const [pagination, setPagination] =
|
|
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] =
|
|
3674
|
-
const [loading, setLoading] =
|
|
3675
|
-
const [filters, setFilters] =
|
|
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 =
|
|
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
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
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
|
-
|
|
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(
|
|
3873
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3874
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
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] =
|
|
4177
|
-
|
|
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 =
|
|
4223
|
+
const onChangeRole = React.useCallback((values, oidcId) => {
|
|
4199
4224
|
dispatch({ type: "patch/oidcRole", oidcId, values });
|
|
4200
4225
|
}, []);
|
|
4201
|
-
const onRegisterWhitelist =
|
|
4226
|
+
const onRegisterWhitelist = React.useCallback((email) => {
|
|
4202
4227
|
dispatch({ type: "user/add", email });
|
|
4203
4228
|
}, []);
|
|
4204
|
-
const onDeleteWhitelist =
|
|
4229
|
+
const onDeleteWhitelist = React.useCallback((email) => {
|
|
4205
4230
|
dispatch({ type: "user/delete", email });
|
|
4206
4231
|
}, []);
|
|
4207
|
-
const onDeleteAll =
|
|
4232
|
+
const onDeleteAll = React.useCallback(() => {
|
|
4208
4233
|
dispatch({ type: "users/clear" });
|
|
4209
4234
|
}, []);
|
|
4210
|
-
const onImport =
|
|
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 =
|
|
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 =
|
|
4252
|
+
const onToggleWhitelist = React.useCallback((e) => {
|
|
4228
4253
|
dispatch({ type: "toggle/useWhitelist", value: e.target.checked });
|
|
4229
4254
|
}, []);
|
|
4230
|
-
const onToggleEnforce =
|
|
4255
|
+
const onToggleEnforce = React.useCallback((e) => {
|
|
4231
4256
|
dispatch({ type: "toggle/enforceOIDC", value: e.target.checked });
|
|
4232
4257
|
}, []);
|
|
4233
|
-
const isDirty =
|
|
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 =
|
|
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 =
|
|
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, {}) }),
|