strapi-plugin-oidc 1.9.7 → 1.10.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 +16 -1
- package/dist/admin/{index-F7EShzfg.js → index-8VB7hT1F.js} +1 -1
- package/dist/admin/{index-BUuwz71P.js → index-CWvZ9OH0.js} +24 -10
- package/dist/admin/{index-CUVEK5GT.mjs → index-CbB2ZVt6.mjs} +24 -10
- package/dist/admin/{index-vkwBXPXn.mjs → index-DMJjpSUm.mjs} +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +26 -5
- package/dist/server/index.mjs +26 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
<img src="https://img.shields.io/npm/v/strapi-plugin-oidc.svg" alt="npm version">
|
|
7
7
|
</a>
|
|
8
8
|
<a href="https://github.com/edmogeor/strapi-plugin-oidc/actions/workflows/ci.yml">
|
|
9
|
-
<img src="https://github.com/edmogeor/strapi-plugin-oidc/actions/workflows/ci.yml/badge.svg" alt="CI"/>
|
|
9
|
+
<img src="https://github.com/edmogeor/strapi-plugin-oidc/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"/>
|
|
10
|
+
</a>
|
|
11
|
+
<a href="https://github.com/fallow-rs/fallow">
|
|
12
|
+
<img src="https://raw.githubusercontent.com/edmogeor/strapi-plugin-oidc/badges/badge.svg" alt="fallow health"/>
|
|
10
13
|
</a>
|
|
11
14
|
<a href="./LICENSE">
|
|
12
15
|
<img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"/>
|
|
@@ -50,6 +53,7 @@ module.exports = ({ env }) => ({
|
|
|
50
53
|
OIDC_REQUIRE_EMAIL_VERIFIED: true, // Reject logins when provider does not report email_verified=true (set false to disable)
|
|
51
54
|
OIDC_TRUSTED_IP_HEADER: '', // Optional: header set by your CDN/proxy containing the real client IP (see note below); only honoured when Koa proxy mode is enabled (see below)
|
|
52
55
|
OIDC_FORCE_SECURE_COOKIES: false, // Set true when behind a trusted HTTPS proxy that Strapi can't auto-detect
|
|
56
|
+
OIDC_SKIP_LOGIN_PAGE: false, // Skip the Strapi login page; redirects unauthenticated users straight to the OIDC provider
|
|
53
57
|
},
|
|
54
58
|
},
|
|
55
59
|
});
|
|
@@ -94,6 +98,17 @@ Only headers that CDN/proxy vendors guarantee to strip from inbound client reque
|
|
|
94
98
|
|
|
95
99
|
Navigate to `/strapi-plugin-oidc/oidc` to start the OIDC flow, or click the **Login via SSO** button injected into the Strapi login page.
|
|
96
100
|
|
|
101
|
+
## Skip Login Page
|
|
102
|
+
|
|
103
|
+
Set `OIDC_SKIP_LOGIN_PAGE: true` to prevent users from ever seeing the Strapi admin login page. Unauthenticated requests are redirected directly to the OIDC provider. This is typically used alongside `OIDC_ENFORCE: true`.
|
|
104
|
+
|
|
105
|
+
Two independent mechanisms cover both entry points:
|
|
106
|
+
|
|
107
|
+
- **Server-side** — A Koa middleware intercepts unauthenticated GET requests to `/admin` and redirects to `/strapi-plugin-oidc/oidc` before the SPA is served. Excluded from this redirect: API routes (`/admin/login`, `/admin/logout`, `/admin/init`, etc.), static assets (`.js`, `.css`, `.png`, etc.), and POST requests.
|
|
108
|
+
- **Client-side** — A DOM `MutationObserver` watches for React Router navigations to `/auth/login` (triggered by session expiry or 401 responses) and redirects before the login form renders.
|
|
109
|
+
|
|
110
|
+
After logout without a provider `end_session_endpoint`, the fallback URL becomes `/strapi-plugin-oidc/oidc` instead of `/admin/auth/login`, ensuring the user lands on the provider's own page.
|
|
111
|
+
|
|
97
112
|
## Logout
|
|
98
113
|
|
|
99
114
|
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.
|
|
@@ -7,7 +7,7 @@ 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-CWvZ9OH0.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 };
|
|
@@ -197,6 +197,7 @@ const PERMISSIONS = {
|
|
|
197
197
|
const AUDIT_LOG_DEFAULTS = {
|
|
198
198
|
ADMIN_PAGE_SIZE: 10
|
|
199
199
|
};
|
|
200
|
+
const OIDC_SIGN_IN_PATH = "/strapi-plugin-oidc/oidc";
|
|
200
201
|
const UI_DEFAULTS = {
|
|
201
202
|
MIN_SPINNER_MS: 400
|
|
202
203
|
};
|
|
@@ -211,7 +212,7 @@ const index = {
|
|
|
211
212
|
id: "settings.configuration",
|
|
212
213
|
defaultMessage: "Configuration"
|
|
213
214
|
},
|
|
214
|
-
Component: () => Promise.resolve().then(() => require("./index-
|
|
215
|
+
Component: () => Promise.resolve().then(() => require("./index-8VB7hT1F.js")),
|
|
215
216
|
permissions: [{ action: PERMISSIONS.READ, subject: null }]
|
|
216
217
|
};
|
|
217
218
|
app.addSettingsLink(
|
|
@@ -237,7 +238,7 @@ const index = {
|
|
|
237
238
|
const defaultButtonText = t("login.sso");
|
|
238
239
|
const isAuthRoute = (path) => /\/auth\/(login|register|forgot-password|reset-password)/.test(path);
|
|
239
240
|
let ssoButtonInjected = false;
|
|
240
|
-
let
|
|
241
|
+
let domObserver = null;
|
|
241
242
|
const injectSSOButton = (buttonText) => {
|
|
242
243
|
if (ssoButtonInjected) return;
|
|
243
244
|
if (!isAuthRoute(window.location.pathname)) return;
|
|
@@ -249,7 +250,7 @@ const index = {
|
|
|
249
250
|
btn.type = "button";
|
|
250
251
|
btn.className = submitButton.className;
|
|
251
252
|
btn.onclick = () => {
|
|
252
|
-
window.location.href =
|
|
253
|
+
window.location.href = OIDC_SIGN_IN_PATH;
|
|
253
254
|
};
|
|
254
255
|
const innerSpan = submitButton.querySelector("span");
|
|
255
256
|
const span = document.createElement("span");
|
|
@@ -287,23 +288,36 @@ const index = {
|
|
|
287
288
|
(el.closest("div")?.parentElement ?? el).remove();
|
|
288
289
|
});
|
|
289
290
|
};
|
|
291
|
+
const startObserver = (tick) => {
|
|
292
|
+
if (domObserver) return;
|
|
293
|
+
tick();
|
|
294
|
+
domObserver = new MutationObserver(tick);
|
|
295
|
+
domObserver.observe(document.body, { childList: true, subtree: true });
|
|
296
|
+
};
|
|
290
297
|
const startLoginObserver = (buttonText, enforced) => {
|
|
291
|
-
|
|
292
|
-
const tick = () => {
|
|
298
|
+
startObserver(() => {
|
|
293
299
|
if (!isAuthRoute(window.location.pathname)) return;
|
|
294
300
|
injectSSOButton(buttonText);
|
|
295
301
|
if (enforced) removeEnforcedElements();
|
|
296
|
-
if (ssoButtonInjected && !enforced)
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
302
|
+
if (ssoButtonInjected && !enforced) domObserver?.disconnect();
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
const startSkipLoginRedirect = () => {
|
|
306
|
+
startObserver(() => {
|
|
307
|
+
if (isAuthRoute(window.location.pathname)) {
|
|
308
|
+
window.location.href = OIDC_SIGN_IN_PATH;
|
|
309
|
+
}
|
|
310
|
+
});
|
|
301
311
|
};
|
|
302
312
|
const applySettings = async () => {
|
|
303
313
|
try {
|
|
304
314
|
const response = await window.fetch("/strapi-plugin-oidc/settings/public");
|
|
305
315
|
if (response.ok) {
|
|
306
316
|
const data = await response.json();
|
|
317
|
+
if (data.skipLoginPage) {
|
|
318
|
+
startSkipLoginRedirect();
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
307
321
|
startLoginObserver(data.ssoButtonText || defaultButtonText, !!data.enforceOIDC);
|
|
308
322
|
} else {
|
|
309
323
|
startLoginObserver(defaultButtonText, false);
|
|
@@ -194,6 +194,7 @@ const PERMISSIONS = {
|
|
|
194
194
|
const AUDIT_LOG_DEFAULTS = {
|
|
195
195
|
ADMIN_PAGE_SIZE: 10
|
|
196
196
|
};
|
|
197
|
+
const OIDC_SIGN_IN_PATH = "/strapi-plugin-oidc/oidc";
|
|
197
198
|
const UI_DEFAULTS = {
|
|
198
199
|
MIN_SPINNER_MS: 400
|
|
199
200
|
};
|
|
@@ -208,7 +209,7 @@ const index = {
|
|
|
208
209
|
id: "settings.configuration",
|
|
209
210
|
defaultMessage: "Configuration"
|
|
210
211
|
},
|
|
211
|
-
Component: () => import("./index-
|
|
212
|
+
Component: () => import("./index-DMJjpSUm.mjs"),
|
|
212
213
|
permissions: [{ action: PERMISSIONS.READ, subject: null }]
|
|
213
214
|
};
|
|
214
215
|
app.addSettingsLink(
|
|
@@ -234,7 +235,7 @@ const index = {
|
|
|
234
235
|
const defaultButtonText = t("login.sso");
|
|
235
236
|
const isAuthRoute = (path) => /\/auth\/(login|register|forgot-password|reset-password)/.test(path);
|
|
236
237
|
let ssoButtonInjected = false;
|
|
237
|
-
let
|
|
238
|
+
let domObserver = null;
|
|
238
239
|
const injectSSOButton = (buttonText) => {
|
|
239
240
|
if (ssoButtonInjected) return;
|
|
240
241
|
if (!isAuthRoute(window.location.pathname)) return;
|
|
@@ -246,7 +247,7 @@ const index = {
|
|
|
246
247
|
btn.type = "button";
|
|
247
248
|
btn.className = submitButton.className;
|
|
248
249
|
btn.onclick = () => {
|
|
249
|
-
window.location.href =
|
|
250
|
+
window.location.href = OIDC_SIGN_IN_PATH;
|
|
250
251
|
};
|
|
251
252
|
const innerSpan = submitButton.querySelector("span");
|
|
252
253
|
const span = document.createElement("span");
|
|
@@ -284,23 +285,36 @@ const index = {
|
|
|
284
285
|
(el.closest("div")?.parentElement ?? el).remove();
|
|
285
286
|
});
|
|
286
287
|
};
|
|
288
|
+
const startObserver = (tick) => {
|
|
289
|
+
if (domObserver) return;
|
|
290
|
+
tick();
|
|
291
|
+
domObserver = new MutationObserver(tick);
|
|
292
|
+
domObserver.observe(document.body, { childList: true, subtree: true });
|
|
293
|
+
};
|
|
287
294
|
const startLoginObserver = (buttonText, enforced) => {
|
|
288
|
-
|
|
289
|
-
const tick = () => {
|
|
295
|
+
startObserver(() => {
|
|
290
296
|
if (!isAuthRoute(window.location.pathname)) return;
|
|
291
297
|
injectSSOButton(buttonText);
|
|
292
298
|
if (enforced) removeEnforcedElements();
|
|
293
|
-
if (ssoButtonInjected && !enforced)
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
299
|
+
if (ssoButtonInjected && !enforced) domObserver?.disconnect();
|
|
300
|
+
});
|
|
301
|
+
};
|
|
302
|
+
const startSkipLoginRedirect = () => {
|
|
303
|
+
startObserver(() => {
|
|
304
|
+
if (isAuthRoute(window.location.pathname)) {
|
|
305
|
+
window.location.href = OIDC_SIGN_IN_PATH;
|
|
306
|
+
}
|
|
307
|
+
});
|
|
298
308
|
};
|
|
299
309
|
const applySettings = async () => {
|
|
300
310
|
try {
|
|
301
311
|
const response = await window.fetch("/strapi-plugin-oidc/settings/public");
|
|
302
312
|
if (response.ok) {
|
|
303
313
|
const data = await response.json();
|
|
314
|
+
if (data.skipLoginPage) {
|
|
315
|
+
startSkipLoginRedirect();
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
304
318
|
startLoginObserver(data.ssoButtonText || defaultButtonText, !!data.enforceOIDC);
|
|
305
319
|
} else {
|
|
306
320
|
startLoginObserver(defaultButtonText, false);
|
|
@@ -5,7 +5,7 @@ import { useState, useRef, useId, useEffect, useCallback, useReducer, useMemo, m
|
|
|
5
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, E as EMAIL_REGEX, e as en, A as AUDIT_LOG_DEFAULTS, U as UI_DEFAULTS } from "./index-
|
|
8
|
+
import { g as getTrad, E as EMAIL_REGEX, e as en, A as AUDIT_LOG_DEFAULTS, U as UI_DEFAULTS } from "./index-CbB2ZVt6.mjs";
|
|
9
9
|
import styled from "styled-components";
|
|
10
10
|
import { Filter, ClipboardList, Server } from "lucide-react";
|
|
11
11
|
function Role({ oidcRoles, roles, onChangeRole }) {
|
package/dist/admin/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
|
|
3
|
-
const index = require("./index-
|
|
3
|
+
const index = require("./index-CWvZ9OH0.js");
|
|
4
4
|
require("react");
|
|
5
5
|
require("react-dom/client");
|
|
6
6
|
exports.default = index.index;
|
package/dist/admin/index.mjs
CHANGED
package/dist/server/index.js
CHANGED
|
@@ -122,7 +122,8 @@ const pluginConfigSchema = zod.z.object({
|
|
|
122
122
|
OIDC_TRUSTED_IP_HEADER: zod.z.string().default(""),
|
|
123
123
|
OIDC_JWKS_URI: zod.z.string().default(""),
|
|
124
124
|
OIDC_ISSUER: zod.z.string().default(""),
|
|
125
|
-
OIDC_FORCE_SECURE_COOKIES: coerceBool(false)
|
|
125
|
+
OIDC_FORCE_SECURE_COOKIES: coerceBool(false),
|
|
126
|
+
OIDC_SKIP_LOGIN_PAGE: coerceBool(false)
|
|
126
127
|
});
|
|
127
128
|
function parseGroupRoleMap(raw) {
|
|
128
129
|
if (typeof raw !== "string") {
|
|
@@ -173,6 +174,7 @@ const DEFAULT_RETENTION_DAYS = 90;
|
|
|
173
174
|
const DAY_MS = 864e5;
|
|
174
175
|
const DISCOVERY_TIMEOUT_MS = 5e3;
|
|
175
176
|
const OIDC_DISCOVERY_PATH = "/.well-known/openid-configuration";
|
|
177
|
+
const OIDC_SIGN_IN_PATH = "/strapi-plugin-oidc/oidc";
|
|
176
178
|
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
177
179
|
function getPluginConfig() {
|
|
178
180
|
return pluginConfigSchema.parse(strapi.config.get("plugin::strapi-plugin-oidc") ?? {});
|
|
@@ -284,15 +286,31 @@ function clearAuthCookies(strapi2, ctx) {
|
|
|
284
286
|
ctx.cookies.set(COOKIE_NAMES.userEmail, "", rootPathOptions);
|
|
285
287
|
}
|
|
286
288
|
const AUTH_ROUTES = ["login", "register", "register-admin", "forgot-password", "reset-password"];
|
|
289
|
+
const STATIC_EXTENSIONS = [".js", ".css", ".png", ".svg", ".ico", ".woff2", ".json", ".map"];
|
|
287
290
|
async function bootstrap({ strapi: strapi2 }) {
|
|
288
291
|
await applyDiscovery(strapi2);
|
|
289
292
|
const adminUrl = strapi2.config.get("admin.url", "/admin");
|
|
290
293
|
const tokenRefreshPath = `${adminUrl}/token/refresh`;
|
|
294
|
+
const EXCLUDED_ADMIN_PATHS = [
|
|
295
|
+
`${adminUrl}/login`,
|
|
296
|
+
`${adminUrl}/access-token`,
|
|
297
|
+
`${adminUrl}/logout`,
|
|
298
|
+
`${adminUrl}/init`,
|
|
299
|
+
`${adminUrl}/register`,
|
|
300
|
+
`${adminUrl}/register-admin`,
|
|
301
|
+
`${adminUrl}/forgot-password`,
|
|
302
|
+
`${adminUrl}/reset-password`
|
|
303
|
+
];
|
|
291
304
|
const enforceOidcMiddleware = async (ctx, next) => {
|
|
292
305
|
const path = ctx.request.path;
|
|
293
306
|
const isPost = ctx.request.method === "POST";
|
|
294
307
|
const isAuthRoute = AUTH_ROUTES.some((r) => path.includes(r));
|
|
295
308
|
const isTokenRefresh = path === tokenRefreshPath;
|
|
309
|
+
const config2 = getPluginConfig();
|
|
310
|
+
if (config2.OIDC_SKIP_LOGIN_PAGE && ctx.request.method === "GET" && (path === adminUrl || path.startsWith(`${adminUrl}/`)) && !EXCLUDED_ADMIN_PATHS.includes(path) && !STATIC_EXTENSIONS.some((ext) => path.endsWith(ext)) && !ctx.cookies.get(COOKIE_NAMES.adminRefresh)) {
|
|
311
|
+
ctx.redirect(OIDC_SIGN_IN_PATH);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
296
314
|
if (isAuthRoute && isPost || isTokenRefresh) {
|
|
297
315
|
try {
|
|
298
316
|
const whitelistService2 = getWhitelistService();
|
|
@@ -412,6 +430,7 @@ const config = {
|
|
|
412
430
|
OIDC_REQUIRE_EMAIL_VERIFIED: true,
|
|
413
431
|
OIDC_TRUSTED_IP_HEADER: "",
|
|
414
432
|
OIDC_FORCE_SECURE_COOKIES: false,
|
|
433
|
+
OIDC_SKIP_LOGIN_PAGE: false,
|
|
415
434
|
// Populated at bootstrap from OIDC_ISSUER — not user-configurable directly
|
|
416
435
|
OIDC_AUTHORIZATION_ENDPOINT: "",
|
|
417
436
|
OIDC_TOKEN_ENDPOINT: "",
|
|
@@ -4011,12 +4030,13 @@ async function logout(ctx) {
|
|
|
4011
4030
|
const logoutUrl = config2.OIDC_END_SESSION_ENDPOINT;
|
|
4012
4031
|
const adminPanelUrl = strapi.config.get("admin.url", "/admin");
|
|
4013
4032
|
const loginUrl = `${adminPanelUrl}/auth/login`;
|
|
4033
|
+
const fallbackUrl = config2.OIDC_SKIP_LOGIN_PAGE ? OIDC_SIGN_IN_PATH : loginUrl;
|
|
4014
4034
|
const isOidcSession = !!ctx.cookies.get(COOKIE_NAMES.authenticated);
|
|
4015
4035
|
const accessToken = ctx.cookies.get(COOKIE_NAMES.accessToken);
|
|
4016
4036
|
const userEmail = ctx.cookies.get(COOKIE_NAMES.userEmail) ?? void 0;
|
|
4017
4037
|
clearAuthCookies(strapi, ctx);
|
|
4018
4038
|
if (!isOidcSession) {
|
|
4019
|
-
return ctx.redirect(
|
|
4039
|
+
return ctx.redirect(fallbackUrl);
|
|
4020
4040
|
}
|
|
4021
4041
|
const logAudit = (action) => userEmail ? auditLog2.log({ action, email: userEmail, ip: getClientIp(ctx) }) : Promise.resolve();
|
|
4022
4042
|
if (logoutUrl && accessToken) {
|
|
@@ -4025,7 +4045,7 @@ async function logout(ctx) {
|
|
|
4025
4045
|
await logAudit("session_expired").catch((err) => {
|
|
4026
4046
|
strapi.log.error("[strapi-plugin-oidc] Audit log failed on session expiry:", err);
|
|
4027
4047
|
});
|
|
4028
|
-
return ctx.redirect(
|
|
4048
|
+
return ctx.redirect(fallbackUrl);
|
|
4029
4049
|
}
|
|
4030
4050
|
logAudit("logout").catch((err) => {
|
|
4031
4051
|
strapi.log.error("[strapi-plugin-oidc] Audit log failed on logout:", err);
|
|
@@ -4035,7 +4055,7 @@ async function logout(ctx) {
|
|
|
4035
4055
|
await logAudit("logout").catch((err) => {
|
|
4036
4056
|
strapi.log.error("[strapi-plugin-oidc] Audit log failed on logout:", err);
|
|
4037
4057
|
});
|
|
4038
|
-
ctx.redirect(logoutUrl ||
|
|
4058
|
+
ctx.redirect(logoutUrl || fallbackUrl);
|
|
4039
4059
|
}
|
|
4040
4060
|
const oidc = {
|
|
4041
4061
|
oidcSignIn,
|
|
@@ -4157,7 +4177,8 @@ async function publicSettings(ctx) {
|
|
|
4157
4177
|
const config2 = getPluginConfig();
|
|
4158
4178
|
ctx.body = {
|
|
4159
4179
|
enforceOIDC: resolveEnforceOIDC(strapi, settings.enforceOIDC),
|
|
4160
|
-
ssoButtonText: config2.OIDC_SSO_BUTTON_TEXT
|
|
4180
|
+
ssoButtonText: config2.OIDC_SSO_BUTTON_TEXT,
|
|
4181
|
+
skipLoginPage: config2.OIDC_SKIP_LOGIN_PAGE
|
|
4161
4182
|
};
|
|
4162
4183
|
}
|
|
4163
4184
|
async function register(ctx) {
|
package/dist/server/index.mjs
CHANGED
|
@@ -116,7 +116,8 @@ const pluginConfigSchema = z.object({
|
|
|
116
116
|
OIDC_TRUSTED_IP_HEADER: z.string().default(""),
|
|
117
117
|
OIDC_JWKS_URI: z.string().default(""),
|
|
118
118
|
OIDC_ISSUER: z.string().default(""),
|
|
119
|
-
OIDC_FORCE_SECURE_COOKIES: coerceBool(false)
|
|
119
|
+
OIDC_FORCE_SECURE_COOKIES: coerceBool(false),
|
|
120
|
+
OIDC_SKIP_LOGIN_PAGE: coerceBool(false)
|
|
120
121
|
});
|
|
121
122
|
function parseGroupRoleMap(raw) {
|
|
122
123
|
if (typeof raw !== "string") {
|
|
@@ -167,6 +168,7 @@ const DEFAULT_RETENTION_DAYS = 90;
|
|
|
167
168
|
const DAY_MS = 864e5;
|
|
168
169
|
const DISCOVERY_TIMEOUT_MS = 5e3;
|
|
169
170
|
const OIDC_DISCOVERY_PATH = "/.well-known/openid-configuration";
|
|
171
|
+
const OIDC_SIGN_IN_PATH = "/strapi-plugin-oidc/oidc";
|
|
170
172
|
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
171
173
|
function getPluginConfig() {
|
|
172
174
|
return pluginConfigSchema.parse(strapi.config.get("plugin::strapi-plugin-oidc") ?? {});
|
|
@@ -278,15 +280,31 @@ function clearAuthCookies(strapi2, ctx) {
|
|
|
278
280
|
ctx.cookies.set(COOKIE_NAMES.userEmail, "", rootPathOptions);
|
|
279
281
|
}
|
|
280
282
|
const AUTH_ROUTES = ["login", "register", "register-admin", "forgot-password", "reset-password"];
|
|
283
|
+
const STATIC_EXTENSIONS = [".js", ".css", ".png", ".svg", ".ico", ".woff2", ".json", ".map"];
|
|
281
284
|
async function bootstrap({ strapi: strapi2 }) {
|
|
282
285
|
await applyDiscovery(strapi2);
|
|
283
286
|
const adminUrl = strapi2.config.get("admin.url", "/admin");
|
|
284
287
|
const tokenRefreshPath = `${adminUrl}/token/refresh`;
|
|
288
|
+
const EXCLUDED_ADMIN_PATHS = [
|
|
289
|
+
`${adminUrl}/login`,
|
|
290
|
+
`${adminUrl}/access-token`,
|
|
291
|
+
`${adminUrl}/logout`,
|
|
292
|
+
`${adminUrl}/init`,
|
|
293
|
+
`${adminUrl}/register`,
|
|
294
|
+
`${adminUrl}/register-admin`,
|
|
295
|
+
`${adminUrl}/forgot-password`,
|
|
296
|
+
`${adminUrl}/reset-password`
|
|
297
|
+
];
|
|
285
298
|
const enforceOidcMiddleware = async (ctx, next) => {
|
|
286
299
|
const path = ctx.request.path;
|
|
287
300
|
const isPost = ctx.request.method === "POST";
|
|
288
301
|
const isAuthRoute = AUTH_ROUTES.some((r) => path.includes(r));
|
|
289
302
|
const isTokenRefresh = path === tokenRefreshPath;
|
|
303
|
+
const config2 = getPluginConfig();
|
|
304
|
+
if (config2.OIDC_SKIP_LOGIN_PAGE && ctx.request.method === "GET" && (path === adminUrl || path.startsWith(`${adminUrl}/`)) && !EXCLUDED_ADMIN_PATHS.includes(path) && !STATIC_EXTENSIONS.some((ext) => path.endsWith(ext)) && !ctx.cookies.get(COOKIE_NAMES.adminRefresh)) {
|
|
305
|
+
ctx.redirect(OIDC_SIGN_IN_PATH);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
290
308
|
if (isAuthRoute && isPost || isTokenRefresh) {
|
|
291
309
|
try {
|
|
292
310
|
const whitelistService2 = getWhitelistService();
|
|
@@ -406,6 +424,7 @@ const config = {
|
|
|
406
424
|
OIDC_REQUIRE_EMAIL_VERIFIED: true,
|
|
407
425
|
OIDC_TRUSTED_IP_HEADER: "",
|
|
408
426
|
OIDC_FORCE_SECURE_COOKIES: false,
|
|
427
|
+
OIDC_SKIP_LOGIN_PAGE: false,
|
|
409
428
|
// Populated at bootstrap from OIDC_ISSUER — not user-configurable directly
|
|
410
429
|
OIDC_AUTHORIZATION_ENDPOINT: "",
|
|
411
430
|
OIDC_TOKEN_ENDPOINT: "",
|
|
@@ -4005,12 +4024,13 @@ async function logout(ctx) {
|
|
|
4005
4024
|
const logoutUrl = config2.OIDC_END_SESSION_ENDPOINT;
|
|
4006
4025
|
const adminPanelUrl = strapi.config.get("admin.url", "/admin");
|
|
4007
4026
|
const loginUrl = `${adminPanelUrl}/auth/login`;
|
|
4027
|
+
const fallbackUrl = config2.OIDC_SKIP_LOGIN_PAGE ? OIDC_SIGN_IN_PATH : loginUrl;
|
|
4008
4028
|
const isOidcSession = !!ctx.cookies.get(COOKIE_NAMES.authenticated);
|
|
4009
4029
|
const accessToken = ctx.cookies.get(COOKIE_NAMES.accessToken);
|
|
4010
4030
|
const userEmail = ctx.cookies.get(COOKIE_NAMES.userEmail) ?? void 0;
|
|
4011
4031
|
clearAuthCookies(strapi, ctx);
|
|
4012
4032
|
if (!isOidcSession) {
|
|
4013
|
-
return ctx.redirect(
|
|
4033
|
+
return ctx.redirect(fallbackUrl);
|
|
4014
4034
|
}
|
|
4015
4035
|
const logAudit = (action) => userEmail ? auditLog2.log({ action, email: userEmail, ip: getClientIp(ctx) }) : Promise.resolve();
|
|
4016
4036
|
if (logoutUrl && accessToken) {
|
|
@@ -4019,7 +4039,7 @@ async function logout(ctx) {
|
|
|
4019
4039
|
await logAudit("session_expired").catch((err) => {
|
|
4020
4040
|
strapi.log.error("[strapi-plugin-oidc] Audit log failed on session expiry:", err);
|
|
4021
4041
|
});
|
|
4022
|
-
return ctx.redirect(
|
|
4042
|
+
return ctx.redirect(fallbackUrl);
|
|
4023
4043
|
}
|
|
4024
4044
|
logAudit("logout").catch((err) => {
|
|
4025
4045
|
strapi.log.error("[strapi-plugin-oidc] Audit log failed on logout:", err);
|
|
@@ -4029,7 +4049,7 @@ async function logout(ctx) {
|
|
|
4029
4049
|
await logAudit("logout").catch((err) => {
|
|
4030
4050
|
strapi.log.error("[strapi-plugin-oidc] Audit log failed on logout:", err);
|
|
4031
4051
|
});
|
|
4032
|
-
ctx.redirect(logoutUrl ||
|
|
4052
|
+
ctx.redirect(logoutUrl || fallbackUrl);
|
|
4033
4053
|
}
|
|
4034
4054
|
const oidc = {
|
|
4035
4055
|
oidcSignIn,
|
|
@@ -4151,7 +4171,8 @@ async function publicSettings(ctx) {
|
|
|
4151
4171
|
const config2 = getPluginConfig();
|
|
4152
4172
|
ctx.body = {
|
|
4153
4173
|
enforceOIDC: resolveEnforceOIDC(strapi, settings.enforceOIDC),
|
|
4154
|
-
ssoButtonText: config2.OIDC_SSO_BUTTON_TEXT
|
|
4174
|
+
ssoButtonText: config2.OIDC_SSO_BUTTON_TEXT,
|
|
4175
|
+
skipLoginPage: config2.OIDC_SKIP_LOGIN_PAGE
|
|
4155
4176
|
};
|
|
4156
4177
|
}
|
|
4157
4178
|
async function register(ctx) {
|
package/package.json
CHANGED