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/index.js
CHANGED
|
@@ -48,8 +48,8 @@ function loadConfig() {
|
|
|
48
48
|
cookies: {
|
|
49
49
|
domain: process.env.COOKIE_DOMAIN,
|
|
50
50
|
secure: (process.env.COOKIE_SECURE || "true") === "true",
|
|
51
|
-
accessTtlMs: 24 * 60 * 60 * 1e3,
|
|
52
|
-
refreshTtlMs:
|
|
51
|
+
accessTtlMs: 7 * 24 * 60 * 60 * 1e3,
|
|
52
|
+
refreshTtlMs: 30 * 24 * 60 * 60 * 1e3
|
|
53
53
|
},
|
|
54
54
|
oidc: {
|
|
55
55
|
jwtSecret: process.env.JWT_SECRET
|
|
@@ -590,7 +590,7 @@ var AuthAdminService = class {
|
|
|
590
590
|
}
|
|
591
591
|
async updateUserPassword(userId, newPassword) {
|
|
592
592
|
const hashed = await bcrypt.hash(newPassword, 10);
|
|
593
|
-
await OrgUser.findOneAndUpdate({ id: userId }, {
|
|
593
|
+
await OrgUser.findOneAndUpdate({ id: userId }, { passwordHash: hashed });
|
|
594
594
|
}
|
|
595
595
|
// -------------------------------------------------------------------
|
|
596
596
|
// ADMIN TOKEN (self-issued JWT)
|
|
@@ -605,11 +605,11 @@ var AuthAdminService = class {
|
|
|
605
605
|
system: true
|
|
606
606
|
};
|
|
607
607
|
const accessToken = jwt2.sign(payload, process.env.JWT_SECRET, {
|
|
608
|
-
expiresIn: "
|
|
608
|
+
expiresIn: "1d"
|
|
609
609
|
});
|
|
610
610
|
this.token = {
|
|
611
611
|
accessToken,
|
|
612
|
-
exp: now +
|
|
612
|
+
exp: now + 84800
|
|
613
613
|
};
|
|
614
614
|
return this.token.accessToken;
|
|
615
615
|
}
|
|
@@ -634,7 +634,7 @@ var EmailService = class {
|
|
|
634
634
|
}
|
|
635
635
|
});
|
|
636
636
|
}
|
|
637
|
-
sign(payload, ttlSec = 60 * 60 * 24) {
|
|
637
|
+
sign(payload, ttlSec = 60 * 60 * 24 * 30) {
|
|
638
638
|
return jwt3.sign(payload, process.env.EMAIL_JWT_SECRET, {
|
|
639
639
|
expiresIn: ttlSec
|
|
640
640
|
});
|
|
@@ -642,11 +642,10 @@ var EmailService = class {
|
|
|
642
642
|
verify(token) {
|
|
643
643
|
return jwt3.verify(token, process.env.EMAIL_JWT_SECRET);
|
|
644
644
|
}
|
|
645
|
-
async send(to, subject, html) {
|
|
646
|
-
console.log("[EmailService] Attempting to send:", { to, subject });
|
|
645
|
+
async send(to, subject, html, from) {
|
|
647
646
|
try {
|
|
648
647
|
const info = await this.transporter.sendMail({
|
|
649
|
-
from: process.env.EMAIL_FROM,
|
|
648
|
+
from: from ? `${from} ` + process.env.EMAIL_FROM : process.env.EMAIL_FROM,
|
|
650
649
|
to,
|
|
651
650
|
subject,
|
|
652
651
|
html
|
|
@@ -671,18 +670,6 @@ var EmailService = class {
|
|
|
671
670
|
}
|
|
672
671
|
}
|
|
673
672
|
canSend(lastEmailSent) {
|
|
674
|
-
console.log(
|
|
675
|
-
process.env.EMAIL_PASSWORD,
|
|
676
|
-
"pssword",
|
|
677
|
-
process.env.EMAIL_USER,
|
|
678
|
-
"user",
|
|
679
|
-
process.env.EMAIL_SECURE,
|
|
680
|
-
"secure",
|
|
681
|
-
process.env.EMAIL_PORT,
|
|
682
|
-
"porat",
|
|
683
|
-
process.env.EMAIL_HOST,
|
|
684
|
-
"hosat"
|
|
685
|
-
);
|
|
686
673
|
const now = Date.now();
|
|
687
674
|
const windowStart = now - this.WINDOW_MINUTES * 60 * 1e3;
|
|
688
675
|
const emailsInWindow = (lastEmailSent || []).map((d) => new Date(d)).filter((d) => d.getTime() >= windowStart);
|
|
@@ -696,6 +683,386 @@ var EmailService = class {
|
|
|
696
683
|
}
|
|
697
684
|
};
|
|
698
685
|
|
|
686
|
+
// src/templates/email.templates.ts
|
|
687
|
+
var colors = {
|
|
688
|
+
background: "#0a0a0a",
|
|
689
|
+
cardBackground: "#111111",
|
|
690
|
+
cardBorder: "#1a1a1a",
|
|
691
|
+
accent: "#ffffff",
|
|
692
|
+
accentMuted: "rgba(255, 255, 255, 0.9)",
|
|
693
|
+
textPrimary: "#ffffff",
|
|
694
|
+
textSecondary: "rgba(255, 255, 255, 0.7)",
|
|
695
|
+
textMuted: "rgba(255, 255, 255, 0.5)",
|
|
696
|
+
divider: "rgba(255, 255, 255, 0.1)",
|
|
697
|
+
subtle: "#161616",
|
|
698
|
+
highlight: "rgba(255, 255, 255, 0.05)"
|
|
699
|
+
};
|
|
700
|
+
var styles = {
|
|
701
|
+
wrapper: `
|
|
702
|
+
margin: 0;
|
|
703
|
+
padding: 40px 20px;
|
|
704
|
+
background-color: ${colors.background};
|
|
705
|
+
background-image:
|
|
706
|
+
radial-gradient(ellipse at top, rgba(255,255,255,0.03) 0%, transparent 50%),
|
|
707
|
+
radial-gradient(ellipse at bottom, rgba(255,255,255,0.02) 0%, transparent 50%);
|
|
708
|
+
min-height: 100vh;
|
|
709
|
+
`,
|
|
710
|
+
container: `
|
|
711
|
+
max-width: 520px;
|
|
712
|
+
margin: 0 auto;
|
|
713
|
+
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
714
|
+
color: ${colors.textPrimary};
|
|
715
|
+
line-height: 1.7;
|
|
716
|
+
`,
|
|
717
|
+
card: `
|
|
718
|
+
background-color: ${colors.cardBackground};
|
|
719
|
+
border: 1px solid ${colors.cardBorder};
|
|
720
|
+
border-radius: 16px;
|
|
721
|
+
overflow: hidden;
|
|
722
|
+
box-shadow:
|
|
723
|
+
0 0 0 1px rgba(255,255,255,0.05),
|
|
724
|
+
0 20px 50px -20px rgba(0,0,0,0.5),
|
|
725
|
+
0 30px 60px -30px rgba(0,0,0,0.3);
|
|
726
|
+
`,
|
|
727
|
+
header: `
|
|
728
|
+
padding: 48px 40px 32px;
|
|
729
|
+
text-align: center;
|
|
730
|
+
border-bottom: 1px solid ${colors.divider};
|
|
731
|
+
`,
|
|
732
|
+
iconWrapper: `
|
|
733
|
+
width: 64px;
|
|
734
|
+
height: 64px;
|
|
735
|
+
margin: 0 auto 24px;
|
|
736
|
+
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%);
|
|
737
|
+
border: 1px solid rgba(255,255,255,0.1);
|
|
738
|
+
border-radius: 16px;
|
|
739
|
+
display: flex;
|
|
740
|
+
align-items: center;
|
|
741
|
+
justify-content: center;
|
|
742
|
+
font-size: 28px;
|
|
743
|
+
`,
|
|
744
|
+
headerTitle: `
|
|
745
|
+
color: ${colors.textPrimary};
|
|
746
|
+
margin: 0;
|
|
747
|
+
font-size: 24px;
|
|
748
|
+
font-weight: 600;
|
|
749
|
+
letter-spacing: -0.5px;
|
|
750
|
+
`,
|
|
751
|
+
headerSubtitle: `
|
|
752
|
+
color: ${colors.textMuted};
|
|
753
|
+
margin: 8px 0 0;
|
|
754
|
+
font-size: 14px;
|
|
755
|
+
font-weight: 400;
|
|
756
|
+
`,
|
|
757
|
+
body: `
|
|
758
|
+
padding: 40px;
|
|
759
|
+
`,
|
|
760
|
+
greeting: `
|
|
761
|
+
margin: 0 0 24px;
|
|
762
|
+
color: ${colors.textPrimary};
|
|
763
|
+
font-size: 18px;
|
|
764
|
+
font-weight: 500;
|
|
765
|
+
`,
|
|
766
|
+
paragraph: `
|
|
767
|
+
margin: 0 0 20px;
|
|
768
|
+
color: ${colors.textSecondary};
|
|
769
|
+
font-size: 15px;
|
|
770
|
+
line-height: 1.7;
|
|
771
|
+
`,
|
|
772
|
+
buttonWrapper: `
|
|
773
|
+
text-align: center;
|
|
774
|
+
margin: 32px 0;
|
|
775
|
+
`,
|
|
776
|
+
button: `
|
|
777
|
+
display: inline-block;
|
|
778
|
+
background-color: ${colors.accent};
|
|
779
|
+
color: #000000 !important;
|
|
780
|
+
text-decoration: none;
|
|
781
|
+
padding: 14px 36px;
|
|
782
|
+
border-radius: 8px;
|
|
783
|
+
font-weight: 600;
|
|
784
|
+
font-size: 14px;
|
|
785
|
+
letter-spacing: 0.3px;
|
|
786
|
+
transition: all 0.2s ease;
|
|
787
|
+
`,
|
|
788
|
+
secondaryButton: `
|
|
789
|
+
display: inline-block;
|
|
790
|
+
background-color: transparent;
|
|
791
|
+
color: ${colors.textPrimary} !important;
|
|
792
|
+
text-decoration: none;
|
|
793
|
+
padding: 12px 28px;
|
|
794
|
+
border-radius: 8px;
|
|
795
|
+
font-weight: 500;
|
|
796
|
+
font-size: 14px;
|
|
797
|
+
border: 1px solid ${colors.divider};
|
|
798
|
+
`,
|
|
799
|
+
infoCard: `
|
|
800
|
+
background-color: ${colors.subtle};
|
|
801
|
+
border: 1px solid ${colors.divider};
|
|
802
|
+
border-radius: 12px;
|
|
803
|
+
padding: 20px 24px;
|
|
804
|
+
margin: 28px 0;
|
|
805
|
+
`,
|
|
806
|
+
infoCardTitle: `
|
|
807
|
+
margin: 0 0 12px;
|
|
808
|
+
color: ${colors.textPrimary};
|
|
809
|
+
font-size: 13px;
|
|
810
|
+
font-weight: 600;
|
|
811
|
+
text-transform: uppercase;
|
|
812
|
+
letter-spacing: 0.5px;
|
|
813
|
+
`,
|
|
814
|
+
infoCardText: `
|
|
815
|
+
margin: 0;
|
|
816
|
+
color: ${colors.textSecondary};
|
|
817
|
+
font-size: 14px;
|
|
818
|
+
line-height: 1.6;
|
|
819
|
+
`,
|
|
820
|
+
warningCard: `
|
|
821
|
+
background: linear-gradient(135deg, rgba(255,180,0,0.1) 0%, rgba(255,140,0,0.05) 100%);
|
|
822
|
+
border: 1px solid rgba(255,180,0,0.2);
|
|
823
|
+
border-radius: 12px;
|
|
824
|
+
padding: 20px 24px;
|
|
825
|
+
margin: 28px 0;
|
|
826
|
+
`,
|
|
827
|
+
warningCardTitle: `
|
|
828
|
+
margin: 0 0 12px;
|
|
829
|
+
color: #ffc107;
|
|
830
|
+
font-size: 13px;
|
|
831
|
+
font-weight: 600;
|
|
832
|
+
text-transform: uppercase;
|
|
833
|
+
letter-spacing: 0.5px;
|
|
834
|
+
`,
|
|
835
|
+
warningCardText: `
|
|
836
|
+
margin: 0;
|
|
837
|
+
color: rgba(255,255,255,0.7);
|
|
838
|
+
font-size: 14px;
|
|
839
|
+
line-height: 1.6;
|
|
840
|
+
`,
|
|
841
|
+
successCard: `
|
|
842
|
+
background: linear-gradient(135deg, rgba(0,255,150,0.1) 0%, rgba(0,200,100,0.05) 100%);
|
|
843
|
+
border: 1px solid rgba(0,255,150,0.2);
|
|
844
|
+
border-radius: 12px;
|
|
845
|
+
padding: 20px 24px;
|
|
846
|
+
margin: 28px 0;
|
|
847
|
+
`,
|
|
848
|
+
successCardTitle: `
|
|
849
|
+
margin: 0 0 12px;
|
|
850
|
+
color: #00ff96;
|
|
851
|
+
font-size: 13px;
|
|
852
|
+
font-weight: 600;
|
|
853
|
+
text-transform: uppercase;
|
|
854
|
+
letter-spacing: 0.5px;
|
|
855
|
+
`,
|
|
856
|
+
linkSection: `
|
|
857
|
+
margin: 32px 0;
|
|
858
|
+
padding: 20px;
|
|
859
|
+
background-color: ${colors.subtle};
|
|
860
|
+
border-radius: 8px;
|
|
861
|
+
border: 1px solid ${colors.divider};
|
|
862
|
+
`,
|
|
863
|
+
linkLabel: `
|
|
864
|
+
margin: 0 0 8px;
|
|
865
|
+
color: ${colors.textMuted};
|
|
866
|
+
font-size: 12px;
|
|
867
|
+
text-transform: uppercase;
|
|
868
|
+
letter-spacing: 0.5px;
|
|
869
|
+
`,
|
|
870
|
+
linkText: `
|
|
871
|
+
word-break: break-all;
|
|
872
|
+
color: ${colors.textSecondary};
|
|
873
|
+
font-size: 13px;
|
|
874
|
+
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
|
|
875
|
+
margin: 0;
|
|
876
|
+
`,
|
|
877
|
+
divider: `
|
|
878
|
+
border: none;
|
|
879
|
+
border-top: 1px solid ${colors.divider};
|
|
880
|
+
margin: 32px 0;
|
|
881
|
+
`,
|
|
882
|
+
footer: `
|
|
883
|
+
padding: 24px 40px 32px;
|
|
884
|
+
text-align: center;
|
|
885
|
+
border-top: 1px solid ${colors.divider};
|
|
886
|
+
`,
|
|
887
|
+
footerText: `
|
|
888
|
+
margin: 0;
|
|
889
|
+
color: ${colors.textMuted};
|
|
890
|
+
font-size: 12px;
|
|
891
|
+
line-height: 1.8;
|
|
892
|
+
`,
|
|
893
|
+
footerLink: `
|
|
894
|
+
color: ${colors.textSecondary};
|
|
895
|
+
text-decoration: none;
|
|
896
|
+
`,
|
|
897
|
+
badge: `
|
|
898
|
+
display: inline-block;
|
|
899
|
+
background-color: rgba(255,255,255,0.1);
|
|
900
|
+
color: ${colors.textSecondary};
|
|
901
|
+
padding: 4px 12px;
|
|
902
|
+
border-radius: 20px;
|
|
903
|
+
font-size: 12px;
|
|
904
|
+
font-weight: 500;
|
|
905
|
+
letter-spacing: 0.3px;
|
|
906
|
+
`,
|
|
907
|
+
listItem: `
|
|
908
|
+
color: ${colors.textSecondary};
|
|
909
|
+
font-size: 14px;
|
|
910
|
+
margin: 8px 0;
|
|
911
|
+
padding-left: 8px;
|
|
912
|
+
`,
|
|
913
|
+
metaRow: `
|
|
914
|
+
display: flex;
|
|
915
|
+
justify-content: space-between;
|
|
916
|
+
padding: 12px 0;
|
|
917
|
+
border-bottom: 1px solid ${colors.divider};
|
|
918
|
+
`,
|
|
919
|
+
metaLabel: `
|
|
920
|
+
color: ${colors.textMuted};
|
|
921
|
+
font-size: 13px;
|
|
922
|
+
`,
|
|
923
|
+
metaValue: `
|
|
924
|
+
color: ${colors.textPrimary};
|
|
925
|
+
font-size: 13px;
|
|
926
|
+
font-weight: 500;
|
|
927
|
+
`
|
|
928
|
+
};
|
|
929
|
+
function buildVerificationEmailTemplate(data) {
|
|
930
|
+
const { firstName, verificationUrl, 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>Verify Your Email</title>
|
|
940
|
+
<!--[if mso]>
|
|
941
|
+
<style type="text/css">
|
|
942
|
+
body, table, td {font-family: Arial, Helvetica, sans-serif !important;}
|
|
943
|
+
</style>
|
|
944
|
+
<![endif]-->
|
|
945
|
+
</head>
|
|
946
|
+
<body style="${styles.wrapper}">
|
|
947
|
+
<div style="${styles.container}">
|
|
948
|
+
<div style="${styles.card}">
|
|
949
|
+
<!-- Header -->
|
|
950
|
+
<div style="${styles.header}">
|
|
951
|
+
<div style="${styles.iconWrapper}">
|
|
952
|
+
\u2709\uFE0F
|
|
953
|
+
</div>
|
|
954
|
+
<h1 style="${styles.headerTitle}">Verify your email</h1>
|
|
955
|
+
<p style="${styles.headerSubtitle}">One quick step to get started</p>
|
|
956
|
+
</div>
|
|
957
|
+
|
|
958
|
+
<!-- Body -->
|
|
959
|
+
<div style="${styles.body}">
|
|
960
|
+
<p style="${styles.greeting}">Hi ${firstName},</p>
|
|
961
|
+
|
|
962
|
+
<p style="${styles.paragraph}">
|
|
963
|
+
Thanks for signing up. To complete your registration and unlock all features,
|
|
964
|
+
please verify your email address by clicking the button below.
|
|
965
|
+
</p>
|
|
966
|
+
|
|
967
|
+
<div style="${styles.buttonWrapper}">
|
|
968
|
+
<a href="${verificationUrl}" style="${styles.button}" target="_blank">
|
|
969
|
+
Verify Email Address
|
|
970
|
+
</a>
|
|
971
|
+
</div>
|
|
972
|
+
|
|
973
|
+
<div style="${styles.infoCard}">
|
|
974
|
+
<p style="${styles.infoCardTitle}">\u23F1 Time Sensitive</p>
|
|
975
|
+
<p style="${styles.infoCardText}">
|
|
976
|
+
This verification link will expire in <strong>${expiresIn}</strong>.
|
|
977
|
+
If you didn't create an account, you can safely ignore this email.
|
|
978
|
+
</p>
|
|
979
|
+
</div>
|
|
980
|
+
</div>
|
|
981
|
+
|
|
982
|
+
<!-- Footer -->
|
|
983
|
+
<div style="${styles.footer}">
|
|
984
|
+
<p style="${styles.footerText}">
|
|
985
|
+
This is an automated message \u2014 please do not reply.<br>
|
|
986
|
+
\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} All rights reserved.
|
|
987
|
+
</p>
|
|
988
|
+
</div>
|
|
989
|
+
</div>
|
|
990
|
+
</div>
|
|
991
|
+
</body>
|
|
992
|
+
</html>
|
|
993
|
+
`;
|
|
994
|
+
}
|
|
995
|
+
function buildResetPasswordEmailTemplate(data) {
|
|
996
|
+
const { firstName, resetUrl, expiresIn } = data;
|
|
997
|
+
return `
|
|
998
|
+
<!DOCTYPE html>
|
|
999
|
+
<html lang="en">
|
|
1000
|
+
<head>
|
|
1001
|
+
<meta charset="UTF-8">
|
|
1002
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1003
|
+
<meta name="color-scheme" content="dark">
|
|
1004
|
+
<meta name="supported-color-schemes" content="dark">
|
|
1005
|
+
<title>Reset Your Password</title>
|
|
1006
|
+
</head>
|
|
1007
|
+
<body style="${styles.wrapper}">
|
|
1008
|
+
<div style="${styles.container}">
|
|
1009
|
+
<div style="${styles.card}">
|
|
1010
|
+
<!-- Header -->
|
|
1011
|
+
<div style="${styles.header}">
|
|
1012
|
+
<div style="${styles.iconWrapper}">
|
|
1013
|
+
\u{1F510}
|
|
1014
|
+
</div>
|
|
1015
|
+
<h1 style="${styles.headerTitle}">Reset your password</h1>
|
|
1016
|
+
<p style="${styles.headerSubtitle}">We received a reset request</p>
|
|
1017
|
+
</div>
|
|
1018
|
+
|
|
1019
|
+
<!-- Body -->
|
|
1020
|
+
<div style="${styles.body}">
|
|
1021
|
+
<p style="${styles.greeting}">Hi ${firstName},</p>
|
|
1022
|
+
|
|
1023
|
+
<p style="${styles.paragraph}">
|
|
1024
|
+
We received a request to reset the password for your account.
|
|
1025
|
+
Click the button below to create a new password.
|
|
1026
|
+
</p>
|
|
1027
|
+
|
|
1028
|
+
<div style="${styles.buttonWrapper}">
|
|
1029
|
+
<a href="${resetUrl}" style="${styles.button}" target="_blank">
|
|
1030
|
+
Reset Password
|
|
1031
|
+
</a>
|
|
1032
|
+
</div>
|
|
1033
|
+
|
|
1034
|
+
<div style="${styles.warningCard}">
|
|
1035
|
+
<p style="${styles.warningCardTitle}">\u26A0\uFE0F Security Notice</p>
|
|
1036
|
+
<p style="${styles.warningCardText}">
|
|
1037
|
+
\u2022 This link expires in <strong>${expiresIn}</strong><br>
|
|
1038
|
+
\u2022 This link can only be used once<br>
|
|
1039
|
+
\u2022 If you didn't request this, ignore this email
|
|
1040
|
+
</p>
|
|
1041
|
+
</div>
|
|
1042
|
+
|
|
1043
|
+
<hr style="${styles.divider}" />
|
|
1044
|
+
|
|
1045
|
+
<p style="${styles.paragraph}; font-size: 13px; color: ${colors.textMuted};">
|
|
1046
|
+
<strong>Didn't request this?</strong><br>
|
|
1047
|
+
Your password remains unchanged. If you're concerned about your account
|
|
1048
|
+
security, please contact our support team immediately.
|
|
1049
|
+
</p>
|
|
1050
|
+
</div>
|
|
1051
|
+
|
|
1052
|
+
<!-- Footer -->
|
|
1053
|
+
<div style="${styles.footer}">
|
|
1054
|
+
<p style="${styles.footerText}">
|
|
1055
|
+
This is an automated message \u2014 please do not reply.<br>
|
|
1056
|
+
\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} All rights reserved.
|
|
1057
|
+
</p>
|
|
1058
|
+
</div>
|
|
1059
|
+
</div>
|
|
1060
|
+
</div>
|
|
1061
|
+
</body>
|
|
1062
|
+
</html>
|
|
1063
|
+
`;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
699
1066
|
// src/express/auth.routes.ts
|
|
700
1067
|
function createAuthRouter(options = {}) {
|
|
701
1068
|
const googleClientId = process.env.GOOGLE_CLIENT_ID;
|
|
@@ -717,7 +1084,7 @@ function createAuthRouter(options = {}) {
|
|
|
717
1084
|
// default: secure in prod
|
|
718
1085
|
domain: options.cookie?.domain ?? void 0,
|
|
719
1086
|
path: options.cookie?.path ?? "/",
|
|
720
|
-
maxAgeMs: options.cookie?.maxAgeMs ?? 24 * 60 * 60 * 1e3
|
|
1087
|
+
maxAgeMs: options.cookie?.maxAgeMs ?? 30 * 24 * 60 * 60 * 1e3
|
|
721
1088
|
};
|
|
722
1089
|
r.use(express.json());
|
|
723
1090
|
r.use(express.urlencoded({ extended: true }));
|
|
@@ -774,6 +1141,7 @@ function createAuthRouter(options = {}) {
|
|
|
774
1141
|
projectId,
|
|
775
1142
|
metadata
|
|
776
1143
|
} = req.body || {};
|
|
1144
|
+
const COMPANY_NAME = process.env.COMPANY_NAME;
|
|
777
1145
|
try {
|
|
778
1146
|
const kcUser = await authAdmin.createUserInRealm({
|
|
779
1147
|
username: emailAddress,
|
|
@@ -802,10 +1170,21 @@ function createAuthRouter(options = {}) {
|
|
|
802
1170
|
emailService: email,
|
|
803
1171
|
user,
|
|
804
1172
|
subject: "Verify your email",
|
|
805
|
-
html: buildVerificationTemplate(
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
)
|
|
1173
|
+
// html: buildVerificationTemplate(
|
|
1174
|
+
// email.sign({ userId: kcUser.id, email: kcUser.email }),
|
|
1175
|
+
// options,
|
|
1176
|
+
// ),
|
|
1177
|
+
html: buildVerificationEmailTemplate({
|
|
1178
|
+
firstName: user.firstName,
|
|
1179
|
+
verificationUrl: `${getFrontendBaseUrl(options)}/verify-email?token=${email.sign(
|
|
1180
|
+
{
|
|
1181
|
+
userId: user.id,
|
|
1182
|
+
email: user.email
|
|
1183
|
+
}
|
|
1184
|
+
)}`,
|
|
1185
|
+
expiresIn: "1 hour"
|
|
1186
|
+
}),
|
|
1187
|
+
from: COMPANY_NAME
|
|
809
1188
|
});
|
|
810
1189
|
if (emailResult.rateLimited) {
|
|
811
1190
|
return res.status(429).json({
|
|
@@ -870,6 +1249,7 @@ function createAuthRouter(options = {}) {
|
|
|
870
1249
|
"/resend-verification-email",
|
|
871
1250
|
validateResendEmail,
|
|
872
1251
|
async (req, res) => {
|
|
1252
|
+
const COMPANY_NAME = process.env.COMPANY_NAME;
|
|
873
1253
|
const user = await OrgUser.findOne({ email: req.body.email });
|
|
874
1254
|
if (!user)
|
|
875
1255
|
return res.status(404).json({ ok: false, error: "User not found" });
|
|
@@ -885,7 +1265,18 @@ function createAuthRouter(options = {}) {
|
|
|
885
1265
|
emailService: email,
|
|
886
1266
|
user,
|
|
887
1267
|
subject: "Verify your email",
|
|
888
|
-
html: buildVerificationTemplate(token, options)
|
|
1268
|
+
// html: buildVerificationTemplate(token, options),
|
|
1269
|
+
html: buildVerificationEmailTemplate({
|
|
1270
|
+
firstName: user.firstName,
|
|
1271
|
+
verificationUrl: `${getFrontendBaseUrl(options)}/verify-email?token=${email.sign(
|
|
1272
|
+
{
|
|
1273
|
+
userId: user.id,
|
|
1274
|
+
email: user.email
|
|
1275
|
+
}
|
|
1276
|
+
)}`,
|
|
1277
|
+
expiresIn: "1 hour"
|
|
1278
|
+
}),
|
|
1279
|
+
from: COMPANY_NAME
|
|
889
1280
|
});
|
|
890
1281
|
if (resendResult.rateLimited) {
|
|
891
1282
|
return res.status(429).json({
|
|
@@ -898,6 +1289,7 @@ function createAuthRouter(options = {}) {
|
|
|
898
1289
|
}
|
|
899
1290
|
);
|
|
900
1291
|
r.post("/forgot-password", validateResendEmail, async (req, res) => {
|
|
1292
|
+
const COMPANY_NAME = process.env.COMPANY_NAME;
|
|
901
1293
|
const user = await OrgUser.findOne({ email: req.body.email });
|
|
902
1294
|
if (!user)
|
|
903
1295
|
return res.status(404).json({ ok: false, error: "User not found" });
|
|
@@ -914,7 +1306,18 @@ function createAuthRouter(options = {}) {
|
|
|
914
1306
|
emailService: email,
|
|
915
1307
|
user,
|
|
916
1308
|
subject: "Reset password",
|
|
917
|
-
html: buildResetTemplate(resetToken, options)
|
|
1309
|
+
// html: buildResetTemplate(resetToken, options),
|
|
1310
|
+
html: buildResetPasswordEmailTemplate({
|
|
1311
|
+
firstName: user.firstName,
|
|
1312
|
+
resetUrl: `${getFrontendBaseUrl(options)}/reset-password?token=${email.sign(
|
|
1313
|
+
{
|
|
1314
|
+
userId: user.id,
|
|
1315
|
+
email: user.email
|
|
1316
|
+
}
|
|
1317
|
+
)}`,
|
|
1318
|
+
expiresIn: "1 hour"
|
|
1319
|
+
}),
|
|
1320
|
+
from: COMPANY_NAME
|
|
918
1321
|
});
|
|
919
1322
|
if (resetResult.rateLimited) {
|
|
920
1323
|
return res.status(429).json({
|
|
@@ -927,9 +1330,16 @@ function createAuthRouter(options = {}) {
|
|
|
927
1330
|
});
|
|
928
1331
|
r.post("/reset-password", validateResetPassword, async (req, res) => {
|
|
929
1332
|
const { token, newPassword } = req.body || {};
|
|
1333
|
+
if (!token || !newPassword) {
|
|
1334
|
+
return res.status(400).json({
|
|
1335
|
+
ok: false,
|
|
1336
|
+
error: "Token and new password are required",
|
|
1337
|
+
code: "MISSING_FIELDS"
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
930
1340
|
try {
|
|
931
1341
|
const payload = email.verify(token);
|
|
932
|
-
const user = await OrgUser.findOne({
|
|
1342
|
+
const user = await OrgUser.findOne({ id: payload.userId });
|
|
933
1343
|
if (!user) {
|
|
934
1344
|
return res.status(404).json({ ok: false, error: "User not found" });
|
|
935
1345
|
}
|
|
@@ -1297,8 +1707,6 @@ function setAuthCookies(res, tokens, cookie) {
|
|
|
1297
1707
|
if (cookie.domain) {
|
|
1298
1708
|
base.domain = cookie.domain;
|
|
1299
1709
|
}
|
|
1300
|
-
console.log(cookie, "cookie");
|
|
1301
|
-
console.log(base, "base");
|
|
1302
1710
|
if (tokens?.access_token) {
|
|
1303
1711
|
res.cookie("access_token", tokens.access_token, base);
|
|
1304
1712
|
}
|
|
@@ -1322,12 +1730,6 @@ function respondWithKeycloakError(res, err, fallback, status = 400) {
|
|
|
1322
1730
|
const description = err?.response?.data?.error_description || err?.response?.data?.errorMessage || err?.message || fallback;
|
|
1323
1731
|
return res.status(status).json({ ok: false, error: description });
|
|
1324
1732
|
}
|
|
1325
|
-
function buildVerificationTemplate(token, options) {
|
|
1326
|
-
return `<a href="${getFrontendBaseUrl(options)}/auth/verify-email?token=${token}">Verify</a>`;
|
|
1327
|
-
}
|
|
1328
|
-
function buildResetTemplate(token, options) {
|
|
1329
|
-
return `<a href="${getFrontendBaseUrl(options)}/auth/reset-password?token=${token}">Reset</a>`;
|
|
1330
|
-
}
|
|
1331
1733
|
function getFrontendBaseUrl(options) {
|
|
1332
1734
|
if (options.frontendBaseUrl)
|
|
1333
1735
|
return options.frontendBaseUrl.replace(/\/$/, "");
|
|
@@ -1339,13 +1741,14 @@ async function sendRateLimitedEmail({
|
|
|
1339
1741
|
emailService,
|
|
1340
1742
|
user,
|
|
1341
1743
|
subject,
|
|
1342
|
-
html
|
|
1744
|
+
html,
|
|
1745
|
+
from
|
|
1343
1746
|
}) {
|
|
1344
1747
|
const can = emailService.canSend(user?.lastEmailSent || []);
|
|
1345
1748
|
if (!can.ok) {
|
|
1346
1749
|
return { rateLimited: true, waitMs: can.waitMs };
|
|
1347
1750
|
}
|
|
1348
|
-
await emailService.send(user.email, subject, html);
|
|
1751
|
+
await emailService.send(user.email, subject, html, from);
|
|
1349
1752
|
user.lastEmailSent = [...user.lastEmailSent || [], /* @__PURE__ */ new Date()];
|
|
1350
1753
|
await user.save();
|
|
1351
1754
|
return { rateLimited: false };
|
|
@@ -1366,7 +1769,7 @@ function generateTokens(user) {
|
|
|
1366
1769
|
type: "user"
|
|
1367
1770
|
};
|
|
1368
1771
|
const accessToken = jwt4.sign(accessPayload, process.env.JWT_SECRET, {
|
|
1369
|
-
expiresIn: "
|
|
1772
|
+
expiresIn: "1d"
|
|
1370
1773
|
});
|
|
1371
1774
|
const refreshToken = jwt4.sign(
|
|
1372
1775
|
{ sub: user._id.toString() },
|
|
@@ -1402,13 +1805,61 @@ function createDashboardRouter(options) {
|
|
|
1402
1805
|
}
|
|
1403
1806
|
|
|
1404
1807
|
// src/express/email.routes.ts
|
|
1405
|
-
import { Router as Router3 } from "express";
|
|
1808
|
+
import express3, { Router as Router3 } from "express";
|
|
1406
1809
|
function createEmailRouter(options) {
|
|
1407
1810
|
const r = Router3();
|
|
1811
|
+
const emailService = new EmailService();
|
|
1812
|
+
r.use(express3.json());
|
|
1813
|
+
r.use(express3.urlencoded({ extended: true }));
|
|
1408
1814
|
r.get(
|
|
1409
1815
|
"/verify",
|
|
1410
1816
|
(req, res) => res.json({ ok: true, token: req.query.token })
|
|
1411
1817
|
);
|
|
1818
|
+
r.post("/send", async (req, res) => {
|
|
1819
|
+
try {
|
|
1820
|
+
const { userId, to, subject, html, from } = req.body ?? {};
|
|
1821
|
+
if (!to || !subject || !html) {
|
|
1822
|
+
return res.status(400).json({
|
|
1823
|
+
ok: false,
|
|
1824
|
+
error: "BAD_REQUEST",
|
|
1825
|
+
message: "`to`, `subject`, and `html` are required."
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
if (userId) {
|
|
1829
|
+
const user = await OrgUser.findOne({ id: userId }).lean();
|
|
1830
|
+
if (!user) {
|
|
1831
|
+
return res.status(404).json({
|
|
1832
|
+
ok: false,
|
|
1833
|
+
error: "NOT_FOUND",
|
|
1834
|
+
message: "User not found."
|
|
1835
|
+
});
|
|
1836
|
+
}
|
|
1837
|
+
const can = emailService.canSend(user?.lastEmailSent || []);
|
|
1838
|
+
if (!can.ok) {
|
|
1839
|
+
return res.status(429).json({
|
|
1840
|
+
ok: false,
|
|
1841
|
+
error: can.reason,
|
|
1842
|
+
waitMs: can.waitMs,
|
|
1843
|
+
message: "Too many emails sent recently. Please retry later."
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
await emailService.send(to, subject, html, from);
|
|
1848
|
+
if (userId) {
|
|
1849
|
+
await OrgUser.updateOne(
|
|
1850
|
+
{ id: userId },
|
|
1851
|
+
{ $push: { lastEmailSent: /* @__PURE__ */ new Date() } }
|
|
1852
|
+
);
|
|
1853
|
+
}
|
|
1854
|
+
return res.json({ ok: true });
|
|
1855
|
+
} catch (err) {
|
|
1856
|
+
return res.status(500).json({
|
|
1857
|
+
ok: false,
|
|
1858
|
+
error: "INTERNAL",
|
|
1859
|
+
message: err?.message ?? "Error"
|
|
1860
|
+
});
|
|
1861
|
+
}
|
|
1862
|
+
});
|
|
1412
1863
|
return r;
|
|
1413
1864
|
}
|
|
1414
1865
|
|
|
@@ -1515,7 +1966,7 @@ function createProjectsRouter(options) {
|
|
|
1515
1966
|
// src/express/admin/admin.routes.ts
|
|
1516
1967
|
import bcrypt3 from "bcryptjs";
|
|
1517
1968
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
1518
|
-
import
|
|
1969
|
+
import express4, { Router as Router5 } from "express";
|
|
1519
1970
|
|
|
1520
1971
|
// src/middlewares/requireRole.ts
|
|
1521
1972
|
function requireRole(...roles) {
|
|
@@ -1578,8 +2029,8 @@ function resolveProjectId(req) {
|
|
|
1578
2029
|
}
|
|
1579
2030
|
function createAdminRouter(_options = {}) {
|
|
1580
2031
|
const r = Router5();
|
|
1581
|
-
r.use(
|
|
1582
|
-
r.use(
|
|
2032
|
+
r.use(express4.json());
|
|
2033
|
+
r.use(express4.urlencoded({ extended: true }));
|
|
1583
2034
|
const adminGuards = [requireAuth(), requireRole("platform_admin")];
|
|
1584
2035
|
r.post(
|
|
1585
2036
|
"/users",
|