@@ -19,23 +19,127 @@ import { Role } from './role/role.model';
1919import { RefreshToken } from './refresh-token/refresh-token.model' ;
2020import { randomUUID } from 'crypto' ;
2121import { compare , hash } from 'bcrypt' ;
22- import { RefreshTokenResponse } from './auth.resolver' ;
22+ import {
23+ EmailConfirmationResponse ,
24+ RefreshTokenResponse ,
25+ } from './auth.resolver' ;
26+ import { MailService } from 'src/mail/mail.service' ;
2327
2428@Injectable ( )
2529export class AuthService {
30+ private readonly isMailEnabled : boolean ;
31+
2632 constructor (
2733 @InjectRepository ( User )
2834 private userRepository : Repository < User > ,
2935 private jwtService : JwtService ,
3036 private jwtCacheService : JwtCacheService ,
3137 private configService : ConfigService ,
38+ private mailService : MailService ,
3239 @InjectRepository ( Menu )
3340 private menuRepository : Repository < Menu > ,
3441 @InjectRepository ( Role )
3542 private roleRepository : Repository < Role > ,
3643 @InjectRepository ( RefreshToken )
3744 private refreshTokenRepository : Repository < RefreshToken > ,
38- ) { }
45+ ) {
46+ // Read the MAIL_ENABLED environment variable, default to 'true'
47+ this . isMailEnabled =
48+ this . configService . get < string > ( 'MAIL_ENABLED' , 'true' ) . toLowerCase ( ) ===
49+ 'true' ;
50+ }
51+
52+ async confirmEmail ( token : string ) : Promise < EmailConfirmationResponse > {
53+ try {
54+ const payload = await this . jwtService . verifyAsync ( token ) ;
55+
56+ // Check if payload has the required email field
57+ if ( ! payload || ! payload . email ) {
58+ return {
59+ message : 'Invalid token format' ,
60+ success : false ,
61+ } ;
62+ }
63+
64+ // Find user and update
65+ const user = await this . userRepository . findOne ( {
66+ where : { email : payload . email } ,
67+ } ) ;
68+
69+ if ( user && ! user . isEmailConfirmed ) {
70+ user . isEmailConfirmed = true ;
71+ await this . userRepository . save ( user ) ;
72+
73+ return {
74+ message : 'Email confirmed successfully!' ,
75+ success : true ,
76+ } ;
77+ }
78+
79+ return {
80+ message : 'Email already confirmed or user not found.' ,
81+ success : false ,
82+ } ;
83+ } catch ( error ) {
84+ return {
85+ message : 'Invalid or expired token' ,
86+ success : false ,
87+ } ;
88+ }
89+ }
90+
91+ async sendVerificationEmail ( user : User ) : Promise < EmailConfirmationResponse > {
92+ // Generate confirmation token
93+ const verifyToken = this . jwtService . sign (
94+ { email : user . email } ,
95+ { expiresIn : '30m' } ,
96+ ) ;
97+
98+ // Send confirmation email
99+ await this . mailService . sendConfirmationEmail ( user . email , verifyToken ) ;
100+
101+ // update user last time send email time
102+ user . lastEmailSendTime = new Date ( ) ;
103+ await this . userRepository . save ( user ) ;
104+
105+ return {
106+ message : 'Verification email sent successfully!' ,
107+ success : true ,
108+ } ;
109+ }
110+
111+ async resendVerificationEmail ( email : string ) {
112+ const user = await this . userRepository . findOne ( {
113+ where : { email } ,
114+ } ) ;
115+
116+ if ( ! user ) {
117+ throw new Error ( 'User not found' ) ;
118+ }
119+
120+ if ( user . isEmailConfirmed ) {
121+ return { message : 'Email already confirmed!' } ;
122+ }
123+
124+ // Check if a cooldown period has passed (e.g., 1 minute)
125+ const cooldownPeriod = 1 * 60 * 1000 ; // 1 minute in milliseconds
126+ if (
127+ user . lastEmailSendTime &&
128+ new Date ( ) . getTime ( ) - user . lastEmailSendTime . getTime ( ) < cooldownPeriod
129+ ) {
130+ const timeLeft = Math . ceil (
131+ ( cooldownPeriod -
132+ ( new Date ( ) . getTime ( ) - user . lastEmailSendTime . getTime ( ) ) ) /
133+ 1000 ,
134+ ) ;
135+ return {
136+ message : `Please wait ${ timeLeft } seconds before requesting another email` ,
137+ success : false ,
138+ } ;
139+ }
140+
141+ return this . sendVerificationEmail ( user ) ;
142+ }
39143
40144 async register ( registerUserInput : RegisterUserInput ) : Promise < User > {
41145 const { username, email, password } = registerUserInput ;
@@ -50,13 +154,31 @@ export class AuthService {
50154 }
51155
52156 const hashedPassword = await hash ( password , 10 ) ;
53- const newUser = this . userRepository . create ( {
54- username,
55- email,
56- password : hashedPassword ,
57- } ) ;
58157
59- return this . userRepository . save ( newUser ) ;
158+ let newUser ;
159+ if ( this . isMailEnabled ) {
160+ newUser = this . userRepository . create ( {
161+ username,
162+ email,
163+ password : hashedPassword ,
164+ isEmailConfirmed : false ,
165+ } ) ;
166+ } else {
167+ newUser = this . userRepository . create ( {
168+ username,
169+ email,
170+ password : hashedPassword ,
171+ isEmailConfirmed : true ,
172+ } ) ;
173+ }
174+
175+ await this . userRepository . save ( newUser ) ;
176+
177+ if ( this . isMailEnabled ) {
178+ await this . sendVerificationEmail ( newUser ) ;
179+ }
180+
181+ return newUser ;
60182 }
61183
62184 async login ( loginUserInput : LoginUserInput ) : Promise < RefreshTokenResponse > {
@@ -70,6 +192,10 @@ export class AuthService {
70192 throw new UnauthorizedException ( 'Invalid credentials' ) ;
71193 }
72194
195+ if ( ! user . isEmailConfirmed ) {
196+ throw new Error ( 'Email not confirmed. Please check your inbox.' ) ;
197+ }
198+
73199 const isPasswordValid = await compare ( password , user . password ) ;
74200
75201 if ( ! isPasswordValid ) {
@@ -113,6 +239,7 @@ export class AuthService {
113239 return false ;
114240 }
115241 }
242+
116243 async logout ( token : string ) : Promise < boolean > {
117244 try {
118245 await this . jwtService . verifyAsync ( token ) ;
0 commit comments