Authentication logic changed

JWT is now stored in cookie instead of localStorage
changed APIs:
-> /api/auth/login
-> /api/auth/signup
new APIs:
-> /api/auth/logout
This commit is contained in:
Moon Patel
2023-07-23 13:20:55 +05:30
parent b2af52f2c6
commit 81ff9e31a0
5 changed files with 166 additions and 83 deletions
+11 -7
View File
@@ -5,6 +5,7 @@ const authRoutes = require("./routes/auth");
const userRoutes = require("./routes/user");
const roomRoutes = require("./routes/room");
const mongoose = require("mongoose");
const cookieParser = require("cookie-parser");
require("dotenv").config();
mongoose
@@ -17,14 +18,15 @@ const http = require("http");
const server = http.createServer(app);
const { socketIOServerInit } = require("./socket");
app.use(cors({ origin: "*" }));
app.use(cors({ origin: "http://localhost:5173", credentials: true }));
app.use(bodyParser.json());
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET,POST,PATCH,DELETE");
res.setHeader("Access-Control-Allow-Headers", "Content-Type,Authorization");
// res.setHeader("Access-Control-Allow-Origin", "http://localhost:5173");
// res.setHeader("Access-Control-Allow-Methods", "GET,POST,PATCH,DELETE");
// res.setHeader("Access-Control-Allow-Headers", "Content-Type,Authorization");
next();
});
app.use(cookieParser());
socketIOServerInit(server);
@@ -40,9 +42,11 @@ app.use("/api/room", roomRoutes);
app.use((error, req, res, next) => {
const status = error.status || 500;
const message = error.message || "Something went wrong.";
console.log(error)
res.status(status).json({ message: message });
console.log(error);
res.status(status).json({
userMessage: "Something went wrong",
devMessage: error?.message || "Internal server error",
});
});
server.listen(8080, () => {
+31 -1
View File
@@ -12,6 +12,7 @@
"bcryptjs": "^2.4.3",
"body-parser": "^1.20.2",
"chess.js": "^1.0.0-beta.6",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
@@ -19,7 +20,8 @@
"mongoose": "^7.2.1",
"nodemailer": "^6.9.3",
"socket.io": "^4.6.1",
"uuid": "^9.0.0"
"uuid": "^9.0.0",
"zod": "^3.21.4"
}
},
"node_modules/@socket.io/component-emitter": {
@@ -177,6 +179,26 @@
"node": ">= 0.6"
}
},
"node_modules/cookie-parser": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
"dependencies": {
"cookie": "0.4.1",
"cookie-signature": "1.0.6"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/cookie-parser/node_modules/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@@ -1225,6 +1247,14 @@
"optional": true
}
}
},
"node_modules/zod": {
"version": "3.21.4",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz",
"integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}
+3 -1
View File
@@ -14,6 +14,7 @@
"bcryptjs": "^2.4.3",
"body-parser": "^1.20.2",
"chess.js": "^1.0.0-beta.6",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
@@ -21,6 +22,7 @@
"mongoose": "^7.2.1",
"nodemailer": "^6.9.3",
"socket.io": "^4.6.1",
"uuid": "^9.0.0"
"uuid": "^9.0.0",
"zod": "^3.21.4"
}
}
+92 -63
View File
@@ -1,93 +1,122 @@
const express = require("express");
const { createJSONToken, isValidPassword, validateJSONToken, generatePasswordHash } = require("../util/auth");
const {
createJSONToken,
isValidPassword,
validateJSONToken,
generatePasswordHash,
checkAuth,
} = require("../util/auth");
const { isValidEmail, isValidText } = require("../util/validation");
const { User } = require("../models/user");
const { z, ZodError } = require("zod");
const router = express.Router();
const loginSchema = z
.object({
username: z
.string()
.min(5, { message: "Username should not be less than 5 characters" })
.max(15, { message: "Username should not be more than 15 characters" }),
password: z
.string()
.min(8, { message: "Password should not be less than 8 characters" })
.max(15, { message: "Password should not be more than 15 characters" }),
})
.required();
const signupSchema = z
.object({
username: z
.string()
.min(5, { message: "Username should not be less than 5 characters" })
.max(15, { message: "Username should not be more than 15 characters" }),
email: z.string().email({ message: "Please enter a valid email address" }),
password: z
.string()
.min(8, { message: "Password should not be less than 8 characters" })
.max(15, { message: "Password should not be more than 15 characters" }),
})
.required();
router.post("/signup", async (req, res, next) => {
console.log(req.body);
const data = { email: req.body.email, password: req.body.password, username: req.body.username };
let errors = {};
if (!isValidEmail(data.email)) {
errors.email = "Invalid email.";
} else {
try {
let user = await User.findOne({ email: data.email });
if (user) {
errors.email = "Email exists already.";
}
} catch (error) {
throw error;
}
}
if (!isValidText(data.password, 6)) {
errors.password = "Password must be at least 6 characters long.";
}
let user = await User.findOne({ username: data.username });
if (user) errors.username = "Username already taken";
if (Object.keys(errors).length > 0) {
console.log(errors);
return res.status(409).json({
success: false,
message: "User signup failed due to validation errors.",
errors,
});
}
try {
const data = { email: req.body.email, password: req.body.password, username: req.body.username };
signupSchema.parse(data);
// check if username or email already exists
if (await User.findOne({ username: data.username }))
return res.status(409).json({ error: { username: "username already taken" } });
if (await User.findOne({ email: data.email }))
return res.status(409).json({ error: { email: "user with this email already exists" } });
let userData = {
email: data.email,
username: data.username,
password_hash: await generatePasswordHash(data.password),
};
let userDoc = new User(userData);
await userDoc.save();
console.log(userDoc.id)
let userDoc = await User.create(userData);
const authToken = createJSONToken(userDoc.id);
const { id, username, email } = userDoc;
res.status(201).json({
res.status(201).cookie("auth-token", authToken, { httpOnly: true, sameSite: "strict" }).json({
success: true,
message: "User created.",
user: { id, username, email },
token: authToken,
});
} catch (err) {
if (err instanceof ZodError) {
return res.status(400).json({ userMessage: "Invalid data submitted", devMessage: "Invalid schema" });
}
next(err);
}
});
router.post("/login", async (req, res, next) => {
try {
let username = req.body.username,
password = req.body.password;
loginSchema.parse({ username, password });
let user;
user = await User.findOne({ username });
if (!user)
return res.status(404).json({
userMessage: "User does not exist",
devMessage: "'username' not found in db",
});
const pwIsValid = await isValidPassword(password, user.password_hash);
if (!pwIsValid) {
return res.status(401).json({
success: false,
userMessage: "Invalid credentials",
devMessage: "Invalid credentials",
});
}
const token = createJSONToken(user.id);
res.cookie("auth-token", token, { httpOnly: true, sameSite: "strict" });
return res
.status(200)
.json({ token, user: { id: user.id, username: user.username, email: user.email }, success: true });
} catch (error) {
if (error instanceof ZodError) {
return res.status(401).json({ userMessage: "Invalid Credentials", devMessage: "Invalid schema" });
}
next(error);
}
});
router.post("/login", async (req, res) => {
const username = req.body.username;
const password = req.body.password;
let user;
router.delete("/logout", checkAuth, (req, res, next) => {
try {
user = await User.findOne({ username });
if (!user) return res.status(401).json({ message: "User not found" });
} catch (error) {
return res.status(401).json({ success: false, message: "AutheFntication failed." });
res.clearCookie("auth-token", { httpOnly: true, sameSite: "strict" });
res.status(200).json({ success: true });
} catch (err) {
next(err);
}
const pwIsValid = await isValidPassword(password, user.password_hash);
if (!pwIsValid) {
return res.status(422).json({
success: false,
message: "Invalid credentials.",
errors: { credentials: "Invalid email or password entered." },
});
}
const token = createJSONToken(user.id);
console.log(username)
return res
.status(200)
.json({ token, user: { id: user.id, username: user.username, email: user.email }, success: true });
});
module.exports = router;
+29 -11
View File
@@ -21,27 +21,45 @@ function isValidPassword(password, storedPassword) {
return compare(password, storedPassword);
}
// function checkAuthMiddleware(req, res, next) {
// if (req.method === "OPTIONS") {
// return next();
// }
// if (!req.headers.authorization) {
// console.log("NOT AUTH. AUTH HEADER MISSING.");
// return next(new NotAuthError("Not authenticated."));
// }
// const authFragments = req.headers.authorization.split(" ");
// if (authFragments.length !== 2) {
// console.log("NOT AUTH. AUTH HEADER INVALID.");
// return next(new NotAuthError("Not authenticated."));
// }
// const authToken = authFragments[1];
// try {
// const validatedToken = validateJSONToken(authToken);
// req.userid = validatedToken;
// } catch (error) {
// console.log("NOT AUTH. TOKEN INVALID.");
// return next(new NotAuthError("Not authenticated."));
// }
// next();
// }
function checkAuthMiddleware(req, res, next) {
if (req.method === "OPTIONS") {
return next();
}
if (!req.headers.authorization) {
console.log("NOT AUTH. AUTH HEADER MISSING.");
return next(new NotAuthError("Not authenticated."));
let authToken = req.cookies["auth-token"];
if (!authToken) {
return res.status(401).json({ userMessage: "Not authenticated", devMessage: "Auth token not found" });
}
const authFragments = req.headers.authorization.split(" ");
if (authFragments.length !== 2) {
console.log("NOT AUTH. AUTH HEADER INVALID.");
return next(new NotAuthError("Not authenticated."));
}
const authToken = authFragments[1];
try {
const validatedToken = validateJSONToken(authToken);
req.userid = validatedToken;
} catch (error) {
console.log("NOT AUTH. TOKEN INVALID.");
return next(new NotAuthError("Not authenticated."));
return res.status(401).json({ userMessage: "Not authenticated", devMessage: "Invalid auth token" });
}
next();
}