try to fix this spaghetti of a code
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const bcrypt = require('bcrypt');
|
||||
const crypto = require('node:crypto');
|
||||
const {EventEmitter} = require('node:events');
|
||||
const {AppError, KeyedQueue, hash} = require('./core');
|
||||
const {schemas, matches} = require('../public/protocol');
|
||||
function createSessionService(repository, config) {
|
||||
const events = new EventEmitter();
|
||||
const queue = new KeyedQueue();
|
||||
let dummyHash;
|
||||
function credentials(body) {
|
||||
if (!matches(body, schemas.credentials) || Buffer.byteLength(body.password, 'utf8') > 72) throw new AppError('INVALID_CREDENTIALS', 'Username must be 1-40 characters; password must be 1-72 UTF-8 bytes');
|
||||
const username = body.username.trim().toLowerCase();
|
||||
if (!username) throw new AppError('INVALID_CREDENTIALS', 'Username is required');
|
||||
return {username, password: body.password};
|
||||
}
|
||||
function issue(username) { return jwt.sign({username}, config.secret, {algorithm: 'HS256', expiresIn: config.sessionSeconds, jwtid: crypto.randomUUID()}); }
|
||||
async function verify(token) {
|
||||
try {
|
||||
if (typeof token !== 'string') return null;
|
||||
const decoded = jwt.verify(token, config.secret, {algorithms: ['HS256']});
|
||||
if (typeof decoded.username !== 'string') return null;
|
||||
const user = await repository.user(decoded.username);
|
||||
if (!user?.sessionHash || user.sessionHash !== hash(token)) return null;
|
||||
return {username: user.username, exp: decoded.exp};
|
||||
} catch (error) {
|
||||
if (error instanceof jwt.JsonWebTokenError || error instanceof jwt.TokenExpiredError || error instanceof jwt.NotBeforeError) return null;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return {events, verify,
|
||||
async register(body) {
|
||||
const {username, password} = credentials(body);
|
||||
return queue.run([username], async () => {
|
||||
const passwordHash = await bcrypt.hash(password, config.bcryptRounds);
|
||||
const token = issue(username);
|
||||
await repository.register(username, passwordHash, hash(token));
|
||||
return {username, token};
|
||||
});
|
||||
},
|
||||
async login(body) {
|
||||
const {username, password} = credentials(body);
|
||||
return queue.run([username], async () => {
|
||||
const user = await repository.user(username);
|
||||
dummyHash ||= bcrypt.hash('invalid-account-placeholder', config.bcryptRounds);
|
||||
const valid = await bcrypt.compare(password, user?.passwordHash || await dummyHash);
|
||||
if (!user || !valid) throw new AppError('INVALID_CREDENTIALS', 'Invalid credentials', 401);
|
||||
const token = issue(user.username);
|
||||
await repository.setSession(user.username, hash(token));
|
||||
events.emit('revoked', user.username);
|
||||
return {username: user.username, token};
|
||||
});
|
||||
},
|
||||
async authorized(token, work) {
|
||||
const decoded = typeof token === 'string' ? jwt.decode(token) : null;
|
||||
if (typeof decoded?.username !== 'string') throw new AppError('UNAUTHORIZED', 'Please sign in', 401);
|
||||
return queue.run([decoded.username], async () => {
|
||||
const user = await verify(token);
|
||||
if (!user) throw new AppError('UNAUTHORIZED', 'Session expired or revoked', 401);
|
||||
return work(user);
|
||||
});
|
||||
},
|
||||
async revoke(username) { await repository.setSession(username, null); events.emit('revoked', username); },
|
||||
close: () => queue.drain()
|
||||
};
|
||||
}
|
||||
module.exports = {createSessionService};
|
||||
Reference in New Issue
Block a user