diff --git a/index.js b/index.js index 9b6270a..04e8885 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,5 @@ +import cookieParser from 'cookie-parser' +import crypto from 'crypto' import { dbInit } from './libs/database.js' import dotenv from 'dotenv' import express from 'express' @@ -19,6 +21,7 @@ await dbInit() } ) +APP.use(cookieParser(process.env.CK_SECRET ?? crypto.generateKeySync("aes", { length: 128 }).export().toString(), {})) APP.use(express.urlencoded({ extended: true })) APP.use('/static', express.static('static')) diff --git a/package.json b/package.json index 9ffe7fd..7c9ba61 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,10 @@ "license": "ISC", "dependencies": { "bcrypt": "^5.0.1", + "cookie-parser": "^1.4.6", "dotenv": "^12.0.3", "express": "^4.17.2", + "jsonwebtoken": "^8.5.1", "nunjucks": "^3.2.3", "pg": "^8.7.1" } diff --git a/routes/auth.js b/routes/auth.js index a7cb720..be36bec 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -1,14 +1,16 @@ import bcrypt from 'bcrypt' import crypto from 'crypto' +import jwt from 'jsonwebtoken' import { Router } from 'express' import { glauth } from '../libs/database.js' import execawait from '../libs/execawait.js' +export const SECRET = process.env.TK_SECRET ?? crypto.generateKeySync("aes", { length: 128 }).export().toString() const AUTH = Router() let valid = {} /** - * ... + * This is needed because GLAuth (GLAuth's bcrypt) and Node (Node's bcrypt) read hashes differently. * @param {string} hash The hash to be converted to hex. * @returns {string} hex of hash */ @@ -25,11 +27,9 @@ function hash2hex(hash) { AUTH.post('/register', async (req, res) => { const { captcha, password, username } = req.body - // Was input sent? if (!username || !password || !captcha) return(res.send(`Not entered:${username ? '' : ' username,'}${password ? '' : ' password,'}${captcha ? '' : ' captcha'}`)) - // is captcha valid if (!valid[captcha]) return(res.send("Invalid captcha!")) @@ -38,10 +38,10 @@ AUTH.post('/register', async (req, res) => { if (captchaAge > 600) return(res.send("Invalid captcha!")) - // expire the captcha + // Expire the captcha. delete valid[captcha] - // does the username match the requirements + // Does the username match the requirements? if (!(/^(?=[a-zA-Z0-9]{2,20}$).*$/.test(username))) return(res.send("Username does not match the requirements")) @@ -59,7 +59,6 @@ AUTH.post('/register', async (req, res) => { ).catch( err => res.json({ _: "Sorry an error occured!", err }) ) - } ) }) @@ -77,11 +76,32 @@ AUTH.post('/login', async (req, res) => { return(res.send("User doesn't exist!")) bcrypt.compare(password, user.qam_pass).then( - match => { + async match => { if (!match) return res.send("Password's is incorrect!") - return res.send("Welcome " + user.name + "!") + const bearer = { + name: user.name, + id: user.uidnumber, + group: (await glauth.query("SELECT name FROM groups WHERE gidnumber = $1::int", [ user.primarygroup ])).rows[0].name + } + + const token = jwt.sign( + bearer, + SECRET, + { expiresIn: '1h' } + ) + + const fourHoursInMillis = 60 * 60 * 1000 + res.cookie( + 'api-token', + token, + { + expires: new Date(Date.now() + fourHoursInMillis), + httpOnly: true, + signed: true + } + ).redirect('/manager') } ) }) diff --git a/routes/routes.js b/routes/routes.js index d51b175..4a899a3 100644 --- a/routes/routes.js +++ b/routes/routes.js @@ -1,11 +1,31 @@ +import jwt from 'jsonwebtoken' import { Router } from 'express' -import AUTH from './auth.js' +import AUTH, { SECRET } from './auth.js' const ROUTES = Router() ROUTES.get('/', (_, res) => res.render("pages/landing.njk")) ROUTES.get('/login', (_, res) => res.render("pages/login.njk")) ROUTES.get('/register', (_, res) => res.render("pages/register.njk")) +ROUTES.get( + '/manager', + (req, res) => { + const apiToken = req.signedCookies['api-token'] ?? null + if (!apiToken) + return res.redirect('/login') + + try { + if (!jwt.verify(apiToken, SECRET)) + return res.redirect('/login') + + const bearer = jwt.decode(apiToken) + + res.render('pages/manager.njk', { user: bearer }) + } catch (error) { + res.redirect('/login') + } + } +) ROUTES.use('/auth', AUTH) export default ROUTES \ No newline at end of file diff --git a/static/css/theme.css b/static/css/theme.css index e6ce47e..64265e3 100644 --- a/static/css/theme.css +++ b/static/css/theme.css @@ -65,7 +65,6 @@ h6 { p { margin: .5em 0 .25em 0; - text-align: justify; overflow-wrap: break-word; } diff --git a/views/pages/login.njk b/views/pages/login.njk index bb60387..847db13 100644 --- a/views/pages/login.njk +++ b/views/pages/login.njk @@ -14,5 +14,7 @@ + +

Logging in will store an API token in a cookie. 😉

{% endblock %} \ No newline at end of file diff --git a/views/pages/manager.njk b/views/pages/manager.njk new file mode 100644 index 0000000..a3d26dc --- /dev/null +++ b/views/pages/manager.njk @@ -0,0 +1,15 @@ +{% extends "templates/base.njk" %} + +{% block head %} + {{ user.name }}@qam +{% endblock %} + +{% block body %} + {# #} +
Welcome {{ user.name }}! :D
+

This is your account manager!

+

+ Currently no features are implemented, but we want to implement at least password changing. + It would also be nice to have proper account deletion. (For now, if you want to delete your account contact an admin!) +

+{% endblock %} \ No newline at end of file