121 lines
3.3 KiB
TypeScript
121 lines
3.3 KiB
TypeScript
|
import { NextFunction, Request, Response } from "express";
|
||
|
import { JwtPayload, sign, verify } from "jsonwebtoken";
|
||
|
import { query } from "./database";
|
||
|
import { errors } from "./errors";
|
||
|
|
||
|
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 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<JwtPayload> {
|
||
|
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.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<JwtPayload | undefined> {
|
||
|
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: object | null = null) => {
|
||
|
if (user === null) {
|
||
|
res.status(403).send({
|
||
|
...errors.BAD_AUTH
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
req.user = 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);
|
||
|
});
|
||
|
};
|
||
|
}
|