half baked user system
This commit is contained in:
parent
601f70510e
commit
15d229704e
|
@ -0,0 +1,51 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
=nU+4
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
|
@ -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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
module.exports = htmlEntities
|
|
@ -1,4 +1,6 @@
|
|||
module.exports = {
|
||||
ViewLoader: require('./view_loader'),
|
||||
TimeSince: require('./timesince')
|
||||
TimeSince: require('./timesince'),
|
||||
HtmlString: require('./htmlstring'),
|
||||
GPG: require('./gpg')
|
||||
}
|
|
@ -1,7 +1,71 @@
|
|||
const { Model, DataTypes } = require('sequelize');
|
||||
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 = {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
25
startup.js
25
startup.js
|
@ -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');
|
||||
|
|
|
@ -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')
|
|
@ -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}
|
Loading…
Reference in New Issue