feat: add register, login, logout, and me auth endpoints
This commit is contained in:
parent
80f0b3535b
commit
d1156745ca
1 changed files with 114 additions and 0 deletions
|
|
@ -1,3 +1,117 @@
|
|||
import { Router } from "express";
|
||||
import type { RowDataPacket, ResultSetHeader } from "mysql2";
|
||||
import bcrypt from "bcrypt";
|
||||
import db from "../db.js";
|
||||
import { signToken, COOKIE_NAME } from "../auth/jwt.js";
|
||||
import { requireAuth } from "../auth/middleware.js";
|
||||
|
||||
const router = Router();
|
||||
const BCRYPT_ROUNDS = 12;
|
||||
const COOKIE_OPTS = {
|
||||
httpOnly: true,
|
||||
sameSite: "lax" as const,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days in ms
|
||||
};
|
||||
|
||||
// POST /api/auth/register
|
||||
router.post("/register", async (req, res) => {
|
||||
try {
|
||||
const { email, username, password } = req.body;
|
||||
|
||||
if (!email?.trim() || !username?.trim() || !password) {
|
||||
res.status(400).json({ error: "email, username, and password are required" });
|
||||
return;
|
||||
}
|
||||
if (password.length < 8) {
|
||||
res.status(400).json({ error: "Password must be at least 8 characters" });
|
||||
return;
|
||||
}
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
res.status(400).json({ error: "Invalid email format" });
|
||||
return;
|
||||
}
|
||||
|
||||
const [existing] = await db.execute<RowDataPacket[]>(
|
||||
"SELECT id FROM users WHERE email = ?",
|
||||
[email.trim().toLowerCase()]
|
||||
);
|
||||
if (existing.length > 0) {
|
||||
res.status(409).json({ error: "Email already registered" });
|
||||
return;
|
||||
}
|
||||
|
||||
const passwordHash = await bcrypt.hash(password, BCRYPT_ROUNDS);
|
||||
const [result] = await db.execute<ResultSetHeader>(
|
||||
"INSERT INTO users (email, username, password_hash) VALUES (?, ?, ?)",
|
||||
[email.trim().toLowerCase(), username.trim(), passwordHash]
|
||||
);
|
||||
|
||||
const token = signToken({
|
||||
userId: result.insertId,
|
||||
email: email.trim().toLowerCase(),
|
||||
username: username.trim(),
|
||||
});
|
||||
res.cookie(COOKIE_NAME, token, COOKIE_OPTS);
|
||||
res.status(201).json({
|
||||
userId: result.insertId,
|
||||
email: email.trim().toLowerCase(),
|
||||
username: username.trim(),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Internal server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/auth/login
|
||||
router.post("/login", async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
if (!email?.trim() || !password) {
|
||||
res.status(400).json({ error: "email and password are required" });
|
||||
return;
|
||||
}
|
||||
|
||||
const [rows] = await db.execute<RowDataPacket[]>(
|
||||
"SELECT id, email, username, password_hash FROM users WHERE email = ?",
|
||||
[email.trim().toLowerCase()]
|
||||
);
|
||||
if (rows.length === 0) {
|
||||
res.status(401).json({ error: "Invalid email or password" });
|
||||
return;
|
||||
}
|
||||
|
||||
const user = rows[0];
|
||||
const valid = await bcrypt.compare(password, user.password_hash as string);
|
||||
if (!valid) {
|
||||
res.status(401).json({ error: "Invalid email or password" });
|
||||
return;
|
||||
}
|
||||
|
||||
const token = signToken({
|
||||
userId: user.id as number,
|
||||
email: user.email as string,
|
||||
username: user.username as string,
|
||||
});
|
||||
res.cookie(COOKIE_NAME, token, COOKIE_OPTS);
|
||||
res.json({ userId: user.id, email: user.email, username: user.username });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: "Internal server error" });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/auth/logout
|
||||
router.post("/logout", (_req, res) => {
|
||||
res.clearCookie(COOKIE_NAME);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// GET /api/auth/me
|
||||
router.get("/me", requireAuth, (req, res) => {
|
||||
res.json(req.user);
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue