strapi-plugin-oidc 1.0.5 → 1.0.7

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.
@@ -201,7 +201,7 @@ async function registerNewUser(userService, oauthService2, roleService2, email,
201
201
  return activateUser;
202
202
  }
203
203
  async function handleUserAuthentication(userService, oauthService2, roleService2, whitelistService2, userResponseData, config2, ctx) {
204
- const email = userResponseData.email;
204
+ const email = String(userResponseData.email).toLowerCase();
205
205
  const whitelistUser = await whitelistService2.checkWhitelistForEmail(email);
206
206
  const dbUser = await userService.findOneByEmail(email);
207
207
  let activateUser;
@@ -316,8 +316,14 @@ async function info(ctx) {
316
316
  };
317
317
  }
318
318
  async function updateSettings(ctx) {
319
- const { useWhitelist, enforceOIDC } = ctx.request.body;
319
+ let { useWhitelist, enforceOIDC } = ctx.request.body;
320
320
  const whitelistService2 = strapi.plugin("strapi-plugin-oidc").service("whitelist");
321
+ if (useWhitelist && enforceOIDC) {
322
+ const users = await whitelistService2.getUsers();
323
+ if (users.length === 0) {
324
+ enforceOIDC = false;
325
+ }
326
+ }
321
327
  await whitelistService2.setSettings({ useWhitelist, enforceOIDC });
322
328
  ctx.body = { useWhitelist, enforceOIDC };
323
329
  }
@@ -334,7 +340,8 @@ async function register(ctx) {
334
340
  ctx.body = { message: "Please enter a valid email address" };
335
341
  return;
336
342
  }
