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 = {
|
module.exports = {
|
||||||
ViewLoader: require('./view_loader'),
|
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 {
|
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 = {
|
const structure = {
|
||||||
|
|
|
@ -28,12 +28,14 @@
|
||||||
"ioredis": "^5.3.1",
|
"ioredis": "^5.3.1",
|
||||||
"js-base64": "^3.7.5",
|
"js-base64": "^3.7.5",
|
||||||
"mocha": "^10.2.0",
|
"mocha": "^10.2.0",
|
||||||
|
"node-gpg": "^0.2.0",
|
||||||
"otplib": "^12.0.1",
|
"otplib": "^12.0.1",
|
||||||
"pg": "^8.9.0",
|
"pg": "^8.9.0",
|
||||||
"pg-hstore": "^2.3.4",
|
"pg-hstore": "^2.3.4",
|
||||||
"pug": "^3.0.2",
|
"pug": "^3.0.2",
|
||||||
"redis": "^4.6.4",
|
"redis": "^4.6.4",
|
||||||
"sequelize": "^7.0.0-alpha.9",
|
"sequelize": "^7.0.0-alpha.9",
|
||||||
|
"totp-generator": "^0.0.14",
|
||||||
"unit.js": "^2.1.1"
|
"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...');
|
console.log('Executing startup jobs...');
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const {Base64} = require('js-base64');
|
|
||||||
const crc32 = require('crc-32');
|
const crc32 = require('crc-32');
|
||||||
|
const glob = require('glob');
|
||||||
|
const { exec } = require('child_process');
|
||||||
|
|
||||||
const hrt = () => {
|
const hrt = () => {
|
||||||
let hr = process.hrtime();
|
let hr = process.hrtime();
|
||||||
|
@ -25,6 +26,28 @@ if (process.env.APP_DEBUG) {
|
||||||
if (!process.env.APP_KEY)
|
if (!process.env.APP_KEY)
|
||||||
throw new Error('APP_KEY is not set.')
|
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????
|
// TODO: perhaps a better approach to storing it????
|
||||||
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
|
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
|
||||||
process.env.APP_KEY = Buffer.from(process.env.APP_KEY, 'base64').toString('ascii');
|
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
|
- var title = error
|
||||||
|
|
||||||
block content
|
block content
|
||||||
h1(align='center')= error
|
h1(align='center') !{error}
|
||||||
hr(style='width:50%')
|
hr(style='width:50%')
|
||||||
p(align='center')= message
|
p(align='center') !{message}
|
Loading…
Reference in New Issue