@@ -15,9 +15,55 @@ const {
1515 verifyOtpSchema
1616} = require ( "@urbackend/common" ) ;
1717
18- const JWT_EXPIRES_IN = '7d' ;
18+ const ACCESS_TOKEN_EXPIRES_IN = '15m' ;
19+ const REFRESH_TOKEN_EXPIRES_IN = '7d' ;
1920const OTP_MAX_ATTEMPTS = 5 ;
2021
22+ const sendTokenResponse = async ( user , statusCode , res ) => {
23+ const accessToken = jwt . sign (
24+ { _id : user . _id , isVerified : user . isVerified , maxProjects : user . maxProjects } ,
25+ process . env . JWT_SECRET ,
26+ { expiresIn : ACCESS_TOKEN_EXPIRES_IN }
27+ ) ;
28+
29+ const refreshToken = jwt . sign (
30+ { _id : user . _id } ,
31+ process . env . JWT_REFRESH_SECRET || process . env . JWT_SECRET ,
32+ { expiresIn : REFRESH_TOKEN_EXPIRES_IN }
33+ ) ;
34+
35+ // Save refresh token to DB (Rotation)
36+ user . refreshToken = refreshToken ;
37+ await user . save ( ) ;
38+
39+ const cookieOptions = {
40+ httpOnly : true ,
41+ expires : new Date ( Date . now ( ) + 7 * 24 * 60 * 60 * 1000 ) , // 7 days
42+ secure : process . env . NODE_ENV === 'production' ,
43+ sameSite : 'Strict' // Use Strict for better CSRF protection
44+ } ;
45+
46+ if ( process . env . NODE_ENV === 'production' ) {
47+ cookieOptions . secure = true ;
48+ }
49+
50+ res . status ( statusCode )
51+ . cookie ( 'accessToken' , accessToken , {
52+ ...cookieOptions ,
53+ expires : new Date ( Date . now ( ) + 15 * 60 * 1000 ) // 15 mins
54+ } )
55+ . cookie ( 'refreshToken' , refreshToken , cookieOptions )
56+ . json ( {
57+ success : true ,
58+ user : {
59+ _id : user . _id ,
60+ email : user . email ,
61+ isVerified : user . isVerified ,
62+ maxProjects : user . maxProjects
63+ }
64+ } ) ;
65+ } ;
66+
2167async function createAndStoreOtp ( userId ) {
2268 const otp = crypto . randomInt ( 100000 , 1000000 ) . toString ( ) ;
2369
@@ -86,13 +132,7 @@ module.exports.login = async (req, res) => {
86132 const validPass = await bcrypt . compare ( password , dev . password ) ;
87133 if ( ! validPass ) return res . status ( 400 ) . json ( { error : "Invalid password" } ) ;
88134
89- // JWT EXPIRE
90- const token = jwt . sign (
91- { _id : dev . _id , isVerified : dev . isVerified , maxProjects : dev . maxProjects } ,
92- process . env . JWT_SECRET ,
93- { expiresIn : JWT_EXPIRES_IN }
94- ) ;
95- res . json ( { token } ) ;
135+ await sendTokenResponse ( dev , 200 , res ) ;
96136 } catch ( err ) {
97137 if ( err instanceof z . ZodError ) {
98138 return res . status ( 400 ) . json ( {
@@ -188,14 +228,7 @@ module.exports.verifyOtp = async (req, res) => {
188228 existingUser . isVerified = true ;
189229 await existingUser . save ( ) ;
190230
191- // JWT
192- const token = jwt . sign (
193- { _id : existingUser . _id , isVerified : true , maxProjects : existingUser . maxProjects } ,
194- process . env . JWT_SECRET ,
195- { expiresIn : JWT_EXPIRES_IN }
196- ) ;
197-
198- res . status ( 200 ) . json ( { message : "OTP verified successfully" , token } ) ;
231+ await sendTokenResponse ( existingUser , 200 , res ) ;
199232 } catch ( err ) {
200233 if ( err . status ) return res . status ( err . status ) . json ( { error : err . message } ) ;
201234 if ( err instanceof z . ZodError ) return res . status ( 400 ) . json ( { error : err . errors } ) ;
@@ -250,4 +283,66 @@ module.exports.resetPassword = async (req, res) => {
250283 console . error ( err ) ;
251284 res . status ( 500 ) . json ( { error : "Internal Server Error" } ) ;
252285 }
253- }
286+ }
287+
288+ // LOGOUT
289+ module . exports . logout = async ( req , res ) => {
290+ try {
291+ if ( req . user ) {
292+ const user = await Developer . findById ( req . user . _id ) ;
293+ if ( user ) {
294+ user . refreshToken = null ;
295+ await user . save ( ) ;
296+ }
297+ }
298+
299+ res . cookie ( 'accessToken' , 'none' , {
300+ expires : new Date ( Date . now ( ) + 10 * 1000 ) ,
301+ httpOnly : true ,
302+ } ) ;
303+ res . cookie ( 'refreshToken' , 'none' , {
304+ expires : new Date ( Date . now ( ) + 10 * 1000 ) ,
305+ httpOnly : true ,
306+ } ) ;
307+
308+ res . status ( 200 ) . json ( { success : true , message : "Logged out successfully" } ) ;
309+ } catch ( err ) {
310+ console . error ( err ) ;
311+ res . status ( 500 ) . json ( { error : "Internal Server Error" } ) ;
312+ }
313+ } ;
314+
315+ // REFRESH TOKEN
316+ module . exports . refreshToken = async ( req , res ) => {
317+ try {
318+ const refreshToken = req . cookies . refreshToken ;
319+
320+ if ( ! refreshToken ) {
321+ return res . status ( 401 ) . json ( { error : "No refresh token provided" } ) ;
322+ }
323+
324+ const decoded = jwt . verify ( refreshToken , process . env . JWT_REFRESH_SECRET || process . env . JWT_SECRET ) ;
325+ const user = await Developer . findById ( decoded . _id ) ;
326+
327+ if ( ! user || user . refreshToken !== refreshToken ) {
328+ return res . status ( 403 ) . json ( { error : "Invalid refresh token" } ) ;
329+ }
330+
331+ await sendTokenResponse ( user , 200 , res ) ;
332+ } catch ( err ) {
333+ console . error ( err ) ;
334+ res . status ( 403 ) . json ( { error : "Invalid or expired refresh token" } ) ;
335+ }
336+ } ;
337+
338+ // GET ME
339+ module . exports . getMe = async ( req , res ) => {
340+ try {
341+ const user = await Developer . findById ( req . user . _id ) . select ( "-password -refreshToken" ) ;
342+ if ( ! user ) return res . status ( 404 ) . json ( { error : "User not found" } ) ;
343+ res . json ( { success : true , user } ) ;
344+ } catch ( err ) {
345+ console . error ( err ) ;
346+ res . status ( 500 ) . json ( { error : "Internal Server Error" } ) ;
347+ }
348+ } ;
0 commit comments