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.
- package/README.md +16 -2
- package/dist/admin/{en-jFPbEFeK.js → en-8UlbiAHW.js} +1 -0
- package/dist/admin/{en-f0TxVfx7.mjs → en-DInn-mdh.mjs} +1 -0
- package/dist/admin/{index-B525UaV3.js → index-Bc2bQNhu.js} +2 -2
- package/dist/admin/{index-D3AvxXlB.mjs → index-Cz9Q6j4e.mjs} +2 -2
- package/dist/admin/{index-DlQ8NUBY.js → index-DNIqscJT.js} +172 -186
- package/dist/admin/{index-BbD-7Z4N.mjs → index-V4-lA3hu.mjs} +172 -186
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +225 -65
- package/dist/server/index.mjs +225 -65
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
|
|
606
|
-
|
|
607
|
-
<
|
|
608
|
-
<
|
|
609
|
-
<
|
|
610
|
-
<
|
|
611
|
-
</
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
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
|
-
|
|
631
|
-
|
|
632
|
-
<
|
|
633
|
-
<
|
|
634
|
-
<
|
|
635
|
-
<
|
|
636
|
-
<
|
|
637
|
-
|
|
638
|
-
</
|
|
791
|
+
const safeMessage = String(message).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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
|
-
|
|
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
|
|
910
|
+
await getPluginStore().set({ key: "settings", value: settings });
|
|
739
911
|
}
|
|
740
912
|
return settings;
|
|
741
913
|
},
|
|
742
914
|
async setSettings(settings) {
|
|
743
|
-
|
|
744
|
-
await pluginStore.set({ key: "settings", value: settings });
|
|
915
|
+
await getPluginStore().set({ key: "settings", value: settings });
|
|
745
916
|
},
|
|
746
917
|
async getUsers() {
|
|
747
|
-
|
|
748
|
-
return query.findMany();
|
|
918
|
+
return getWhitelistQuery().findMany();
|
|
749
919
|
},
|
|
750
920
|
async registerUser(email, roles2) {
|
|
751
|
-
|
|
752
|
-
|
|
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
|
-
|
|
761
|
-
|
|
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
|
|
774
|
-
|
|
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) {
|