2022-04-05 22:34:06 +03:00
|
|
|
import { NextFunction, Request, Response } from "express";
|
|
|
|
import { JwtPayload, sign, verify } from "jsonwebtoken";
|
|
|
|
import { query } from "./database";
|
|
|
|
import { errors } from "./errors";
|
2022-08-04 04:51:09 +03:00
|
|
|
import serverConfig from "./serverconfig";
|
2022-04-05 22:34:06 +03:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-08-04 04:51:09 +03:00
|
|
|
export function getUserPermissions(user: User) {
|
|
|
|
return {
|
|
|
|
create_channel: serverConfig.superuserRequirement.createChannel ? user.is_superuser : true
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-04-10 21:10:19 +03:00
|
|
|
export function getPublicUserObject(user: User) {
|
2022-08-04 04:51:09 +03:00
|
|
|
const newUser = { ...user, permissions: getUserPermissions(user) };
|
2022-04-10 21:10:19 +03:00
|
|
|
newUser.password = undefined;
|
|
|
|
delete newUser.password;
|
|
|
|
|
|
|
|
return newUser;
|
|
|
|
}
|
2022-04-05 22:34:06 +03:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-04-06 12:29:11 +03:00
|
|
|
export function decodeToken(encoded: string): Promise<User> {
|
2022-04-05 22:34:06 +03:00
|
|
|
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]);
|
|
|
|
}
|
|
|
|
)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-04-06 12:29:11 +03:00
|
|
|
export async function decodeTokenOrNull(encoded: string): Promise<User | undefined> {
|
2022-04-05 22:34:06 +03:00
|
|
|
try {
|
|
|
|
const decoded = await decodeToken(encoded);
|
|
|
|
return decoded;
|
|
|
|
} catch (o_o) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function authenticateRoute() {
|
|
|
|
return async (req: Request, res: Response, next: NextFunction) => {
|
2022-04-06 12:29:11 +03:00
|
|
|
const pass = (user: User | null = null) => {
|
|
|
|
if (!user) {
|
2022-04-05 22:34:06 +03:00
|
|
|
res.status(403).send({
|
|
|
|
...errors.BAD_AUTH
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
2022-04-06 12:29:11 +03:00
|
|
|
|
2022-04-05 22:34:06 +03:00
|
|
|
req.user = user;
|
2022-04-10 21:10:19 +03:00
|
|
|
req.publicUser = getPublicUserObject(user);
|
2022-04-05 22:34:06 +03:00
|
|
|
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);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|