strapi-plugin-oidc 1.2.3 → 1.3.1

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.
@@ -49,11 +49,15 @@ const en = {
49
49
  "roles.placeholder": "Select default role(s)",
50
50
  "whitelist.title": "Whitelist",
51
51
  "whitelist.error.unique": "Already registered email address.",
52
- "whitelist.enabled": "Whitelist is currently enabled.",
53
- "whitelist.disabled": "Whitelist is currently disabled.",
54
52
  "whitelist.description": "Restrict OIDC authentication to specific email addresses and optionally assign them custom role(s).",
55
53
  "whitelist.user_exists": "User already exists, matching existing role(s)",
56
54
  "whitelist.users_exists": "Users already exist, matching existing role(s)",
55
+ "alert.title.success": "Success",
56
+ "alert.title.error": "Error",
57
+ "alert.title.info": "Info",
58
+ "pagination.previous": "Go to previous page",
59
+ "pagination.page": "Go to page {page}",
60
+ "pagination.next": "Go to next page",
57
61
  "whitelist.table.no": "No.",
58
62
  "whitelist.table.email": "Email",
59
63
  "whitelist.table.created": "Created At",
@@ -65,7 +69,6 @@ const en = {
65
69
  "whitelist.email.placeholder": "Email address",
66
70
  "whitelist.roles.placeholder": "Select specific role(s)",
67
71
  "whitelist.table.roles": "Role(s)",
68
- "whitelist.table.roles.default": "Default",
69
72
  "whitelist.table.empty": "No email addresses",
70
73
  "whitelist.delete.label": "Delete",
71
74
  "page.title.oidc": "OIDC",
@@ -73,12 +76,20 @@ const en = {
73
76
  "enforce.toggle.enabled": "Enabled",
74
77
  "enforce.toggle.disabled": "Disabled",
75
78
  "enforce.warning": "Make sure OIDC is setup correctly before saving changes, you won't be able to login normally.",
79
+ "enforce.config.info": "Enforcement is controlled by the OIDC_ENFORCE config variable and cannot be changed here.",
80
+ "login.settings.title": "Login Settings",
76
81
  "login.sso": "Login via SSO"
77
82
  };
78
- const en$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
79
- __proto__: null,
80
- default: en
81
- }, Symbol.toStringTag, { value: "Module" }));
83
+ function getTrad(id) {
84
+ const pluginIdWithId = `${pluginId}.${id}`;
85
+ return {
86
+ id: pluginIdWithId,
87
+ defaultMessage: en[id] || pluginIdWithId
88
+ };
89
+ }
90
+ function t(id) {
91
+ return en[id];
92
+ }
82
93
  const name = pluginPkg.strapi.displayName;
83
94
  const index = {
84
95
  register(app) {
@@ -98,7 +109,7 @@ const index = {
98
109
  defaultMessage: "Configuration"
99
110
  },
100
111
  Component: async () => {
101
- return await import("./index-DuJfeoFu.mjs");
112
+ return await import("./index-CFmg9Kxl.mjs");
102
113
  },
103
114
  permissions: [{ action: "plugin::strapi-plugin-oidc.read", subject: null }]
104
115
  }
@@ -110,41 +121,11 @@ const index = {
110
121
  });
111
122
  },
