strapi-plugin-oidc 1.7.5 → 1.8.0
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 +77 -41
- package/dist/admin/{index-BjmTHbr9.mjs → index-8YTLPV3h.mjs} +113 -100
- package/dist/admin/{index-BfX_taLq.mjs → index-B-K4X_N9.mjs} +8 -2
- package/dist/admin/{index-CLUIKIK3.js → index-BSgVStns.js} +8 -2
- package/dist/admin/{index-CacQfQ3a.js → index-CgG_mHzZ.js} +112 -99
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +188 -49
- package/dist/server/index.mjs +188 -49
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -44,19 +44,38 @@ module.exports = ({ env }) => ({
|
|
|
44
44
|
OIDC_GRANT_TYPE: 'authorization_code',
|
|
45
45
|
OIDC_FAMILY_NAME_FIELD: 'family_name',
|
|
46
46
|
OIDC_GIVEN_NAME_FIELD: 'given_name',
|
|
47
|
-
OIDC_END_SESSION_ENDPOINT: '', // Provider end-session URL
|
|
47
|
+
OIDC_END_SESSION_ENDPOINT: '', // Provider end-session URL (from discovery `end_session_endpoint`)
|
|
48
48
|
OIDC_SSO_BUTTON_TEXT: 'Login via SSO',
|
|
49
49
|
OIDC_ENFORCE: null, // null = use Admin UI toggle; true/false = override in config
|
|
50
50
|
REMEMBER_ME: false, // Persist session across browser restarts
|
|
51
51
|
AUDIT_LOG_RETENTION_DAYS: 90, // Set to 0 to disable audit logging; otherwise entries older than this many days are purged daily at midnight
|
|
52
52
|
OIDC_GROUP_FIELD: 'groups', // OIDC claim field containing group membership
|
|
53
53
|
OIDC_GROUP_ROLE_MAP: '{}', // JSON map of group names to Strapi role names
|
|
54
|
+
OIDC_REQUIRE_EMAIL_VERIFIED: true, // Reject logins when provider does not report email_verified=true (set false to disable)
|
|
55
|
+
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
|
+
OIDC_FORCE_SECURE_COOKIES: false, // Set true when behind a trusted HTTPS proxy that Strapi can't auto-detect
|
|
54
59
|
},
|
|
55
60
|
},
|
|
56
61
|
});
|
|
57
62
|
```
|
|
58
63
|
|
|
59
|
-
All
|
|
64
|
+
All OIDC values come from your provider's discovery document, typically available at `https://your-provider/.well-known/openid-configuration`.
|
|
65
|
+
|
|
66
|
+
### Security features
|
|
67
|
+
|
|
68
|
+
- **ID token verification** — `OIDC_JWKS_URI` and `OIDC_ISSUER` enable signature, issuer, audience, and expiry validation via [`jose`](https://github.com/panva/jose)
|
|
69
|
+
- **Email verification** — `OIDC_REQUIRE_EMAIL_VERIFIED: true` (default) rejects unverified emails
|
|
70
|
+
- **CSRF protection** — OIDC state/nonce and POST-only logout endpoint
|
|
71
|
+
- **Rate limiting** — 1 000 req/min per IP+UA (in-process; use a reverse-proxy-level limiter for multi-node)
|
|
72
|
+
- **Secure cookies** — `OIDC_FORCE_SECURE_COOKIES` ensures cookies are marked Secure
|
|
73
|
+
|
|
74
|
+
### Client IP attribution and reverse proxies
|
|
75
|
+
|
|
76
|
+
The plugin logs client IPs for rate-limit buckets and audit logs. When Strapi runs behind a reverse proxy, **set `server.proxy: true`** so Koa trusts `X-Forwarded-For`; otherwise all IPs will be the proxy's.
|
|
77
|
+
|
|
78
|
+
Set `OIDC_TRUSTED_IP_HEADER: 'cf-connecting-ip'` when behind Cloudflare. The header is only honoured when `server.proxy: true` is set.
|
|
60
79
|
|
|
61
80
|
## Login
|
|
62
81
|
|
|
@@ -64,27 +83,27 @@ Navigate to `/strapi-plugin-oidc/oidc` to start the OIDC flow, or click the **Lo
|
|
|
64
83
|
|
|
65
84
|
## Logout
|
|
66
85
|
|
|
67
|
-
When `OIDC_END_SESSION_ENDPOINT` is set, clicking logout
|
|
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.
|
|
87
|
+
|
|
88
|
+
The logout endpoint is `POST /strapi-plugin-oidc/logout`. Using POST instead of GET prevents CSRF-forced-logout attacks.
|
|
68
89
|
|
|
69
90
|
## Admin Settings
|
|
70
91
|
|
|
71
92
|
Manage the plugin under **Settings → OIDC Plugin**.
|
|
72
93
|
|
|
73
|
-
**Default Roles** —
|
|
94
|
+
**Default Roles** — Strapi admin role(s) assigned to new users on first login.
|
|
74
95
|
|
|
75
|
-
**Whitelist** — Restrict access to specific email addresses. When
|
|
96
|
+
**Whitelist** — Restrict access to specific email addresses. When empty, any authenticated OIDC user gets an account. Supports:
|
|
76
97
|
|
|
77
|
-
-
|
|
78
|
-
- JSON import / export
|
|
98
|
+
- Individual emails with optional role overrides
|
|
99
|
+
- JSON import / export
|
|
79
100
|
- Bulk delete with confirmation
|
|
80
|
-
- Unsaved changes are held in the UI until **Save Changes** is clicked
|
|
81
101
|
|
|
82
|
-
**Audit Logs** —
|
|
102
|
+
**Audit Logs** — Authentication events recorded and visible in the settings page. Filter by action, email, IP, and date. **Download** exports the current view as NDJSON. Set `AUDIT_LOG_RETENTION_DAYS` to `0` to disable. Records older than the configured value (default: 90 days) are purged daily.
|
|
83
103
|
|
|
84
|
-
**Enforce OIDC Login** — Removes
|
|
104
|
+
**Enforce OIDC Login** — Removes email/password fields from the login page and blocks direct login API calls. Automatically disabled when the whitelist is empty to prevent lockout.
|
|
85
105
|
|
|
86
|
-
|
|
87
|
-
- **Lockout recovery**: set `OIDC_ENFORCE: false` in your plugin config and restart Strapi. This writes through to the database so removing the variable afterwards keeps the setting.
|
|
106
|
+
The toggle is grayed out when `OIDC_ENFORCE` is set in config. **Lockout recovery**: set `OIDC_ENFORCE: false` in your plugin config and restart Strapi.
|
|
88
107
|
|
|
89
108
|
## Group-to-Role Mapping
|
|
90
109
|
|
|
@@ -118,18 +137,26 @@ Role names are the **display names** shown in **Settings → Roles** (e.g. `"Edi
|
|
|
118
137
|
|
|
119
138
|
### Role assignment precedence
|
|
120
139
|
|
|
121
|
-
1. **
|
|
122
|
-
2. **No
|
|
140
|
+
1. **OIDC groups match `OIDC_GROUP_ROLE_MAP`** → mapped Strapi roles
|
|
141
|
+
2. **No match or no mapping** → default OIDC roles (new users only)
|
|
123
142
|
|
|
124
143
|
### Role updates on subsequent logins
|
|
125
144
|
|
|
126
|
-
- **New users** — Roles
|
|
127
|
-
- **Existing users with
|
|
128
|
-
- **Existing users
|
|
145
|
+
- **New users** — Roles assigned on first login (group-mapped or default).
|
|
146
|
+
- **Existing users with group match** — Roles updated to reflect current mapping.
|
|
147
|
+
- **Existing users without group match** — Roles left unchanged. Manually-assigned roles are never overwritten.
|
|
129
148
|
|
|
130
149
|
## Whitelist API
|
|
131
150
|
|
|
132
|
-
The whitelist can be managed programmatically using a Strapi **API token
|
|
151
|
+
The whitelist can be managed programmatically using a Strapi **API token**. All endpoints are under `/api/strapi-plugin-oidc` and require `Authorization: Bearer <token>`.
|
|
152
|
+
|
|
153
|
+
**Full-access tokens** can call all routes. **Custom tokens** must be granted one of the following scopes (Settings → API Tokens → Custom → plugin permissions):
|
|
154
|
+
|
|
155
|
+
| Scope | Routes |
|
|
156
|
+
| --------------------------------------------- | ----------------------------------------------- |
|
|
157
|
+
| `plugin::strapi-plugin-oidc.whitelist.read` | `GET /whitelist`, `GET /whitelist/export` |
|
|
158
|
+
| `plugin::strapi-plugin-oidc.whitelist.write` | `POST /whitelist`, `POST /whitelist/import` |
|
|
159
|
+
| `plugin::strapi-plugin-oidc.whitelist.delete` | `DELETE /whitelist`, `DELETE /whitelist/:email` |
|
|
133
160
|
|
|
134
161
|
| Method | Path | Description |
|
|
135
162
|
| -------- | ------------------------------------------ | ---------------------- |
|
|
@@ -185,7 +212,14 @@ curl -X DELETE -H "Authorization: Bearer <token>" \
|
|
|
185
212
|
|
|
186
213
|
## Audit Log API
|
|
187
214
|
|
|
188
|
-
Audit log entries can be fetched programmatically using a Strapi **API token
|
|
215
|
+
Audit log entries can be fetched programmatically using a Strapi **API token**. Endpoints are under `/api/strapi-plugin-oidc` and require `Authorization: Bearer <token>`.
|
|
216
|
+
|
|
217
|
+
**Full-access tokens** can call all routes. **Custom tokens** must be granted one of the following scopes:
|
|
218
|
+
|
|
219
|
+
| Scope | Routes |
|
|
220
|
+
| ----------------------------------------- | ------------------------------------------- |
|
|
221
|
+
| `plugin::strapi-plugin-oidc.audit.read` | `GET /audit-logs`, `GET /audit-logs/export` |
|
|
222
|
+
| `plugin::strapi-plugin-oidc.audit.delete` | `DELETE /audit-logs` |
|
|
189
223
|
|
|
190
224
|
| Method | Path | Description |
|
|
191
225
|
| -------- | ------------------------------------------- | ----------------------------------- |
|
|
@@ -246,18 +280,20 @@ curl -H "Authorization: Bearer <token>" -G \
|
|
|
246
280
|
|
|
247
281
|
### Recorded actions
|
|
248
282
|
|
|
249
|
-
| Action | Trigger
|
|
250
|
-
| ----------------------- |
|
|
251
|
-
| `login_success` | Successful OIDC authentication
|
|
252
|
-
| `user_created` | New Strapi admin user created during login
|
|
253
|
-
| `login_failure` | Unexpected error during the OIDC login flow
|
|
254
|
-
| `missing_code` | Callback received without an authorisation code
|
|
255
|
-
| `state_mismatch` | CSRF state cookie does not match callback parameter
|
|
256
|
-
| `nonce_mismatch` | ID token nonce does not match the session nonce
|
|
257
|
-
| `token_exchange_failed` | Provider returned an error during token exchange
|
|
258
|
-
| `whitelist_rejected` | Email not present in the active whitelist
|
|
259
|
-
| `
|
|
260
|
-
| `
|
|
283
|
+
| Action | Trigger |
|
|
284
|
+
| ----------------------- | ----------------------------------------------------------------- |
|
|
285
|
+
| `login_success` | Successful OIDC authentication |
|
|
286
|
+
| `user_created` | New Strapi admin user created during login |
|
|
287
|
+
| `login_failure` | Unexpected error during the OIDC login flow |
|
|
288
|
+
| `missing_code` | Callback received without an authorisation code |
|
|
289
|
+
| `state_mismatch` | CSRF state cookie does not match callback parameter |
|
|
290
|
+
| `nonce_mismatch` | ID token nonce does not match the session nonce |
|
|
291
|
+
| `token_exchange_failed` | Provider returned an error during token exchange |
|
|
292
|
+
| `whitelist_rejected` | Email not present in the active whitelist |
|
|
293
|
+
| `email_not_verified` | Provider did not report `email_verified=true` |
|
|
294
|
+
| `id_token_invalid` | ID token failed signature, issuer, audience, or expiry validation |
|
|
295
|
+
| `logout` | User logged out via `/logout` |
|
|
296
|
+
| `session_expired` | Logout attempted but provider session already stale |
|
|
261
297
|
|
|
262
298
|
Each event is also emitted on Strapi's internal eventHub as `strapi-plugin-oidc::auth.<action>`, which Enterprise audit log listeners pick up automatically.
|
|
263
299
|
|
|
@@ -280,16 +316,16 @@ This plugin is a hard fork of [`strapi-plugin-sso`](https://github.com/yasudaclo
|
|
|
280
316
|
|
|
281
317
|
### Changes from the original:
|
|
282
318
|
|
|
283
|
-
-
|
|
284
|
-
- Redesigned
|
|
285
|
-
-
|
|
286
|
-
-
|
|
287
|
-
- Migrated to Vitest with
|
|
288
|
-
- Config variable names aligned with OIDC discovery document field names
|
|
289
|
-
-
|
|
290
|
-
- Whitelist
|
|
291
|
-
- Hardened OIDC flow: server-generated state and nonce, PKCE, Bearer token auth for userinfo,
|
|
292
|
-
- Audit log: records all auth events to a queryable table with
|
|
319
|
+
- OIDC-only (removed other SSO methods)
|
|
320
|
+
- Redesigned whitelist and role management UI using native Strapi components
|
|
321
|
+
- OIDC enforcement via admin toggle or `OIDC_ENFORCE` config
|
|
322
|
+
- RP-initiated logout with smart session detection
|
|
323
|
+
- Migrated to Vitest with e2e coverage
|
|
324
|
+
- Config variable names aligned with OIDC discovery document field names
|
|
325
|
+
- **Login via SSO** button always injected; text configurable via `OIDC_SSO_BUTTON_TEXT`
|
|
326
|
+
- Whitelist REST API with JSON import/export, bulk delete, delete by email
|
|
327
|
+
- Hardened OIDC flow: server-generated state and nonce, PKCE, Bearer token auth for userinfo, generic error messages on failure
|
|
328
|
+
- Audit log: records all auth events to a queryable table with UI, JSON/NDJSON export, and REST API
|
|
293
329
|
|
|
294
330
|
## License
|
|
295
331
|
|
|
@@ -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,
|
|
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";
|
|
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-
|
|
8
|
+
import { g as getTrad } from "./index-B-K4X_N9.mjs";
|
|
9
9
|
import styled from "styled-components";
|
|
10
10
|
import { Filter, ClipboardList, Server } from "lucide-react";
|
|
11
11
|
function Role({ oidcRoles, roles, onChangeRole }) {
|
|
@@ -66,15 +66,21 @@ const TagInputWrapper = styled(Box)`
|
|
|
66
66
|
flex-wrap: wrap;
|
|
67
67
|
gap: 4px;
|
|
68
68
|
align-items: center;
|
|
69
|
-
padding:
|
|
69
|
+
padding-inline: ${({ theme }) => theme.spaces[4]};
|
|
70
|
+
padding-block: ${({ theme }) => theme.spaces[3]};
|
|
70
71
|
border-radius: 4px;
|
|
71
72
|
border: 1px solid ${({ theme }) => theme.colors.neutral200};
|
|
72
73
|
background-color: ${({ theme }) => theme.colors.neutral0};
|
|
73
74
|
cursor: text;
|
|
74
75
|
min-width: 220px;
|
|
75
|
-
min-height:
|
|
76
|
+
min-height: 4.8rem;
|
|
76
77
|
flex: 0 0 auto;
|
|
77
78
|
|
|
79
|
+
${({ theme }) => theme.breakpoints.medium} {
|
|
80
|
+
padding-block: ${({ theme }) => theme.spaces[2]};
|
|
81
|
+
min-height: 4rem;
|
|
82
|
+
}
|
|
83
|
+
|
|
78
84
|
&:focus-within {
|
|
79
85
|
border-color: ${({ theme }) => theme.colors.primary600};
|
|
80
86
|
box-shadow: 0 0 0 2px ${({ theme }) => theme.colors.primary100};
|
|
@@ -139,7 +145,6 @@ function TagInputShell({
|
|
|
139
145
|
inputProps,
|
|
140
146
|
children
|
|
141
147
|
}) {
|
|
142
|
-
const inputId = useId();
|
|
143
148
|
return /* @__PURE__ */ jsxs(TagInputWrapper, { ref: wrapperRef, onClick: () => inputRef.current?.focus(), children: [
|
|
144
149
|
/* @__PURE__ */ jsxs(Flex, { gap: 2, wrap: "wrap", alignItems: "center", style: { flex: 1, minWidth: 0 }, children: [
|
|
145
150
|
startIcon && /* @__PURE__ */ jsx(StartIconSlot, { children: startIcon }),
|
|
@@ -151,14 +156,6 @@ function TagInputShell({
|
|
|
151
156
|
type: "text",
|
|
152
157
|
placeholder: value.length === 0 ? placeholder : "",
|
|
153
158
|
"aria-label": placeholder,
|
|
154
|
-
autoComplete: "off",
|
|
155
|
-
autoCorrect: "off",
|
|
156
|
-
autoCapitalize: "off",
|
|
157
|
-
spellCheck: false,
|
|
158
|
-
name: `tag-input-${inputId}`,
|
|
159
|
-
"data-form-type": "other",
|
|
160
|
-
"data-lpignore": "true",
|
|
161
|
-
"data-1p-ignore": "true",
|
|
162
159
|
...inputProps
|
|
163
160
|
}
|
|
164
161
|
)
|
|
@@ -743,6 +740,16 @@ function TagDateInput({ value = [], onChange, placeholder, startIcon }) {
|
|
|
743
740
|
}
|
|
744
741
|
);
|
|
745
742
|
}
|
|
743
|
+
const SizedButton = styled(Button)`
|
|
744
|
+
&& {
|
|
745
|
+
height: 4.8rem;
|
|
746
|
+
}
|
|
747
|
+
${({ theme }) => theme.breakpoints.medium} {
|
|
748
|
+
&& {
|
|
749
|
+
height: 4rem;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
`;
|
|
746
753
|
const Icon = styled.span`
|
|
747
754
|
display: inline-flex;
|
|
748
755
|
align-items: center;
|
|
@@ -907,91 +914,98 @@ function Whitelist({
|
|
|
907
914
|
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
908
915
|
/* @__PURE__ */ jsx(Typography, { tag: "p", variant: "omega", textColor: "neutral600", marginBottom: 4, children: formatMessage(getTrad("whitelist.description")) }),
|
|
909
916
|
useWhitelist && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
910
|
-
/* @__PURE__ */ jsxs(
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
style: {
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
)
|
|
936
|
-
] }),
|
|
937
|
-
/* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
|
|
938
|
-
/* @__PURE__ */ jsx(
|
|
939
|
-
Button,
|
|
940
|
-
{
|
|
941
|
-
size: "S",
|
|
942
|
-
variant: "tertiary",
|
|
943
|
-
startIcon: /* @__PURE__ */ jsx(Download, {}),
|
|
944
|
-
onClick: onExport,
|
|
945
|
-
disabled: users.length === 0,
|
|
946
|
-
style: { paddingTop: "1.1rem", paddingBottom: "1.1rem", height: "auto" },
|
|
947
|
-
children: formatMessage(getTrad("whitelist.export"))
|
|
948
|
-
}
|
|
949
|
-
),
|
|
950
|
-
/* @__PURE__ */ jsx(
|
|
951
|
-
Button,
|
|
952
|
-
{
|
|
953
|
-
size: "S",
|
|
954
|
-
variant: "tertiary",
|
|
955
|
-
startIcon: /* @__PURE__ */ jsx(Upload, {}),
|
|
956
|
-
onClick: () => fileInputRef.current?.click(),
|
|
957
|
-
style: { paddingTop: "1.1rem", paddingBottom: "1.1rem", height: "auto" },
|
|
958
|
-
children: formatMessage(getTrad("whitelist.import"))
|
|
959
|
-
}
|
|
960
|
-
),
|
|
961
|
-
/* @__PURE__ */ jsx(
|
|
962
|
-
"input",
|
|
963
|
-
{
|
|
964
|
-
ref: fileInputRef,
|
|
965
|
-
type: "file",
|
|
966
|
-
accept: ".json,application/json",
|
|
967
|
-
style: { display: "none" },
|
|
968
|
-
onChange: handleImport
|
|
969
|
-
}
|
|
970
|
-
),
|
|
971
|
-
/* @__PURE__ */ jsx(
|
|
972
|
-
ConfirmDialog,
|
|
973
|
-
{
|
|
974
|
-
trigger: /* @__PURE__ */ jsx(
|
|
975
|
-
Button,
|
|
917
|
+
/* @__PURE__ */ jsxs(
|
|
918
|
+
Flex,
|
|
919
|
+
{
|
|
920
|
+
gap: 8,
|
|
921
|
+
marginTop: 5,
|
|
922
|
+
marginBottom: 5,
|
|
923
|
+
alignItems: "stretch",
|
|
924
|
+
wrap: "wrap",
|
|
925
|
+
style: { rowGap: "0.8rem" },
|
|
926
|
+
children: [
|
|
927
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", style: { minWidth: "280px", flex: "1 1 280px" }, children: [
|
|
928
|
+
/* @__PURE__ */ jsx(Box, { style: { flex: 1, minWidth: "200px" }, children: /* @__PURE__ */ jsx(Field.Root, { children: /* @__PURE__ */ jsx(
|
|
929
|
+
Field.Input,
|
|
930
|
+
{
|
|
931
|
+
type: "text",
|
|
932
|
+
disabled: loading,
|
|
933
|
+
value: email,
|
|
934
|
+
hasError: Boolean(email && !EMAIL_REGEX.test(email)),
|
|
935
|
+
onChange: (e) => setEmail(e.currentTarget.value),
|
|
936
|
+
placeholder: formatMessage(getTrad("whitelist.email.placeholder")),
|
|
937
|
+
style: { fontSize: "1.4rem", lineHeight: "2.2rem" }
|
|
938
|
+
}
|
|
939
|
+
) }) }),
|
|
940
|
+
/* @__PURE__ */ jsx(
|
|
941
|
+
SizedButton,
|
|
976
942
|
{
|
|
977
943
|
size: "S",
|
|
978
|
-
|
|
979
|
-
|
|
944
|
+
startIcon: /* @__PURE__ */ jsx(Plus, {}),
|
|
945
|
+
disabled: loading || email.trim() === "" || !EMAIL_REGEX.test(email),
|
|
946
|
+
loading,
|
|
947
|
+
onClick: onSaveEmail,
|
|
948
|
+
children: formatMessage(getTrad("page.add"))
|
|
949
|
+
}
|
|
950
|
+
)
|
|
951
|
+
] }),
|
|
952
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
|
|
953
|
+
/* @__PURE__ */ jsx(
|
|
954
|
+
SizedButton,
|
|
955
|
+
{
|
|
956
|
+
size: "S",
|
|
957
|
+
variant: "tertiary",
|
|
958
|
+
startIcon: /* @__PURE__ */ jsx(Download, {}),
|
|
959
|
+
onClick: onExport,
|
|
980
960
|
disabled: users.length === 0,
|
|
981
|
-
|
|
982
|
-
children: formatMessage(getTrad("whitelist.delete.all.label"))
|
|
961
|
+
children: formatMessage(getTrad("whitelist.export"))
|
|
983
962
|
}
|
|
984
963
|
),
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
964
|
+
/* @__PURE__ */ jsx(
|
|
965
|
+
SizedButton,
|
|
966
|
+
{
|
|
967
|
+
size: "S",
|
|
968
|
+
variant: "tertiary",
|
|
969
|
+
startIcon: /* @__PURE__ */ jsx(Upload, {}),
|
|
970
|
+
onClick: () => fileInputRef.current?.click(),
|
|
971
|
+
children: formatMessage(getTrad("whitelist.import"))
|
|
972
|
+
}
|
|
973
|
+
),
|
|
974
|
+
/* @__PURE__ */ jsx(
|
|
975
|
+
"input",
|
|
976
|
+
{
|
|
977
|
+
ref: fileInputRef,
|
|
978
|
+
type: "file",
|
|
979
|
+
accept: ".json,application/json",
|
|
980
|
+
style: { display: "none" },
|
|
981
|
+
onChange: handleImport
|
|
982
|
+
}
|
|
983
|
+
),
|
|
984
|
+
/* @__PURE__ */ jsx(
|
|
985
|
+
ConfirmDialog,
|
|
986
|
+
{
|
|
987
|
+
trigger: /* @__PURE__ */ jsx(
|
|
988
|
+
SizedButton,
|
|
989
|
+
{
|
|
990
|
+
size: "S",
|
|
991
|
+
variant: "danger-light",
|
|
992
|
+
startIcon: /* @__PURE__ */ jsx(Trash, {}),
|
|
993
|
+
disabled: users.length === 0,
|
|
994
|
+
children: formatMessage(getTrad("whitelist.delete.all.label"))
|
|
995
|
+
}
|
|
996
|
+
),
|
|
997
|
+
title: formatMessage(getTrad("whitelist.delete.all.title")),
|
|
998
|
+
body: /* @__PURE__ */ jsx(Flex, { justifyContent: "center", children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral800", textAlign: "center", children: formatMessage(getTrad("whitelist.delete.all.description"), {
|
|
999
|
+
count: users.length
|
|
1000
|
+
}) }) }),
|
|
1001
|
+
confirmLabel: formatMessage(getTrad("whitelist.delete.all.label")),
|
|
1002
|
+
onConfirm: onDeleteAll
|
|
1003
|
+
}
|
|
1004
|
+
)
|
|
1005
|
+
] })
|
|
1006
|
+
]
|
|
1007
|
+
}
|
|
1008
|
+
),
|
|
995
1009
|
/* @__PURE__ */ jsx(Divider, {}),
|
|
996
1010
|
/* @__PURE__ */ jsxs(CustomTable, { colCount: 4, rowCount: users.length, children: [
|
|
997
1011
|
/* @__PURE__ */ jsx(Thead, { children: /* @__PURE__ */ jsxs(Tr, { children: [
|
|
@@ -3597,6 +3611,8 @@ const AUDIT_ACTIONS = [
|
|
|
3597
3611
|
"nonce_mismatch",
|
|
3598
3612
|
"token_exchange_failed",
|
|
3599
3613
|
"whitelist_rejected",
|
|
3614
|
+
"email_not_verified",
|
|
3615
|
+
"id_token_invalid",
|
|
3600
3616
|
"logout",
|
|
3601
3617
|
"session_expired",
|
|
3602
3618
|
"user_created"
|
|
@@ -3736,14 +3752,13 @@ function AuditLog({ title } = {}) {
|
|
|
3736
3752
|
title ?? /* @__PURE__ */ jsx("span", {}),
|
|
3737
3753
|
/* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
|
|
3738
3754
|
/* @__PURE__ */ jsx(
|
|
3739
|
-
|
|
3755
|
+
SizedButton,
|
|
3740
3756
|
{
|
|
3741
3757
|
size: "S",
|
|
3742
3758
|
variant: "tertiary",
|
|
3743
3759
|
startIcon: /* @__PURE__ */ jsx(Download, {}),
|
|
3744
3760
|
onClick: handleExport,
|
|
3745
3761
|
disabled: pagination.total === 0,
|
|
3746
|
-
style: { paddingTop: "1.1rem", paddingBottom: "1.1rem", height: "auto" },
|
|
3747
3762
|
children: formatMessage(getTrad("auditlog.export"))
|
|
3748
3763
|
}
|
|
3749
3764
|
),
|
|
@@ -3751,13 +3766,12 @@ function AuditLog({ title } = {}) {
|
|
|
3751
3766
|
ConfirmDialog,
|
|
3752
3767
|
{
|
|
3753
3768
|
trigger: /* @__PURE__ */ jsx(
|
|
3754
|
-
|
|
3769
|
+
SizedButton,
|
|
3755
3770
|
{
|
|
3756
3771
|
size: "S",
|
|
3757
3772
|
variant: "danger-light",
|
|
3758
3773
|
startIcon: /* @__PURE__ */ jsx(Trash, {}),
|
|
3759
3774
|
disabled: pagination.total === 0,
|
|
3760
|
-
style: { paddingTop: "1.1rem", paddingBottom: "1.1rem", height: "auto" },
|
|
3761
3775
|
children: formatMessage(getTrad("auditlog.clear"))
|
|
3762
3776
|
}
|
|
3763
3777
|
),
|
|
@@ -3838,13 +3852,12 @@ function AuditLog({ title } = {}) {
|
|
|
3838
3852
|
}
|
|
3839
3853
|
),
|
|
3840
3854
|
hasActiveFilters && /* @__PURE__ */ jsx(
|
|
3841
|
-
|
|
3855
|
+
SizedButton,
|
|
3842
3856
|
{
|
|
3843
3857
|
size: "S",
|
|
3844
3858
|
variant: "danger-light",
|
|
3845
3859
|
startIcon: /* @__PURE__ */ jsx(Trash, {}),
|
|
3846
3860
|
onClick: clearFilters,
|
|
3847
|
-
style: { height: "4rem" },
|
|
3848
3861
|
children: formatMessage(getTrad("auditlog.filters.clear"))
|
|
3849
3862
|
}
|
|
3850
3863
|
)
|
|
@@ -4365,7 +4378,7 @@ function HomePage() {
|
|
|
4365
4378
|
] }) })
|
|
4366
4379
|
] })
|
|
4367
4380
|
] }),
|
|
4368
|
-
/* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsx(
|
|
4381
|
+
/* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", marginBottom: 8, children: /* @__PURE__ */ jsx(
|
|
4369
4382
|
Button,
|
|
4370
4383
|
{
|
|
4371
4384
|
size: "L",
|
|
@@ -125,6 +125,8 @@ const en = {
|
|
|
125
125
|
"auditlog.action.nonce_mismatch": "The nonce in the ID token did not match the one generated at login. This may indicate a token replay attack.",
|
|
126
126
|
"auditlog.action.token_exchange_failed": "The authorisation code could not be exchanged for tokens. The OIDC provider rejected the request.",
|
|
127
127
|
"auditlog.action.whitelist_rejected": "The user's email address is not on the whitelist. Access was denied.",
|
|
128
|
+
"auditlog.action.email_not_verified": "The OIDC provider did not confirm the user's email address as verified. Access was denied.",
|
|
129
|
+
"auditlog.action.id_token_invalid": "The ID token failed signature, issuer, audience, or expiry validation. Access was denied.",
|
|
128
130
|
"auth.page.authenticating.title": "Authenticating...",
|
|
129
131
|
"auth.page.authenticating.noscript.heading": "JavaScript Required",
|
|
130
132
|
"auth.page.authenticating.noscript.body": "JavaScript must be enabled for authentication to complete.",
|
|
@@ -165,7 +167,7 @@ const index = {
|
|
|
165
167
|
defaultMessage: "Configuration"
|
|
166
168
|
},
|
|
167
169
|
Component: async () => {
|
|
168
|
-
return await import("./index-
|
|
170
|
+
return await import("./index-8YTLPV3h.mjs");
|
|
169
171
|
},
|
|
170
172
|
permissions: [{ action: "plugin::strapi-plugin-oidc.read", subject: null }]
|
|
171
173
|
}
|
|
@@ -268,7 +270,11 @@ const index = {
|
|
|
268
270
|
window.sessionStorage.removeItem("isLoggedIn");
|
|
269
271
|
document.cookie = "jwtToken=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/";
|
|
270
272
|
document.cookie = "jwtToken=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/admin";
|
|
271
|
-
|
|
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();
|
|
272
278
|
return new Promise(() => {
|
|
273
279
|
});
|
|
274
280
|
}
|
|
@@ -126,6 +126,8 @@ const en = {
|
|
|
126
126
|
"auditlog.action.nonce_mismatch": "The nonce in the ID token did not match the one generated at login. This may indicate a token replay attack.",
|
|
127
127
|
"auditlog.action.token_exchange_failed": "The authorisation code could not be exchanged for tokens. The OIDC provider rejected the request.",
|
|
128
128
|
"auditlog.action.whitelist_rejected": "The user's email address is not on the whitelist. Access was denied.",
|
|
129
|
+
"auditlog.action.email_not_verified": "The OIDC provider did not confirm the user's email address as verified. Access was denied.",
|
|
130
|
+
"auditlog.action.id_token_invalid": "The ID token failed signature, issuer, audience, or expiry validation. Access was denied.",
|
|
129
131
|
"auth.page.authenticating.title": "Authenticating...",
|
|
130
132
|
"auth.page.authenticating.noscript.heading": "JavaScript Required",
|
|
131
133
|
"auth.page.authenticating.noscript.body": "JavaScript must be enabled for authentication to complete.",
|
|
@@ -166,7 +168,7 @@ const index = {
|
|
|
166
168
|
defaultMessage: "Configuration"
|
|
167
169
|
},
|
|
168
170
|
Component: async () => {
|
|
169
|
-
return await Promise.resolve().then(() => require("./index-
|
|
171
|
+
return await Promise.resolve().then(() => require("./index-CgG_mHzZ.js"));
|
|
170
172
|
},
|
|
171
173
|
permissions: [{ action: "plugin::strapi-plugin-oidc.read", subject: null }]
|
|
172
174
|
}
|
|
@@ -269,7 +271,11 @@ const index = {
|
|
|
269
271
|
window.sessionStorage.removeItem("isLoggedIn");
|
|
270
272
|
document.cookie = "jwtToken=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/";
|
|
271
273
|
document.cookie = "jwtToken=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/admin";
|
|
272
|
-
|
|
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();
|
|
273
279
|
return new Promise(() => {
|
|
274
280
|
});
|
|
275
281
|
}
|