half baked user system

This commit is contained in:
b1ek 2023-02-25 16:32:11 +10:00
parent 601f70510e
commit 15d229704e
Signed by: blek
GPG Key ID: 14546221E3595D0C
9 changed files with 201 additions and 5 deletions

View File

@ -0,0 +1,51 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
=nU+4
-----END PGP PUBLIC KEY BLOCK-----

6
helpers/htmlstring.js Normal file
View File

@ -0,0 +1,6 @@
// https://stackoverflow.com/questions/14129953/how-to-encode-a-string-in-javascript-for-displaying-in-html
function htmlEntities(str) {
return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
module.exports = htmlEntities

View File

@ -1,4 +1,6 @@
module.exports = {
ViewLoader: require('./view_loader'),
TimeSince: require('./timesince')
TimeSince: require('./timesince'),
HtmlString: require('./htmlstring'),
GPG: require('./gpg')
}

View File

@ -1,7 +1,71 @@
const { Model, DataTypes } = require('sequelize');
const bcrypt = require('bcrypt');
const totp = require('totp-generator');
const crypto = require('crypto');
class User extends Model {
/**
* @returns number[]|false
*/
async getTotpCodes() {
if (this.totp && this.totpRec) {
/** @type number[] */
let codes = this.totpRec.split(',').map(x => {return parseInt(x, 10);});
const period = (30 * 1000)
codes.push(parseInt(totp(this.totp, { timestamp: Date.now() - period})));
codes.push(parseInt(totp(this.totp)));
codes.push(parseInt(totp(this.totp, { timestamp: Date.now() + period})));
console.log(codes);
return codes;
}
return false;
}
/**
*
* @param {number} code
* @returns bool
*/
async checkTotp(code) {
const codes = await this.getTotpCodes();
return codes.indexOf(code) !== 1;
}
/**
* @param {{login: string, pass: string, totp?: string}} data
* @return {User}
*/
static async authenticate(data) {
const rows = await User.findAndCountAll({where: {
login: data.login
}});
if (rows.count == 0) return false;
/** @type User */
const this_ = rows.rows[0];
if (!(await bcrypt.compare(data.pass, this_.pass))) {
return false;
}
if (this_.totp) {
if (!data.totp) return false;
if (!this_.checkTotp(data.totp)) return false;
}
return rows.rows[0];
}
async createSession() {
let session = {};
session.user_id = this.id;
session.login = this.login;
session.expires = Date.now() + (60 * 60 * 1000);
session.secret = crypto.randomBytes(256).toString('base64');
return session;
}
}
const structure = {

View File

@ -28,12 +28,14 @@
"ioredis": "^5.3.1",
"js-base64": "^3.7.5",
"mocha": "^10.2.0",
"node-gpg": "^0.2.0",
"otplib": "^12.0.1",
"pg": "^8.9.0",
"pg-hstore": "^2.3.4",
"pug": "^3.0.2",
"redis": "^4.6.4",
"sequelize": "^7.0.0-alpha.9",
"totp-generator": "^0.0.14",
"unit.js": "^2.1.1"
}
}

34
routes/admin.js Normal file
View File

@ -0,0 +1,34 @@
const handler = require('express-async-handler')
const Helpers = require('../helpers');
const db = require('../models');
async function login(req, res) {
res.send(await Helpers.ViewLoader.load('admin/login.pug', {
current_route: req.originalUrl
}));
}
async function apiLogin(req, res) {
if (req.session.user) {
res.send('Already logged in');
return;
}
const user = (await db.User.authenticate(req.body));
if (!user) {
res.status(401).send('Bad auth');
}
const session = await user.createSession();
req.session.user = session;
res.send(req.session);
//res.redirect('/admin/panel');
return;
}
module.exports = (router) => {
router.get('/login', handler(login));
router.get('/admin/login', handler(login));
router.post('/admin/login', handler(apiLogin));
}

View File

@ -1,8 +1,9 @@
console.log('Executing startup jobs...');
const fs = require('fs');
const {Base64} = require('js-base64');
const crc32 = require('crc-32');
const glob = require('glob');
const { exec } = require('child_process');
const hrt = () => {
let hr = process.hrtime();
@ -25,6 +26,28 @@ if (process.env.APP_DEBUG) {
if (!process.env.APP_KEY)
throw new Error('APP_KEY is not set.')
// import gpg keys
glob('data/userdata/*_gpgkey', async (err, files) => {
if (err) {
console.error(err);
process.exit(-1);
}
files.filter(
file => {
return !file.startsWith('.')
}
).forEach(file => {
exec('gpg --import ' + file, (err, stdout, stderr) => {
if (err) {
console.error(`Errors while importing ${file}: ${err}`);
process.exit(-1);
}
console.log(`Imported ${file} key`);
});
});
});
// TODO: perhaps a better approach to storing it????
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
process.env.APP_KEY = Buffer.from(process.env.APP_KEY, 'base64').toString('ascii');

14
view/admin/login.pug Normal file
View File

@ -0,0 +1,14 @@
extends ../layout/main.pug
block root
- var title = 'Login'
block content
p Login
form(method='POST' action='/admin/login')
input(type='text' name='login')
br
input(type='password' name='pass')
br
input(type='text' name='totp')
br
input(type='submit')

View File

@ -3,6 +3,6 @@ block root
- var title = error
block content
h1(align='center')= error
h1(align='center') !{error}
hr(style='width:50%')
p(align='center')= message
p(align='center') !{message}