337
- const emailList = Array.isArray(email) ? email : email.split(",").map((e) => e.trim()).filter(Boolean);
343
+ const rawEmails = Array.isArray(email) ? email : email.split(",");
344
+ const emailList = rawEmails.map((e) => String(e).trim().toLowerCase()).filter(Boolean);
338
345
  const existingUsers = await strapi.query("admin::user").findMany({
339
346
  where: { email: { $in: emailList } },
340
347
  populate: ["roles"]
@@ -364,7 +371,8 @@ async function removeEmail(ctx) {
364
371
  ctx.body = {};
365
372
  }
366
373
  async function syncUsers(ctx) {
367
- const { users } = ctx.request.body;
374
+ let { users } = ctx.request.body;
375
+ users = users.map((u) => ({ ...u, email: String(u.email).toLowerCase() }));
368
376
  const whitelistService2 = strapi.plugin("strapi-plugin-oidc").service("whitelist");
369
377
  const currentUsers = await whitelistService2.getUsers();
370
378
  let matchedExistingUsersCount = 0;
@@ -410,6 +418,23 @@ const controllers = {
410
418
  role,
411
419
  whitelist
412
420
  };
421
+ const rateLimitMap = /* @__PURE__ */ new Map();
422
+ const RATE_LIMIT_WINDOW = 6e4;
423
+ const MAX_REQUESTS = 20;
424
+ const rateLimitMiddleware = async (ctx, next) => {
425
+ const ip = ctx.request.ip;
426
+ const now = Date.now();
427
+ const windowStart = now - RATE_LIMIT_WINDOW;
428
+ const requestStamps = (rateLimitMap.get(ip) || []).filter((timestamp) => timestamp > windowStart);
429
+ if (requestStamps.length >= MAX_REQUESTS) {
430
+ ctx.status = 429;
431
+ ctx.body = "Too Many Requests";
432
+ return;
433
+ }
434
+ requestStamps.push(now);
435
+ rateLimitMap.set(ip, requestStamps);
436
+ await next();
437
+ };
413
438
  const routes = [
414
439
  {
415
440
  method: "GET",
@@ -429,7 +454,10 @@ const routes = [
429
454
  config: {
430
455
  policies: [
431
456
  "admin::isAuthenticatedAdmin",
432
- { name: "admin::hasPermissions", config: { actions: ["plugin::strapi-plugin-oidc.update"] } }
457
+ {
458
+ name: "admin::hasPermissions",
459
+ config: { actions: ["plugin::strapi-plugin-oidc.update"] }
460
+ }
433
461
  ]
434
462
  }
435
463
  },
@@ -438,7 +466,8 @@ const routes = [
438
466
  path: "/oidc",
439
467
  handler: "oidc.oidcSignIn",
440
468
  config: {
441
- auth: false
469
+ auth: false,
470
+ middlewares: [rateLimitMiddleware]
442
471
  }
443
472
  },
444
473
  {
@@ -446,7 +475,8 @@ const routes = [
446
475
  path: "/oidc/callback",
447
476
  handler: "oidc.oidcSignInCallback",
448
477
  config: {
449
- auth: false
478
+ auth: false,
479
+ middlewares: [rateLimitMiddleware]
450
480
  }
451
481
  },
452
482
  {
@@ -475,7 +505,10 @@ const routes = [
475
505
  config: {
476
506
  policies: [
477
507
  "admin::isAuthenticatedAdmin",
478
- { name: "admin::hasPermissions", config: { actions: ["plugin::strapi-plugin-oidc.update"] } }
508
+ {
509
+ name: "admin::hasPermissions",
510
+ config: { actions: ["plugin::strapi-plugin-oidc.update"] }
511
+ }
479
512
  ]
480
513
  }
481
514
  },
@@ -494,7 +527,10 @@ const routes = [
494
527
  config: {
495
528
  policies: [
496
529
  "admin::isAuthenticatedAdmin",
497
- { name: "admin::hasPermissions", config: { actions: ["plugin::strapi-plugin-oidc.update"] } }
530
+ {
531
+ name: "admin::hasPermissions",
532
+ config: { actions: ["plugin::strapi-plugin-oidc.update"] }
533
+ }
498
534
  ]
499
535
  }
500
536
  },
@@ -505,7 +541,10 @@ const routes = [
505
541
  config: {
506
542
  policies: [
507
543
  "admin::isAuthenticatedAdmin",
508
- { name: "admin::hasPermissions", config: { actions: ["plugin::strapi-plugin-oidc.update"] } }
544
+ {
545
+ name: "admin::hasPermissions",
546
+ config: { actions: ["plugin::strapi-plugin-oidc.update"] }
547
+ }
509
548
  ]
510
549
  }
511
550
  },
@@ -516,12 +555,132 @@ const routes = [
516
555
  config: {
517
556
  policies: [
518
557
  "admin::isAuthenticatedAdmin",
519
- { name: "admin::hasPermissions", config: { actions: ["plugin::strapi-plugin-oidc.update"] } }
558
+ {
559
+ name: "admin::hasPermissions",
560
+ config: { actions: ["plugin::strapi-plugin-oidc.update"] }
561
+ }
520
562
  ]
521
563
  }
522
564
  }
523
565
  ];
524
566
  const policies = {};
567
+ function renderHtmlTemplate(title, content) {
568
+ return `
569
+ <!doctype html>
570
+ <html lang="en">
571
+ <head>
572
+ <meta charset="utf-8">
573
+ <meta name="viewport" content="width=device-width, initial-scale=1">
574
+ <title>${title}</title>
575
+ <style>
576
+ :root {
577
+ --bg-color: #f6f6f9;
578
+ --card-bg: #ffffff;
579
+ --text-color: #32324d;
580
+ --text-muted: #666687;
581
+ --btn-bg: #4945ff;
582
+ --btn-hover: #271fe0;
583
+ --btn-text: #ffffff;
584
+ --icon-bg: #fcecea;
585
+ --icon-color: #d02b20;
586
+ --success-bg: #eafbe7;
587
+ --success-color: #328048;
588
+ --shadow: 0 1px 4px rgba(33, 33, 52, 0.1);
589
+ }
590
+ @media (prefers-color-scheme: dark) {
591
+ :root {
592
+ --bg-color: #181826;
593
+ --card-bg: #212134;
594
+ --text-color: #ffffff;
595
+ --text-muted: #a5a5ba;
596
+ --btn-bg: #4945ff;
597
+ --btn-hover: #7b79ff;
598
+ --btn-text: #ffffff;
599
+ --icon-bg: #4a2123;
600
+ --icon-color: #f23628;
601
+ --success-bg: #1c3523;
602
+ --success-color: #55ca76;
603
+ --shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
604
+ }
605
+ }
606
+ body {
607
+ margin: 0;
608
+ padding: 0;
609
+ display: flex;
610
+ justify-content: center;
611
+ align-items: center;
612
+ height: 100vh;
613
+ background-color: var(--bg-color);
614
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
615
+ color: var(--text-color);
616
+ }
617
+ .card {
618
+ background: var(--card-bg);
619
+ padding: 32px 40px;
620
+ border-radius: 8px;
621
+ box-shadow: var(--shadow);
622
+ max-width: 400px;
623
+ width: 100%;
624
+ text-align: center;
625
+ box-sizing: border-box;
626
+ }
627
+ .icon {
628
+ width: 48px;
629
+ height: 48px;
630
+ background-color: var(--icon-bg);
631
+ color: var(--icon-color);
632
+ border-radius: 50%;
633
+ display: inline-flex;
634
+ justify-content: center;
635
+ align-items: center;
636
+ margin-bottom: 24px;
637
+ }
638
+ .icon.success {
639
+ background-color: var(--success-bg);
640
+ color: var(--success-color);
641
+ }
642
+ .icon svg {
643
+ width: 24px;
644
+ height: 24px;
645
+ stroke: currentColor;
646
+ stroke-width: 2;
647
+ stroke-linecap: round;
648
+ stroke-linejoin: round;
649
+ fill: none;
650
+ }
651
+ h1 {
652
+ margin: 0 0 12px 0;
653
+ font-size: 20px;
654
+ font-weight: 600;
655
+ color: var(--text-color);
656
+ }
657
+ p {
658
+ margin: 0 0 32px 0;
659
+ font-size: 14px;
660
+ line-height: 1.5;
661
+ color: var(--text-muted);
662
+ }
663
+ .btn {
664
+ display: inline-block;
665
+ background-color: var(--btn-bg);
666
+ color: var(--btn-text);
667
+ padding: 10px 16px;
668
+ border-radius: 4px;
669
+ text-decoration: none;
670
+ font-size: 14px;
671
+ font-weight: 500;
672
+ transition: background-color 0.2s;
673
+ }
674
+ .btn:hover {
675
+ background-color: var(--btn-hover);
676
+ }
677
+ </style>
678
+ </head>
679
+ <body>
680
+ ${content}
681
+ </body>
682
+ </html>`;
683
+ }
525
684
  function oauthService({ strapi: strapi2 }) {
526
685
  return {
527
686
  async createUser(email, lastname, firstname, locale, roles2 = []) {
@@ -602,40 +761,48 @@ function oauthService({ strapi: strapi2 }) {
602
761
  const config2 = strapi2.config.get("plugin::strapi-plugin-oidc");
603
762
  const REMEMBER_ME = config2["REMEMBER_ME"];
604
763
  const isRememberMe = !!REMEMBER_ME;
605
- return `
606
- <!doctype html>
607
- <html>
608
- <head>
609
- <noscript>
610
- <h3>JavaScript must be enabled for authentication</h3>
611
- </noscript>
612
- <script nonce="${nonce}">
613
- window.addEventListener('load', function() {
614
- if(${isRememberMe}){
615
- localStorage.setItem('jwtToken', '"${jwtToken}"');
616
- }else{
617
- document.cookie = 'jwtToken=${encodeURIComponent(jwtToken)}; Path=/';
618
- }
619
- localStorage.setItem('isLoggedIn', 'true');
620
- location.href = '${strapi2.config.admin.url}'
621
- })
622
- <\/script>
623
- </head>
624
- <body>
625
- </body>
626
- </html>`;
764
+ const content = `
765
+ <noscript>
766
+ <div class="card">
767
+ <div class="icon success">
768
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check">
769
+ <path d="M20 6 9 17l-5-5"/>
770
+ </svg>
771
+ </div>
772
+ <h1>JavaScript Required</h1>
773
+ <p>JavaScript must be enabled for authentication to complete.</p>
774
+ </div>
775
+ </noscript>
776
+ <script nonce="${nonce}">
777
+ window.addEventListener('load', function() {
778
+ if(${isRememberMe}){
779
+ localStorage.setItem('jwtToken', '"${jwtToken}"');
780
+ }else{
781
+ document.cookie = 'jwtToken=${encodeURIComponent(jwtToken)}; Path=/';
782
+ }
783
+ localStorage.setItem('isLoggedIn', 'true');
784
+ location.href = '${strapi2.config.admin.url}'
785
+ })
786
+ <\/script>`;
787
+ return renderHtmlTemplate("Authenticating...", content);
627
788
  },
628
789
  // Sign In Error
629
790
  renderSignUpError(message) {
630
- return `
631
- <!doctype html>
632
- <html>
633
- <head></head>
634
- <body>
635
- <h3>Authentication failed</h3>
636
- <p>${message}</p>
637
- </body>
638
- </html>`;
791
+ const safeMessage = String(message).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
792
+ const content = `
793
+ <div class="card">
794
+ <div class="icon">
795
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-triangle-alert">
796
+ <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/>
797
+ <path d="M12 9v4"/>
798
+ <path d="M12 17h.01"/>
799
+ </svg>
800
+ </div>
801
+ <h1>Authentication Failed</h1>
802
+ <p>${safeMessage}</p>
803
+ <a href="${strapi2.config.admin.url}" class="btn">Return to Login</a>
804
+ </div>`;
805
+ return renderHtmlTemplate("Authentication Failed", content);
639
806
  },
640
807
  async generateToken(user, ctx) {
641
808
  const sessionManager = strapi2.sessionManager;
@@ -729,39 +896,35 @@ function roleService({ strapi: strapi2 }) {
729
896
  };
730
897
  }
731
898
  function whitelistService({ strapi: strapi2 }) {
899
+ const getPluginStore = () => strapi2.store({
900
+ environment: "",
901
+ type: "plugin",
902
+ name: "strapi-plugin-oidc"
903
+ });
904
+ const getWhitelistQuery = () => strapi2.query("plugin::strapi-plugin-oidc.whitelists");
732
905
  return {
733
906
  async getSettings() {
734
- const pluginStore = strapi2.store({ type: "plugin", name: "strapi-plugin-oidc" });
735
- let settings = await pluginStore.get({ key: "settings" });
907
+ let settings = await getPluginStore().get({ key: "settings" });
736
908
  if (!settings) {
737
909
  settings = { useWhitelist: true, enforceOIDC: false };
738
- await pluginStore.set({ key: "settings", value: settings });
910
+ await getPluginStore().set({ key: "settings", value: settings });
739
911
  }
740
912
  return settings;
741
913
  },
742
914
  async setSettings(settings) {
743
- const pluginStore = strapi2.store({ type: "plugin", name: "strapi-plugin-oidc" });
744
- await pluginStore.set({ key: "settings", value: settings });
915
+ await getPluginStore().set({ key: "settings", value: settings });
745
916
  },
746
917
  async getUsers() {
747
- const query = strapi2.query("plugin::strapi-plugin-oidc.whitelists");
748
- return query.findMany();
918
+ return getWhitelistQuery().findMany();
749
919
  },
750
920
  async registerUser(email, roles2) {
751
- const query = strapi2.query("plugin::strapi-plugin-oidc.whitelists");
752
- await query.create({
753
- data: {
754
- email,
755
- roles: roles2
756
- }
921
+ await getWhitelistQuery().create({
922
+ data: { email, roles: roles2 }
757
923
  });
758
924
  },
759
925
  async removeUser(id) {
760
- const query = strapi2.query("plugin::strapi-plugin-oidc.whitelists");
761
- await query.delete({
762
- where: {
763
- id
764
- }
926
+ await getWhitelistQuery().delete({
927
+ where: { id }
765
928
  });
766
929
  },
767
930
  async checkWhitelistForEmail(email) {
@@ -770,11 +933,8 @@ function whitelistService({ strapi: strapi2 }) {
770
933
  if (!settings.useWhitelist) {
771
934
  return null;
772
935
  }
773
- const query = strapi2.query("plugin::strapi-plugin-oidc.whitelists");
774
- const result = await query.findOne({
775
- where: {
776
- email
777
- }
936
+ const result = await getWhitelistQuery().findOne({
937
+ where: { email }
778
938
  });
779
939
  console.log("checkWhitelistForEmail result:", result);
780
940
  if (!result) {