@@ -11,6 +11,16 @@ import User from "../models/User";
1111import Response from "classes/Response" ;
1212import { generateToken } from "classes/JWT" ;
1313import Error from "../classes/Error" ;
14+ import crypto from "crypto" ;
15+
16+ const md5 = ( text : string ) => {
17+ return crypto . createHash ( "md5" ) . update ( text ) . digest ( ) ;
18+ } ;
19+
20+ interface IAccessCode {
21+ email : string ;
22+ token : string ;
23+ }
1424
1525class Authentication {
1626 private app : App ;
@@ -22,26 +32,44 @@ class Authentication {
2232
2333 // }
2434
25- async signinWithEmail ( {
26- email,
27- accessToken,
28- } : {
29- email : Email ;
30- accessToken ?: string ;
31- } ) : Promise < Response < undefined | { token : string } > > {
35+ // for encrypting AccessCode object into a string
36+ private encrypt ( text : string ) {
37+ let secretKey = md5 ( process . env . SECRET as string ) ;
38+ secretKey = Buffer . concat ( [ secretKey , secretKey . subarray ( 0 , 8 ) ] ) ;
39+ const cipher = crypto . createCipheriv ( "des-ede3" , secretKey , "" ) ;
40+ const encrypted = cipher . update ( text , "utf8" , "hex" ) ;
41+ return encrypted + cipher . final ( "hex" ) ;
42+ }
43+
44+ // for decrypting AccessCode string into an object
45+ private decrypt ( text : string ) {
46+ let secretKey = md5 ( process . env . SECRET as string ) ;
47+ secretKey = Buffer . concat ( [ secretKey , secretKey . subarray ( 0 , 8 ) ] ) ;
48+ const decipher = crypto . createDecipheriv ( "des-ede3" , secretKey , "" ) ;
49+ let decrypted = decipher . update ( text , "utf8" , "hex" ) ;
50+ decrypted += decipher . final ( ) ;
51+ return decrypted ;
52+ }
53+
54+ async signinWithEmail (
55+ accessCode : string ,
56+ ) : Promise < Response < undefined | { token : string } > > {
57+ const { email, token : accessToken } = JSON . parse (
58+ this . decrypt ( accessCode ) ,
59+ ) as IAccessCode ;
3260 if ( ! email )
3361 throw new Error ( {
3462 status : 400 ,
3563 message : "No email address was received" ,
3664 } ) ;
37- if ( ! email . isValid )
65+ if ( ! new Email ( email ) . isValid )
3866 throw new Error ( {
3967 status : 400 ,
4068 message : "Invalid email address provided" ,
4169 } ) ;
4270
4371 try {
44- const user = await User . findOne ( { email : email . email } ) ;
72+ const user = await User . findOne ( { email } ) ;
4573 if ( ! user ) {
4674 // is user cannot be found, then they are not allowed in.
4775 throw new Error ( {
@@ -53,28 +81,25 @@ class Authentication {
5381 try {
5482 // init access token
5583 const accessToken = uuidv4 ( ) ;
56- await User . findOneAndUpdate (
57- { email : email . email } ,
58- {
59- accessToken : {
84+ await user . update ( {
85+ accessToken : this . encrypt (
86+ JSON . stringify ( {
6087 value : accessToken ,
6188 createdAt : new Date ( ) . toISOString ( ) ,
62- used : false ,
63- } ,
64- } ,
65- ) ;
89+ exprired : false ,
90+ } ) ,
91+ ) ,
92+ } ) ;
6693
6794 // send email with magic link
6895 const link =
6996 process . env [ "DOMAIN" ] +
70- "/auth/email/verify?email=" +
71- email . email +
72- "&token=" +
73- accessToken ;
97+ "/auth/email/verify?token=" +
98+ this . encrypt ( JSON . stringify ( { email : email , token : accessToken } ) ) ;
7499 const template = new SigninTemplate ( ) ;
75100 await Mail . send ( await template . html ( { link } ) , {
76101 ...template . config ,
77- to : email . email ,
102+ to : email ,
78103 } ) ;
79104 return new Response ( {
80105 message : "An email has been sent to your inbox." ,
@@ -85,20 +110,42 @@ class Authentication {
85110 message : e ?. message || "An unknown error occured" ,
86111 } ) ;
87112 }
88- } else if (
89- user . accessToken ?. value == accessToken &&
90- new Date ( ) . getTime ( ) <
91- new Date ( user . accessToken ?. createdAt ) . getTime ( ) + 5 * 60 * 1000
92- ) {
93- const token = generateToken ( user . toJSON ( ) ) ;
94- await User . findOneAndUpdate (
95- { email : email . email } ,
96- { "accessToken.used" : true } ,
97- ) ;
98- return new Response < { token : string } > ( {
99- message : `Welcome, ${ user . name } ` ,
100- details : { token } ,
101- } ) ;
113+ } else if ( user . accessToken ) {
114+ const accessCode = JSON . parse ( this . decrypt ( user . accessToken ) ) as {
115+ value : string ;
116+ createdAt : string ;
117+ expired : boolean ;
118+ } ;
119+ if (
120+ accessCode ?. value == accessToken &&
121+ new Date ( ) . getTime ( ) <
122+ new Date ( accessCode ?. createdAt ) . getTime ( ) + 5 * 60 * 1000
123+ ) {
124+ // generate JWT token
125+ const token = generateToken ( user . toJSON ( ) ) ;
126+
127+ // update user
128+ await user . updateOne ( {
129+ accessToken : this . encrypt (
130+ JSON . stringify ( { ...accessCode , expired : true } ) ,
131+ ) ,
132+ refreshToken : this . encrypt (
133+ JSON . stringify ( {
134+ value : uuidv4 ( ) ,
135+ createdAt : new Date ( ) . toISOString ( ) ,
136+ expired : false ,
137+ } ) ,
138+ ) ,
139+ } ) ;
140+ return new Response < { token : string } > ( {
141+ message : `Login successful.` ,
142+ details : { token } ,
143+ } ) ;
144+ } else
145+ throw new Error ( {
146+ status : 500 ,
147+ message : "Login link expired or is invalid." ,
148+ } ) ;
102149 } else
103150 throw new Error ( {
104151 status : 500 ,
0 commit comments