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