waffle/src/auth.ts

136 lines
3.8 KiB
TypeScript

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<User> {
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<User | 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: 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);
});
};
}