strapi-plugin-oidc 1.9.6 → 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 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-BUuwz71P.js");
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-F7EShzfg.js")),
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 loginObserver = null;
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 = "/strapi-plugin-oidc/oidc";
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
- if (loginObserver) return;
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) loginObserver?.disconnect();
297
- };
298
- tick();
299
- loginObserver = new MutationObserver(tick);
300
- loginObserver.observe(document.body, { childList: true, subtree: true });
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-vkwBXPXn.mjs"),
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 loginObserver = null;
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 = "/strapi-plugin-oidc/oidc";
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
- if (loginObserver) return;
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) loginObserver?.disconnect();
294
- };
295
- tick();
296
- loginObserver = new MutationObserver(tick);
297
- loginObserver.observe(document.body, { childList: true, subtree: true });
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-CUVEK5GT.mjs";
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 }) {
@@ -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-BUuwz71P.js");
3
+ const index = require("./index-CWvZ9OH0.js");
4
4
  require("react");
5
5
  require("react-dom/client");
6
6
  exports.default = index.index;
@@ -1,4 +1,4 @@
1
- import { i } from "./index-CUVEK5GT.mjs";
1
+ import { i } from "./index-CbB2ZVt6.mjs";
2
2
  import "react";
3
3
  import "react-dom/client";
4
4
  export {
@@ -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(loginUrl);
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(loginUrl);
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 || loginUrl);
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) {
@@ -4552,19 +4573,19 @@ const routes = {
4552
4573
  method: "GET",
4553
4574
  path: "/audit-logs",
4554
4575
  handler: "auditLog.find",
4555
- config: { policies: ["admin::isAuthenticatedAdmin"] }
4576
+ config: adminPolicies("read")
4556
4577
  },
4557
4578
  {
4558
4579
  method: "GET",
4559
4580
  path: "/audit-logs/export",
4560
4581
  handler: "auditLog.export",
4561
- config: { policies: ["admin::isAuthenticatedAdmin"] }
4582
+ config: adminPolicies("read")
4562
4583
  },
4563
4584
  {
4564
4585
  method: "DELETE",
4565
4586
  path: "/audit-logs",
4566
4587
  handler: "auditLog.clearAll",
4567
- config: { policies: ["admin::isAuthenticatedAdmin"] }
4588
+ config: adminPolicies("update")
4568
4589
  }
4569
4590
  ]
4570
4591
  },
@@ -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(loginUrl);
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(loginUrl);
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 || loginUrl);
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) {
@@ -4546,19 +4567,19 @@ const routes = {
4546
4567
  method: "GET",
4547
4568
  path: "/audit-logs",
4548
4569
  handler: "auditLog.find",
4549
- config: { policies: ["admin::isAuthenticatedAdmin"] }
4570
+ config: adminPolicies("read")
4550
4571
  },
4551
4572
  {
4552
4573
  method: "GET",
4553
4574
  path: "/audit-logs/export",
4554
4575
  handler: "auditLog.export",
4555
- config: { policies: ["admin::isAuthenticatedAdmin"] }
4576
+ config: adminPolicies("read")
4556
4577
  },
4557
4578
  {
4558
4579
  method: "DELETE",
4559
4580
  path: "/audit-logs",
4560
4581
  handler: "auditLog.clearAll",
4561
- config: { policies: ["admin::isAuthenticatedAdmin"] }
4582
+ config: adminPolicies("update")
4562
4583
  }
4563
4584
  ]
4564
4585
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-oidc",
3
- "version": "1.9.6",
3
+ "version": "1.10.0",
4
4
  "description": "A Strapi plugin that provides OpenID Connect (OIDC) authentication functionality for the Strapi Admin Panel.",
5
5
  "strapi": {
6
6
  "displayName": "OIDC Plugin",