feat: add JWT utility and requireAuth/requireCampaignRole middleware
This commit is contained in:
parent
39f8220eb7
commit
bd433286ae
2 changed files with 66 additions and 0 deletions
19
server/src/auth/jwt.ts
Normal file
19
server/src/auth/jwt.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
|
const SECRET = process.env.JWT_SECRET ?? "dev_jwt_secret_change_me";
|
||||||
|
export const COOKIE_NAME = "darkwatch_token";
|
||||||
|
const EXPIRY = "7d";
|
||||||
|
|
||||||
|
export interface JWTPayload {
|
||||||
|
userId: number;
|
||||||
|
email: string;
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function signToken(payload: JWTPayload): string {
|
||||||
|
return jwt.sign(payload, SECRET, { expiresIn: EXPIRY });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verifyToken(token: string): JWTPayload {
|
||||||
|
return jwt.verify(token, SECRET) as JWTPayload;
|
||||||
|
}
|
||||||
47
server/src/auth/middleware.ts
Normal file
47
server/src/auth/middleware.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import type { Request, Response, NextFunction } from "express";
|
||||||
|
import type { RowDataPacket } from "mysql2";
|
||||||
|
import db from "../db.js";
|
||||||
|
import { verifyToken, COOKIE_NAME } from "./jwt.js";
|
||||||
|
import type { JWTPayload } from "./jwt.js";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace Express {
|
||||||
|
interface Request {
|
||||||
|
user?: JWTPayload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function requireAuth(req: Request, res: Response, next: NextFunction): void {
|
||||||
|
const token = req.cookies?.[COOKIE_NAME];
|
||||||
|
if (!token) {
|
||||||
|
res.status(401).json({ error: "Unauthorized" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
req.user = verifyToken(token);
|
||||||
|
next();
|
||||||
|
} catch {
|
||||||
|
res.status(401).json({ error: "Unauthorized" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function requireCampaignRole(role: "dm" | "player") {
|
||||||
|
return async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
||||||
|
const campaignId = req.params.campaignId ?? req.params.id;
|
||||||
|
const userId = req.user!.userId;
|
||||||
|
const [rows] = await db.execute<RowDataPacket[]>(
|
||||||
|
"SELECT role FROM campaign_members WHERE campaign_id = ? AND user_id = ?",
|
||||||
|
[campaignId, userId]
|
||||||
|
);
|
||||||
|
if (rows.length === 0) {
|
||||||
|
res.status(403).json({ error: "Not a campaign member" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (role === "dm" && rows[0].role !== "dm") {
|
||||||
|
res.status(403).json({ error: "DM access required" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue