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.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
|
|
599
|
-
|
|
600
|
-
<
|
|
601
|
-
<
|
|
602
|
-
<
|
|
603
|
-
<
|
|
604
|
-
</
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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
|
-
|
|
624
|
-
|
|
625
|
-
<
|
|
626
|
-
<
|
|
627
|
-
<
|
|
628
|
-
<
|
|
629
|
-
<
|
|
630
|
-
|
|
631
|
-
</
|
|
784
|
+
const safeMessage = String(message).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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
|
-
|
|
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
|
|
903
|
+
await getPluginStore().set({ key: "settings", value: settings });
|
|
732
904
|
}
|
|
733
905
|
return settings;
|
|
734
906
|
},
|
|
735
907
|
async setSettings(settings) {
|
|
736
|
-
|
|
737
|
-
await pluginStore.set({ key: "settings", value: settings });
|
|
908
|
+
await getPluginStore().set({ key: "settings", value: settings });
|
|
738
909
|
},
|
|
739
910
|
async getUsers() {
|
|
740
|
-
|
|
741
|
-
return query.findMany();
|
|
911
|
+
return getWhitelistQuery().findMany();
|
|
742
912
|
},
|
|
743
913
|
async registerUser(email, roles2) {
|
|
744
|
-
|
|
745
|
-
|
|
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
|
-
|
|
754
|
-
|
|
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
|
|
767
|
-
|
|
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