import { NextFunction, Request, Response } from "express"; import { JwtPayload, sign, verify } from "jsonwebtoken"; import { query } from "./database"; import { errors } from "./errors"; import serverConfig from "./serverconfig"; const jwtSecret = process.env.JWT_SECRET || "[generic token]"; const tokenTypes = { BEARER: 1 } if (jwtSecret === "[generic token]") { console.error("ERROR: No JWT_SECRET environment variable was specified."); console.error("ERROR: exiting..."); process.exit(1); } export function getUserPermissions(user: User) { return { create_channel: serverConfig.superuserRequirement.createChannel ? user.is_superuser : true }; } export function getPublicUserObject(user: User) { const newUser = { ...user, permissions: getUserPermissions(user) }; newUser.password = undefined; delete newUser.password; return newUser; } export function signToken(userId: number) { return new Promise((resolve, reject) => { const payload = { id: userId, type: tokenTypes.BEARER }; sign( payload, jwtSecret, { expiresIn: "2d" }, (error, encoded) => { if (error) { reject(error); return; } if (!encoded) { reject("got undefined encoded value"); return; } resolve(encoded); } ); }); } export function decodeToken(encoded: string): Promise { return new Promise((resolve, reject) => { verify( encoded, jwtSecret, async (error, decoded) => { if (error) { reject(error); return; } if (!decoded) { reject("got undefined decoded value"); return; } if (typeof decoded === "string") { reject("decoded value is a string, expected JwtPayload"); return; } if (!decoded.id || !decoded.type) { reject("token does not match format"); return; } const user = await query("SELECT * FROM users WHERE id = $1", [decoded.id]); if (!user || user.rowCount < 1) { reject("user does not exist (could not find in database by id)"); return; } resolve(user.rows[0]); } ) }); } export async function decodeTokenOrNull(encoded: string): Promise { try { const decoded = await decodeToken(encoded); return decoded; } catch (o_o) { return undefined; } } export function authenticateRoute() { return async (req: Request, res: Response, next: NextFunction) => { const pass = (user: User | null = null) => { if (!user) { res.status(403).send({ ...errors.BAD_AUTH }); return; } req.user = user; req.publicUser = getPublicUserObject(user); next(); }; const authHeader = req.get("Authorization"); if (!authHeader) return pass(); const authParts = authHeader.split(" "); if (authParts.length !== 2) return pass(); const [ authType, authToken ] = authParts; if (authType !== "Bearer") return pass(); if (typeof authToken !== "string") return pass(); decodeTokenOrNull(authToken).then((decoded) => { pass(decoded); }).catch(() => { pass(null); }); }; }