-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: UtkarshAhuja2003 <[email protected]>
- Loading branch information
1 parent
9b81bf4
commit 4bd00f9
Showing
8 changed files
with
310 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
const jwt = require("jsonwebtoken"); | ||
const { GraphQLResponse } = require("../utils/GraphQLResponse"); | ||
const User = require("../models/user"); | ||
|
||
/** | ||
* Middleware to verify JWT token in GraphQL context. | ||
* @param {Object} context - GraphQL context object | ||
* @returns {Promise<GraphQLResponse>} - GraphQLResponse object | ||
*/ | ||
const verifyJWT = async (context) => { | ||
let token; | ||
if (!context.req.headers && !context.token) { | ||
return new GraphQLResponse(null, false, "Unauthorized request", ["Token missing"], new Error().stack); | ||
} | ||
else if(context.req.headers) { | ||
token = context.req.headers.authorization?.replace("Bearer ", ""); | ||
} | ||
else { | ||
token = context.token; | ||
} | ||
|
||
if (!token) { | ||
return new GraphQLResponse(null, false, "Unauthorized request", ["Token missing"], new Error().stack); | ||
} | ||
|
||
try { | ||
const decodedToken = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET); | ||
const user = await User.findById(decodedToken._id); | ||
|
||
if (!user) { | ||
return new GraphQLResponse(null, false, "Invalid access token", ["User not found"], new Error().stack); | ||
} | ||
|
||
context.user = user; | ||
return new GraphQLResponse(user, true, "User authenticated successfully"); | ||
} catch (error) { | ||
return new GraphQLResponse(null, false, "Invalid access token", ["User not found"], new Error().stack); | ||
} | ||
}; | ||
|
||
module.exports = verifyJWT; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
const jwt = require("jsonwebtoken"); | ||
const User = require("../models/user"); | ||
const verifyJWT = require("../middlewares/auth"); | ||
const { GraphQLResponse } = require("../utils/GraphQLResponse"); | ||
const { validateUserInput } = require("../validators/userValidator"); | ||
|
||
const registerUser = async (_, args, context) => { | ||
const { name, email, password } = args.input; | ||
|
||
try { | ||
validateUserInput(args.input, "register"); | ||
const existingUser = await User.findOne({ email }); | ||
if (existingUser) { | ||
return new GraphQLResponse(null, false, "User already exists", ["User with this email already exists"], new Error().stack); | ||
} | ||
|
||
const user = await User.create({ email, password, name }); | ||
await user.save(); | ||
|
||
const { accessToken, refreshToken } = await generateAccessAndRefreshToken(user._id); | ||
const createdUser = await User.findById(user._id).select("-password -refreshToken"); | ||
if (!createdUser) { | ||
return new GraphQLResponse(null, false, "User registration unsuccessful", ["Failed to register user"], new Error().stack); | ||
} | ||
|
||
const cookieOptions = { | ||
httpOnly: true, | ||
secure: process.env.NODE_ENV === "production", | ||
sameSite: "Strict", | ||
}; | ||
context.res.cookie("accessToken", accessToken, { ...cookieOptions, maxAge: 1000 * 60 * 15 }); | ||
context.res.cookie("refreshToken", refreshToken, { ...cookieOptions, maxAge: 1000 * 60 * 60 * 24 * 7 }); | ||
|
||
return new GraphQLResponse(createdUser, true, "User registered successfully"); | ||
} catch (error) { | ||
return new GraphQLResponse(null, false, error.message, [error.message], error.stack); | ||
} | ||
}; | ||
|
||
const loginUser = async (_, args, context) => { | ||
const { email, password } = args.input; | ||
|
||
try { | ||
validateUserInput(args.input, "login"); | ||
|
||
const user = await User.findOne({ email }); | ||
if (!user) { | ||
return new GraphQLResponse(null, false, "User not found", ["User with this email not found"], new Error().stack); | ||
} | ||
|
||
const isPasswordValid = await user.comparePassword(password); | ||
if (!isPasswordValid) { | ||
return new GraphQLResponse(null, false, "Invalid credentials", ["Incorrect password"], new Error().stack); | ||
} | ||
|
||
const { accessToken, refreshToken } = await generateAccessAndRefreshToken(user._id); | ||
const loggedInUser = await User.findById(user._id).select("-password -refreshToken"); | ||
|
||
if (!loggedInUser) { | ||
return new GraphQLResponse(null, false, "User login unsuccessful", ["Failed to log in user"], new Error().stack); | ||
} | ||
|
||
const cookieOptions = { | ||
httpOnly: true, | ||
secure: process.env.NODE_ENV === "production", | ||
sameSite: "Strict", | ||
}; | ||
context.res.cookie("accessToken", accessToken, { ...cookieOptions, maxAge: 1000 * 60 * 15 }); | ||
context.res.cookie("refreshToken", refreshToken, { ...cookieOptions, maxAge: 1000 * 60 * 60 * 24 * 7 }); | ||
|
||
return new GraphQLResponse(loggedInUser, true, "User logged in successfully"); | ||
|
||
} catch (error) { | ||
return new GraphQLResponse(null, false, error.message, [error.message], error.stack); | ||
} | ||
}; | ||
|
||
const getCurrentUser = async (_, __, context) => { | ||
try { | ||
const jwtResponse = await verifyJWT(context); | ||
if(!jwtResponse.success) return jwtResponse; | ||
|
||
const user = jwtResponse.data; | ||
if (!user) { | ||
return new GraphQLResponse(null, false, "User not found", ["User not authenticated"], new Error().stack); | ||
} | ||
|
||
const { _id, name, email } = user; | ||
return new GraphQLResponse({ _id, name, email }, true, "User retrieved successfully"); | ||
} catch (error) { | ||
return new GraphQLResponse(null, false, error.message, [error.message], error.stack); | ||
} | ||
}; | ||
|
||
const logoutUser = async (_, args, context) => { | ||
try { | ||
const jwtResponse = await verifyJWT(context); | ||
if(!jwtResponse.success) return jwtResponse; | ||
|
||
const user = jwtResponse.data; | ||
if (!user) { | ||
return new GraphQLResponse(null, false, "User not found", ["User not authenticated"], new Error().stack); | ||
} | ||
|
||
await User.findByIdAndUpdate(user._id, { refreshToken: "" }); | ||
context.res.cookie("accessToken", "", { maxAge: 0 }); | ||
context.res.cookie("refreshToken", "", { maxAge: 0 }); | ||
|
||
return new GraphQLResponse(null, true, "User logged out successfully"); | ||
} catch (error) { | ||
return new GraphQLResponse(null, false, error.message, [error.message], error.stack); | ||
} | ||
}; | ||
|
||
const generateAccessAndRefreshToken = async (userId) => { | ||
try { | ||
const user = await User.findById(userId); | ||
|
||
if (!user) { | ||
throw new Error("User not found"); | ||
} | ||
|
||
const accessToken = user.generateAccessToken(); | ||
const refreshToken = user.generateRefreshToken(); | ||
|
||
user.refreshToken = refreshToken; | ||
await user.save(); | ||
|
||
return { accessToken, refreshToken }; | ||
} catch (error) { | ||
throw new Error(error.message || "Failed to generate tokens"); | ||
} | ||
}; | ||
|
||
const refreshAccessToken = async (_, args, context) => { | ||
const { incomingRefreshToken } = args.input; | ||
if (!incomingRefreshToken) { | ||
return new GraphQLResponse(null, false, "Invalid refresh token", ["Refresh token is missing"], new Error().stack); | ||
} | ||
|
||
try { | ||
const decodedToken = jwt.verify(incomingRefreshToken, process.env.REFRESH_TOKEN_SECRET); | ||
const user = await User.findById(decodedToken._id); | ||
|
||
if (!user) { | ||
return new GraphQLResponse(null, false, "User not found", ["User with this token not found"], new Error().stack); | ||
} | ||
|
||
if (incomingRefreshToken !== user.refreshToken) { | ||
return new GraphQLResponse(null, false, "Refresh token is expired or used", ["Invalid or expired refresh token"], new Error().stack); | ||
} | ||
|
||
try { | ||
const { accessToken, refreshToken } = await generateAccessAndRefreshToken(user._id); | ||
const cookieOptions = { | ||
httpOnly: true, | ||
secure: process.env.NODE_ENV === "production", | ||
sameSite: "Strict", | ||
}; | ||
context.res.cookie("accessToken", accessToken, { ...cookieOptions, maxAge: 1000 * 60 * 15 }); | ||
context.res.cookie("refreshToken", refreshToken, { ...cookieOptions, maxAge: 1000 * 60 * 60 * 24 * 7 }); | ||
|
||
return new GraphQLResponse(null, true, "Tokens refreshed successfully"); | ||
} catch (tokenError) { | ||
return new GraphQLResponse(null, false, "Token generation failed", [tokenError.message], tokenError.stack); | ||
} | ||
|
||
} catch (error) { | ||
return new GraphQLResponse(null, false, error.message, [error.message], error.stack); | ||
} | ||
}; | ||
|
||
module.exports = { | ||
refreshAccessToken, | ||
registerUser, | ||
loginUser, | ||
logoutUser, | ||
getCurrentUser | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
const validator = require("validator"); | ||
const { GraphQLResponse } = require("../utils/GraphQLResponse"); | ||
|
||
const validateUserInput = (input, type) => { | ||
const { name, email, password } = input; | ||
|
||
if (!email || !password) { | ||
throw new GraphQLResponse(null, false, "Email and password are required", ["Email and password must be provided"], new Error().stack); | ||
} | ||
if (!validator.isEmail(email)) { | ||
throw new GraphQLResponse(null, false, "Invalid email address", ["The provided email is not valid"], new Error().stack); | ||
} | ||
if (password.length < 8) { | ||
throw new GraphQLResponse(null, false, "Password must be at least 8 characters long", ["Password must be at least 8 characters"], new Error().stack); | ||
} | ||
if (!/[a-z]/.test(password) || !/[A-Z]/.test(password) || !/\d/.test(password) || !/[!@#$%^&*()]/.test(password)) { | ||
throw new GraphQLResponse(null, false, "Password must contain one lowercase, uppercase, number, and special character", ["Password must meet complexity requirements"], new Error().stack); | ||
} | ||
|
||
if (type === "register") { | ||
if (!name) { | ||
throw new GraphQLResponse(null, false, "All fields are required", ["Name is required for registration"], new Error().stack); | ||
} | ||
if (name.length < 2 || name.length > 200) { | ||
throw new GraphQLResponse(null, false, "Name should be between 2 and 200 characters", ["Name length is invalid"], new Error().stack); | ||
} | ||
} | ||
}; | ||
|
||
module.exports = { | ||
validateUserInput | ||
}; |