1
0
mirror of https://github.com/Foltik/Shimapan synced 2024-11-15 17:18:05 -05:00
shimapan/app/routes/api/auth.js

128 lines
3.9 KiB
JavaScript

const express = require('express');
const router = express.Router();
const config = require('config');
const fs = require('fs').promises;
const passport = require('passport');
const canonicalize = require('../../util/auth/canonicalize');
const ModelPath = '../../models/';
const User = require(ModelPath + 'User.js');
const Invite = require(ModelPath + 'Invite.js');
const requireAuth = require('../../util/auth').requireAuth;
const verifyBody = require('../../util/verifyBody');
const rateLimit = require('../../util/rateLimit');
// Wraps passport.authenticate to return a promise
const authenticate = (req, res, next) => {
return new Promise((resolve) => {
passport.authenticate('local', (err, user) => {
resolve(user);
})(req, res, next);
});
};
// Wraps passport session creation to return a promise
const login = (user, req) => {
return new Promise((resolve) => {
req.login(user, resolve);
});
};
const registerParams = [
{name: 'displayname', type: 'string', maxLength: config.get('User.Username.maxLength'), sanitize: true, restrict: new RegExp(config.get('User.Username.restrictedChars'))},
{name: 'password', type: 'string'},
{name: 'invite', type: 'string'}];
router.post('/register',
rateLimit(config.get('RateLimit.register.window'), config.get('RateLimit.register.max'), true),
verifyBody(registerParams),
async (req, res) => {
const username = canonicalize(req.body.displayname);
// Retrieve invite and username status
const [invite, usernameCount] = await Promise.all([
Invite.findOne({code: req.body.invite}),
User.countDocuments({username: username})
]);
// Validate the invite
if (!invite)
return res.status(422).json({message: 'Invalid invite code.'});
if (invite.used)
return res.status(422).json({message: 'Invite already used.'});
if (invite.expires != null && invite.expires < Date.now())
return res.status(422).json({message: 'Invite expired.'});
// Validate the username
if (usernameCount !== 0)
return res.status(422).json({message: 'Username in use.'});
// Create the user object
await User.register({
username: username,
displayname: req.body.displayname,
scope: invite.scope,
date: Date.now()
}, req.body.password);
// Update the invite as used
await Invite.updateOne({code: invite.code}, {recipient: username, used: Date.now()});
res.status(200).json({'message': 'Registration successful.'});
});
const loginParams = [
{name: 'displayname', type: 'string'},
{name: 'password', type: 'string'}];
router.post('/login',
rateLimit(config.get('RateLimit.login.window'), config.get('RateLimit.login.max'), true),
verifyBody(loginParams),
async (req, res, next) => {
req.body.username = canonicalize(req.body.displayname);
// Authenticate
const user = await authenticate(req, res, next);
if (!user) {
// Log failure
await fs.appendFile('auth.log', `${new Date().toISOString()} login ${req.ip}\n`);
return res.status(401).json({'message': 'Unauthorized.'});
}
// Create session
await login(user, req);
// Set session vars
req.session.passport.displayname = user.displayname;
req.session.passport.scope = user.scope;
res.status(200).json({'message': 'Logged in.'});
});
router.post('/logout', (req, res) => {
if (!req.isAuthenticated())
return res.status(400).json({message: 'Not logged in.'});
req.logout();
res.status(200).json({'message': 'Logged out.'});
});
router.get('/whoami', requireAuth(), (req, res) => {
res.status(200).json({
username: req.username,
displayname: req.displayname,
scope: req.scope,
key: req.key
});
});
module.exports = router;