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.cjs
CHANGED
|
@@ -100,8 +100,8 @@ function loadConfig() {
|
|
|
100
100
|
cookies: {
|
|
101
101
|
domain: process.env.COOKIE_DOMAIN,
|
|
102
102
|
secure: (process.env.COOKIE_SECURE || "true") === "true",
|
|
103
|
-
accessTtlMs: 24 * 60 * 60 * 1e3,
|
|
104
|
-
refreshTtlMs:
|
|
103
|
+
accessTtlMs: 7 * 24 * 60 * 60 * 1e3,
|
|
104
|
+
refreshTtlMs: 30 * 24 * 60 * 60 * 1e3
|
|
105
105
|
},
|
|
106
106
|
oidc: {
|
|
107
107
|
jwtSecret: process.env.JWT_SECRET
|
|
@@ -642,7 +642,7 @@ var AuthAdminService = class {
|
|
|
642
642
|
}
|
|
643
643
|
async updateUserPassword(userId, newPassword) {
|
|
644
644
|
const hashed = await import_bcrypt.default.hash(newPassword, 10);
|
|
645
|
-
await OrgUser.findOneAndUpdate({ id: userId }, {
|
|
645
|
+
await OrgUser.findOneAndUpdate({ id: userId }, { passwordHash: hashed });
|
|
646
646
|
}
|
|
647
647
|
// -------------------------------------------------------------------
|
|
648
648
|
// ADMIN TOKEN (self-issued JWT)
|
|
@@ -657,11 +657,11 @@ var AuthAdminService = class {
|
|
|
657
657
|
system: true
|
|
658
658
|
};
|
|
659
659
|
const accessToken = import_jsonwebtoken2.default.sign(payload, process.env.JWT_SECRET, {
|
|
660
|
-
expiresIn: "
|
|
660
|
+
expiresIn: "1d"
|
|
661
661
|
});
|
|
662
662
|
this.token = {
|
|
663
663
|
accessToken,
|
|
664
|
-
exp: now +
|
|
664
|
+
exp: now + 84800
|
|
665
665
|
};
|
|
666
666
|
return this.token.accessToken;
|
|
667
667
|
}
|
|
@@ -686,7 +686,7 @@ var EmailService = class {
|
|
|
686
686
|
}
|
|
687
687
|
});
|
|
688
688
|
}
|
|
689
|
-
sign(payload, ttlSec = 60 * 60 * 24) {
|
|
689
|
+
sign(payload, ttlSec = 60 * 60 * 24 * 30) {
|
|
690
690
|
return import_jsonwebtoken3.default.sign(payload, process.env.EMAIL_JWT_SECRET, {
|
|
691
691
|
expiresIn: ttlSec
|
|
692
692
|
});
|
|
@@ -694,11 +694,10 @@ var EmailService = class {
|
|
|
694
694
|
verify(token) {
|
|
695
695
|
return import_jsonwebtoken3.default.verify(token, process.env.EMAIL_JWT_SECRET);
|
|
696
696
|
}
|
|
697
|
-
async send(to, subject, html) {
|
|
698
|
-
console.log("[EmailService] Attempting to send:", { to, subject });
|
|
697
|
+
async send(to, subject, html, from) {
|
|
699
698
|
try {
|
|
700
699
|
const info = await this.transporter.sendMail({
|
|
701
|
-
from: process.env.EMAIL_FROM,
|
|
700
|
+
from: from ? `${from} ` + process.env.EMAIL_FROM : process.env.EMAIL_FROM,
|
|
702
701
|
to,
|
|
703
702
|
subject,
|
|
704
703
|
html
|
|
@@ -723,18 +722,6 @@ var EmailService = class {
|
|
|
723
722
|
}
|
|
724
723
|
}
|
|
725
724
|
canSend(lastEmailSent) {
|
|
726
|
-
console.log(
|
|
727
|
-
process.env.EMAIL_PASSWORD,
|
|
728
|
-
"pssword",
|
|
729
|
-
process.env.EMAIL_USER,
|
|
730
|
-
"user",
|
|
731
|
-
process.env.EMAIL_SECURE,
|
|
732
|
-
"secure",
|
|
733
|
-
process.env.EMAIL_PORT,
|
|
734
|
-
"porat",
|
|
735
|
-
process.env.EMAIL_HOST,
|
|
736
|
-
"hosat"
|
|
737
|
-
);
|
|
738
725
|
const now = Date.now();
|
|
739
726
|
const windowStart = now - this.WINDOW_MINUTES * 60 * 1e3;
|
|
740
727
|
const emailsInWindow = (lastEmailSent || []).map((d) => new Date(d)).filter((d) => d.getTime() >= windowStart);
|
|
@@ -748,6 +735,386 @@ var EmailService = class {
|
|
|
748
735
|
}
|
|
749
736
|
};
|
|
750
737
|
|
|
738
|
+
// src/templates/email.templates.ts
|
|
739
|
+
var colors = {
|
|
740
|
+
background: "#0a0a0a",
|
|
741
|
+
cardBackground: "#111111",
|
|
742
|
+
cardBorder: "#1a1a1a",
|
|
743
|
+
accent: "#ffffff",
|
|
744
|
+
accentMuted: "rgba(255, 255, 255, 0.9)",
|
|
745
|
+
textPrimary: "#ffffff",
|
|
746
|
+
textSecondary: "rgba(255, 255, 255, 0.7)",
|
|
747
|
+
textMuted: "rgba(255, 255, 255, 0.5)",
|
|
748
|
+
divider: "rgba(255, 255, 255, 0.1)",
|
|
749
|
+
subtle: "#161616",
|
|
750
|
+
highlight: "rgba(255, 255, 255, 0.05)"
|
|
751
|
+
};
|
|
752
|
+
var styles = {
|
|
753
|
+
wrapper: `
|
|
754
|
+
margin: 0;
|
|
755
|
+
padding: 40px 20px;
|
|
756
|
+
background-color: ${colors.background};
|
|
757
|
+
background-image:
|
|
758
|
+
radial-gradient(ellipse at top, rgba(255,255,255,0.03) 0%, transparent 50%),
|
|
759
|
+
radial-gradient(ellipse at bottom, rgba(255,255,255,0.02) 0%, transparent 50%);
|
|
760
|
+
min-height: 100vh;
|
|
761
|
+
`,
|
|
762
|
+
container: `
|
|
763
|
+
max-width: 520px;
|
|
764
|
+
margin: 0 auto;
|
|
765
|
+
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
766
|
+
color: ${colors.textPrimary};
|
|
767
|
+
line-height: 1.7;
|
|
768
|
+
`,
|
|
769
|
+
card: `
|
|
770
|
+
background-color: ${colors.cardBackground};
|
|
771
|
+
border: 1px solid ${colors.cardBorder};
|
|
772
|
+
border-radius: 16px;
|
|
773
|
+
overflow: hidden;
|
|
774
|
+
box-shadow:
|
|
775
|
+
0 0 0 1px rgba(255,255,255,0.05),
|
|
776
|
+
0 20px 50px -20px rgba(0,0,0,0.5),
|
|
777
|
+
0 30px 60px -30px rgba(0,0,0,0.3);
|
|
778
|
+
`,
|
|
779
|
+
header: `
|
|
780
|
+
padding: 48px 40px 32px;
|
|
781
|
+
text-align: center;
|
|
782
|
+
border-bottom: 1px solid ${colors.divider};
|
|
783
|
+
`,
|
|
784
|
+
iconWrapper: `
|
|
785
|
+
width: 64px;
|
|
786
|
+
height: 64px;
|
|
787
|
+
margin: 0 auto 24px;
|
|
788
|
+
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%);
|
|
789
|
+
border: 1px solid rgba(255,255,255,0.1);
|
|
790
|
+
border-radius: 16px;
|
|
791
|
+
display: flex;
|
|
792
|
+
align-items: center;
|
|
793
|
+
justify-content: center;
|
|
794
|
+
font-size: 28px;
|
|
795
|
+
`,
|
|
796
|
+
headerTitle: `
|
|
797
|
+
color: ${colors.textPrimary};
|
|
798
|
+
margin: 0;
|
|
799
|
+
font-size: 24px;
|
|
800
|
+
font-weight: 600;
|
|
801
|
+
letter-spacing: -0.5px;
|
|
802
|
+
`,
|
|
803
|
+
headerSubtitle: `
|
|
804
|
+
color: ${colors.textMuted};
|
|
805
|
+
margin: 8px 0 0;
|
|
806
|
+
font-size: 14px;
|
|
807
|
+
font-weight: 400;
|
|
808
|
+
`,
|
|
809
|
+
body: `
|
|
810
|
+
padding: 40px;
|
|
811
|
+
`,
|
|
812
|
+
greeting: `
|
|
813
|
+
margin: 0 0 24px;
|
|
814
|
+
color: ${colors.textPrimary};
|
|
815
|
+
font-size: 18px;
|
|
816
|
+
font-weight: 500;
|
|
817
|
+
`,
|
|
818
|
+
paragraph: `
|
|
819
|
+
margin: 0 0 20px;
|
|
820
|
+
color: ${colors.textSecondary};
|
|
821
|
+
font-size: 15px;
|
|
822
|
+
line-height: 1.7;
|
|
823
|
+
`,
|
|
824
|
+
buttonWrapper: `
|
|
825
|
+
text-align: center;
|
|
826
|
+
margin: 32px 0;
|
|
827
|
+
`,
|
|
828
|
+
button: `
|
|
829
|
+
display: inline-block;
|
|
830
|
+
background-color: ${colors.accent};
|
|
831
|
+
color: #000000 !important;
|
|
832
|
+
text-decoration: none;
|
|
833
|
+
padding: 14px 36px;
|
|
834
|
+
border-radius: 8px;
|
|
835
|
+
font-weight: 600;
|
|
836
|
+
font-size: 14px;
|
|
837
|
+
letter-spacing: 0.3px;
|
|
838
|
+
transition: all 0.2s ease;
|
|
839
|
+
`,
|
|
840
|
+
secondaryButton: `
|
|
841
|
+
display: inline-block;
|
|
842
|
+
background-color: transparent;
|
|
843
|
+
color: ${colors.textPrimary} !important;
|
|
844
|
+
text-decoration: none;
|
|
845
|
+
padding: 12px 28px;
|
|
846
|
+
border-radius: 8px;
|
|
847
|
+
font-weight: 500;
|
|
848
|
+
font-size: 14px;
|
|
849
|
+
border: 1px solid ${colors.divider};
|
|
850
|
+
`,
|
|
851
|
+
infoCard: `
|
|
852
|
+
background-color: ${colors.subtle};
|
|
853
|
+
border: 1px solid ${colors.divider};
|
|
854
|
+
border-radius: 12px;
|
|
855
|
+
padding: 20px 24px;
|
|
856
|
+
margin: 28px 0;
|
|
857
|
+
`,
|
|
858
|
+
infoCardTitle: `
|
|
859
|
+
margin: 0 0 12px;
|
|
860
|
+
color: ${colors.textPrimary};
|
|
861
|
+
font-size: 13px;
|
|
862
|
+
font-weight: 600;
|
|
863
|
+
text-transform: uppercase;
|
|
864
|
+
letter-spacing: 0.5px;
|
|
865
|
+
`,
|
|
866
|
+
infoCardText: `
|
|
867
|
+
margin: 0;
|
|
868
|
+
color: ${colors.textSecondary};
|
|
869
|
+
font-size: 14px;
|
|
870
|
+
line-height: 1.6;
|
|
871
|
+
`,
|
|
872
|
+
warningCard: `
|
|
873
|
+
background: linear-gradient(135deg, rgba(255,180,0,0.1) 0%, rgba(255,140,0,0.05) 100%);
|
|
874
|
+
border: 1px solid rgba(255,180,0,0.2);
|
|
875
|
+
border-radius: 12px;
|
|
876
|
+
padding: 20px 24px;
|
|
877
|
+
margin: 28px 0;
|
|
878
|
+
`,
|
|
879
|
+
warningCardTitle: `
|
|
880
|
+
margin: 0 0 12px;
|
|
881
|
+
color: #ffc107;
|
|
882
|
+
font-size: 13px;
|
|
883
|
+
font-weight: 600;
|
|
884
|
+
text-transform: uppercase;
|
|
885
|
+
letter-spacing: 0.5px;
|
|
886
|
+
`,
|
|
887
|
+
warningCardText: `
|
|
888
|
+
margin: 0;
|
|
889
|
+
color: rgba(255,255,255,0.7);
|
|
890
|
+
font-size: 14px;
|
|
891
|
+
line-height: 1.6;
|
|
892
|
+
`,
|
|
893
|
+
successCard: `
|
|
894
|
+
background: linear-gradient(135deg, rgba(0,255,150,0.1) 0%, rgba(0,200,100,0.05) 100%);
|
|
895
|
+
border: 1px solid rgba(0,255,150,0.2);
|
|
896
|
+
border-radius: 12px;
|
|
897
|
+
padding: 20px 24px;
|
|
898
|
+
margin: 28px 0;
|
|
899
|
+
`,
|
|
900
|
+
successCardTitle: `
|
|
901
|
+
margin: 0 0 12px;
|
|
902
|
+
color: #00ff96;
|
|
903
|
+
font-size: 13px;
|
|
904
|
+
font-weight: 600;
|
|
905
|
+
text-transform: uppercase;
|
|
906
|
+
letter-spacing: 0.5px;
|
|
907
|
+
`,
|
|
908
|
+
linkSection: `
|
|
909
|
+
margin: 32px 0;
|
|
910
|
+
padding: 20px;
|
|
911
|
+
background-color: ${colors.subtle};
|
|
912
|
+
border-radius: 8px;
|
|
913
|
+
border: 1px solid ${colors.divider};
|
|
914
|
+
`,
|
|
915
|
+
linkLabel: `
|
|
916
|
+
margin: 0 0 8px;
|
|
917
|
+
color: ${colors.textMuted};
|
|
918
|
+
font-size: 12px;
|
|
919
|
+
text-transform: uppercase;
|
|
920
|
+
letter-spacing: 0.5px;
|
|
921
|
+
`,
|
|
922
|
+
linkText: `
|
|
923
|
+
word-break: break-all;
|
|
924
|
+
color: ${colors.textSecondary};
|
|
925
|
+
font-size: 13px;
|
|
926
|
+
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', monospace;
|
|
927
|
+
margin: 0;
|
|
928
|
+
`,
|
|
929
|
+
divider: `
|
|
930
|
+
border: none;
|
|
931
|
+
border-top: 1px solid ${colors.divider};
|
|
932
|
+
margin: 32px 0;
|
|
933
|
+
`,
|
|
934
|
+
footer: `
|
|
935
|
+
padding: 24px 40px 32px;
|
|
936
|
+
text-align: center;
|
|
937
|
+
border-top: 1px solid ${colors.divider};
|
|
938
|
+
`,
|
|
939
|
+
footerText: `
|
|
940
|
+
margin: 0;
|
|
941
|
+
color: ${colors.textMuted};
|
|
942
|
+
font-size: 12px;
|
|
943
|
+
line-height: 1.8;
|
|
944
|
+
`,
|
|
945
|
+
footerLink: `
|
|
946
|
+
color: ${colors.textSecondary};
|
|
947
|
+
text-decoration: none;
|
|
948
|
+
`,
|
|
949
|
+
badge: `
|
|
950
|
+
display: inline-block;
|
|
951
|
+
background-color: rgba(255,255,255,0.1);
|
|
952
|
+
color: ${colors.textSecondary};
|
|
953
|
+
padding: 4px 12px;
|
|
954
|
+
border-radius: 20px;
|
|
955
|
+
font-size: 12px;
|
|
956
|
+
font-weight: 500;
|
|
957
|
+
letter-spacing: 0.3px;
|
|
958
|
+
`,
|
|
959
|
+
listItem: `
|
|
960
|
+
color: ${colors.textSecondary};
|
|
961
|
+
font-size: 14px;
|
|
962
|
+
margin: 8px 0;
|
|
963
|
+
padding-left: 8px;
|
|
964
|
+
`,
|
|
965
|
+
metaRow: `
|
|
966
|
+
display: flex;
|
|
967
|
+
justify-content: space-between;
|
|
968
|
+
padding: 12px 0;
|
|
969
|
+
border-bottom: 1px solid ${colors.divider};
|
|
970
|
+
`,
|
|
971
|
+
metaLabel: `
|
|
972
|
+
color: ${colors.textMuted};
|
|
973
|
+
font-size: 13px;
|
|
974
|
+
`,
|
|
975
|
+
metaValue: `
|
|
976
|
+
color: ${colors.textPrimary};
|
|
977
|
+
font-size: 13px;
|
|
978
|
+
font-weight: 500;
|
|
979
|
+
`
|
|
980
|
+
};
|
|
981
|
+
function buildVerificationEmailTemplate(data) {
|
|
982
|
+
const { firstName, verificationUrl, expiresIn } = data;
|
|
983
|
+
return `
|
|
984
|
+
<!DOCTYPE html>
|
|
985
|
+
<html lang="en">
|
|
986
|
+
<head>
|
|
987
|
+
<meta charset="UTF-8">
|
|
988
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
989
|
+
<meta name="color-scheme" content="dark">
|
|
990
|
+
<meta name="supported-color-schemes" content="dark">
|
|
991
|
+
<title>Verify Your Email</title>
|
|
992
|
+
<!--[if mso]>
|
|
993
|
+
<style type="text/css">
|
|
994
|
+
body, table, td {font-family: Arial, Helvetica, sans-serif !important;}
|
|
995
|
+
</style>
|
|
996
|
+
<![endif]-->
|
|
997
|
+
</head>
|
|
998
|
+
<body style="${styles.wrapper}">
|
|
999
|
+
<div style="${styles.container}">
|
|
1000
|
+
<div style="${styles.card}">
|
|
1001
|
+
<!-- Header -->
|
|
1002
|
+
<div style="${styles.header}">
|
|
1003
|
+
<div style="${styles.iconWrapper}">
|
|
1004
|
+
\u2709\uFE0F
|
|
1005
|
+
</div>
|
|
1006
|
+
<h1 style="${styles.headerTitle}">Verify your email</h1>
|
|
1007
|
+
<p style="${styles.headerSubtitle}">One quick step to get started</p>
|
|
1008
|
+
</div>
|
|
1009
|
+
|
|
1010
|
+
<!-- Body -->
|
|
1011
|
+
<div style="${styles.body}">
|
|
1012
|
+
<p style="${styles.greeting}">Hi ${firstName},</p>
|
|
1013
|
+
|
|
1014
|
+
<p style="${styles.paragraph}">
|
|
1015
|
+
Thanks for signing up. To complete your registration and unlock all features,
|
|
1016
|
+
please verify your email address by clicking the button below.
|
|
1017
|
+
</p>
|
|
1018
|
+
|
|
1019
|
+
<div style="${styles.buttonWrapper}">
|
|
1020
|
+
<a href="${verificationUrl}" style="${styles.button}" target="_blank">
|
|
1021
|
+
Verify Email Address
|
|
1022
|
+
</a>
|
|
1023
|
+
</div>
|
|
1024
|
+
|
|
1025
|
+
<div style="${styles.infoCard}">
|
|
1026
|
+
<p style="${styles.infoCardTitle}">\u23F1 Time Sensitive</p>
|
|
1027
|
+
<p style="${styles.infoCardText}">
|
|
1028
|
+
This verification link will expire in <strong>${expiresIn}</strong>.
|
|
1029
|
+
If you didn't create an account, you can safely ignore this email.
|
|
1030
|
+
</p>
|
|
1031
|
+
</div>
|
|
1032
|
+
</div>
|
|
1033
|
+
|
|
1034
|
+
<!-- Footer -->
|
|
1035
|
+
<div style="${styles.footer}">
|
|
1036
|
+
<p style="${styles.footerText}">
|
|
1037
|
+
This is an automated message \u2014 please do not reply.<br>
|
|
1038
|
+
\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} All rights reserved.
|
|
1039
|
+
</p>
|
|
1040
|
+
</div>
|
|
1041
|
+
</div>
|
|
1042
|
+
</div>
|
|
1043
|
+
</body>
|
|
1044
|
+
</html>
|
|
1045
|
+
`;
|
|
1046
|
+
}
|
|
1047
|
+
function buildResetPasswordEmailTemplate(data) {
|
|
1048
|
+
const { firstName, resetUrl, expiresIn } = data;
|
|
1049
|
+
return `
|
|
1050
|
+
<!DOCTYPE html>
|
|
1051
|
+
<html lang="en">
|
|
1052
|
+
<head>
|
|
1053
|
+
<meta charset="UTF-8">
|
|
1054
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1055
|
+
<meta name="color-scheme" content="dark">
|
|
1056
|
+
<meta name="supported-color-schemes" content="dark">
|
|
1057
|
+
<title>Reset Your Password</title>
|
|
1058
|
+
</head>
|
|
1059
|
+
<body style="${styles.wrapper}">
|
|
1060
|
+
<div style="${styles.container}">
|
|
1061
|
+
<div style="${styles.card}">
|
|
1062
|
+
<!-- Header -->
|
|
1063
|
+
<div style="${styles.header}">
|
|
1064
|
+
<div style="${styles.iconWrapper}">
|
|
1065
|
+
\u{1F510}
|
|
1066
|
+
</div>
|
|
1067
|
+
<h1 style="${styles.headerTitle}">Reset your password</h1>
|
|
1068
|
+
<p style="${styles.headerSubtitle}">We received a reset request</p>
|
|
1069
|
+
</div>
|
|
1070
|
+
|
|
1071
|
+
<!-- Body -->
|
|
1072
|
+
<div style="${styles.body}">
|
|
1073
|
+
<p style="${styles.greeting}">Hi ${firstName},</p>
|
|
1074
|
+
|
|
1075
|
+
<p style="${styles.paragraph}">
|
|
1076
|
+
We received a request to reset the password for your account.
|
|
1077
|
+
Click the button below to create a new password.
|
|
1078
|
+
</p>
|
|
1079
|
+
|
|
1080
|
+
<div style="${styles.buttonWrapper}">
|
|
1081
|
+
<a href="${resetUrl}" style="${styles.button}" target="_blank">
|
|
1082
|
+
Reset Password
|
|
1083
|
+
</a>
|
|
1084
|
+
</div>
|
|
1085
|
+
|
|
1086
|
+
<div style="${styles.warningCard}">
|
|
1087
|
+
<p style="${styles.warningCardTitle}">\u26A0\uFE0F Security Notice</p>
|
|
1088
|
+
<p style="${styles.warningCardText}">
|
|
1089
|
+
\u2022 This link expires in <strong>${expiresIn}</strong><br>
|
|
1090
|
+
\u2022 This link can only be used once<br>
|
|
1091
|
+
\u2022 If you didn't request this, ignore this email
|
|
1092
|
+
</p>
|
|
1093
|
+
</div>
|
|
1094
|
+
|
|
1095
|
+
<hr style="${styles.divider}" />
|
|
1096
|
+
|
|
1097
|
+
<p style="${styles.paragraph}; font-size: 13px; color: ${colors.textMuted};">
|
|
1098
|
+
<strong>Didn't request this?</strong><br>
|
|
1099
|
+
Your password remains unchanged. If you're concerned about your account
|
|
1100
|
+
security, please contact our support team immediately.
|
|
1101
|
+
</p>
|
|
1102
|
+
</div>
|
|
1103
|
+
|
|
1104
|
+
<!-- Footer -->
|
|
1105
|
+
<div style="${styles.footer}">
|
|
1106
|
+
<p style="${styles.footerText}">
|
|
1107
|
+
This is an automated message \u2014 please do not reply.<br>
|
|
1108
|
+
\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} All rights reserved.
|
|
1109
|
+
</p>
|
|
1110
|
+
</div>
|
|
1111
|
+
</div>
|
|
1112
|
+
</div>
|
|
1113
|
+
</body>
|
|
1114
|
+
</html>
|
|
1115
|
+
`;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
751
1118
|
// src/express/auth.routes.ts
|
|
752
1119
|
function createAuthRouter(options = {}) {
|
|
753
1120
|
const googleClientId = process.env.GOOGLE_CLIENT_ID;
|
|
@@ -769,7 +1136,7 @@ function createAuthRouter(options = {}) {
|
|
|
769
1136
|
// default: secure in prod
|
|
770
1137
|
domain: options.cookie?.domain ?? void 0,
|
|
771
1138
|
path: options.cookie?.path ?? "/",
|
|
772
|
-
maxAgeMs: options.cookie?.maxAgeMs ?? 24 * 60 * 60 * 1e3
|
|
1139
|
+
maxAgeMs: options.cookie?.maxAgeMs ?? 30 * 24 * 60 * 60 * 1e3
|
|
773
1140
|
};
|
|
774
1141
|
r.use(import_express.default.json());
|
|
775
1142
|
r.use(import_express.default.urlencoded({ extended: true }));
|
|
@@ -826,6 +1193,7 @@ function createAuthRouter(options = {}) {
|
|
|
826
1193
|
projectId,
|
|
827
1194
|
metadata
|
|
828
1195
|
} = req.body || {};
|
|
1196
|
+
const COMPANY_NAME = process.env.COMPANY_NAME;
|
|
829
1197
|
try {
|
|
830
1198
|
const kcUser = await authAdmin.createUserInRealm({
|
|
831
1199
|
username: emailAddress,
|
|
@@ -854,10 +1222,21 @@ function createAuthRouter(options = {}) {
|
|
|
854
1222
|
emailService: email,
|
|
855
1223
|
user,
|
|
856
1224
|
subject: "Verify your email",
|
|
857
|
-
html: buildVerificationTemplate(
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
)
|
|
1225
|
+
// html: buildVerificationTemplate(
|
|
1226
|
+
// email.sign({ userId: kcUser.id, email: kcUser.email }),
|
|
1227
|
+
// options,
|
|
1228
|
+
// ),
|
|
1229
|
+
html: buildVerificationEmailTemplate({
|
|
1230
|
+
firstName: user.firstName,
|
|
1231
|
+
verificationUrl: `${getFrontendBaseUrl(options)}/verify-email?token=${email.sign(
|
|
1232
|
+
{
|
|
1233
|
+
userId: user.id,
|
|
1234
|
+
email: user.email
|
|
1235
|
+
}
|
|
1236
|
+
)}`,
|
|
1237
|
+
expiresIn: "1 hour"
|
|
1238
|
+
}),
|
|
1239
|
+
from: COMPANY_NAME
|
|
861
1240
|
});
|
|
862
1241
|
if (emailResult.rateLimited) {
|
|
863
1242
|
return res.status(429).json({
|
|
@@ -922,6 +1301,7 @@ function createAuthRouter(options = {}) {
|
|
|
922
1301
|
"/resend-verification-email",
|
|
923
1302
|
validateResendEmail,
|
|
924
1303
|
async (req, res) => {
|
|
1304
|
+
const COMPANY_NAME = process.env.COMPANY_NAME;
|
|
925
1305
|
const user = await OrgUser.findOne({ email: req.body.email });
|
|
926
1306
|
if (!user)
|
|
927
1307
|
return res.status(404).json({ ok: false, error: "User not found" });
|
|
@@ -937,7 +1317,18 @@ function createAuthRouter(options = {}) {
|
|
|
937
1317
|
emailService: email,
|
|
938
1318
|
user,
|
|
939
1319
|
subject: "Verify your email",
|
|
940
|
-
html: buildVerificationTemplate(token, options)
|
|
1320
|
+
// html: buildVerificationTemplate(token, options),
|
|
1321
|
+
html: buildVerificationEmailTemplate({
|
|
1322
|
+
firstName: user.firstName,
|
|
1323
|
+
verificationUrl: `${getFrontendBaseUrl(options)}/verify-email?token=${email.sign(
|
|
1324
|
+
{
|
|
1325
|
+
userId: user.id,
|
|
1326
|
+
email: user.email
|
|
1327
|
+
}
|
|
1328
|
+
)}`,
|
|
1329
|
+
expiresIn: "1 hour"
|
|
1330
|
+
}),
|
|
1331
|
+
from: COMPANY_NAME
|
|
941
1332
|
});
|
|
942
1333
|
if (resendResult.rateLimited) {
|
|
943
1334
|
return res.status(429).json({
|
|
@@ -950,6 +1341,7 @@ function createAuthRouter(options = {}) {
|
|
|
950
1341
|
}
|
|
951
1342
|
);
|
|
952
1343
|
r.post("/forgot-password", validateResendEmail, async (req, res) => {
|
|
1344
|
+
const COMPANY_NAME = process.env.COMPANY_NAME;
|
|
953
1345
|
const user = await OrgUser.findOne({ email: req.body.email });
|
|
954
1346
|
if (!user)
|
|
955
1347
|
return res.status(404).json({ ok: false, error: "User not found" });
|
|
@@ -966,7 +1358,18 @@ function createAuthRouter(options = {}) {
|
|
|
966
1358
|
emailService: email,
|
|
967
1359
|
user,
|
|
968
1360
|
subject: "Reset password",
|
|
969
|
-
html: buildResetTemplate(resetToken, options)
|
|
1361
|
+
// html: buildResetTemplate(resetToken, options),
|
|
1362
|
+
html: buildResetPasswordEmailTemplate({
|
|
1363
|
+
firstName: user.firstName,
|
|
1364
|
+
resetUrl: `${getFrontendBaseUrl(options)}/reset-password?token=${email.sign(
|
|
1365
|
+
{
|
|
1366
|
+
userId: user.id,
|
|
1367
|
+
email: user.email
|
|
1368
|
+
}
|
|
1369
|
+
)}`,
|
|
1370
|
+
expiresIn: "1 hour"
|
|
1371
|
+
}),
|
|
1372
|
+
from: COMPANY_NAME
|
|
970
1373
|
});
|
|
971
1374
|
if (resetResult.rateLimited) {
|
|
972
1375
|
return res.status(429).json({
|
|
@@ -979,9 +1382,16 @@ function createAuthRouter(options = {}) {
|
|
|
979
1382
|
});
|
|
980
1383
|
r.post("/reset-password", validateResetPassword, async (req, res) => {
|
|
981
1384
|
const { token, newPassword } = req.body || {};
|
|
1385
|
+
if (!token || !newPassword) {
|
|
1386
|
+
return res.status(400).json({
|
|
1387
|
+
ok: false,
|
|
1388
|
+
error: "Token and new password are required",
|
|
1389
|
+
code: "MISSING_FIELDS"
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
982
1392
|
try {
|
|
983
1393
|
const payload = email.verify(token);
|
|
984
|
-
const user = await OrgUser.findOne({
|
|
1394
|
+
const user = await OrgUser.findOne({ id: payload.userId });
|
|
985
1395
|
if (!user) {
|
|
986
1396
|
return res.status(404).json({ ok: false, error: "User not found" });
|
|
987
1397
|
}
|
|
@@ -1349,8 +1759,6 @@ function setAuthCookies(res, tokens, cookie) {
|
|
|
1349
1759
|
if (cookie.domain) {
|
|
1350
1760
|
base.domain = cookie.domain;
|
|
1351
1761
|
}
|
|
1352
|
-
console.log(cookie, "cookie");
|
|
1353
|
-
console.log(base, "base");
|
|
1354
1762
|
if (tokens?.access_token) {
|
|
1355
1763
|
res.cookie("access_token", tokens.access_token, base);
|
|
1356
1764
|
}
|
|
@@ -1374,12 +1782,6 @@ function respondWithKeycloakError(res, err, fallback, status = 400) {
|
|
|
1374
1782
|
const description = err?.response?.data?.error_description || err?.response?.data?.errorMessage || err?.message || fallback;
|
|
1375
1783
|
return res.status(status).json({ ok: false, error: description });
|
|
1376
1784
|
}
|
|
1377
|
-
function buildVerificationTemplate(token, options) {
|
|
1378
|
-
return `<a href="${getFrontendBaseUrl(options)}/auth/verify-email?token=${token}">Verify</a>`;
|
|
1379
|
-
}
|
|
1380
|
-
function buildResetTemplate(token, options) {
|
|
1381
|
-
return `<a href="${getFrontendBaseUrl(options)}/auth/reset-password?token=${token}">Reset</a>`;
|
|
1382
|
-
}
|
|
1383
1785
|
function getFrontendBaseUrl(options) {
|
|
1384
1786
|
if (options.frontendBaseUrl)
|
|
1385
1787
|
return options.frontendBaseUrl.replace(/\/$/, "");
|
|
@@ -1391,13 +1793,14 @@ async function sendRateLimitedEmail({
|
|
|
1391
1793
|
emailService,
|
|
1392
1794
|
user,
|
|
1393
1795
|
subject,
|
|
1394
|
-
html
|
|
1796
|
+
html,
|
|
1797
|
+
from
|
|
1395
1798
|
}) {
|
|
1396
1799
|
const can = emailService.canSend(user?.lastEmailSent || []);
|
|
1397
1800
|
if (!can.ok) {
|
|
1398
1801
|
return { rateLimited: true, waitMs: can.waitMs };
|
|
1399
1802
|
}
|
|
1400
|
-
await emailService.send(user.email, subject, html);
|
|
1803
|
+
await emailService.send(user.email, subject, html, from);
|
|
1401
1804
|
user.lastEmailSent = [...user.lastEmailSent || [], /* @__PURE__ */ new Date()];
|
|
1402
1805
|
await user.save();
|
|
1403
1806
|
return { rateLimited: false };
|
|
@@ -1418,7 +1821,7 @@ function generateTokens(user) {
|
|
|
1418
1821
|
type: "user"
|
|
1419
1822
|
};
|
|
1420
1823
|
const accessToken = import_jsonwebtoken4.default.sign(accessPayload, process.env.JWT_SECRET, {
|
|
1421
|
-
expiresIn: "
|
|
1824
|
+
expiresIn: "1d"
|
|
1422
1825
|
});
|
|
1423
1826
|
const refreshToken = import_jsonwebtoken4.default.sign(
|
|
1424
1827
|
{ sub: user._id.toString() },
|
|
@@ -1454,13 +1857,61 @@ function createDashboardRouter(options) {
|
|
|
1454
1857
|
}
|
|
1455
1858
|
|
|
1456
1859
|
// src/express/email.routes.ts
|
|
1457
|
-
var import_express3 = require("express");
|
|
1860
|
+
var import_express3 = __toESM(require("express"), 1);
|
|
1458
1861
|
function createEmailRouter(options) {
|
|
1459
1862
|
const r = (0, import_express3.Router)();
|
|
1863
|
+
const emailService = new EmailService();
|
|
1864
|
+
r.use(import_express3.default.json());
|
|
1865
|
+
r.use(import_express3.default.urlencoded({ extended: true }));
|
|
1460
1866
|
r.get(
|
|
1461
1867
|
"/verify",
|
|
1462
1868
|
(req, res) => res.json({ ok: true, token: req.query.token })
|
|
1463
1869
|
);
|
|
1870
|
+
r.post("/send", async (req, res) => {
|
|
1871
|
+
try {
|
|
1872
|
+
const { userId, to, subject, html, from } = req.body ?? {};
|
|
1873
|
+
if (!to || !subject || !html) {
|
|
1874
|
+
return res.status(400).json({
|
|
1875
|
+
ok: false,
|
|
1876
|
+
error: "BAD_REQUEST",
|
|
1877
|
+
message: "`to`, `subject`, and `html` are required."
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1880
|
+
if (userId) {
|
|
1881
|
+
const user = await OrgUser.findOne({ id: userId }).lean();
|
|
1882
|
+
if (!user) {
|
|
1883
|
+
return res.status(404).json({
|
|
1884
|
+
ok: false,
|
|
1885
|
+
error: "NOT_FOUND",
|
|
1886
|
+
message: "User not found."
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
const can = emailService.canSend(user?.lastEmailSent || []);
|
|
1890
|
+
if (!can.ok) {
|
|
1891
|
+
return res.status(429).json({
|
|
1892
|
+
ok: false,
|
|
1893
|
+
error: can.reason,
|
|
1894
|
+
waitMs: can.waitMs,
|
|
1895
|
+
message: "Too many emails sent recently. Please retry later."
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
await emailService.send(to, subject, html, from);
|
|
1900
|
+
if (userId) {
|
|
1901
|
+
await OrgUser.updateOne(
|
|
1902
|
+
{ id: userId },
|
|
1903
|
+
{ $push: { lastEmailSent: /* @__PURE__ */ new Date() } }
|
|
1904
|
+
);
|
|
1905
|
+
}
|
|
1906
|
+
return res.json({ ok: true });
|
|
1907
|
+
} catch (err) {
|
|
1908
|
+
return res.status(500).json({
|
|
1909
|
+
ok: false,
|
|
1910
|
+
error: "INTERNAL",
|
|
1911
|
+
message: err?.message ?? "Error"
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
});
|
|
1464
1915
|
return r;
|
|
1465
1916
|
}
|
|
1466
1917
|
|