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/express/index.cjs
CHANGED
|
@@ -61,8 +61,8 @@ function loadConfig() {
|
|
|
61
61
|
cookies: {
|
|
62
62
|
domain: process.env.COOKIE_DOMAIN,
|
|
63
63
|
secure: (process.env.COOKIE_SECURE || "true") === "true",
|
|
64
|
-
accessTtlMs: 24 * 60 * 60 * 1e3,
|
|
65
|
-
refreshTtlMs:
|
|
64
|
+
accessTtlMs: 7 * 24 * 60 * 60 * 1e3,
|
|
65
|
+
refreshTtlMs: 30 * 24 * 60 * 60 * 1e3
|
|
66
66
|
},
|
|
67
67
|
oidc: {
|
|
68
68
|
jwtSecret: process.env.JWT_SECRET
|
|
@@ -562,7 +562,7 @@ var AuthAdminService = class {
|
|
|
562
562
|
}
|
|
563
563
|
async updateUserPassword(userId, newPassword) {
|
|
564
564
|
const hashed = await import_bcrypt.default.hash(newPassword, 10);
|
|
565
|
-
await OrgUser.findOneAndUpdate({ id: userId }, {
|
|
565
|
+
await OrgUser.findOneAndUpdate({ id: userId }, { passwordHash: hashed });
|
|
566
566
|
}
|
|
567
567
|
// -------------------------------------------------------------------
|
|
568
568
|
// ADMIN TOKEN (self-issued JWT)
|
|
@@ -577,11 +577,11 @@ var AuthAdminService = class {
|
|
|
577
577
|
system: true
|
|
578
578
|
};
|
|
579
579
|
const accessToken = import_jsonwebtoken2.default.sign(payload, process.env.JWT_SECRET, {
|
|
580
|
-
expiresIn: "
|
|
580
|
+
expiresIn: "1d"
|
|
581
581
|
});
|
|
582
582
|
this.token = {
|
|
583
583
|
accessToken,
|
|
584
|
-
exp: now +
|
|
584
|
+
exp: now + 84800
|
|
585
585
|
};
|
|
586
586
|
return this.token.accessToken;
|
|
587
587
|
}
|
|
@@ -606,7 +606,7 @@ var EmailService = class {
|
|
|
606
606
|
}
|
|
607
607
|
});
|
|
608
608
|
}
|
|
609
|
-
sign(payload, ttlSec = 60 * 60 * 24) {
|
|
609
|
+
sign(payload, ttlSec = 60 * 60 * 24 * 30) {
|
|
610
610
|
return import_jsonwebtoken3.default.sign(payload, process.env.EMAIL_JWT_SECRET, {
|
|
611
611
|
expiresIn: ttlSec
|
|
612
612
|
});
|
|
@@ -614,11 +614,10 @@ var EmailService = class {
|
|
|
614
614
|
verify(token) {
|
|
615
615
|
return import_jsonwebtoken3.default.verify(token, process.env.EMAIL_JWT_SECRET);
|
|
616
616
|
}
|
|
617
|
-
async send(to, subject, html) {
|
|
618
|
-
console.log("[EmailService] Attempting to send:", { to, subject });
|
|
617
|
+
async send(to, subject, html, from) {
|
|
619
618
|
try {
|
|
620
619
|
const info = await this.transporter.sendMail({
|
|
621
|
-
from: process.env.EMAIL_FROM,
|
|
620
|
+
from: from ? `${from} ` + process.env.EMAIL_FROM : process.env.EMAIL_FROM,
|
|
622
621
|
to,
|
|
623
622
|
subject,
|
|
624
623
|
html
|
|
@@ -643,18 +642,6 @@ var EmailService = class {
|
|
|
643
642
|
}
|
|
644
643
|
}
|
|
645
644
|
canSend(lastEmailSent) {
|
|
646
|
-
console.log(
|
|
647
|
-
process.env.EMAIL_PASSWORD,
|
|
648
|
-
"pssword",
|
|
649
|
-
process.env.EMAIL_USER,
|
|
650
|
-
"user",
|
|
651
|
-
process.env.EMAIL_SECURE,
|
|
652
|
-
"secure",
|
|
653
|
-
process.env.EMAIL_PORT,
|
|
654
|
-
"porat",
|
|
655
|
-
process.env.EMAIL_HOST,
|
|
656
|
-
"hosat"
|
|
657
|
-
);
|
|
658
645
|
const now = Date.now();
|
|
659
646
|
const windowStart = now - this.WINDOW_MINUTES * 60 * 1e3;
|
|
660
647
|
const emailsInWindow = (lastEmailSent || []).map((d) => new Date(d)).filter((d) => d.getTime() >= windowStart);
|
|
@@ -668,6 +655,386 @@ var EmailService = class {
|
|
|
668
655
|
}
|
|
669
656
|
};
|
|
670
657
|
|
|
658
|
+
// src/templates/email.templates.ts
|
|
659
|
+
var colors = {
|
|
660
|
+
background: "#0a0a0a",
|
|
661
|
+
cardBackground: "#111111",
|
|
662
|
+
cardBorder: "#1a1a1a",
|
|
663
|
+
accent: "#ffffff",
|
|
664
|
+
accentMuted: "rgba(255, 255, 255, 0.9)",
|
|
665
|
+
textPrimary: "#ffffff",
|
|
666
|
+
textSecondary: "rgba(255, 255, 255, 0.7)",
|
|
667
|
+
textMuted: "rgba(255, 255, 255, 0.5)",
|
|
668
|
+
divider: "rgba(255, 255, 255, 0.1)",
|
|
669
|
+
subtle: "#161616",
|
|
670
|
+
highlight: "rgba(255, 255, 255, 0.05)"
|
|
671
|
+
};
|
|
672
|
+
var styles = {
|
|
673
|
+
wrapper: `
|
|
674
|
+
margin: 0;
|
|
675
|
+
padding: 40px 20px;
|
|
676
|
+
background-color: ${colors.background};
|
|
677
|
+
background-image:
|
|
678
|
+
radial-gradient(ellipse at top, rgba(255,255,255,0.03) 0%, transparent 50%),
|
|
679
|
+
radial-gradient(ellipse at bottom, rgba(255,255,255,0.02) 0%, transparent 50%);
|
|
680
|
+
min-height: 100vh;
|
|
681
|
+
`,
|
|
682
|
+
container: `
|
|
683
|
+
max-width: 520px;
|
|
684
|
+
margin: 0 auto;
|
|
685
|
+
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
686
|
+
color: ${colors.textPrimary};
|
|
687
|
+
line-height: 1.7;
|
|
688
|
+
`,
|
|
689
|
+
card: `
|
|
690
|
+
background-color: ${colors.cardBackground};
|
|
691
|
+
border: 1px solid ${colors.cardBorder};
|
|
692
|
+
border-radius: 16px;
|
|
693
|
+
overflow: hidden;
|
|
694
|
+
box-shadow:
|
|
695
|
+
0 0 0 1px rgba(255,255,255,0.05),
|
|
696
|
+
0 20px 50px -20px rgba(0,0,0,0.5),
|
|
697
|
+
0 30px 60px -30px rgba(0,0,0,0.3);
|
|
698
|
+
`,
|
|
699
|
+
header: `
|
|
700
|
+
padding: 48px 40px 32px;
|
|
701
|
+
text-align: center;
|
|
702
|
+
border-bottom: 1px solid ${colors.divider};
|
|
703
|
+
`,
|
|
704
|
+
iconWrapper: `
|
|
705
|
+
width: 64px;
|
|
706
|
+
height: 64px;
|
|
707
|
+
margin: 0 auto 24px;
|
|
708
|
+
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%);
|
|
709
|
+
border: 1px solid rgba(255,255,255,0.1);
|
|
710
|
+
border-radius: 16px;
|
|
711
|
+
display: flex;
|
|
712
|
+
align-items: center;
|
|
713
|
+
justify-content: center;
|
|
714
|
+
font-size: 28px;
|
|
715
|
+
`,
|
|
716
|
+
headerTitle: `
|
|
717
|
+
color: ${colors.textPrimary};
|
|
718
|
+
margin: 0;
|
|
719
|
+
font-size: 24px;
|
|
720
|
+
font-weight: 600;
|
|
721
|
+
letter-spacing: -0.5px;
|
|
722
|
+
`,
|
|
723
|
+
headerSubtitle: `
|
|
724
|
+
color: ${colors.textMuted};
|
|
725
|
+
margin: 8px 0 0;
|
|
726
|
+
font-size: 14px;
|
|
727
|
+
font-weight: 400;
|
|
728
|
+
`,
|
|
729
|
+
body: `
|
|
730
|
+
padding: 40px;
|
|
731
|
+
`,
|
|
732
|
+
greeting: `
|
|
733
|
+
margin: 0 0 24px;
|
|
734
|
+
color: ${colors.textPrimary};
|
|
735
|
+
font-size: 18px;
|
|
736
|
+
font-weight: 500;
|
|
737
|
+
`,
|
|
738
|
+
paragraph: `
|
|
739
|
+
margin: 0 0 20px;
|
|
740
|
+
color: ${colors.textSecondary};
|
|
741
|
+
font-size: 15px;
|
|
742
|
+
line-height: 1.7;
|
|
743
|
+
`,
|
|
744
|
+
buttonWrapper: `
|
|
745
|
+
text-align: center;
|
|
746
|
+
margin: 32px 0;
|
|
747
|
+
`,
|
|
748
|
+
button: `
|
|
749
|
+
display: inline-block;
|
|
750
|
+
background-color: ${colors.accent};
|
|
751
|
+
color: #000000 !important;
|
|
752
|
+
text-decoration: none;
|
|
753
|
+
padding: 14px 36px;
|
|
754
|
+
border-radius: 8px;
|
|
755
|
+
font-weight: 600;
|
|
756
|
+
font-size: 14px;
|
|
757
|
+
letter-spacing: 0.3px;
|
|
758
|
+
transition: all 0.2s ease;
|
|
759
|
+
`,
|
|
760
|
+
secondaryButton: `
|
|
761
|
+
display: inline-block;
|
|
762
|
+
background-color: transparent;
|
|
763
|
+
color: ${colors.textPrimary} !important;
|
|
764
|
+
text-decoration: none;
|
|
765
|
+
padding: 12px 28px;
|
|
766
|
+
border-radius: 8px;
|
|
767
|
+
font-weight: 500;
|
|
768
|
+
font-size: 14px;
|
|
769
|
+
border: 1px solid ${colors.divider};
|
|
770
|
+
`,
|
|
771
|
+
infoCard: `
|
|
772
|
+
background-color: ${colors.subtle};
|
|
773
|
+
border: 1px solid ${colors.divider};
|
|
774
|
+
border-radius: 12px;
|
|
775
|
+
padding: 20px 24px;
|
|
776
|
+
margin: 28px 0;
|
|
777
|
+
`,
|
|
778
|
+
infoCardTitle: `
|
|
779
|
+
margin: 0 0 12px;
|
|
780
|
+
color: ${colors.textPrimary};
|
|
781
|
+
font-size: 13px;
|
|
782
|
+
font-weight: 600;
|
|
783
|
+
text-transform: uppercase;
|
|
784
|
+
letter-spacing: 0.5px;
|
|
785
|
+
`,
|
|
786
|
+
infoCardText: `
|
|
787
|
+
margin: 0;
|
|
788
|
+
color: ${colors.textSecondary};
|
|
789
|
+
font-size: 14px;
|
|
790
|
+
line-height: 1.6;
|
|
791
|
+
`,
|
|
792
|
+
warningCard: `
|
|
793
|
+
background: linear-gradient(135deg, rgba(255,180,0,0.1) 0%, rgba(255,140,0,0.05) 100%);
|
|
794
|
+
border: 1px solid rgba(255,180,0,0.2);
|
|
795
|
+
border-radius: 12px;
|
|
796
|
+
padding: 20px 24px;
|
|
797
|
+
margin: 28px 0;
|
|
798
|
+
`,
|
|
799
|
+
warningCardTitle: `
|
|
800
|
+
margin: 0 0 12px;
|
|
801
|
+
color: #ffc107;
|
|
802
|
+
font-size: 13px;
|
|
803
|
+
font-weight: 600;
|
|
804
|
+
text-transform: uppercase;
|
|
805
|
+
letter-spacing: 0.5px;
|
|
806
|
+
`,
|
|
807
|
+
warningCardText: `
|
|
808
|
+
margin: 0;
|
|
809
|
+
color: rgba(255,255,255,0.7);
|
|
810
|
+
font-size: 14px;
|
|
811
|
+
line-height: 1.6;
|
|
812
|
+
`,
|
|
813
|
+
successCard: `
|
|
814
|
+
background: linear-gradient(135deg, rgba(0,255,150,0.1) 0%, rgba(0,200,100,0.05) 100%);
|
|
815
|
+
border: 1px solid rgba(0,255,150,0.2);
|
|
816
|
+
border-radius: 12px;
|
|
817
|
+
padding: 20px 24px;
|
|
818
|
+
margin: 28px 0;
|
|
819
|
+
`,
|
|
820
|
+
successCardTitle: `
|
|
821
|
+
margin: 0 0 12px;
|
|
822
|
+
color: #00ff96;
|
|
823
|
+
font-size: 13px;
|
|
824
|
+
font-weight: 600;
|
|
825
|
+
text-transform: uppercase;
|
|
826
|
+
letter-spacing: 0.5px;
|
|
827
|
+
`,
|
|
828
|
+
linkSection: `
|
|
829
|
+
margin: 32px 0;
|
|
830
|
+
padding: 20px;
|
|
831
|
+
background-color: ${colors.subtle};
|
|
832
|
+
border-radius: 8px;
|
|
833
|
+
border: 1px solid ${colors.divider};
|
|
834
|
+
`,
|
|
835
|
+
linkLabel: `
|
|
836
|
+
margin: 0 0 8px;
|
|
837
|
+
color: ${colors.textMuted};
|
|
838
|
+
font-size: 12px;
|
|
839
|
+
text-transform: uppercase;
|
|
840
|
+
letter-spacing: 0.5px;
|
|
841
|
+
`,
|
|
842
|
+
linkText: `
|
|
843
|
+
word-break: break-all;
|
|
844
|
+
color: ${colors.textSecondary};
|
|
845
|
+
font-size: 13px;
|
|
846
|
+
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
|
|
847
|
+
margin: 0;
|
|
848
|
+
`,
|
|
849
|
+
divider: `
|
|
850
|
+
border: none;
|
|
851
|
+
border-top: 1px solid ${colors.divider};
|
|
852
|
+
margin: 32px 0;
|
|
853
|
+
`,
|
|
854
|
+
footer: `
|
|
855
|
+
padding: 24px 40px 32px;
|
|
856
|
+
text-align: center;
|
|
857
|
+
border-top: 1px solid ${colors.divider};
|
|
858
|
+
`,
|
|
859
|
+
footerText: `
|
|
860
|
+
margin: 0;
|
|
861
|
+
color: ${colors.textMuted};
|
|
862
|
+
font-size: 12px;
|
|
863
|
+
line-height: 1.8;
|
|
864
|
+
`,
|
|
865
|
+
footerLink: `
|
|
866
|
+
color: ${colors.textSecondary};
|
|
867
|
+
text-decoration: none;
|
|
868
|
+
`,
|
|
869
|
+
badge: `
|
|
870
|
+
display: inline-block;
|
|
871
|
+
background-color: rgba(255,255,255,0.1);
|
|
872
|
+
color: ${colors.textSecondary};
|
|
873
|
+
padding: 4px 12px;
|
|
874
|
+
border-radius: 20px;
|
|
875
|
+
font-size: 12px;
|
|
876
|
+
font-weight: 500;
|
|
877
|
+
letter-spacing: 0.3px;
|
|
878
|
+
`,
|
|
879
|
+
listItem: `
|
|
880
|
+
color: ${colors.textSecondary};
|
|
881
|
+
font-size: 14px;
|
|
882
|
+
margin: 8px 0;
|
|
883
|
+
padding-left: 8px;
|
|
884
|
+
`,
|
|
885
|
+
metaRow: `
|
|
886
|
+
display: flex;
|
|
887
|
+
justify-content: space-between;
|
|
888
|
+
padding: 12px 0;
|
|
889
|
+
border-bottom: 1px solid ${colors.divider};
|
|
890
|
+
`,
|
|
891
|
+
metaLabel: `
|
|
892
|
+
color: ${colors.textMuted};
|
|
893
|
+
font-size: 13px;
|
|
894
|
+
`,
|
|
895
|
+
metaValue: `
|
|
896
|
+
color: ${colors.textPrimary};
|
|
897
|
+
font-size: 13px;
|
|
898
|
+
font-weight: 500;
|
|
899
|
+
`
|
|
900
|
+
};
|
|
901
|
+
function buildVerificationEmailTemplate(data) {
|
|
902
|
+
const { firstName, verificationUrl, expiresIn } = data;
|
|
903
|
+
return `
|
|
904
|
+
<!DOCTYPE html>
|
|
905
|
+
<html lang="en">
|
|
906
|
+
<head>
|
|
907
|
+
<meta charset="UTF-8">
|
|
908
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
909
|
+
<meta name="color-scheme" content="dark">
|
|
910
|
+
<meta name="supported-color-schemes" content="dark">
|
|
911
|
+
<title>Verify Your Email</title>
|
|
912
|
+
<!--[if mso]>
|
|
913
|
+
<style type="text/css">
|
|
914
|
+
body, table, td {font-family: Arial, Helvetica, sans-serif !important;}
|
|
915
|
+
</style>
|
|
916
|
+
<![endif]-->
|
|
917
|
+
</head>
|
|
918
|
+
<body style="${styles.wrapper}">
|
|
919
|
+
<div style="${styles.container}">
|
|
920
|
+
<div style="${styles.card}">
|
|
921
|
+
<!-- Header -->
|
|
922
|
+
<div style="${styles.header}">
|
|
923
|
+
<div style="${styles.iconWrapper}">
|
|
924
|
+
\u2709\uFE0F
|
|
925
|
+
</div>
|
|
926
|
+
<h1 style="${styles.headerTitle}">Verify your email</h1>
|
|
927
|
+
<p style="${styles.headerSubtitle}">One quick step to get started</p>
|
|
928
|
+
</div>
|
|
929
|
+
|
|
930
|
+
<!-- Body -->
|
|
931
|
+
<div style="${styles.body}">
|
|
932
|
+
<p style="${styles.greeting}">Hi ${firstName},</p>
|
|
933
|
+
|
|
934
|
+
<p style="${styles.paragraph}">
|
|
935
|
+
Thanks for signing up. To complete your registration and unlock all features,
|
|
936
|
+
please verify your email address by clicking the button below.
|
|
937
|
+
</p>
|
|
938
|
+
|
|
939
|
+
<div style="${styles.buttonWrapper}">
|
|
940
|
+
<a href="${verificationUrl}" style="${styles.button}" target="_blank">
|
|
941
|
+
Verify Email Address
|
|
942
|
+
</a>
|
|
943
|
+
</div>
|
|
944
|
+
|
|
945
|
+
<div style="${styles.infoCard}">
|
|
946
|
+
<p style="${styles.infoCardTitle}">\u23F1 Time Sensitive</p>
|
|
947
|
+
<p style="${styles.infoCardText}">
|
|
948
|
+
This verification link will expire in <strong>${expiresIn}</strong>.
|
|
949
|
+
If you didn't create an account, you can safely ignore this email.
|
|
950
|
+
</p>
|
|
951
|
+
</div>
|
|
952
|
+
</div>
|
|
953
|
+
|
|
954
|
+
<!-- Footer -->
|
|
955
|
+
<div style="${styles.footer}">
|
|
956
|
+
<p style="${styles.footerText}">
|
|
957
|
+
This is an automated message \u2014 please do not reply.<br>
|
|
958
|
+
\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} All rights reserved.
|
|
959
|
+
</p>
|
|
960
|
+
</div>
|
|
961
|
+
</div>
|
|
962
|
+
</div>
|
|
963
|
+
</body>
|
|
964
|
+
</html>
|
|
965
|
+
`;
|
|
966
|
+
}
|
|
967
|
+
function buildResetPasswordEmailTemplate(data) {
|
|
968
|
+
const { firstName, resetUrl, expiresIn } = data;
|
|
969
|
+
return `
|
|
970
|
+
<!DOCTYPE html>
|
|
971
|
+
<html lang="en">
|
|
972
|
+
<head>
|
|
973
|
+
<meta charset="UTF-8">
|
|
974
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
975
|
+
<meta name="color-scheme" content="dark">
|
|
976
|
+
<meta name="supported-color-schemes" content="dark">
|
|
977
|
+
<title>Reset Your Password</title>
|
|
978
|
+
</head>
|
|
979
|
+
<body style="${styles.wrapper}">
|
|
980
|
+
<div style="${styles.container}">
|
|
981
|
+
<div style="${styles.card}">
|
|
982
|
+
<!-- Header -->
|
|
983
|
+
<div style="${styles.header}">
|
|
984
|
+
<div style="${styles.iconWrapper}">
|
|
985
|
+
\u{1F510}
|
|
986
|
+
</div>
|
|
987
|
+
<h1 style="${styles.headerTitle}">Reset your password</h1>
|
|
988
|
+
<p style="${styles.headerSubtitle}">We received a reset request</p>
|
|
989
|
+
</div>
|
|
990
|
+
|
|
991
|
+
<!-- Body -->
|
|
992
|
+
<div style="${styles.body}">
|
|
993
|
+
<p style="${styles.greeting}">Hi ${firstName},</p>
|
|
994
|
+
|
|
995
|
+
<p style="${styles.paragraph}">
|
|
996
|
+
We received a request to reset the password for your account.
|
|
997
|
+
Click the button below to create a new password.
|
|
998
|
+
</p>
|
|
999
|
+
|
|
1000
|
+
<div style="${styles.buttonWrapper}">
|
|
1001
|
+
<a href="${resetUrl}" style="${styles.button}" target="_blank">
|
|
1002
|
+
Reset Password
|
|
1003
|
+
</a>
|
|
1004
|
+
</div>
|
|
1005
|
+
|
|
1006
|
+
<div style="${styles.warningCard}">
|
|
1007
|
+
<p style="${styles.warningCardTitle}">\u26A0\uFE0F Security Notice</p>
|
|
1008
|
+
<p style="${styles.warningCardText}">
|
|
1009
|
+
\u2022 This link expires in <strong>${expiresIn}</strong><br>
|
|
1010
|
+
\u2022 This link can only be used once<br>
|
|
1011
|
+
\u2022 If you didn't request this, ignore this email
|
|
1012
|
+
</p>
|
|
1013
|
+
</div>
|
|
1014
|
+
|
|
1015
|
+
<hr style="${styles.divider}" />
|
|
1016
|
+
|
|
1017
|
+
<p style="${styles.paragraph}; font-size: 13px; color: ${colors.textMuted};">
|
|
1018
|
+
<strong>Didn't request this?</strong><br>
|
|
1019
|
+
Your password remains unchanged. If you're concerned about your account
|
|
1020
|
+
security, please contact our support team immediately.
|
|
1021
|
+
</p>
|
|
1022
|
+
</div>
|
|
1023
|
+
|
|
1024
|
+
<!-- Footer -->
|
|
1025
|
+
<div style="${styles.footer}">
|
|
1026
|
+
<p style="${styles.footerText}">
|
|
1027
|
+
This is an automated message \u2014 please do not reply.<br>
|
|
1028
|
+
\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} All rights reserved.
|
|
1029
|
+
</p>
|
|
1030
|
+
</div>
|
|
1031
|
+
</div>
|
|
1032
|
+
</div>
|
|
1033
|
+
</body>
|
|
1034
|
+
</html>
|
|
1035
|
+
`;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
671
1038
|
// src/express/auth.routes.ts
|
|
672
1039
|
function createAuthRouter(options = {}) {
|
|
673
1040
|
const googleClientId = process.env.GOOGLE_CLIENT_ID;
|
|
@@ -689,7 +1056,7 @@ function createAuthRouter(options = {}) {
|
|
|
689
1056
|
// default: secure in prod
|
|
690
1057
|
domain: options.cookie?.domain ?? void 0,
|
|
691
1058
|
path: options.cookie?.path ?? "/",
|
|
692
|
-
maxAgeMs: options.cookie?.maxAgeMs ?? 24 * 60 * 60 * 1e3
|
|
1059
|
+
maxAgeMs: options.cookie?.maxAgeMs ?? 30 * 24 * 60 * 60 * 1e3
|
|
693
1060
|
};
|
|
694
1061
|
r.use(import_express.default.json());
|
|
695
1062
|
r.use(import_express.default.urlencoded({ extended: true }));
|
|
@@ -746,6 +1113,7 @@ function createAuthRouter(options = {}) {
|
|
|
746
1113
|
projectId,
|
|
747
1114
|
metadata
|
|
748
1115
|
} = req.body || {};
|
|
1116
|
+
const COMPANY_NAME = process.env.COMPANY_NAME;
|
|
749
1117
|
try {
|
|
750
1118
|
const kcUser = await authAdmin.createUserInRealm({
|
|
751
1119
|
username: emailAddress,
|
|
@@ -774,10 +1142,21 @@ function createAuthRouter(options = {}) {
|
|
|
774
1142
|
emailService: email,
|
|
775
1143
|
user,
|
|
776
1144
|
subject: "Verify your email",
|
|
777
|
-
html: buildVerificationTemplate(
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
)
|
|
1145
|
+
// html: buildVerificationTemplate(
|
|
1146
|
+
// email.sign({ userId: kcUser.id, email: kcUser.email }),
|
|
1147
|
+
// options,
|
|
1148
|
+
// ),
|
|
1149
|
+
html: buildVerificationEmailTemplate({
|
|
1150
|
+
firstName: user.firstName,
|
|
1151
|
+
verificationUrl: `${getFrontendBaseUrl(options)}/verify-email?token=${email.sign(
|
|
1152
|
+
{
|
|
1153
|
+
userId: user.id,
|
|
1154
|
+
email: user.email
|
|
1155
|
+
}
|
|
1156
|
+
)}`,
|
|
1157
|
+
expiresIn: "1 hour"
|
|
1158
|
+
}),
|
|
1159
|
+
from: COMPANY_NAME
|
|
781
1160
|
});
|
|
782
1161
|
if (emailResult.rateLimited) {
|
|
783
1162
|
return res.status(429).json({
|
|
@@ -842,6 +1221,7 @@ function createAuthRouter(options = {}) {
|
|
|
842
1221
|
"/resend-verification-email",
|
|
843
1222
|
validateResendEmail,
|
|
844
1223
|
async (req, res) => {
|
|
1224
|
+
const COMPANY_NAME = process.env.COMPANY_NAME;
|
|
845
1225
|
const user = await OrgUser.findOne({ email: req.body.email });
|
|
846
1226
|
if (!user)
|
|
847
1227
|
return res.status(404).json({ ok: false, error: "User not found" });
|
|
@@ -857,7 +1237,18 @@ function createAuthRouter(options = {}) {
|
|
|
857
1237
|
emailService: email,
|
|
858
1238
|
user,
|
|
859
1239
|
subject: "Verify your email",
|
|
860
|
-
html: buildVerificationTemplate(token, options)
|
|
1240
|
+
// html: buildVerificationTemplate(token, options),
|
|
1241
|
+
html: buildVerificationEmailTemplate({
|
|
1242
|
+
firstName: user.firstName,
|
|
1243
|
+
verificationUrl: `${getFrontendBaseUrl(options)}/verify-email?token=${email.sign(
|
|
1244
|
+
{
|
|
1245
|
+
userId: user.id,
|
|
1246
|
+
email: user.email
|
|
1247
|
+
}
|
|
1248
|
+
)}`,
|
|
1249
|
+
expiresIn: "1 hour"
|
|
1250
|
+
}),
|
|
1251
|
+
from: COMPANY_NAME
|
|
861
1252
|
});
|
|
862
1253
|
if (resendResult.rateLimited) {
|
|
863
1254
|
return res.status(429).json({
|
|
@@ -870,6 +1261,7 @@ function createAuthRouter(options = {}) {
|
|
|
870
1261
|
}
|
|
871
1262
|
);
|
|
872
1263
|
r.post("/forgot-password", validateResendEmail, async (req, res) => {
|
|
1264
|
+
const COMPANY_NAME = process.env.COMPANY_NAME;
|
|
873
1265
|
const user = await OrgUser.findOne({ email: req.body.email });
|
|
874
1266
|
if (!user)
|
|
875
1267
|
return res.status(404).json({ ok: false, error: "User not found" });
|
|
@@ -886,7 +1278,18 @@ function createAuthRouter(options = {}) {
|
|
|
886
1278
|
emailService: email,
|
|
887
1279
|
user,
|
|
888
1280
|
subject: "Reset password",
|
|
889
|
-
html: buildResetTemplate(resetToken, options)
|
|
1281
|
+
// html: buildResetTemplate(resetToken, options),
|
|
1282
|
+
html: buildResetPasswordEmailTemplate({
|
|
1283
|
+
firstName: user.firstName,
|
|
1284
|
+
resetUrl: `${getFrontendBaseUrl(options)}/reset-password?token=${email.sign(
|
|
1285
|
+
{
|
|
1286
|
+
userId: user.id,
|
|
1287
|
+
email: user.email
|
|
1288
|
+
}
|
|
1289
|
+
)}`,
|
|
1290
|
+
expiresIn: "1 hour"
|
|
1291
|
+
}),
|
|
1292
|
+
from: COMPANY_NAME
|
|
890
1293
|
});
|
|
891
1294
|
if (resetResult.rateLimited) {
|
|
892
1295
|
return res.status(429).json({
|
|
@@ -899,9 +1302,16 @@ function createAuthRouter(options = {}) {
|
|
|
899
1302
|
});
|
|
900
1303
|
r.post("/reset-password", validateResetPassword, async (req, res) => {
|
|
901
1304
|
const { token, newPassword } = req.body || {};
|
|
1305
|
+
if (!token || !newPassword) {
|
|
1306
|
+
return res.status(400).json({
|
|
1307
|
+
ok: false,
|
|
1308
|
+
error: "Token and new password are required",
|
|
1309
|
+
code: "MISSING_FIELDS"
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
902
1312
|
try {
|
|
903
1313
|
const payload = email.verify(token);
|
|
904
|
-
const user = await OrgUser.findOne({
|
|
1314
|
+
const user = await OrgUser.findOne({ id: payload.userId });
|
|
905
1315
|
if (!user) {
|
|
906
1316
|
return res.status(404).json({ ok: false, error: "User not found" });
|
|
907
1317
|
}
|
|
@@ -1269,8 +1679,6 @@ function setAuthCookies(res, tokens, cookie) {
|
|
|
1269
1679
|
if (cookie.domain) {
|
|
1270
1680
|
base.domain = cookie.domain;
|
|
1271
1681
|
}
|
|
1272
|
-
console.log(cookie, "cookie");
|
|
1273
|
-
console.log(base, "base");
|
|
1274
1682
|
if (tokens?.access_token) {
|
|
1275
1683
|
res.cookie("access_token", tokens.access_token, base);
|
|
1276
1684
|
}
|
|
@@ -1294,12 +1702,6 @@ function respondWithKeycloakError(res, err, fallback, status = 400) {
|
|
|
1294
1702
|
const description = err?.response?.data?.error_description || err?.response?.data?.errorMessage || err?.message || fallback;
|
|
1295
1703
|
return res.status(status).json({ ok: false, error: description });
|
|
1296
1704
|
}
|
|
1297
|
-
function buildVerificationTemplate(token, options) {
|
|
1298
|
-
return `<a href="${getFrontendBaseUrl(options)}/auth/verify-email?token=${token}">Verify</a>`;
|
|
1299
|
-
}
|
|
1300
|
-
function buildResetTemplate(token, options) {
|
|
1301
|
-
return `<a href="${getFrontendBaseUrl(options)}/auth/reset-password?token=${token}">Reset</a>`;
|
|
1302
|
-
}
|
|
1303
1705
|
function getFrontendBaseUrl(options) {
|
|
1304
1706
|
if (options.frontendBaseUrl)
|
|
1305
1707
|
return options.frontendBaseUrl.replace(/\/$/, "");
|
|
@@ -1311,13 +1713,14 @@ async function sendRateLimitedEmail({
|
|
|
1311
1713
|
emailService,
|
|
1312
1714
|
user,
|
|
1313
1715
|
subject,
|
|
1314
|
-
html
|
|
1716
|
+
html,
|
|
1717
|
+
from
|
|
1315
1718
|
}) {
|
|
1316
1719
|
const can = emailService.canSend(user?.lastEmailSent || []);
|
|
1317
1720
|
if (!can.ok) {
|
|
1318
1721
|
return { rateLimited: true, waitMs: can.waitMs };
|
|
1319
1722
|
}
|
|
1320
|
-
await emailService.send(user.email, subject, html);
|
|
1723
|
+
await emailService.send(user.email, subject, html, from);
|
|
1321
1724
|
user.lastEmailSent = [...user.lastEmailSent || [], /* @__PURE__ */ new Date()];
|
|
1322
1725
|
await user.save();
|
|
1323
1726
|
return { rateLimited: false };
|
|
@@ -1338,7 +1741,7 @@ function generateTokens(user) {
|
|
|
1338
1741
|
type: "user"
|
|
1339
1742
|
};
|
|
1340
1743
|
const accessToken = import_jsonwebtoken4.default.sign(accessPayload, process.env.JWT_SECRET, {
|
|
1341
|
-
expiresIn: "
|
|
1744
|
+
expiresIn: "1d"
|
|
1342
1745
|
});
|
|
1343
1746
|
const refreshToken = import_jsonwebtoken4.default.sign(
|
|
1344
1747
|
{ sub: user._id.toString() },
|
|
@@ -1374,13 +1777,61 @@ function createDashboardRouter(options) {
|
|
|
1374
1777
|
}
|
|
1375
1778
|
|
|
1376
1779
|
// src/express/email.routes.ts
|
|
1377
|
-
var import_express3 = require("express");
|
|
1780
|
+
var import_express3 = __toESM(require("express"), 1);
|
|
1378
1781
|
function createEmailRouter(options) {
|
|
1379
1782
|
const r = (0, import_express3.Router)();
|
|
1783
|
+
const emailService = new EmailService();
|
|
1784
|
+
r.use(import_express3.default.json());
|
|
1785
|
+
r.use(import_express3.default.urlencoded({ extended: true }));
|
|
1380
1786
|
r.get(
|
|
1381
1787
|
"/verify",
|
|
1382
1788
|
(req, res) => res.json({ ok: true, token: req.query.token })
|
|
1383
1789
|
);
|
|
1790
|
+
r.post("/send", async (req, res) => {
|
|
1791
|
+
try {
|
|
1792
|
+
const { userId, to, subject, html, from } = req.body ?? {};
|
|
1793
|
+
if (!to || !subject || !html) {
|
|
1794
|
+
return res.status(400).json({
|
|
1795
|
+
ok: false,
|
|
1796
|
+
error: "BAD_REQUEST",
|
|
1797
|
+
message: "`to`, `subject`, and `html` are required."
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1800
|
+
if (userId) {
|
|
1801
|
+
const user = await OrgUser.findOne({ id: userId }).lean();
|
|
1802
|
+
if (!user) {
|
|
1803
|
+
return res.status(404).json({
|
|
1804
|
+
ok: false,
|
|
1805
|
+
error: "NOT_FOUND",
|
|
1806
|
+
message: "User not found."
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
const can = emailService.canSend(user?.lastEmailSent || []);
|
|
1810
|
+
if (!can.ok) {
|
|
1811
|
+
return res.status(429).json({
|
|
1812
|
+
ok: false,
|
|
1813
|
+
error: can.reason,
|
|
1814
|
+
waitMs: can.waitMs,
|
|
1815
|
+
message: "Too many emails sent recently. Please retry later."
|
|
1816
|
+
});
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
await emailService.send(to, subject, html, from);
|
|
1820
|
+
if (userId) {
|
|
1821
|
+
await OrgUser.updateOne(
|
|
1822
|
+
{ id: userId },
|
|
1823
|
+
{ $push: { lastEmailSent: /* @__PURE__ */ new Date() } }
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1826
|
+
return res.json({ ok: true });
|
|
1827
|
+
} catch (err) {
|
|
1828
|
+
return res.status(500).json({
|
|
1829
|
+
ok: false,
|
|
1830
|
+
error: "INTERNAL",
|
|
1831
|
+
message: err?.message ?? "Error"
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
});
|
|
1384
1835
|
return r;
|
|
1385
1836
|
}
|
|
1386
1837
|
|