todo/backend/src/user.js

296 lines
7.2 KiB
JavaScript

const express = require("express");
const crypto = require("crypto");
const Config = require("./config.js");
const Database = require("./db_interface.js");
const Mail = require('./mail.js');
let router = express.Router();
router.use(express.json());
let session_entropy = {};
user_cache = {};
email_cache = {};
async function get_user_details(id) {
if (!id || id === "undefined") {
return undefined;
}
console.log(`search for user with id ${id}`);
if (!user_cache[id]) {
let user = await Database.schemas.user.findOne({ where: { id: id } });
if (!user) {
return undefined;
}
user_cache[user.id] = {
id: user.id,
email: user.email,
password_hash: user.password_hash,
};
email_cache[user.email] = user.id;
}
console.log(`returning ${JSON.stringify(user_cache[id])}`);
return user_cache[id];
}
async function get_user_details_by_email(email) {
if (!email || email === "undefined") {
return undefined;
}
console.log(`search for user with email ${email}}`);
if (!email_cache[email] || !user_cache[email_cache[email]]) {
let user = await Database.schemas.user.findOne({ where: { email: email } });
if (!user) {
return undefined;
}
user_cache[user.id] = {
id: user.id,
email: user.email,
password_hash: user.password_hash,
};
email_cache[user.email] = user.id;
}
console.log(`returning ${JSON.stringify(user_cache[email_cache[email]])}`);
return user_cache[email_cache[email]];
}
router.get("/byEmail/:email", async (req, res) => {
if (!req.params?.email) {
res.status(400).json({
error: "email is a required parameter",
});
}
let user = get_user_details_by_email(req.params.email);
console.log(user);
if (user != null) {
res.json({
id: user.id,
email: user.email,
});
} else {
res.sendStatus(404);
}
});
function hash(secret, password) {
let pw_hash = crypto.pbkdf2Sync(
password,
secret,
Config.config.key?.iterations || 1000,
Config.config.key?.length || 64,
"sha512"
);
return pw_hash.toString("base64");
}
function verify(secret, password, hash) {
let pw_hash = crypto.pbkdf2Sync(
password,
secret,
Config.config.key?.iterations || 1000,
Config.config.key?.length || 64,
"sha512"
);
return hash === pw_hash.toString("base64");
}
function hash_password(password) {
return hash(Config.config.secret, password);
}
function verify_password(password, hash) {
return verify(Config.config.secret, password, hash);
}
function get_session_token(id, password_hash) {
session_entropy[id] = crypto.randomBytes(Config.config.session_entropy || 32);
return hash(session_entropy[id], password_hash);
}
function verify_session_token(id, hash, token) {
if (session_entropy[id]) {
return verify(session_entropy[id], hash, token);
} else {
return false;
}
}
async function enforce_session_login(req, res, next) {
let userid = req.get("id");
let session_token = req.get("authorization");
console.log("a", userid, session_token);
if (!userid || !session_token) {
return res.sendStatus(401);
}
let user = await get_user_details(userid);
if (!user) {
return res.sendStatus(401);
}
let verified_session = verify_session_token(
userid,
user.password_hash,
session_token
);
if (!verified_session) {
return res.sendStatus(401);
}
return next();
}
router.post("/signup", async (res, req) => {
if (!req.body?.email || !req.body?.password) {
return res.status(400).json({
error: "must have email and password fields",
});
}
let user = find_user_by_email(req.body?.email);
if (user != null) {
return res.status(403).json({
error: `email ${req.body.email} is already in use.`,
});
} else {
let randomString = "Signup"
for (let i = 0; i < 16, i++) {
randomString += Math.floor(Math.random() * 10)
}
let user = await Database.schemas.unverifiedUser.create({
email: String(req.body.email),
password_hash: hash_password(req.body.password),
verificationToken: get_session_token(randomString, )
});
return res.sendStatus(204);
}
})
router.post("/verify", async (req, res) => {
if (!req.params?.verification) {
return res.status(400).send(
`<html>
<body>
<h1>Unknown Verification Link</h1>
</body>
</html>`
));
}
let verification =
let user = await Database.schemas.unverifiedUser.findOne({ where: { id: id } });
if (user != null) {
let newUser = await Database.schemas.user.create({
email: user.email,
password_hash: user.password_hash,
});
return res.json({
id: user.id,
email: user.email,
});
} else {
return res.status(403).json({
error: `email ${req.body.email} is already in use.`,
});
}
});
router.post("/login", async (req, res) => {
if (!req.body?.email || !req.body?.password) {
return res.status(400).json({
error: "must have email and password fields",
});
}
let user = await get_user_details_by_email(req.body.email);
if (!user) {
return res.status(401).json({
error: "incorrect email or password",
});
}
let verified = verify_password(req.body.password, user.password_hash);
if (!verified) {
return res.status(401).json({
error: "incorrect email or password",
});
}
return res.json({
userid: user.id,
session_token: get_session_token(user.id, user.password_hash),
});
});
router.post("/logout", async (req, res) => {
if (!req.body?.session_token || !req.body?.userid) {
return res.status(400).json({
error: "must include user id and session token to log out",
});
}
let userid = req.body?.userid;
let session_token = req.body?.session_token;
let user = await get_user_details(userid);
if (!user) {
return res.status(401).json({
error: "invalid user data",
});
}
let verified = verify_session_token(
user.id,
user.password_hash,
session_token
);
if (!verified) {
return res.status(401).json({
error: "invalid user data",
});
}
delete session_entropy[user.id];
return res.sendStatus(204);
});
router.get("/:id([a-f0-9-]+)", async (req, res) => {
console.log(req.params);
if (!req.params?.id) {
return res.status(400).json({
error: "must have id parameter",
});
}
let id = req.params?.id;
console.log(id);
let user = await get_user_details(id);
console.log(user);
if (user != null) {
return res.json({
id: user.id,
email: user.email,
});
} else {
return res.sendStatus(404);
}
});
router.use("/authorized", enforce_session_login);
router.get("/authorized", async (req, res) => {
let userid = req.get("id");
let user = await get_user_details(userid);
return res.json({
authorized: true,
user: {
id: user.id,
email: user.email,
},
});
});
module.exports = {
router: router,
enforce_session_login: enforce_session_login,
};