11import type { Request , Response , NextFunction } from "express" ;
22import bcrypt from "bcryptjs" ;
3- import jwt from "jsonwebtoken" ; // <-- ADDED
3+ import jwt from "jsonwebtoken" ;
44import User , { type IUser } from "../models/userModel.js" ;
55import {
66 generateToken ,
7- generateAccessToken , // <-- RENAMED/UPDATED
8- generateRefreshToken , // <-- ADDED
7+ generateRefreshToken ,
98} from "../utils/generateToken.js" ;
109import { userSchema , loginSchema } from "../utils/validateInputs.js" ;
1110import dotenv from "dotenv" ;
12-
1311import { Session } from "../models/sessionModel.js" ;
1412
1513dotenv . config ( ) ;
14+
1615const asTypedUser = ( user : any ) : IUser & { _id : string } =>
1716 user as IUser & { _id : string } ;
1817
19- // A helper function to send tokens
20- const sendTokens = ( res : Response , user : IUser & { _id : string } ) => {
21- const accessToken = generateAccessToken ( user . _id . toString ( ) ) ;
22- const newRefreshToken = generateRefreshToken ( user . _id . toString ( ) ) ;
23-
24- // Update user's refresh tokens in DB
25- // Only one refresh token per user is supported (single device).
26- user . refreshTokens = [ newRefreshToken ] ;
27-
28- // Set refresh token in secure httpOnly cookie
29- res . cookie ( "jwt" , newRefreshToken , {
30- httpOnly : true ,
31- secure : process . env . NODE_ENV !== "development" ,
32- sameSite : "strict" ,
33- maxAge : 7 * 24 * 60 * 60 * 1000 , // 7 days (matches token expiry)
34- } ) ;
18+ const cookieOptions = {
19+ httpOnly : true ,
20+ secure : process . env . NODE_ENV !== "development" ,
21+ sameSite : "strict" as const ,
22+ maxAge : 7 * 24 * 60 * 60 * 1000 , // 7 days
23+ } ;
24+
25+ async function createSessionForAccessToken ( userId : string , token : string ) {
26+ const decoded = jwt . decode ( token ) as { exp ?: number } | null ;
27+ const expiresAt = decoded ?. exp
28+ ? new Date ( decoded . exp * 1000 )
29+ : new Date ( Date . now ( ) + 15 * 60 * 1000 ) ;
3530
36- // Send access token in response body
37- res . json ( {
38- success : true ,
39- message : "Login successful" ,
40- accessToken : accessToken ,
31+ await Session . create ( {
32+ userId,
33+ token,
34+ expiresAt,
4135 } ) ;
42- } ;
36+ }
4337
44- // ✅ SIGNUP CONTROLLER (Updated)
38+ //signup controller
4539export const registerUser = async (
4640 req : Request ,
4741 res : Response ,
@@ -70,31 +64,29 @@ export const registerUser = async (
7064 const newUser = await User . create ( { name, email, password : hashedPassword } ) ;
7165 const typedUser = asTypedUser ( newUser ) ;
7266
73- const token = generateToken ( typedUser . _id . toString ( ) ) ;
74- const decoded = jwt . verify ( token , process . env . JWT_SECRET ! ) as { exp ?: number } ;
67+ // Generate tokens
68+ const accessToken = generateToken ( typedUser . _id . toString ( ) ) ;
69+ const refreshToken = generateRefreshToken ( typedUser . _id . toString ( ) ) ;
7570
76- if ( ! decoded || ! decoded . exp ) {
77- throw new Error ( "Invalid token format or missing expiration" ) ;
78- }
71+ typedUser . refreshTokens = [ refreshToken ] ;
72+ await typedUser . save ( ) ;
73+
74+ res . cookie ( "jwt" , refreshToken , cookieOptions ) ;
7975
80- const expiresAt = new Date ( decoded . exp * 1000 ) ;
81- await Session . create ( {
82- userId : typedUser . _id ,
83- token,
84- expiresAt,
85- } ) ;
76+ //a session document
77+ await createSessionForAccessToken ( typedUser . _id . toString ( ) , accessToken ) ;
8678
8779 res . status ( 201 ) . json ( {
8880 success : true ,
8981 message : "User registered successfully" ,
90- token ,
82+ accessToken ,
9183 } ) ;
9284 } catch ( err ) {
9385 next ( err ) ;
9486 }
9587} ;
9688
97- // ✅ LOGIN CONTROLLER (Updated)
89+ //login controller
9890export const loginUser = async (
9991 req : Request ,
10092 res : Response ,
@@ -132,40 +124,31 @@ export const loginUser = async (
132124
133125 const typedUser = asTypedUser ( foundUser ) ;
134126
135- const token = generateToken ( typedUser . _id . toString ( ) ) ;
136- const decoded = jwt . verify ( token , process . env . JWT_SECRET ! ) as { exp ?: number } ;
137-
138- if ( ! decoded . exp ) {
139- throw new Error ( "Token missing expiration claim" ) ;
140- }
127+ // Generate tokens
128+ const accessToken = generateToken ( typedUser . _id . toString ( ) ) ;
129+ const refreshToken = generateRefreshToken ( typedUser . _id . toString ( ) ) ;
141130
142- const expiresAt = new Date ( decoded . exp * 1000 ) ;
131+ // Store refresh token
132+ typedUser . refreshTokens = [ refreshToken ] ;
133+ await typedUser . save ( ) ;
143134
144- await Session . create ( {
145- userId : typedUser . _id ,
146- token,
147- expiresAt,
148- } ) ;
135+ // Set cookie
136+ res . cookie ( "jwt" , refreshToken , cookieOptions ) ;
149137
138+ // Create session
139+ await createSessionForAccessToken ( typedUser . _id . toString ( ) , accessToken ) ;
150140
151- res . json ( {
141+ return res . json ( {
152142 success : true ,
153143 message : "Login successful" ,
154- token ,
144+ accessToken ,
155145 } ) ;
156-
157- // Redirect to the frontend without passing the token in the URL
158- const redirectUrl = `${ process . env . FRONTEND_URL } /auth-success` ;
159- res . redirect ( redirectUrl ) ;
160-
161146 } catch ( err ) {
162147 next ( err ) ;
163148 }
164149} ;
165150
166- // ... (keep all other functions as they are)
167-
168- // ✅ REFRESH TOKEN CONTROLLER (Updated with fixes)
151+ //refresh token controller
169152export const handleRefreshToken = async (
170153 req : Request ,
171154 res : Response ,
@@ -181,76 +164,64 @@ export const handleRefreshToken = async (
181164
182165 const refreshToken = cookies . jwt ;
183166 // Clear the old cookie immediately
184- res . clearCookie ( "jwt" , { httpOnly : true , sameSite : "strict" , secure : process . env . NODE_ENV !== "development" } ) ;
167+ res . clearCookie ( "jwt" , {
168+ httpOnly : true ,
169+ sameSite : "strict" ,
170+ secure : process . env . NODE_ENV !== "development" ,
171+ } ) ;
185172
186173 const foundUser = await User . findOne ( { refreshTokens : refreshToken } ) ;
187174
188- // Detected refresh token reuse!
189175 if ( ! foundUser ) {
190176 try {
191177 const decoded = jwt . verify (
192178 refreshToken ,
193179 process . env . JWT_REFRESH_SECRET as string
194180 ) as { id : string } ;
195181
196- // We know who the user is, now we hack-proof them
197- // by deleting all their refresh tokens
198182 const compromisedUser = await User . findById ( decoded . id ) ;
199183 if ( compromisedUser ) {
200184 compromisedUser . refreshTokens = [ ] ;
201185 await compromisedUser . save ( ) ;
202186 }
203187 } catch ( err ) {
204- // Token was invalid in the first place
188+ // nothing to do
205189 } finally {
206190 return res
207191 . status ( 403 )
208192 . json ( { success : false , message : "Forbidden, token reuse" } ) ;
209193 }
210194 }
211195
212- // Valid token, let's rotate it
213196 const typedUser = asTypedUser ( foundUser ) ;
214197
215198 try {
216- // Verify the token is still valid
199+ // Verify
217200 jwt . verify ( refreshToken , process . env . JWT_REFRESH_SECRET as string ) as {
218201 id : string ;
219202 } ;
220203
221204 // Generate new tokens
222- const newAccessToken = generateAccessToken ( typedUser . _id . toString ( ) ) ;
205+ const newAccessToken = generateToken ( typedUser . _id . toString ( ) ) ;
223206 const newRefreshToken = generateRefreshToken ( typedUser . _id . toString ( ) ) ;
224207
225- // ==================
226- // FIX #1
227- // ==================
228- // Filter out the old token and default to an empty array
229208 const otherRefreshTokens =
230209 typedUser . refreshTokens ?. filter ( ( rt ) => rt !== refreshToken ) || [ ] ;
231210
232- // Assign the new array (filtered list + new token)
233211 typedUser . refreshTokens = [ ...otherRefreshTokens , newRefreshToken ] ;
234212 await typedUser . save ( ) ;
235213
236- // Send new tokens
237- res . cookie ( "jwt" , newRefreshToken , {
238- httpOnly : true ,
239- secure : process . env . NODE_ENV !== "development" ,
240- sameSite : "strict" ,
241- maxAge : 7 * 24 * 60 * 60 * 1000 ,
242- } ) ;
214+ //new refresh cookie
215+ res . cookie ( "jwt" , newRefreshToken , cookieOptions ) ;
216+
217+ //new session
218+ await createSessionForAccessToken ( typedUser . _id . toString ( ) , newAccessToken ) ;
243219
244- res . json ( {
220+ return res . json ( {
245221 success : true ,
246222 accessToken : newAccessToken ,
247223 } ) ;
248224 } catch ( err ) {
249- // Token expired or invalid
250- // ==================
251- // FIX #2
252- // ==================
253- // Clear out the bad token, default to an empty array
254225 typedUser . refreshTokens =
255226 typedUser . refreshTokens ?. filter ( ( rt ) => rt !== refreshToken ) || [ ] ;
256227 await typedUser . save ( ) ;
@@ -264,49 +235,47 @@ export const handleRefreshToken = async (
264235 }
265236} ;
266237
267-
268- // ✅ LOGOUT CONTROLLER (Updated with fix)
238+ //logout controller
269239export const logoutUser = async (
270240 req : Request ,
271241 res : Response ,
272242 next : NextFunction
273243) => {
274244 try {
275245 const cookies = req . cookies ;
276- if ( ! cookies ?. jwt ) {
277- return res . sendStatus ( 204 ) ; // No cookie, already logged out
278- }
279-
280- const refreshToken = cookies . jwt ;
281-
282- // Find user and remove this specific refresh token
283- const foundUser = await User . findOne ( { refreshTokens : refreshToken } ) ;
284- if ( foundUser ) {
285- foundUser . refreshTokens =
286- foundUser . refreshTokens ?. filter ( ( rt ) => rt !== refreshToken ) || [ ] ;
246+ const authHeader = req . headers . authorization ;
247+ const accessToken = authHeader ?. split ( " " ) [ 1 ] ;
248+
249+ // If cookie exists- remove refresh token
250+ if ( cookies ?. jwt ) {
251+ const refreshToken = cookies . jwt ;
252+ const foundUser = await User . findOne ( { refreshTokens : refreshToken } ) ;
253+ if ( foundUser ) {
254+ foundUser . refreshTokens =
255+ foundUser . refreshTokens ?. filter ( ( rt ) => rt !== refreshToken ) || [ ] ;
256+ await foundUser . save ( ) ;
257+ }
287258
288- await foundUser . save ( ) ;
259+ res . clearCookie ( "jwt" , {
260+ httpOnly : true ,
261+ sameSite : "strict" ,
262+ secure : process . env . NODE_ENV !== "development" ,
263+ } ) ;
264+ }
265+ if ( accessToken ) {
266+ await Session . deleteOne ( { token : accessToken } ) ;
289267 }
290268
291- // Clear the cookie
292- res . clearCookie ( "jwt" , {
293- httpOnly : true ,
294- sameSite : "strict" ,
295- secure : process . env . NODE_ENV !== "development" ,
296- } ) ;
297-
298- res . status ( 200 ) . json ( { success : true , message : "Logged out successfully" } ) ;
269+ return res . status ( 200 ) . json ( { success : true , message : "Logged out successfully" } ) ;
299270 } catch ( err ) {
300271 next ( err ) ;
301272 }
302273} ;
303274
304- // ✅ GET PROFILE CONTROLLER (Unchanged, but for completeness)
275+ //get profile controller
305276export const getUserProfile = async ( req : Request , res : Response ) => {
306277 try {
307- // req.userId comes from the 'protect' middleware
308- // @ts -ignore
309- const user = await User . findById ( req . userId ) . select ( "-password -refreshTokens" ) ;
278+ const user = await User . findById ( ( req as any ) . userId ) . select ( "-password -refreshTokens" ) ;
310279
311280 if ( ! user ) {
312281 return res . status ( 404 ) . json ( {
0 commit comments