first commit
This commit is contained in:
22
apps/server/src/auth/auth.middleware.ts
Normal file
22
apps/server/src/auth/auth.middleware.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { verifyToken } from "./auth.service"
|
||||
|
||||
export const requireAuth = (req: Request, res: Response, next: NextFunction) => {
|
||||
const token = req.cookies?.["qbuffer_token"];
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: "Unauthorized" });
|
||||
}
|
||||
try {
|
||||
const payload = verifyToken(token);
|
||||
req.user = payload;
|
||||
return next();
|
||||
} catch (error) {
|
||||
return res.status(401).json({ error: "Unauthorized" });
|
||||
}
|
||||
};
|
||||
|
||||
declare module "express-serve-static-core" {
|
||||
interface Request {
|
||||
user?: { username: string };
|
||||
}
|
||||
}
|
||||
64
apps/server/src/auth/auth.routes.ts
Normal file
64
apps/server/src/auth/auth.routes.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { Router } from "express";
|
||||
import rateLimit from "express-rate-limit";
|
||||
import { signToken, verifyCredentials, verifyToken } from "./auth.service"
|
||||
import { isDev } from "../config"
|
||||
|
||||
const router = Router();
|
||||
|
||||
const loginLimiter = rateLimit({
|
||||
windowMs: 60_000,
|
||||
max: 5,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
});
|
||||
|
||||
router.post("/login", loginLimiter, async (req, res) => {
|
||||
const { username, password } = req.body ?? {};
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({ error: "Missing credentials" });
|
||||
}
|
||||
const user = await verifyCredentials(username, password);
|
||||
if (!user) {
|
||||
return res.status(401).json({ error: "Invalid credentials" });
|
||||
}
|
||||
const token = signToken({ username: user.username });
|
||||
res.cookie("qbuffer_token", token, {
|
||||
httpOnly: true,
|
||||
sameSite: "lax",
|
||||
secure: !isDev,
|
||||
});
|
||||
return res.json({ username: user.username });
|
||||
});
|
||||
|
||||
router.post("/logout", (_req, res) => {
|
||||
res.clearCookie("qbuffer_token");
|
||||
return res.json({ ok: true });
|
||||
});
|
||||
|
||||
router.get("/me", (req, res) => {
|
||||
const token = req.cookies?.["qbuffer_token"];
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: "Unauthorized" });
|
||||
}
|
||||
try {
|
||||
const payload = verifyToken(token);
|
||||
return res.json({ ok: true, username: payload.username });
|
||||
} catch (error) {
|
||||
return res.status(401).json({ error: "Unauthorized" });
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/socket-token", (req, res) => {
|
||||
const token = req.cookies?.["qbuffer_token"];
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: "Unauthorized" });
|
||||
}
|
||||
try {
|
||||
verifyToken(token);
|
||||
return res.json({ token });
|
||||
} catch (error) {
|
||||
return res.status(401).json({ error: "Unauthorized" });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
48
apps/server/src/auth/auth.service.ts
Normal file
48
apps/server/src/auth/auth.service.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import bcrypt from "bcryptjs";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { config } from "../config"
|
||||
import { readDb, writeDb } from "../storage/jsondb"
|
||||
import { nowIso } from "../utils/time"
|
||||
import { User } from "../types"
|
||||
|
||||
const ensureSeedUser = async (): Promise<User | null> => {
|
||||
if (!config.appUsername || !config.appPassword) {
|
||||
return null;
|
||||
}
|
||||
const db = await readDb();
|
||||
const existing = db.users.find((user) => user.username === config.appUsername);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
const passwordHash = await bcrypt.hash(config.appPassword, 10);
|
||||
const newUser: User = {
|
||||
username: config.appUsername,
|
||||
passwordHash,
|
||||
createdAt: nowIso(),
|
||||
};
|
||||
db.users.push(newUser);
|
||||
await writeDb(db);
|
||||
return newUser;
|
||||
};
|
||||
|
||||
export const initAuth = async () => {
|
||||
await ensureSeedUser();
|
||||
};
|
||||
|
||||
export const verifyCredentials = async (username: string, password: string) => {
|
||||
const db = await readDb();
|
||||
const user = db.users.find((u) => u.username === username);
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
const match = await bcrypt.compare(password, user.passwordHash);
|
||||
return match ? user : null;
|
||||
};
|
||||
|
||||
export const signToken = (payload: { username: string }) => {
|
||||
return jwt.sign(payload, config.jwtSecret, { expiresIn: "7d" });
|
||||
};
|
||||
|
||||
export const verifyToken = (token: string) => {
|
||||
return jwt.verify(token, config.jwtSecret) as { username: string };
|
||||
};
|
||||
Reference in New Issue
Block a user