aaspai-authx 0.1.4 → 0.1.6
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/dist/express/index.cjs +492 -41
- package/dist/express/index.cjs.map +1 -1
- package/dist/express/index.js +495 -44
- package/dist/express/index.js.map +1 -1
- package/dist/index.cjs +492 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +495 -44
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +492 -41
- package/dist/nest/index.cjs.map +1 -1
- package/dist/nest/index.js +495 -44
- package/dist/nest/index.js.map +1 -1
- package/package.json +1 -1
package/dist/nest/index.js
CHANGED
|
@@ -23,8 +23,8 @@ function loadConfig() {
|
|
|
23
23
|
cookies: {
|
|
24
24
|
domain: process.env.COOKIE_DOMAIN,
|
|
25
25
|
secure: (process.env.COOKIE_SECURE || "true") === "true",
|
|
26
|
-
accessTtlMs: 24 * 60 * 60 * 1e3,
|
|
27
|
-
refreshTtlMs:
|
|
26
|
+
accessTtlMs: 7 * 24 * 60 * 60 * 1e3,
|
|
27
|
+
refreshTtlMs: 30 * 24 * 60 * 60 * 1e3
|
|
28
28
|
},
|
|
29
29
|
oidc: {
|
|
30
30
|
jwtSecret: process.env.JWT_SECRET
|
|
@@ -524,7 +524,7 @@ var AuthAdminService = class {
|
|
|
524
524
|
}
|
|
525
525
|
async updateUserPassword(userId, newPassword) {
|
|
526
526
|
const hashed = await bcrypt.hash(newPassword, 10);
|
|
527
|
-
await OrgUser.findOneAndUpdate({ id: userId }, {
|
|
527
|
+
await OrgUser.findOneAndUpdate({ id: userId }, { passwordHash: hashed });
|
|
528
528
|
}
|
|
529
529
|
// -------------------------------------------------------------------
|
|
530
530
|
// ADMIN TOKEN (self-issued JWT)
|
|
@@ -539,11 +539,11 @@ var AuthAdminService = class {
|
|
|
539
539
|
system: true
|
|
540
540
|
};
|
|
541
541
|
const accessToken = jwt2.sign(payload, process.env.JWT_SECRET, {
|
|
542
|
-
expiresIn: "
|
|
542
|
+
expiresIn: "1d"
|
|
543
543
|
});
|
|
544
544
|
this.token = {
|
|
545
545
|
accessToken,
|
|
546
|
-
exp: now +
|
|
546
|
+
exp: now + 84800
|
|
547
547
|
};
|
|
548
548
|
return this.token.accessToken;
|
|
549
549
|
}
|
|
@@ -568,7 +568,7 @@ var EmailService = class {
|
|
|
568
568
|
}
|
|
569
569
|
});
|
|
570
570
|
}
|
|
571
|
-
sign(payload, ttlSec = 60 * 60 * 24) {
|
|
571
|
+
sign(payload, ttlSec = 60 * 60 * 24 * 30) {
|
|
572
572
|
return jwt3.sign(payload, process.env.EMAIL_JWT_SECRET, {
|
|
573
573
|
expiresIn: ttlSec
|
|
574
574
|
});
|
|
@@ -576,11 +576,10 @@ var EmailService = class {
|
|
|
576
576
|
verify(token) {
|
|
577
577
|
return jwt3.verify(token, process.env.EMAIL_JWT_SECRET);
|
|
578
578
|
}
|
|
579
|
-
async send(to, subject, html) {
|
|
580
|
-
console.log("[EmailService] Attempting to send:", { to, subject });
|
|
579
|
+
async send(to, subject, html, from) {
|
|
581
580
|
try {
|
|
582
581
|
const info = await this.transporter.sendMail({
|
|
583
|
-
from: process.env.EMAIL_FROM,
|
|
582
|
+
from: from ? `${from} ` + process.env.EMAIL_FROM : process.env.EMAIL_FROM,
|
|
584
583
|
to,
|
|
585
584
|
subject,
|
|
586
585
|
html
|
|
@@ -605,18 +604,6 @@ var EmailService = class {
|
|
|
605
604
|
}
|
|
606
605
|
}
|
|
607
606
|
canSend(lastEmailSent) {
|
|
608
|
-
console.log(
|
|
609
|
-
process.env.EMAIL_PASSWORD,
|
|
610
|
-
"pssword",
|
|
611
|
-
process.env.EMAIL_USER,
|
|
612
|
-
"user",
|
|
613
|
-
process.env.EMAIL_SECURE,
|
|
614
|
-
"secure",
|
|
615
|
-
process.env.EMAIL_PORT,
|
|
616
|
-
"porat",
|
|
617
|
-
process.env.EMAIL_HOST,
|
|
618
|
-
"hosat"
|
|
619
|
-
);
|
|
620
607
|
const now = Date.now();
|
|
621
608
|
const windowStart = now - this.WINDOW_MINUTES * 60 * 1e3;
|
|
622
609
|
const emailsInWindow = (lastEmailSent || []).map((d) => new Date(d)).filter((d) => d.getTime() >= windowStart);
|
|
@@ -630,6 +617,386 @@ var EmailService = class {
|
|
|
630
617
|
}
|
|
631
618
|
};
|
|
632
619
|
|
|
620
|
+
// src/templates/email.templates.ts
|
|
621
|
+
var colors = {
|
|
622
|
+
background: "#0a0a0a",
|
|
623
|
+
cardBackground: "#111111",
|
|
624
|
+
cardBorder: "#1a1a1a",
|
|
625
|
+
accent: "#ffffff",
|
|
626
|
+
accentMuted: "rgba(255, 255, 255, 0.9)",
|
|
627
|
+
textPrimary: "#ffffff",
|
|
628
|
+
textSecondary: "rgba(255, 255, 255, 0.7)",
|
|
629
|
+
textMuted: "rgba(255, 255, 255, 0.5)",
|
|
630
|
+
divider: "rgba(255, 255, 255, 0.1)",
|
|
631
|
+
subtle: "#161616",
|
|
632
|
+
highlight: "rgba(255, 255, 255, 0.05)"
|
|
633
|
+
};
|
|
634
|
+
var styles = {
|
|
635
|
+
wrapper: `
|
|
636
|
+
margin: 0;
|
|
637
|
+
padding: 40px 20px;
|
|
638
|
+
background-color: ${colors.background};
|
|
639
|
+
background-image:
|
|
640
|
+
radial-gradient(ellipse at top, rgba(255,255,255,0.03) 0%, transparent 50%),
|
|
641
|
+
radial-gradient(ellipse at bottom, rgba(255,255,255,0.02) 0%, transparent 50%);
|
|
642
|
+
min-height: 100vh;
|
|
643
|
+
`,
|
|
644
|
+
container: `
|
|
645
|
+
max-width: 520px;
|
|
646
|
+
margin: 0 auto;
|
|
647
|
+
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
648
|
+
color: ${colors.textPrimary};
|
|
649
|
+
line-height: 1.7;
|
|
650
|
+
`,
|
|
651
|
+
card: `
|
|
652
|
+
background-color: ${colors.cardBackground};
|
|
653
|
+
border: 1px solid ${colors.cardBorder};
|
|
654
|
+
border-radius: 16px;
|
|
655
|
+
overflow: hidden;
|
|
656
|
+
box-shadow:
|
|
657
|
+
0 0 0 1px rgba(255,255,255,0.05),
|
|
658
|
+
0 20px 50px -20px rgba(0,0,0,0.5),
|
|
659
|
+
0 30px 60px -30px rgba(0,0,0,0.3);
|
|
660
|
+
`,
|
|
661
|
+
header: `
|
|
662
|
+
padding: 48px 40px 32px;
|
|
663
|
+
text-align: center;
|
|
664
|
+
border-bottom: 1px solid ${colors.divider};
|
|
665
|
+
`,
|
|
666
|
+
iconWrapper: `
|
|
667
|
+
width: 64px;
|
|
668
|
+
height: 64px;
|
|
669
|
+
margin: 0 auto 24px;
|
|
670
|
+
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%);
|
|
671
|
+
border: 1px solid rgba(255,255,255,0.1);
|
|
672
|
+
border-radius: 16px;
|
|
673
|
+
display: flex;
|
|
674
|
+
align-items: center;
|
|
675
|
+
justify-content: center;
|
|
676
|
+
font-size: 28px;
|
|
677
|
+
`,
|
|
678
|
+
headerTitle: `
|
|
679
|
+
color: ${colors.textPrimary};
|
|
680
|
+
margin: 0;
|
|
681
|
+
font-size: 24px;
|
|
682
|
+
font-weight: 600;
|
|
683
|
+
letter-spacing: -0.5px;
|
|
684
|
+
`,
|
|
685
|
+
headerSubtitle: `
|
|
686
|
+
color: ${colors.textMuted};
|
|
687
|
+
margin: 8px 0 0;
|
|
688
|
+
font-size: 14px;
|
|
689
|
+
font-weight: 400;
|
|
690
|
+
`,
|
|
691
|
+
body: `
|
|
692
|
+
padding: 40px;
|
|
693
|
+
`,
|
|
694
|
+
greeting: `
|
|
695
|
+
margin: 0 0 24px;
|
|
696
|
+
color: ${colors.textPrimary};
|
|
697
|
+
font-size: 18px;
|
|
698
|
+
font-weight: 500;
|
|
699
|
+
`,
|
|
700
|
+
paragraph: `
|
|
701
|
+
margin: 0 0 20px;
|
|
702
|
+
color: ${colors.textSecondary};
|
|
703
|
+
font-size: 15px;
|
|
704
|
+
line-height: 1.7;
|
|
705
|
+
`,
|
|
706
|
+
buttonWrapper: `
|
|
707
|
+
text-align: center;
|
|
708
|
+
margin: 32px 0;
|
|
709
|
+
`,
|
|
710
|
+
button: `
|
|
711
|
+
display: inline-block;
|
|
712
|
+
background-color: ${colors.accent};
|
|
713
|
+
color: #000000 !important;
|
|
714
|
+
text-decoration: none;
|
|
715
|
+
padding: 14px 36px;
|
|
716
|
+
border-radius: 8px;
|
|
717
|
+
font-weight: 600;
|
|
718
|
+
font-size: 14px;
|
|
719
|
+
letter-spacing: 0.3px;
|
|
720
|
+
transition: all 0.2s ease;
|
|
721
|
+
`,
|
|
722
|
+
secondaryButton: `
|
|
723
|
+
display: inline-block;
|
|
724
|
+
background-color: transparent;
|
|
725
|
+
color: ${colors.textPrimary} !important;
|
|
726
|
+
text-decoration: none;
|
|
727
|
+
padding: 12px 28px;
|
|
728
|
+
border-radius: 8px;
|
|
729
|
+
font-weight: 500;
|
|
730
|
+
font-size: 14px;
|
|
731
|
+
border: 1px solid ${colors.divider};
|
|
732
|
+
`,
|
|
733
|
+
infoCard: `
|
|
734
|
+
background-color: ${colors.subtle};
|
|
735
|
+
border: 1px solid ${colors.divider};
|
|
736
|
+
border-radius: 12px;
|
|
737
|
+
padding: 20px 24px;
|
|
738
|
+
margin: 28px 0;
|
|
739
|
+
`,
|
|
740
|
+
infoCardTitle: `
|
|
741
|
+
margin: 0 0 12px;
|
|
742
|
+
color: ${colors.textPrimary};
|
|
743
|
+
font-size: 13px;
|
|
744
|
+
font-weight: 600;
|
|
745
|
+
text-transform: uppercase;
|
|
746
|
+
letter-spacing: 0.5px;
|
|
747
|
+
`,
|
|
748
|
+
infoCardText: `
|
|
749
|
+
margin: 0;
|
|
750
|
+
color: ${colors.textSecondary};
|
|
751
|
+
font-size: 14px;
|
|
752
|
+
line-height: 1.6;
|
|
753
|
+
`,
|
|
754
|
+
warningCard: `
|
|
755
|
+
background: linear-gradient(135deg, rgba(255,180,0,0.1) 0%, rgba(255,140,0,0.05) 100%);
|
|
756
|
+
border: 1px solid rgba(255,180,0,0.2);
|
|
757
|
+
border-radius: 12px;
|
|
758
|
+
padding: 20px 24px;
|
|
759
|
+
margin: 28px 0;
|
|
760
|
+
`,
|
|
761
|
+
warningCardTitle: `
|
|
762
|
+
margin: 0 0 12px;
|
|
763
|
+
color: #ffc107;
|
|
764
|
+
font-size: 13px;
|
|
765
|
+
font-weight: 600;
|
|
766
|
+
text-transform: uppercase;
|
|
767
|
+
letter-spacing: 0.5px;
|
|
768
|
+
`,
|
|
769
|
+
warningCardText: `
|
|
770
|
+
margin: 0;
|
|
771
|
+
color: rgba(255,255,255,0.7);
|
|
772
|
+
font-size: 14px;
|
|
773
|
+
line-height: 1.6;
|
|
774
|
+
`,
|
|
775
|
+
successCard: `
|
|
776
|
+
background: linear-gradient(135deg, rgba(0,255,150,0.1) 0%, rgba(0,200,100,0.05) 100%);
|
|
777
|
+
border: 1px solid rgba(0,255,150,0.2);
|
|
778
|
+
border-radius: 12px;
|
|
779
|
+
padding: 20px 24px;
|
|
780
|
+
margin: 28px 0;
|
|
781
|
+
`,
|
|
782
|
+
successCardTitle: `
|
|
783
|
+
margin: 0 0 12px;
|
|
784
|
+
color: #00ff96;
|
|
785
|
+
font-size: 13px;
|
|
786
|
+
font-weight: 600;
|
|
787
|
+
text-transform: uppercase;
|
|
788
|
+
letter-spacing: 0.5px;
|
|
789
|
+
`,
|
|
790
|
+
linkSection: `
|
|
791
|
+
margin: 32px 0;
|
|
792
|
+
padding: 20px;
|
|
793
|
+
background-color: ${colors.subtle};
|
|
794
|
+
border-radius: 8px;
|
|
795
|
+
border: 1px solid ${colors.divider};
|
|
796
|
+
`,
|
|
797
|
+
linkLabel: `
|
|
798
|
+
margin: 0 0 8px;
|
|
799
|
+
color: ${colors.textMuted};
|
|
800
|
+
font-size: 12px;
|
|
801
|
+
text-transform: uppercase;
|
|
802
|
+
letter-spacing: 0.5px;
|
|
803
|
+
`,
|
|
804
|
+
linkText: `
|
|
805
|
+
word-break: break-all;
|
|
806
|
+
color: ${colors.textSecondary};
|
|
807
|
+
font-size: 13px;
|
|
808
|
+
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
|
|
809
|
+
margin: 0;
|
|
810
|
+
`,
|
|
811
|
+
divider: `
|
|
812
|
+
border: none;
|
|
813
|
+
border-top: 1px solid ${colors.divider};
|
|
814
|
+
margin: 32px 0;
|
|
815
|
+
`,
|
|
816
|
+
footer: `
|
|
817
|
+
padding: 24px 40px 32px;
|
|
818
|
+
text-align: center;
|
|
819
|
+
border-top: 1px solid ${colors.divider};
|
|
820
|
+
`,
|
|
821
|
+
footerText: `
|
|
822
|
+
margin: 0;
|
|
823
|
+
color: ${colors.textMuted};
|
|
824
|
+
font-size: 12px;
|
|
825
|
+
line-height: 1.8;
|
|
826
|
+
`,
|
|
827
|
+
footerLink: `
|
|
828
|
+
color: ${colors.textSecondary};
|
|
829
|
+
text-decoration: none;
|
|
830
|
+
`,
|
|
831
|
+
badge: `
|
|
832
|
+
display: inline-block;
|
|
833
|
+
background-color: rgba(255,255,255,0.1);
|
|
834
|
+
color: ${colors.textSecondary};
|
|
835
|
+
padding: 4px 12px;
|
|
836
|
+
border-radius: 20px;
|
|
837
|
+
font-size: 12px;
|
|
838
|
+
font-weight: 500;
|
|
839
|
+
letter-spacing: 0.3px;
|
|
840
|
+
`,
|
|
841
|
+
listItem: `
|
|
842
|
+
color: ${colors.textSecondary};
|
|
843
|
+
font-size: 14px;
|
|
844
|
+
margin: 8px 0;
|
|
845
|
+
padding-left: 8px;
|
|
846
|
+
`,
|
|
847
|
+
metaRow: `
|
|
848
|
+
display: flex;
|
|
849
|
+
justify-content: space-between;
|
|
850
|
+
padding: 12px 0;
|
|
851
|
+
border-bottom: 1px solid ${colors.divider};
|
|
852
|
+
`,
|
|
853
|
+
metaLabel: `
|
|
854
|
+
color: ${colors.textMuted};
|
|
855
|
+
font-size: 13px;
|
|
856
|
+
`,
|
|
857
|
+
metaValue: `
|
|
858
|
+
color: ${colors.textPrimary};
|
|
859
|
+
font-size: 13px;
|
|
860
|
+
font-weight: 500;
|
|
861
|
+
`
|
|
862
|
+
};
|
|
863
|
+
function buildVerificationEmailTemplate(data) {
|
|
864
|
+
const { firstName, verificationUrl, expiresIn } = data;
|
|
865
|
+
return `
|
|
866
|
+
<!DOCTYPE html>
|
|
867
|
+
<html lang="en">
|
|
868
|
+
<head>
|
|
869
|
+
<meta charset="UTF-8">
|
|
870
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
871
|
+
<meta name="color-scheme" content="dark">
|
|
872
|
+
<meta name="supported-color-schemes" content="dark">
|
|
873
|
+
<title>Verify Your Email</title>
|
|
874
|
+
<!--[if mso]>
|
|
875
|
+
<style type="text/css">
|
|
876
|
+
body, table, td {font-family: Arial, Helvetica, sans-serif !important;}
|
|
877
|
+
</style>
|
|
878
|
+
<![endif]-->
|
|
879
|
+
</head>
|
|
880
|
+
<body style="${styles.wrapper}">
|
|
881
|
+
<div style="${styles.container}">
|
|
882
|
+
<div style="${styles.card}">
|
|
883
|
+
<!-- Header -->
|
|
884
|
+
<div style="${styles.header}">
|
|
885
|
+
<div style="${styles.iconWrapper}">
|
|
886
|
+
\u2709\uFE0F
|
|
887
|
+
</div>
|
|
888
|
+
<h1 style="${styles.headerTitle}">Verify your email</h1>
|
|
889
|
+
<p style="${styles.headerSubtitle}">One quick step to get started</p>
|
|
890
|
+
</div>
|
|
891
|
+
|
|
892
|
+
<!-- Body -->
|
|
893
|
+
<div style="${styles.body}">
|
|
894
|
+
<p style="${styles.greeting}">Hi ${firstName},</p>
|
|
895
|
+
|
|
896
|
+
<p style="${styles.paragraph}">
|
|
897
|
+
Thanks for signing up. To complete your registration and unlock all features,
|
|
898
|
+
please verify your email address by clicking the button below.
|
|
899
|
+
</p>
|
|
900
|
+
|
|
901
|
+
<div style="${styles.buttonWrapper}">
|
|
902
|
+
<a href="${verificationUrl}" style="${styles.button}" target="_blank">
|
|
903
|
+
Verify Email Address
|
|
904
|
+
</a>
|
|
905
|
+
</div>
|
|
906
|
+
|
|
907
|
+
<div style="${styles.infoCard}">
|
|
908
|
+
<p style="${styles.infoCardTitle}">\u23F1 Time Sensitive</p>
|
|
909
|
+
<p style="${styles.infoCardText}">
|
|
910
|
+
This verification link will expire in <strong>${expiresIn}</strong>.
|
|
911
|
+
If you didn't create an account, you can safely ignore this email.
|
|
912
|
+
</p>
|
|
913
|
+
</div>
|
|
914
|
+
</div>
|
|
915
|
+
|
|
916
|
+
<!-- Footer -->
|
|
917
|
+
<div style="${styles.footer}">
|
|
918
|
+
<p style="${styles.footerText}">
|
|
919
|
+
This is an automated message \u2014 please do not reply.<br>
|
|
920
|
+
\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} All rights reserved.
|
|
921
|
+
</p>
|
|
922
|
+
</div>
|
|
923
|
+
</div>
|
|
924
|
+
</div>
|
|
925
|
+
</body>
|
|
926
|
+
</html>
|
|
927
|
+
`;
|
|
928
|
+
}
|
|
929
|
+
function buildResetPasswordEmailTemplate(data) {
|
|
930
|
+
const { firstName, resetUrl, expiresIn } = data;
|
|
931
|
+
return `
|
|
932
|
+
<!DOCTYPE html>
|
|
933
|
+
<html lang="en">
|
|
934
|
+
<head>
|
|
935
|
+
<meta charset="UTF-8">
|
|
936
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
937
|
+
<meta name="color-scheme" content="dark">
|
|
938
|
+
<meta name="supported-color-schemes" content="dark">
|
|
939
|
+
<title>Reset Your Password</title>
|
|
940
|
+
</head>
|
|
941
|
+
<body style="${styles.wrapper}">
|
|
942
|
+
<div style="${styles.container}">
|
|
943
|
+
<div style="${styles.card}">
|
|
944
|
+
<!-- Header -->
|
|
945
|
+
<div style="${styles.header}">
|
|
946
|
+
<div style="${styles.iconWrapper}">
|
|
947
|
+
\u{1F510}
|
|
948
|
+
</div>
|
|
949
|
+
<h1 style="${styles.headerTitle}">Reset your password</h1>
|
|
950
|
+
<p style="${styles.headerSubtitle}">We received a reset request</p>
|
|
951
|
+
</div>
|
|
952
|
+
|
|
953
|
+
<!-- Body -->
|
|
954
|
+
<div style="${styles.body}">
|
|
955
|
+
<p style="${styles.greeting}">Hi ${firstName},</p>
|
|
956
|
+
|
|
957
|
+
<p style="${styles.paragraph}">
|
|
958
|
+
We received a request to reset the password for your account.
|
|
959
|
+
Click the button below to create a new password.
|
|
960
|
+
</p>
|
|
961
|
+
|
|
962
|
+
<div style="${styles.buttonWrapper}">
|
|
963
|
+
<a href="${resetUrl}" style="${styles.button}" target="_blank">
|
|
964
|
+
Reset Password
|
|
965
|
+
</a>
|
|
966
|
+
</div>
|
|
967
|
+
|
|
968
|
+
<div style="${styles.warningCard}">
|
|
969
|
+
<p style="${styles.warningCardTitle}">\u26A0\uFE0F Security Notice</p>
|
|
970
|
+
<p style="${styles.warningCardText}">
|
|
971
|
+
\u2022 This link expires in <strong>${expiresIn}</strong><br>
|
|
972
|
+
\u2022 This link can only be used once<br>
|
|
973
|
+
\u2022 If you didn't request this, ignore this email
|
|
974
|
+
</p>
|
|
975
|
+
</div>
|
|
976
|
+
|
|
977
|
+
<hr style="${styles.divider}" />
|
|
978
|
+
|
|
979
|
+
<p style="${styles.paragraph}; font-size: 13px; color: ${colors.textMuted};">
|
|
980
|
+
<strong>Didn't request this?</strong><br>
|
|
981
|
+
Your password remains unchanged. If you're concerned about your account
|
|
982
|
+
security, please contact our support team immediately.
|
|
983
|
+
</p>
|
|
984
|
+
</div>
|
|
985
|
+
|
|
986
|
+
<!-- Footer -->
|
|
987
|
+
<div style="${styles.footer}">
|
|
988
|
+
<p style="${styles.footerText}">
|
|
989
|
+
This is an automated message \u2014 please do not reply.<br>
|
|
990
|
+
\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} All rights reserved.
|
|
991
|
+
</p>
|
|
992
|
+
</div>
|
|
993
|
+
</div>
|
|
994
|
+
</div>
|
|
995
|
+
</body>
|
|
996
|
+
</html>
|
|
997
|
+
`;
|
|
998
|
+
}
|
|
999
|
+
|
|
633
1000
|
// src/express/auth.routes.ts
|
|
634
1001
|
function createAuthRouter(options = {}) {
|
|
635
1002
|
const googleClientId = process.env.GOOGLE_CLIENT_ID;
|
|
@@ -651,7 +1018,7 @@ function createAuthRouter(options = {}) {
|
|
|
651
1018
|
// default: secure in prod
|
|
652
1019
|
domain: options.cookie?.domain ?? void 0,
|
|
653
1020
|
path: options.cookie?.path ?? "/",
|
|
654
|
-
maxAgeMs: options.cookie?.maxAgeMs ?? 24 * 60 * 60 * 1e3
|
|
1021
|
+
maxAgeMs: options.cookie?.maxAgeMs ?? 30 * 24 * 60 * 60 * 1e3
|
|
655
1022
|
};
|
|
656
1023
|
r.use(express.json());
|
|
657
1024
|
r.use(express.urlencoded({ extended: true }));
|
|
@@ -708,6 +1075,7 @@ function createAuthRouter(options = {}) {
|
|
|
708
1075
|
projectId,
|
|
709
1076
|
metadata
|
|
710
1077
|
} = req.body || {};
|
|
1078
|
+
const COMPANY_NAME = process.env.COMPANY_NAME;
|
|
711
1079
|
try {
|
|
712
1080
|
const kcUser = await authAdmin.createUserInRealm({
|
|
713
1081
|
username: emailAddress,
|
|
@@ -736,10 +1104,21 @@ function createAuthRouter(options = {}) {
|
|
|
736
1104
|
emailService: email,
|
|
737
1105
|
user,
|
|
738
1106
|
subject: "Verify your email",
|
|
739
|
-
html: buildVerificationTemplate(
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
)
|
|
1107
|
+
// html: buildVerificationTemplate(
|
|
1108
|
+
// email.sign({ userId: kcUser.id, email: kcUser.email }),
|
|
1109
|
+
// options,
|
|
1110
|
+
// ),
|
|
1111
|
+
html: buildVerificationEmailTemplate({
|
|
1112
|
+
firstName: user.firstName,
|
|
1113
|
+
verificationUrl: `${getFrontendBaseUrl(options)}/verify-email?token=${email.sign(
|
|
1114
|
+
{
|
|
1115
|
+
userId: user.id,
|
|
1116
|
+
email: user.email
|
|
1117
|
+
}
|
|
1118
|
+
)}`,
|
|
1119
|
+
expiresIn: "1 hour"
|
|
1120
|
+
}),
|
|
1121
|
+
from: COMPANY_NAME
|
|
743
1122
|
});
|
|
744
1123
|
if (emailResult.rateLimited) {
|
|
745
1124
|
return res.status(429).json({
|
|
@@ -804,6 +1183,7 @@ function createAuthRouter(options = {}) {
|
|
|
804
1183
|
"/resend-verification-email",
|
|
805
1184
|
validateResendEmail,
|
|
806
1185
|
async (req, res) => {
|
|
1186
|
+
const COMPANY_NAME = process.env.COMPANY_NAME;
|
|
807
1187
|
const user = await OrgUser.findOne({ email: req.body.email });
|
|
808
1188
|
if (!user)
|
|
809
1189
|
return res.status(404).json({ ok: false, error: "User not found" });
|
|
@@ -819,7 +1199,18 @@ function createAuthRouter(options = {}) {
|
|
|
819
1199
|
emailService: email,
|
|
820
1200
|
user,
|
|
821
1201
|
subject: "Verify your email",
|
|
822
|
-
html: buildVerificationTemplate(token, options)
|
|
1202
|
+
// html: buildVerificationTemplate(token, options),
|
|
1203
|
+
html: buildVerificationEmailTemplate({
|
|
1204
|
+
firstName: user.firstName,
|
|
1205
|
+
verificationUrl: `${getFrontendBaseUrl(options)}/verify-email?token=${email.sign(
|
|
1206
|
+
{
|
|
1207
|
+
userId: user.id,
|
|
1208
|
+
email: user.email
|
|
1209
|
+
}
|
|
1210
|
+
)}`,
|
|
1211
|
+
expiresIn: "1 hour"
|
|
1212
|
+
}),
|
|
1213
|
+
from: COMPANY_NAME
|
|
823
1214
|
});
|
|
824
1215
|
if (resendResult.rateLimited) {
|
|
825
1216
|
return res.status(429).json({
|
|
@@ -832,6 +1223,7 @@ function createAuthRouter(options = {}) {
|
|
|
832
1223
|
}
|
|
833
1224
|
);
|
|
834
1225
|
r.post("/forgot-password", validateResendEmail, async (req, res) => {
|
|
1226
|
+
const COMPANY_NAME = process.env.COMPANY_NAME;
|
|
835
1227
|
const user = await OrgUser.findOne({ email: req.body.email });
|
|
836
1228
|
if (!user)
|
|
837
1229
|
return res.status(404).json({ ok: false, error: "User not found" });
|
|
@@ -848,7 +1240,18 @@ function createAuthRouter(options = {}) {
|
|
|
848
1240
|
emailService: email,
|
|
849
1241
|
user,
|
|
850
1242
|
subject: "Reset password",
|
|
851
|
-
html: buildResetTemplate(resetToken, options)
|
|
1243
|
+
// html: buildResetTemplate(resetToken, options),
|
|
1244
|
+
html: buildResetPasswordEmailTemplate({
|
|
1245
|
+
firstName: user.firstName,
|
|
1246
|
+
resetUrl: `${getFrontendBaseUrl(options)}/reset-password?token=${email.sign(
|
|
1247
|
+
{
|
|
1248
|
+
userId: user.id,
|
|
1249
|
+
email: user.email
|
|
1250
|
+
}
|
|
1251
|
+
)}`,
|
|
1252
|
+
expiresIn: "1 hour"
|
|
1253
|
+
}),
|
|
1254
|
+
from: COMPANY_NAME
|
|
852
1255
|
});
|
|
853
1256
|
if (resetResult.rateLimited) {
|
|
854
1257
|
return res.status(429).json({
|
|
@@ -861,9 +1264,16 @@ function createAuthRouter(options = {}) {
|
|
|
861
1264
|
});
|
|
862
1265
|
r.post("/reset-password", validateResetPassword, async (req, res) => {
|
|
863
1266
|
const { token, newPassword } = req.body || {};
|
|
1267
|
+
if (!token || !newPassword) {
|
|
1268
|
+
return res.status(400).json({
|
|
1269
|
+
ok: false,
|
|
1270
|
+
error: "Token and new password are required",
|
|
1271
|
+
code: "MISSING_FIELDS"
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
864
1274
|
try {
|
|
865
1275
|
const payload = email.verify(token);
|
|
866
|
-
const user = await OrgUser.findOne({
|
|
1276
|
+
const user = await OrgUser.findOne({ id: payload.userId });
|
|
867
1277
|
if (!user) {
|
|
868
1278
|
return res.status(404).json({ ok: false, error: "User not found" });
|
|
869
1279
|
}
|
|
@@ -1231,8 +1641,6 @@ function setAuthCookies(res, tokens, cookie) {
|
|
|
1231
1641
|
if (cookie.domain) {
|
|
1232
1642
|
base.domain = cookie.domain;
|
|
1233
1643
|
}
|
|
1234
|
-
console.log(cookie, "cookie");
|
|
1235
|
-
console.log(base, "base");
|
|
1236
1644
|
if (tokens?.access_token) {
|
|
1237
1645
|
res.cookie("access_token", tokens.access_token, base);
|
|
1238
1646
|
}
|
|
@@ -1256,12 +1664,6 @@ function respondWithKeycloakError(res, err, fallback, status = 400) {
|
|
|
1256
1664
|
const description = err?.response?.data?.error_description || err?.response?.data?.errorMessage || err?.message || fallback;
|
|
1257
1665
|
return res.status(status).json({ ok: false, error: description });
|
|
1258
1666
|
}
|
|
1259
|
-
function buildVerificationTemplate(token, options) {
|
|
1260
|
-
return `<a href="${getFrontendBaseUrl(options)}/auth/verify-email?token=${token}">Verify</a>`;
|
|
1261
|
-
}
|
|
1262
|
-
function buildResetTemplate(token, options) {
|
|
1263
|
-
return `<a href="${getFrontendBaseUrl(options)}/auth/reset-password?token=${token}">Reset</a>`;
|
|
1264
|
-
}
|
|
1265
1667
|
function getFrontendBaseUrl(options) {
|
|
1266
1668
|
if (options.frontendBaseUrl)
|
|
1267
1669
|
return options.frontendBaseUrl.replace(/\/$/, "");
|
|
@@ -1273,13 +1675,14 @@ async function sendRateLimitedEmail({
|
|
|
1273
1675
|
emailService,
|
|
1274
1676
|
user,
|
|
1275
1677
|
subject,
|
|
1276
|
-
html
|
|
1678
|
+
html,
|
|
1679
|
+
from
|
|
1277
1680
|
}) {
|
|
1278
1681
|
const can = emailService.canSend(user?.lastEmailSent || []);
|
|
1279
1682
|
if (!can.ok) {
|
|
1280
1683
|
return { rateLimited: true, waitMs: can.waitMs };
|
|
1281
1684
|
}
|
|
1282
|
-
await emailService.send(user.email, subject, html);
|
|
1685
|
+
await emailService.send(user.email, subject, html, from);
|
|
1283
1686
|
user.lastEmailSent = [...user.lastEmailSent || [], /* @__PURE__ */ new Date()];
|
|
1284
1687
|
await user.save();
|
|
1285
1688
|
return { rateLimited: false };
|
|
@@ -1300,7 +1703,7 @@ function generateTokens(user) {
|
|
|
1300
1703
|
type: "user"
|
|
1301
1704
|
};
|
|
1302
1705
|
const accessToken = jwt4.sign(accessPayload, process.env.JWT_SECRET, {
|
|
1303
|
-
expiresIn: "
|
|
1706
|
+
expiresIn: "1d"
|
|
1304
1707
|
});
|
|
1305
1708
|
const refreshToken = jwt4.sign(
|
|
1306
1709
|
{ sub: user._id.toString() },
|
|
@@ -1336,13 +1739,61 @@ function createDashboardRouter(options) {
|
|
|
1336
1739
|
}
|
|
1337
1740
|
|
|
1338
1741
|
// src/express/email.routes.ts
|
|
1339
|
-
import { Router as Router3 } from "express";
|
|
1742
|
+
import express3, { Router as Router3 } from "express";
|
|
1340
1743
|
function createEmailRouter(options) {
|
|
1341
1744
|
const r = Router3();
|
|
1745
|
+
const emailService = new EmailService();
|
|
1746
|
+
r.use(express3.json());
|
|
1747
|
+
r.use(express3.urlencoded({ extended: true }));
|
|
1342
1748
|
r.get(
|
|
1343
1749
|
"/verify",
|
|
1344
1750
|
(req, res) => res.json({ ok: true, token: req.query.token })
|
|
1345
1751
|
);
|
|
1752
|
+
r.post("/send", async (req, res) => {
|
|
1753
|
+
try {
|
|
1754
|
+
const { userId, to, subject, html, from } = req.body ?? {};
|
|
1755
|
+
if (!to || !subject || !html) {
|
|
1756
|
+
return res.status(400).json({
|
|
1757
|
+
ok: false,
|
|
1758
|
+
error: "BAD_REQUEST",
|
|
1759
|
+
message: "`to`, `subject`, and `html` are required."
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
if (userId) {
|
|
1763
|
+
const user = await OrgUser.findOne({ id: userId }).lean();
|
|
1764
|
+
if (!user) {
|
|
1765
|
+
return res.status(404).json({
|
|
1766
|
+
ok: false,
|
|
1767
|
+
error: "NOT_FOUND",
|
|
1768
|
+
message: "User not found."
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
const can = emailService.canSend(user?.lastEmailSent || []);
|
|
1772
|
+
if (!can.ok) {
|
|
1773
|
+
return res.status(429).json({
|
|
1774
|
+
ok: false,
|
|
1775
|
+
error: can.reason,
|
|
1776
|
+
waitMs: can.waitMs,
|
|
1777
|
+
message: "Too many emails sent recently. Please retry later."
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
await emailService.send(to, subject, html, from);
|
|
1782
|
+
if (userId) {
|
|
1783
|
+
await OrgUser.updateOne(
|
|
1784
|
+
{ id: userId },
|
|
1785
|
+
{ $push: { lastEmailSent: /* @__PURE__ */ new Date() } }
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
return res.json({ ok: true });
|
|
1789
|
+
} catch (err) {
|
|
1790
|
+
return res.status(500).json({
|
|
1791
|
+
ok: false,
|
|
1792
|
+
error: "INTERNAL",
|
|
1793
|
+
message: err?.message ?? "Error"
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
});
|
|
1346
1797
|
return r;
|
|
1347
1798
|
}
|
|
1348
1799
|
|
|
@@ -1449,7 +1900,7 @@ function createProjectsRouter(options) {
|
|
|
1449
1900
|
// src/express/admin/admin.routes.ts
|
|
1450
1901
|
import bcrypt3 from "bcryptjs";
|
|
1451
1902
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
1452
|
-
import
|
|
1903
|
+
import express4, { Router as Router5 } from "express";
|
|
1453
1904
|
|
|
1454
1905
|
// src/middlewares/requireRole.ts
|
|
1455
1906
|
function requireRole(...roles) {
|
|
@@ -1512,8 +1963,8 @@ function resolveProjectId(req) {
|
|
|
1512
1963
|
}
|
|
1513
1964
|
function createAdminRouter(_options = {}) {
|
|
1514
1965
|
const r = Router5();
|
|
1515
|
-
r.use(
|
|
1516
|
-
r.use(
|
|
1966
|
+
r.use(express4.json());
|
|
1967
|
+
r.use(express4.urlencoded({ extended: true }));
|
|
1517
1968
|
const adminGuards = [requireAuth(), requireRole("platform_admin")];
|
|
1518
1969
|
r.post(
|
|
1519
1970
|
"/users",
|