112
123
  bootstrap() {
113
- let isLogoutInProgress = false;
114
- let historyPatched = false;
115
- const ENFORCE_CACHE_KEY = "strapi_oidc_enforced";
124
+ const defaultButtonText = t("login.sso");
116
125
  const isAuthRoute = (path) => /\/auth\/(login|register|forgot-password|reset-password)/.test(path);
117
- const patchHistory = () => {
118
- if (historyPatched) return;
119
- historyPatched = true;
120
- const interceptHistory = (originalMethod) => {
121
- return function(...args) {
122
- const url = args[2];
123
- if (url && typeof url === "string") {
124
- const urlWithoutQuery = url.split("?")[0].split("#")[0];
125
- if (isAuthRoute(urlWithoutQuery)) {
126
- if (isLogoutInProgress) {
127
- return;
128
- }
129
- document.documentElement.style.visibility = "hidden";
130
- window.location.href = "/strapi-plugin-oidc/oidc";
131
- return;
132
- }
133
- }
134
- return originalMethod.apply(window.history, args);
135
- };
136
- };
137
- window.history.pushState = interceptHistory(window.history.pushState);
138
- window.history.replaceState = interceptHistory(window.history.replaceState);
139
- if (isAuthRoute(window.location.pathname)) {
140
- document.documentElement.style.visibility = "hidden";
141
- window.location.replace("/strapi-plugin-oidc/oidc");
142
- }
143
- };
144
126
  let ssoButtonInjected = false;
145
- let ssoObserver = null;
146
- let ssoButtonText = en["login.sso"];
147
- const injectSSOButton = () => {
127
+ let loginObserver = null;
128
+ const injectSSOButton = (buttonText) => {
148
129
  if (ssoButtonInjected) return;
149
130
  if (!isAuthRoute(window.location.pathname)) return;
150
131
  if (document.getElementById("strapi-oidc-sso-btn")) return;
@@ -154,71 +135,75 @@ const index = {
154
135
  btn.id = "strapi-oidc-sso-btn";
155
136
  btn.type = "button";
156
137
  btn.className = submitButton.className;
157
- btn.style.marginTop = "8px";
158
138
  btn.onclick = () => {
159
139
  window.location.href = "/strapi-plugin-oidc/oidc";
160
140
  };
161
141
  const innerSpan = submitButton.querySelector("span");
162
142
  const span = document.createElement("span");
163
143
  if (innerSpan) span.className = innerSpan.className;
164
- span.textContent = ssoButtonText;
144
+ span.style.display = "inline-flex";
145
+ span.style.alignItems = "center";
146
+ span.style.gap = "8px";
147
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
148
+ svg.setAttribute("width", "16");
149
+ svg.setAttribute("height", "16");
150
+ svg.setAttribute("viewBox", "0 0 24 24");
151
+ svg.setAttribute("fill", "none");
152
+ svg.setAttribute("stroke", "currentColor");
153
+ svg.setAttribute("stroke-width", "2");
154
+ svg.setAttribute("stroke-linecap", "round");
155
+ svg.setAttribute("stroke-linejoin", "round");
156
+ svg.setAttribute("aria-hidden", "true");
157
+ svg.innerHTML = '<path d="M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z"/><circle cx="16.5" cy="7.5" r=".5" fill="currentColor"/>';
158
+ span.appendChild(svg);
159
+ span.appendChild(document.createTextNode(buttonText));
165
160
  btn.appendChild(span);
166
161
  submitButton.parentNode.insertBefore(btn, submitButton.nextSibling);
167
162
  ssoButtonInjected = true;
168
163
  };
169
- const startSSOButtonObserver = () => {
170
- if (ssoObserver) return;
171
- injectSSOButton();
172
- ssoObserver = new MutationObserver(() => {
173
- if (isAuthRoute(window.location.pathname)) injectSSOButton();
164
+ const removeEnforcedElements = () => {
165
+ [
166
+ 'form > div > div:has(input[name="email"])',
167
+ 'form > div > div:has(input[name="password"])',
168
+ 'form > div > div:has(button[role="checkbox"])',
169
+ 'form > div > button[type="submit"]:not(#strapi-oidc-sso-btn)'
170
+ ].forEach((selector) => {
171
+ document.querySelectorAll(selector).forEach((el) => el.remove());
172
+ });
173
+ document.querySelectorAll('a[href*="forgot-password"]').forEach((el) => {
174
+ (el.closest("div")?.parentElement ?? el).remove();
174
175
  });
175
- ssoObserver.observe(document.body, { childList: true, subtree: true });
176
176
  };
177
- const stopSSOButtonObserver = () => {
178
- ssoObserver?.disconnect();
179
- ssoObserver = null;
180
- document.getElementById("strapi-oidc-sso-btn")?.remove();
181
- ssoButtonInjected = false;
177
+ const startLoginObserver = (buttonText, enforced) => {
178
+ if (loginObserver) return;
179
+ const tick = () => {
180
+ if (!isAuthRoute(window.location.pathname)) return;
181
+ injectSSOButton(buttonText);
182
+ if (enforced) removeEnforcedElements();
183
+ };
184
+ tick();
185
+ loginObserver = new MutationObserver(tick);
186
+ loginObserver.observe(document.body, { childList: true, subtree: true });
182
187
  };
183
- if (localStorage.getItem(ENFORCE_CACHE_KEY) === "1") {
184
- patchHistory();
185
- }
186
- if (isAuthRoute(window.location.pathname)) {
187
- document.documentElement.style.visibility = "hidden";
188
- }
189
- const checkEnforceOIDC = async () => {
188
+ const applySettings = async () => {
190
189
  try {
191
190
  const response = await window.fetch("/strapi-plugin-oidc/settings/public");
192
191
  if (response.ok) {
193
192
  const data = await response.json();
194
- if (data.enforceOIDC) {
195
- localStorage.setItem(ENFORCE_CACHE_KEY, "1");
196
- stopSSOButtonObserver();
197
- patchHistory();
198
- } else {
199
- localStorage.removeItem(ENFORCE_CACHE_KEY);
200
- document.documentElement.style.visibility = "";
201
- if (data.showSSOButton !== false) {
202
- ssoButtonText = data.ssoButtonText || en["login.sso"];
203
- startSSOButtonObserver();
204
- } else {
205
- stopSSOButtonObserver();
206
- }
207
- }
193
+ startLoginObserver(data.ssoButtonText || defaultButtonText, !!data.enforceOIDC);
194
+ } else {
195
+ startLoginObserver(defaultButtonText, false);
208
196
  }
209
197
  } catch (error) {
210
- document.documentElement.style.visibility = "";
211
- console.error("Failed to check OIDC enforcement setting:", error);
198
+ startLoginObserver(defaultButtonText, false);
199
+ console.error("Failed to fetch OIDC settings:", error);
212
200
  }
213
201
  };
214
- checkEnforceOIDC();
202
+ applySettings();
215
203
  const originalFetch = window.fetch;
216
204
  window.fetch = async (...args) => {
217
205
  const url = typeof args[0] === "string" ? args[0] : args[0].url;
218
206
  const isLogout = url && url.endsWith("/admin/logout") && args[1]?.method?.toUpperCase() === "POST";
219
- if (isLogout) {
220
- isLogoutInProgress = true;
221
- }
222
207
  const response = await originalFetch(...args);
223
208
  if (isLogout && response.ok) {
224
209
  window.localStorage.removeItem("jwtToken");
@@ -230,39 +215,29 @@ const index = {
230
215
  window.location.href = "/strapi-plugin-oidc/logout";
231
216
  return new Promise(() => {
232
217
  });
233
- } else if (isLogout) {
234
- isLogoutInProgress = false;
235
218
  }
236
219
  return response;
237
220
  };
238
221
  },
239
222
  async registerTrads({ locales }) {
223
+ const transformKeys = (data) => Object.fromEntries(
224
+ Object.entries(data).map(([key, value]) => [
225
+ key.startsWith("global.") ? key : getTranslation(key),
226
+ value
227
+ ])
228
+ );
240
229
  const importedTrads = await Promise.all(
241
230
  locales.map((locale) => {
242
- return __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => Promise.resolve().then(() => en$1) }), `./translations/${locale}.json`, 3).then(({ default: data }) => {
243
- const newData = Object.fromEntries(
244
- Object.entries(data).map(([key, value]) => [
245
- key.startsWith("global.") ? key : getTranslation(key),
246
- value
247
- ])
248
- );
249
- return {
250
- data: newData,
251
- locale
252
- };
253
- }).catch(() => {
254
- return {
255
- data: {},
256
- locale
257
- };
258
- });
231
+ if (locale === "en") {
232
+ return Promise.resolve({ data: transformKeys(en), locale });
233
+ }
234
+ return __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({}), `./translations/locales/${locale}.json`, 4).then(({ default: data }) => ({ data: transformKeys(data), locale })).catch(() => ({ data: {}, locale }));
259
235
  })
260
236
  );
261
- return Promise.resolve(importedTrads);
237
+ return importedTrads;
262
238
  }
263
239
  };
264
240
  export {
265
- en as e,
266
- index as i,
267
- pluginId as p
241
+ getTrad as g,
242
+ index as i
268
243
  };
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
- const index = require("./index-ENl8_IZn.js");
3
+ const index = require("./index-Cse9ex24.js");
4
4
  exports.default = index.index;
@@ -1,4 +1,4 @@
1
- import { i } from "./index-CZ_FdaEz.mjs";
1
+ import { i } from "./index-D1ypRUlq.mjs";
2
2
  export {
3
3
  i as default
4
4
  };
@@ -10,22 +10,19 @@ const strapiUtils__default = /* @__PURE__ */ _interopDefault(strapiUtils);
10
10
  const generator__default = /* @__PURE__ */ _interopDefault(generator);
11
11
  function register$1() {
12
12
  }
13
- function getExpiredCookieOptions(strapi2, ctx) {
14
- const isProduction = strapi2.config.get("environment") === "production";
15
- return {
16
- httpOnly: true,
17
- secure: isProduction && ctx.request.secure,
18
- path: strapi2.config.get("admin.auth.cookie.path", "/admin"),
19
- domain: strapi2.config.get("admin.auth.cookie.domain") || strapi2.config.get("admin.auth.domain"),
20
- sameSite: strapi2.config.get("admin.auth.cookie.sameSite", "lax"),
21
- maxAge: 0,
22
- expires: /* @__PURE__ */ new Date(0)
23
- };
13
+ function getEnforceOIDCConfig(strapi2) {
14
+ const config2 = strapi2.config.get("plugin::strapi-plugin-oidc");
15
+ const val = config2.OIDC_ENFORCE;
16
+ if (val === null || val === void 0) return null;
17
+ if (typeof val === "boolean") return val;
18
+ if (val === "true") return true;
19
+ if (val === "false") return false;
20
+ return null;
24
21
  }
25
- function clearAuthCookies(strapi2, ctx) {
26
- const options2 = getExpiredCookieOptions(strapi2, ctx);
27
- ctx.cookies.set("strapi_admin_refresh", "", options2);
28
- ctx.cookies.set("oidc_authenticated", "", options2);
22
+ function resolveEnforceOIDC(strapi2, dbValue) {
23
+ const configValue = getEnforceOIDCConfig(strapi2);
24
+ if (configValue !== null) return configValue;
25
+ return dbValue ?? false;
29
26
  }
30
27
  async function bootstrap({ strapi: strapi2 }) {
31
28
  const enforceOidcMiddleware = async (ctx, next) => {
@@ -38,13 +35,12 @@ async function bootstrap({ strapi: strapi2 }) {
38
35
  ];
39
36
  const isPostAuth = authRoutes.includes(ctx.request.path) && ctx.request.method === "POST";
40
37
  const isTokenRefresh = ctx.request.path === `${adminUrl}/token/refresh` && ctx.request.method === "POST";
41
- const isHtmlRequest = ctx.request.accepts("html", "json") === "html" && !ctx.request.path.match(/\.[a-zA-Z0-9]+$/);
42
- const isGetAdminHtml = ctx.request.method === "GET" && ctx.request.path.startsWith(adminUrl) && isHtmlRequest;
43
- if (isPostAuth || isTokenRefresh || isGetAdminHtml) {
38
+ if (isPostAuth || isTokenRefresh) {
44
39
  try {
45
40
  const whitelistService2 = strapi2.plugin("strapi-plugin-oidc").service("whitelist");
46
41
  const settings = await whitelistService2.getSettings();
47
- if (settings?.enforceOIDC) {
42
+ const enforceOIDC = resolveEnforceOIDC(strapi2, settings?.enforceOIDC);
43
+ if (enforceOIDC) {
48
44
  if (isPostAuth) {
49
45
  ctx.status = 403;
50
46
  ctx.body = {
@@ -72,18 +68,6 @@ async function bootstrap({ strapi: strapi2 }) {
72
68
  };
73
69
  return;
74
70
  }
75
- if (isGetAdminHtml) {
76
- const hasRefreshCookie = !!ctx.cookies.get("strapi_admin_refresh");
77
- if (hasRefreshCookie && !hasOidcSession) {
78
- clearAuthCookies(strapi2, ctx);
79
- ctx.redirect("/strapi-plugin-oidc/oidc");
80
- return;
81
- }
82
- if (!hasRefreshCookie) {
83
- ctx.redirect("/strapi-plugin-oidc/oidc");
84
- return;
85
- }
86
- }
87
71
  }
88
72
  } catch (err) {
89
73
  strapi2.log.error("Error checking OIDC enforcement in middleware:", err);
@@ -158,8 +142,6 @@ function destroy() {
158
142
  const config = {
159
143
  default: {
160
144
  REMEMBER_ME: false,
161
- REMEMBER_ME_DAYS: 30,
162
- // 30 days
163
145
  OIDC_REDIRECT_URI: "http://localhost:1337/strapi-plugin-oidc/oidc/callback",
164
146
  OIDC_CLIENT_ID: "",
165
147
  OIDC_CLIENT_SECRET: "",
@@ -171,7 +153,10 @@ const config = {
171
153
  OIDC_GRANT_TYPE: "authorization_code",
172
154
  OIDC_FAMILY_NAME_FIELD: "family_name",
173
155
  OIDC_GIVEN_NAME_FIELD: "given_name",
174
- OIDC_LOGOUT_URL: ""
156
+ OIDC_LOGOUT_URL: "",
157
+ OIDC_SSO_BUTTON_TEXT: "Login via SSO",
158
+ OIDC_ENFORCE: null
159
+ // null = use DB setting; true/false = override DB (useful for lockout recovery)
175
160
  },
176
161
  validator() {
177
162
  }
@@ -206,6 +191,23 @@ const contentTypes = {
206
191
  roles,
207
192
  whitelists
208
193
  };
194
+ function getExpiredCookieOptions(strapi2, ctx) {
195
+ const isProduction = strapi2.config.get("environment") === "production";
196
+ return {
197
+ httpOnly: true,
198
+ secure: isProduction && ctx.request.secure,
199
+ path: strapi2.config.get("admin.auth.cookie.path", "/admin"),
200
+ domain: strapi2.config.get("admin.auth.cookie.domain") || strapi2.config.get("admin.auth.domain"),
201
+ sameSite: strapi2.config.get("admin.auth.cookie.sameSite", "lax"),
202
+ maxAge: 0,
203
+ expires: /* @__PURE__ */ new Date(0)
204
+ };
205
+ }
206
+ function clearAuthCookies(strapi2, ctx) {
207
+ const options2 = getExpiredCookieOptions(strapi2, ctx);
208
+ ctx.cookies.set("strapi_admin_refresh", "", options2);
209
+ ctx.cookies.set("oidc_authenticated", "", { ...options2, path: "/" });
210
+ }
209
211
  function configValidation() {
210
212
  const config2 = strapi.config.get("plugin::strapi-plugin-oidc");
211
213
  const requiredKeys = [
@@ -345,6 +347,8 @@ async function oidcSignInCallback(ctx) {
345
347
  }
346
348
  const oidcState = ctx.cookies.get("oidc_state");
347
349
  const codeVerifier = ctx.cookies.get("oidc_code_verifier");
350
+ ctx.cookies.set("oidc_state", null);
351
+ ctx.cookies.set("oidc_code_verifier", null);
348
352
  if (!ctx.query.state || ctx.query.state !== oidcState) {
349
353
  return ctx.send(oauthService2.renderSignUpError("Invalid state"));
350
354
  }
@@ -425,7 +429,8 @@ async function info(ctx) {
425
429
  const whitelistUsers = await whitelistService2.getUsers();
426
430
  ctx.body = {
427
431
  useWhitelist: settings.useWhitelist,
428
- enforceOIDC: settings.enforceOIDC || false,
432
+ enforceOIDC: resolveEnforceOIDC(strapi, settings.enforceOIDC),
433
+ enforceOIDCConfig: getEnforceOIDCConfig(strapi),
429
434
  whitelistUsers
430
435
  };
431
436
  }
@@ -444,8 +449,10 @@ async function updateSettings(ctx) {
444
449
  async function publicSettings(ctx) {
445
450
  const whitelistService2 = strapi.plugin("strapi-plugin-oidc").service("whitelist");
446
451
  const settings = await whitelistService2.getSettings();
452
+ const config2 = strapi.config.get("plugin::strapi-plugin-oidc");
447
453
  ctx.body = {
448
- enforceOIDC: settings.enforceOIDC || false
454
+ enforceOIDC: resolveEnforceOIDC(strapi, settings.enforceOIDC),
455
+ ssoButtonText: config2.OIDC_SSO_BUTTON_TEXT
449
456
  };
450
457
  }
451
458
  async function register(ctx) {
@@ -930,13 +937,11 @@ function oauthService({ strapi: strapi2 }) {
930
937
  const config2 = strapi2.config.get("plugin::strapi-plugin-oidc");
931
938
  const REMEMBER_ME = config2["REMEMBER_ME"];
932
939
  const rememberMe = !!REMEMBER_ME;
933
- const { token: refreshToken } = await sessionManager("admin").generateRefreshToken(
934
- userId,
935
- deviceId,
936
- {
937
- type: rememberMe ? "refresh" : "session"
938
- }
939
- );
940
+ const { token: refreshToken, absoluteExpiresAt } = await sessionManager(
941
+ "admin"
942
+ ).generateRefreshToken(userId, deviceId, {
943
+ type: rememberMe ? "refresh" : "session"
944
+ });
940
945
  const isProduction = strapi2.config.get("environment") === "production";
941
946
  const domain = strapi2.config.get("admin.auth.cookie.domain") || strapi2.config.get("admin.auth.domain");
942
947
  const path = strapi2.config.get("admin.auth.cookie.path", "/admin");
@@ -950,13 +955,19 @@ function oauthService({ strapi: strapi2 }) {
950
955
  sameSite
951
956
  };
952
957
  if (rememberMe) {
953
- const REMEMBER_ME_DAYS = config2["REMEMBER_ME_DAYS"] || 30;
954
- const durationInMs = REMEMBER_ME_DAYS * 24 * 60 * 60 * 1e3;
955
- cookieOptions.maxAge = durationInMs;
956
- cookieOptions.expires = new Date(Date.now() + durationInMs);
958
+ const idleLifespanSec = strapi2.config.get(
959
+ "admin.auth.sessions.idleRefreshTokenLifespan",
960
+ 1209600
961
+ // 14 days Strapi default
962
+ );
963
+ const idleMs = idleLifespanSec * 1e3;
964
+ const absoluteMs = new Date(absoluteExpiresAt).getTime() - Date.now();
965
+ const ms = Math.min(idleMs, absoluteMs);
966
+ cookieOptions.maxAge = ms;
967
+ cookieOptions.expires = new Date(Date.now() + ms);
957
968
  }
958
969
  ctx.cookies.set("strapi_admin_refresh", refreshToken, cookieOptions);
959
- ctx.cookies.set("oidc_authenticated", "1", cookieOptions);
970
+ ctx.cookies.set("oidc_authenticated", "1", { ...cookieOptions, path: "/" });
960
971
  const accessResult = await sessionManager("admin").generateAccessToken(refreshToken);
961
972
  if ("error" in accessResult) {
962
973
  throw new Error(accessResult.error);
@@ -1027,7 +1038,10 @@ function whitelistService({ strapi: strapi2 }) {
1027
1038
  }
1028
1039
  let settings = await getPluginStore().get({ key: "settings" });
1029
1040
  if (!settings) {
1030
- settings = { useWhitelist: true, enforceOIDC: false };
1041
+ settings = {
1042
+ useWhitelist: true,
1043
+ enforceOIDC: false
1044
+ };
1031
1045
  await getPluginStore().set({ key: "settings", value: settings });
1032
1046
  }
1033
1047
  settingsCache = { value: settings, ts: now };