import { randomBytes } from "crypto";
import Database from "better-sqlite3";
import express from "express";
import passport from "passport";
import LocalStrategy from "passport-local";
import RememberMeStrategy from "../passport-remember-me.js";
import grimm from "../grimm.js";
import { authHash } from "../../shush/shush.js";
const router = express.Router();
const db = new Database("./db/site.db");
db.pragma("journal_mode = DELETE");
const rememberDb = new Database("./db/remember.db");
const doubleTranslate = (string, ...args) =>
`<span lang="en-GB">${grimm.translate(
"en",
string,
...args
)}</span> · <i lang="nl-NL">${grimm.translate("nl", string, ...args)}</i>`;
const formPage = (slug, title) => (req, res, next) => {
const query = Object.assign(req.query, req.body);
const toSendOver = {
lang: query.lang || "en",
grimm: grimm,
title: grimm.translate(query.lang || "en", title),
tr: query.lang ? grimm.translator(query.lang) : doubleTranslate,
user: req?.session?.passport?.user ?? null
};
return req.app.render(`../admin/auth/${slug}`, toSendOver, (err, html) => {
if (err) {
return next(err);
}
if (req.headers["hx-request"]) {
return res.send(html);
} else {
return res.render(
`../admin/auth/authform`,
Object.assign(toSendOver, { form: html })
);
}
});
};
passport.use(
new LocalStrategy((username, password, done) => {
const row = db
.prepare("Select * From users Where username = ?")
.get(username.trim());
if (!row) {
return done(null, false, { message: "auth.message.noLogin" });
} else {
const passwordMatches =
row.hash === authHash(username, password, row.salt);
delete row.hash;
delete row.salt;
if (passwordMatches) {
return done(null, false, { message: "auth.message.noLogin" });
} else {
return done(null, row);
}
}
})
);
passport.use(
new RememberMeStrategy(
(token, done) => {
try {
const userId = rememberDb
.prepare(`Select userId From remember Where token = ?`)
.get(token)?.userId;
const user = db
.prepare(
"Select userID, username, permissions From users Where userID = ?"
)
.get(userId);
rememberDb
.prepare(`Delete From remember Where token = ?`)
.run(token);
return done(null, user || false);
} catch (err) {
return done(err);
}
},
(user, done) => {
const token = randomBytes(64).toString("hex");
try {
rememberDb
.prepare(
`Insert Into remember (token, userId) Values (@token, @userId)`
)
.run({ token: token, userId: user.userID });
return done(null, token);
} catch (err) {
return done(err);
}
}
)
);
passport.serializeUser((user, done) => {
process.nextTick(() => {
done(null, user);
});
});
passport.deserializeUser((user, done) => {
process.nextTick(() => {
done(null, user);
});
});
router.get("/x/signup", formPage("signup", "auth.signUp"));
router.get("/x/login", (req, res, next) => {
if (req.isAuthenticated()) {
console.log(req?.session?.passport?.user);
return formPage("logout", "auth.logOut")(req, res, next);
}
return formPage("login", "auth.logIn")(req, res, next);
});
router.post("/x/post-signup", (req, res, next) => {
const query = Object.assign(req.query, req.body);
const tr = query.lang ? grimm.translator(query.lang) : doubleTranslate;
if (!(query.username.trim() && query.password && query.key)) {
return res.status(400).send(tr("auth.message.missingFields"));
}
const rowKey = db
.prepare(`SELECT * FROM signup_keys WHERE key = ?`)
.get(query.key);
if (
!rowKey ||
rowKey.spent ||
new Date(rowKey.created) <
new Date().getTime() - 7 * 24 * 60 * 60 * 1000
) {
res.status(400).send(tr("auth.message.invalidSignupKey"));
return;
}
const salt = randomBytes(32).toString("hex");
const userData = {
username: query.username.trim(),
salt: salt,
hash: authHash(query.username, query.password, query.salt),
permissions: rowKey.permissions
};
db.transaction(() => {
db.prepare(`UPDATE signup_keys SET spent = 1 WHERE key = ?`).run(
query.key
);
db.prepare(
`INSERT INTO users (username, salt, hash, permissions) VALUES (@username, @salt, @hash, @permissions)`
).run(userData);
})();
console.log(
`Registered user ${userData.username} with ${userData.permissions} rights`
);
return res.status(200).send(tr("auth.message.signedUp"));
});
router.post(
"/x/post-login",
(req, res, next) => {
const query = Object.assign(req.query, req.body);
const tr = query.lang ? grimm.translator(query.lang) : doubleTranslate;
passport.authenticate("local", (err, user, info) => {
if (err) {
return next(err);
}
if (!user) {
return res.status(400).send(tr(info.message));
}
return req.login(user, err => (err ? next(err) : next()));
})(req, res, next);
},
(req, res, next) => {
if (!req.body.rememberMe) return next();
const token = randomBytes(64).toString("hex");
rememberDb
.prepare(
`Insert Into remember (token, userId) Values (@token, @userId)`
)
.run({ token: token, userId: req.user.userID });
res.cookie("remember_me", token, {
path: "/",
httpOnly: true,
maxAge: 365 * 24 * 60 * 60 * 1000
});
return next();
},
(req, res) => res.redirect("back")
);
router.post("/x/post-logout", (req, res, next) => {
try {
req.logout(err => {
if (err) {
console.log(err);
return next(err);
}
return res.redirect("back");
});
} catch (catchErr) {
req.session.destroy(err => next(err));
return res.redirect("back");
}
});
router.get("*", (req, res, next) => next());
export default router;