strapi-plugin-oidc 1.3.2 → 1.4.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.
@@ -158,7 +158,6 @@ const config = {
158
158
  OIDC_AUTHORIZATION_ENDPOINT: "",
159
159
  OIDC_TOKEN_ENDPOINT: "",
160
160
  OIDC_USER_INFO_ENDPOINT: "",
161
- OIDC_USER_INFO_ENDPOINT_WITH_AUTH_HEADER: false,
162
161
  OIDC_GRANT_TYPE: "authorization_code",
163
162
  OIDC_FAMILY_NAME_FIELD: "family_name",
164
163
  OIDC_GIVEN_NAME_FIELD: "given_name",
@@ -217,45 +216,43 @@ function clearAuthCookies(strapi2, ctx) {
217
216
  ctx.cookies.set("strapi_admin_refresh", "", options2);
218
217
  ctx.cookies.set("oidc_authenticated", "", { ...options2, path: "/" });
219
218
  }
219
+ const REQUIRED_CONFIG_KEYS = [
220
+ "OIDC_CLIENT_ID",
221
+ "OIDC_CLIENT_SECRET",
222
+ "OIDC_REDIRECT_URI",
223
+ "OIDC_SCOPES",
224
+ "OIDC_TOKEN_ENDPOINT",
225
+ "OIDC_USER_INFO_ENDPOINT",
226
+ "OIDC_GRANT_TYPE",
227
+ "OIDC_FAMILY_NAME_FIELD",
228
+ "OIDC_GIVEN_NAME_FIELD",
229
+ "OIDC_AUTHORIZATION_ENDPOINT"
230
+ ];
220
231
  function configValidation() {
221
232
  const config2 = strapi.config.get("plugin::strapi-plugin-oidc");
222
- const requiredKeys = [
223
- "OIDC_CLIENT_ID",
224
- "OIDC_CLIENT_SECRET",
225
- "OIDC_REDIRECT_URI",
226
- "OIDC_SCOPES",
227
- "OIDC_TOKEN_ENDPOINT",
228
- "OIDC_USER_INFO_ENDPOINT",
229
- "OIDC_GRANT_TYPE",
230
- "OIDC_FAMILY_NAME_FIELD",
231
- "OIDC_GIVEN_NAME_FIELD",
232
- "OIDC_AUTHORIZATION_ENDPOINT"
233
- ];
234
- if (requiredKeys.every((key) => config2[key])) {
233
+ if (REQUIRED_CONFIG_KEYS.every((key) => config2[key])) {
235
234
  return config2;
236
235
  }
237
- throw new Error(`The following configuration keys are required: ${requiredKeys.join(", ")}`);
236
+ throw new Error(
237
+ `The following configuration keys are required: ${REQUIRED_CONFIG_KEYS.join(", ")}`
238
+ );
238
239
  }
239
240
  async function oidcSignIn(ctx) {
240
- let { state } = ctx.query;
241
241
  const { OIDC_CLIENT_ID, OIDC_REDIRECT_URI, OIDC_SCOPES, OIDC_AUTHORIZATION_ENDPOINT } = configValidation();
242
242
  const { code_verifier: codeVerifier, code_challenge: codeChallenge } = await pkceChallenge();
243
- if (!state) {
244
- state = randomBytes(32).toString("base64url");
245
- }
243
+ const state = randomBytes(32).toString("base64url");
244
+ const nonce = randomBytes(32).toString("base64url");
246
245
  const isProduction = strapi.config.get("environment") === "production";
247
- ctx.cookies.set("oidc_code_verifier", codeVerifier, {
246
+ const cookieOptions = {
248
247
  httpOnly: true,
249
248
  maxAge: 6e5,
249
+ // 10 minutes
250
250
  secure: isProduction && ctx.request.secure,
251
251
  sameSite: "lax"
252
- });
253
- ctx.cookies.set("oidc_state", state, {
254
- httpOnly: true,
255
- maxAge: 6e5,
256
- secure: isProduction && ctx.request.secure,
257
- sameSite: "lax"
258
- });
252
+ };
253
+ ctx.cookies.set("oidc_code_verifier", codeVerifier, cookieOptions);
254
+ ctx.cookies.set("oidc_state", state, cookieOptions);
255
+ ctx.cookies.set("oidc_nonce", nonce, cookieOptions);
259
256
  const params = new URLSearchParams();
260
257
  params.append("response_type", "code");
261
258
  params.append("client_id", OIDC_CLIENT_ID);
@@ -264,11 +261,12 @@ async function oidcSignIn(ctx) {
264
261
  params.append("code_challenge", codeChallenge);
265
262
  params.append("code_challenge_method", "S256");
266
263
  params.append("state", state);
264
+ params.append("nonce", nonce);
267
265
  const authorizationUrl = `${OIDC_AUTHORIZATION_ENDPOINT}?${params.toString()}`;
268
266
  ctx.set("Location", authorizationUrl);
269
267
  return ctx.send({}, 302);
270
268
  }
271
- async function exchangeTokenAndFetchUserInfo(config2, params) {
269
+ async function exchangeTokenAndFetchUserInfo(config2, params, expectedNonce) {
272
270
  const response = await fetch(config2.OIDC_TOKEN_ENDPOINT, {
273
271
  method: "POST",
274
272
  body: params,
@@ -277,31 +275,28 @@ async function exchangeTokenAndFetchUserInfo(config2, params) {
277
275
  }
278
276
  });
279
277
  if (!response.ok) {
280
- const errText = await response.text();
281
- throw new Error(
282
- `Failed to exchange token: ${response.status} ${response.statusText} - ${errText}`
283
- );
278
+ throw new Error("Token exchange failed");
284
279
  }
285
280
  const tokenData = await response.json();
286
- let userInfoEndpointHeaders = {};
287
- let userInfoEndpointParameters = `?access_token=${tokenData.access_token}`;
288
- if (config2.OIDC_USER_INFO_ENDPOINT_WITH_AUTH_HEADER) {
289
- userInfoEndpointHeaders = {
290
- Authorization: `Bearer ${tokenData.access_token}`
291
- };
292
- userInfoEndpointParameters = "";
281
+ if (tokenData.id_token) {
282
+ try {
283
+ const payloadB64 = tokenData.id_token.split(".")[1];
284
+ const idTokenPayload = JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf8"));
285
+ if (idTokenPayload.nonce !== expectedNonce) {
286
+ throw new Error("Nonce mismatch");
287
+ }
288
+ } catch (e) {
289
+ if (e.message === "Nonce mismatch") throw e;
290
+ throw new Error("Failed to parse ID token");
291
+ }
293
292
  }
294
- const userInfoEndpoint = `${config2.OIDC_USER_INFO_ENDPOINT}${userInfoEndpointParameters}`;
295
- const userResponse = await fetch(userInfoEndpoint, {
296
- headers: userInfoEndpointHeaders
293
+ const userResponse = await fetch(config2.OIDC_USER_INFO_ENDPOINT, {
294
+ headers: { Authorization: `Bearer ${tokenData.access_token}` }
297
295
  });
298
296
  if (!userResponse.ok) {
299
- const errText = await userResponse.text();
300
- throw new Error(
301
- `Failed to fetch user info: ${userResponse.status} ${userResponse.statusText} - ${errText}`
302
- );
297
+ throw new Error("Failed to fetch user info");
303
298
  }
304
- return await userResponse.json();
299
+ return userResponse.json();
305
300
  }
306
301
  async function registerNewUser(userService, oauthService2, roleService2, email, userResponseData, whitelistUser, config2, ctx) {
307
302
  let roles2 = [];
@@ -325,22 +320,16 @@ async function registerNewUser(userService, oauthService2, roleService2, email,
325
320
  async function handleUserAuthentication(userService, oauthService2, roleService2, whitelistService2, userResponseData, config2, ctx) {
326
321
  const email = String(userResponseData.email).toLowerCase();
327
322
  const whitelistUser = await whitelistService2.checkWhitelistForEmail(email);
328
- const dbUser = await userService.findOneByEmail(email);
329
- let activateUser;
330
- if (dbUser) {
331
- activateUser = dbUser;
332
- } else {
333
- activateUser = await registerNewUser(
334
- userService,
335
- oauthService2,
336
- roleService2,
337
- email,
338
- userResponseData,
339
- whitelistUser,
340
- config2,
341
- ctx
342
- );
343
- }
323
+ const activateUser = await userService.findOneByEmail(email) ?? await registerNewUser(
324
+ userService,
325
+ oauthService2,
326
+ roleService2,
327
+ email,
328
+ userResponseData,
329
+ whitelistUser,
330
+ config2,
331
+ ctx
332
+ );
344
333
  const jwtToken = await oauthService2.generateToken(activateUser, ctx);
345
334
  oauthService2.triggerSignInSuccess(activateUser);
346
335
  return { activateUser, jwtToken };
@@ -356,8 +345,10 @@ async function oidcSignInCallback(ctx) {
356
345
  }
357
346
  const oidcState = ctx.cookies.get("oidc_state");
358
347
  const codeVerifier = ctx.cookies.get("oidc_code_verifier");
348
+ const oidcNonce = ctx.cookies.get("oidc_nonce");
359
349
  ctx.cookies.set("oidc_state", null);
360
350
  ctx.cookies.set("oidc_code_verifier", null);
351
+ ctx.cookies.set("oidc_nonce", null);
361
352
  if (!ctx.query.state || ctx.query.state !== oidcState) {
362
353
  return ctx.send(oauthService2.renderSignUpError("Invalid state"));
363
354
  }
@@ -369,7 +360,7 @@ async function oidcSignInCallback(ctx) {
369
360
  params.append("grant_type", config2.OIDC_GRANT_TYPE);
370
361
  params.append("code_verifier", codeVerifier);
371
362
  try {
372
- const userResponseData = await exchangeTokenAndFetchUserInfo(config2, params);
363
+ const userResponseData = await exchangeTokenAndFetchUserInfo(config2, params, oidcNonce);
373
364
  const { activateUser, jwtToken } = await handleUserAuthentication(
374
365
  userService,
375
366
  oauthService2,
@@ -385,7 +376,7 @@ async function oidcSignInCallback(ctx) {
385
376
  ctx.send(html);
386
377
  } catch (e) {
387
378
  console.error("ERROR CAUGHT IN OIDC SIGNIN:", e);
388
- ctx.send(oauthService2.renderSignUpError(e.message));
379
+ ctx.send(oauthService2.renderSignUpError("Authentication failed. Please try again."));
389
380
  }
390
381
  }
391
382
  async function logout(ctx) {
@@ -432,8 +423,11 @@ const role = {
432
423
  find,
433
424
  update
434
425
  };
426
+ function getWhitelistService() {
427
+ return strapi.plugin("strapi-plugin-oidc").service("whitelist");
428
+ }
435
429
  async function info(ctx) {
436
- const whitelistService2 = strapi.plugin("strapi-plugin-oidc").service("whitelist");
430
+ const whitelistService2 = getWhitelistService();
437
431
  const settings = await whitelistService2.getSettings();
438
432
  const whitelistUsers = await whitelistService2.getUsers();
439
433
  ctx.body = {
@@ -444,8 +438,9 @@ async function info(ctx) {
444
438
  };
445
439
  }
446
440
  async function updateSettings(ctx) {
447
- let { useWhitelist, enforceOIDC } = ctx.request.body;
448
- const whitelistService2 = strapi.plugin("strapi-plugin-oidc").service("whitelist");
441
+ const { useWhitelist } = ctx.request.body;
442
+ let { enforceOIDC } = ctx.request.body;
443
+ const whitelistService2 = getWhitelistService();
449
444
  if (useWhitelist && enforceOIDC) {
450
445
  const users = await whitelistService2.getUsers();
451
446
  if (users.length === 0) {
@@ -456,7 +451,7 @@ async function updateSettings(ctx) {
456
451
  ctx.body = { useWhitelist, enforceOIDC };
457
452
  }
458
453
  async function publicSettings(ctx) {
459
- const whitelistService2 = strapi.plugin("strapi-plugin-oidc").service("whitelist");
454
+ const whitelistService2 = getWhitelistService();
460
455
  const settings = await whitelistService2.getSettings();
461
456
  const config2 = strapi.config.get("plugin::strapi-plugin-oidc");
462
457
  ctx.body = {
@@ -476,10 +471,11 @@ async function register(ctx) {
476
471
  where: { email: { $in: emailList } },
477
472
  populate: ["roles"]
478
473
  });
479
- const whitelistService2 = strapi.plugin("strapi-plugin-oidc").service("whitelist");
474
+ const existingUsersByEmail = new Map(existingUsers.map((u) => [u.email, u]));
475
+ const whitelistService2 = getWhitelistService();
480
476
  let matchedExistingUsersCount = 0;
481
477
  for (const singleEmail of emailList) {
482
- const existingUser = existingUsers.find((u) => u.email === singleEmail);
478
+ const existingUser = existingUsersByEmail.get(singleEmail);
483
479
  let finalRoles = roles2;
484
480
  if (existingUser?.roles) {
485
481
  finalRoles = existingUser.roles.map((r) => String(r.id));
@@ -496,14 +492,56 @@ async function register(ctx) {
496
492
  }
497
493
  async function removeEmail(ctx) {
498
494
  const { id } = ctx.params;
499
- const whitelistService2 = strapi.plugin("strapi-plugin-oidc").service("whitelist");
495
+ const whitelistService2 = getWhitelistService();
500
496
  await whitelistService2.removeUser(id);
501
497
  ctx.body = {};
502
498
  }
499
+ async function deleteAll(ctx) {
500
+ await strapi.query("plugin::strapi-plugin-oidc.whitelists").deleteMany({});
501
+ ctx.body = {};
502
+ }
503
+ async function importUsers(ctx) {
504
+ const { users } = ctx.request.body;
505
+ if (!Array.isArray(users)) {
506
+ ctx.status = 400;
507
+ ctx.body = { error: "Expected { users: [{email, roles}] }" };
508
+ return;
509
+ }
510
+ const allRoles = await strapi.query("admin::role").findMany({});
511
+ const roleNameToId = new Map(allRoles.map((r) => [r.name, String(r.id)]));
512
+ const resolveRole = (nameOrId) => roleNameToId.get(nameOrId) ?? nameOrId;
513
+ const normalized = users.filter((u) => u?.email).map((u) => ({
514
+ email: String(u.email).trim().toLowerCase(),
515
+ roles: (Array.isArray(u.roles) ? u.roles : []).map(resolveRole)
516
+ }));
517
+ const seen = /* @__PURE__ */ new Set();
518
+ const deduped = normalized.filter((u) => {
519
+ if (seen.has(u.email)) return false;
520
+ seen.add(u.email);
521
+ return true;
522
+ });
523
+ const strapiUsers = await strapi.query("admin::user").findMany({
524
+ where: { email: { $in: deduped.map((u) => u.email) } },
525
+ populate: ["roles"]
526
+ });
527
+ const strapiUserMap = new Map(strapiUsers.map((u) => [u.email, u]));
528
+ const whitelistService2 = getWhitelistService();
529
+ const existing = await whitelistService2.getUsers();
530
+ const existingEmails = new Set(existing.map((u) => u.email));
531
+ let importedCount = 0;
532
+ for (const user of deduped) {
533
+ if (existingEmails.has(user.email)) continue;
534
+ const strapiUser = strapiUserMap.get(user.email);
535
+ const finalRoles = strapiUser?.roles?.length ? strapiUser.roles.map((r) => String(r.id)) : user.roles;
536
+ await whitelistService2.registerUser(user.email, finalRoles);
537
+ importedCount++;
538
+ }
539
+ ctx.body = { importedCount };
540
+ }
503
541
  async function syncUsers(ctx) {
504
- let { users } = ctx.request.body;
505
- users = users.map((u) => ({ ...u, email: String(u.email).toLowerCase() }));
506
- const whitelistService2 = strapi.plugin("strapi-plugin-oidc").service("whitelist");
542
+ const { users: rawUsers } = ctx.request.body;
543
+ const users = rawUsers.map((u) => ({ ...u, email: String(u.email).toLowerCase() }));
544
+ const whitelistService2 = getWhitelistService();
507
545
  const currentUsers = await whitelistService2.getUsers();
508
546
  let matchedExistingUsersCount = 0;
509
547
  const emailsToSync = users.map((u) => u.email);
@@ -511,26 +549,29 @@ async function syncUsers(ctx) {
511
549
  where: { email: { $in: emailsToSync } },
512
550
  populate: ["roles"]
513
551
  });
552
+ const syncEmailSet = new Set(emailsToSync);
553
+ const currentUsersByEmail = new Map(currentUsers.map((u) => [u.email, u]));
554
+ const strapiUsersByEmail = new Map(existingStrapiUsers.map((u) => [u.email, u]));
514
555
  for (const currUser of currentUsers) {
515
- if (!users.find((u) => u.email === currUser.email)) {
556
+ if (!syncEmailSet.has(currUser.email)) {
516
557
  await whitelistService2.removeUser(currUser.id);
517
558
  }
518
559
  }
519
560
  for (const user of users) {
520
- const existingStrapiUser = existingStrapiUsers.find((u) => u.email === user.email);
561
+ const currUser = currentUsersByEmail.get(user.email);
521
562
  let finalRoles = user.roles;
522
- const currUser = currentUsers.find((u) => u.email === user.email);
523
- if (!currUser && existingStrapiUser?.roles) {
524
- finalRoles = existingStrapiUser.roles.map((r) => String(r.id));
525
- matchedExistingUsersCount++;
526
- }
527
- if (currUser) {
563
+ if (!currUser) {
564
+ const existingStrapiUser = strapiUsersByEmail.get(user.email);
565
+ if (existingStrapiUser?.roles) {
566
+ finalRoles = existingStrapiUser.roles.map((r) => String(r.id));
567
+ matchedExistingUsersCount++;
568
+ }
569
+ await whitelistService2.registerUser(user.email, finalRoles);
570
+ } else {
528
571
  await strapi.query("plugin::strapi-plugin-oidc.whitelists").update({
529
572
  where: { id: currUser.id },
530
573
  data: { roles: finalRoles }
531
574
  });
532
- } else {
533
- await whitelistService2.registerUser(user.email, finalRoles);
534
575
  }
535
576
  }
536
577
  ctx.body = { matchedExistingUsersCount };
@@ -541,7 +582,9 @@ const whitelist = {
541
582
  publicSettings,
542
583
  register,
543
584
  removeEmail,
544
- syncUsers
585
+ deleteAll,
586
+ syncUsers,
587
+ importUsers
545
588
  };
546
589
  const controllers = {
547
590
  oidc,
@@ -565,134 +608,133 @@ const rateLimitMiddleware = async (ctx, next) => {
565
608
  rateLimitMap.set(ip, requestStamps);
566
609
  await next();
567
610
  };
568
- const routes = [
569
- {
570
- method: "GET",
571
- path: "/oidc-roles",
572
- handler: "role.find",
573
- config: {
574
- policies: [
575
- "admin::isAuthenticatedAdmin",
576
- { name: "admin::hasPermissions", config: { actions: ["plugin::strapi-plugin-oidc.read"] } }
577
- ]
578
- }
579
- },
580
- {
581
- method: "PUT",
582
- path: "/oidc-roles",
583
- handler: "role.update",
584
- config: {
585
- policies: [
586
- "admin::isAuthenticatedAdmin",
587
- {
588
- name: "admin::hasPermissions",
589
- config: { actions: ["plugin::strapi-plugin-oidc.update"] }
590
- }
591
- ]
592
- }
593
- },
594
- {
595
- method: "GET",
596
- path: "/oidc",
597
- handler: "oidc.oidcSignIn",
598
- config: {
599
- auth: false,
600
- middlewares: [rateLimitMiddleware]
601
- }
602
- },
603
- {
604
- method: "GET",
605
- path: "/oidc/callback",
606
- handler: "oidc.oidcSignInCallback",
607
- config: {
608
- auth: false,
609
- middlewares: [rateLimitMiddleware]
610
- }
611
- },
612
- {
613
- method: "GET",
614
- path: "/logout",
615
- handler: "oidc.logout",
616
- config: {
617
- auth: false
618
- }
619
- },
620
- {
621
- method: "GET",
622
- path: "/whitelist",
623
- handler: "whitelist.info",
624
- config: {
625
- policies: [
626
- "admin::isAuthenticatedAdmin",
627
- { name: "admin::hasPermissions", config: { actions: ["plugin::strapi-plugin-oidc.read"] } }
628
- ]
629
- }
630
- },
631
- {
632
- method: "PUT",
633
- path: "/whitelist/settings",
634
- handler: "whitelist.updateSettings",
635
- config: {
636
- policies: [
637
- "admin::isAuthenticatedAdmin",
638
- {
639
- name: "admin::hasPermissions",
640
- config: { actions: ["plugin::strapi-plugin-oidc.update"] }
641
- }
642
- ]
643
- }
644
- },
645
- {
646
- method: "GET",
647
- path: "/settings/public",
648
- handler: "whitelist.publicSettings",
649
- config: {
650
- auth: false
651
- }
652
- },
653
- {
654
- method: "PUT",
655
- path: "/whitelist/sync",
656
- handler: "whitelist.syncUsers",
657
- config: {
658
- policies: [
659
- "admin::isAuthenticatedAdmin",
660
- {
661
- name: "admin::hasPermissions",
662
- config: { actions: ["plugin::strapi-plugin-oidc.update"] }
663
- }
664
- ]
665
- }
666
- },
667
- {
668
- method: "POST",
669
- path: "/whitelist",
670
- handler: "whitelist.register",
671
- config: {
672
- policies: [
673
- "admin::isAuthenticatedAdmin",
674
- {
675
- name: "admin::hasPermissions",
676
- config: { actions: ["plugin::strapi-plugin-oidc.update"] }
677
- }
678
- ]
611
+ const adminPolicies = (action) => ({
612
+ policies: [
613
+ "admin::isAuthenticatedAdmin",
614
+ {
615
+ name: "admin::hasPermissions",
616
+ config: { actions: [`plugin::strapi-plugin-oidc.${action}`] }
679
617
  }
618
+ ]
619
+ });
620
+ const routes = {
621
+ admin: {
622
+ type: "admin",
623
+ routes: [
624
+ {
625
+ method: "GET",
626
+ path: "/oidc-roles",
627
+ handler: "role.find",
628
+ config: adminPolicies("read")
629
+ },
630
+ {
631
+ method: "PUT",
632
+ path: "/oidc-roles",
633
+ handler: "role.update",
634
+ config: adminPolicies("update")
635
+ },
636
+ {
637
+ method: "GET",
638
+ path: "/oidc",
639
+ handler: "oidc.oidcSignIn",
640
+ config: { auth: false, middlewares: [rateLimitMiddleware] }
641
+ },
642
+ {
643
+ method: "GET",
644
+ path: "/oidc/callback",
645
+ handler: "oidc.oidcSignInCallback",
646
+ config: { auth: false, middlewares: [rateLimitMiddleware] }
647
+ },
648
+ {
649
+ method: "GET",
650
+ path: "/logout",
651
+ handler: "oidc.logout",
652
+ config: { auth: false }
653
+ },
654
+ {
655
+ method: "GET",
656
+ path: "/whitelist",
657
+ handler: "whitelist.info",
658
+ config: adminPolicies("read")
659
+ },
660
+ {
661
+ method: "PUT",
662
+ path: "/whitelist/settings",
663
+ handler: "whitelist.updateSettings",
664
+ config: adminPolicies("update")
665
+ },
666
+ {
667
+ method: "GET",
668
+ path: "/settings/public",
669
+ handler: "whitelist.publicSettings",
670
+ config: { auth: false }
671
+ },
672
+ {
673
+ method: "PUT",
674
+ path: "/whitelist/sync",
675
+ handler: "whitelist.syncUsers",
676
+ config: adminPolicies("update")
677
+ },
678
+ {
679
+ method: "POST",
680
+ path: "/whitelist/import",
681
+ handler: "whitelist.importUsers",
682
+ config: adminPolicies("update")
683
+ },
684
+ {
685
+ method: "POST",
686
+ path: "/whitelist",
687
+ handler: "whitelist.register",
688
+ config: adminPolicies("update")
689
+ },
690
+ {
691
+ method: "DELETE",
692
+ path: "/whitelist/:id",
693
+ handler: "whitelist.removeEmail",
694
+ config: adminPolicies("update")
695
+ },
696
+ {
697
+ method: "DELETE",
698
+ path: "/whitelist",
699
+ handler: "whitelist.deleteAll",
700
+ config: adminPolicies("update")
701
+ }
702
+ ]
680
703
  },
681
- {
682
- method: "DELETE",
683
- path: "/whitelist/:id",
684
- handler: "whitelist.removeEmail",
685
- config: {
686
- policies: [
687
- "admin::isAuthenticatedAdmin",
688
- {
689
- name: "admin::hasPermissions",
690
- config: { actions: ["plugin::strapi-plugin-oidc.update"] }
691
- }
692
- ]
693
- }
704
+ // API-token-authenticated routes for programmatic whitelist management.
705
+ // Accessible at /strapi-plugin-oidc/... using a Strapi API token
706
+ // (full-access or custom) in the Authorization: Bearer <token> header.
707
+ "content-api": {
708
+ type: "content-api",
709
+ routes: [
710
+ {
711
+ method: "GET",
712
+ path: "/whitelist",
713
+ handler: "whitelist.info"
714
+ },
715
+ {
716
+ method: "POST",
717
+ path: "/whitelist",
718
+ handler: "whitelist.register"
719
+ },
720
+ {
721
+ method: "POST",
722
+ path: "/whitelist/import",
723
+ handler: "whitelist.importUsers"
724
+ },
725
+ {
726
+ method: "DELETE",
727
+ path: "/whitelist/:id",
728
+ handler: "whitelist.removeEmail"
729
+ },
730
+ {
731
+ method: "DELETE",
732
+ path: "/whitelist",
733
+ handler: "whitelist.deleteAll"
734
+ }
735
+ ]
694
736
  }
695
- ];
737
+ };
696
738
  const policies = {};
697
739
  function renderHtmlTemplate(title, content) {
698
740
  return `
@@ -828,7 +870,7 @@ function oauthService({ strapi: strapi2 }) {
828
870
  roles: roles2,
829
871
  preferedLanguage: locale
830
872
  });
831
- return await userService.register({
873
+ return userService.register({
832
874
  registrationToken: createdUser.registrationToken,
833
875
  userInfo: {
834
876
  firstname: firstname || "unset",
@@ -849,7 +891,7 @@ function oauthService({ strapi: strapi2 }) {
849
891
  if (!baseAlias) {
850
892
  return baseEmail;
851
893
  }
852
- const alias = baseAlias.replace("/+/g", "");
894
+ const alias = baseAlias.replace(/\+/g, "");
853
895
  const beforePosition = baseEmail.indexOf("@");
854
896
  const origin = baseEmail.substring(0, beforePosition);
855
897
  const domain = baseEmail.substring(beforePosition);
@@ -886,11 +928,9 @@ function oauthService({ strapi: strapi2 }) {
886
928
  provider: "strapi-plugin-oidc"
887
929
  });
888
930
  },
889
- // Sign In Success
890
931
  renderSignUpSuccess(jwtToken, user, nonce) {
891
932
  const config2 = strapi2.config.get("plugin::strapi-plugin-oidc");
892
- const REMEMBER_ME = config2["REMEMBER_ME"];
893
- const isRememberMe = !!REMEMBER_ME;
933
+ const isRememberMe = !!config2["REMEMBER_ME"];
894
934
  const content = `
895
935
  <noscript>
896
936
  <div class="card">
@@ -916,7 +956,6 @@ function oauthService({ strapi: strapi2 }) {
916
956
  <\/script>`;
917
957
  return renderHtmlTemplate("Authenticating...", content);
918
958
  },
919
- // Sign In Error
920
959
  renderSignUpError(message) {
921
960
  const safeMessage = String(message).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
922
961
  const content = `
@@ -944,8 +983,7 @@ function oauthService({ strapi: strapi2 }) {
944
983
  const userId = String(user.id);
945
984
  const deviceId = randomUUID();
946
985
  const config2 = strapi2.config.get("plugin::strapi-plugin-oidc");
947
- const REMEMBER_ME = config2["REMEMBER_ME"];
948
- const rememberMe = !!REMEMBER_ME;
986
+ const rememberMe = !!config2["REMEMBER_ME"];
949
987
  const { token: refreshToken, absoluteExpiresAt } = await sessionManager(
950
988
  "admin"
951
989
  ).generateRefreshToken(userId, deviceId, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-oidc",
3
- "version": "1.3.2",
3
+ "version": "1.4.1",
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",
@@ -26,10 +26,17 @@
26
26
  },
27
27
  "keywords": [
28
28
  "strapi",
29
- "plugin",
29
+ "strapi-plugin",
30
+ "oidc",
30
31
  "oauth",
31
- "OIDC",
32
- "Zitadel"
32
+ "sso",
33
+ "authentication",
34
+ "keycloak",
35
+ "auth0",
36
+ "okta",
37
+ "azure-ad",
38
+ "authentik",
39
+ "authelia"
33
40
  ],
34
41
  "peerDependencies": {
35
42
  "@strapi/strapi": "^5.24.1",