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

152 lines
4.7 KiB
JavaScript
Raw Normal View History

2018-07-25 01:45:05 -04:00
const express = require('express');
const router = express.Router();
const config = require('config');
2018-12-26 19:02:59 -05:00
const fs = require('fs').promises;
2018-08-01 11:54:35 -04:00
const ModelPath = '../../models/';
const User = require(ModelPath + 'User.js');
const Invite = require(ModelPath + 'Invite.js');
const passport = require('passport');
2018-08-01 11:54:35 -04:00
const canonicalizeRequest = require('../../util/canonicalize').canonicalizeRequest;
const requireAuth = require('../../util/auth').requireAuth;
const wrap = require('../../util/wrap.js');
const bodyVerifier = require('../../util/verifyBody').bodyVerifier;
2018-12-26 20:47:04 -05:00
const rateLimit = require('express-rate-limit');
2018-07-25 01:45:05 -04:00
2018-07-25 18:45:38 -04:00
// Wraps passport.authenticate to return a promise
const authenticate = (req, res, next) => {
2018-07-25 18:45:38 -04:00
return new Promise((resolve) => {
passport.authenticate('local', (err, user) => {
resolve(user);
})(req, res, next);
});
};
2018-07-25 01:45:05 -04:00
2018-07-25 18:45:38 -04:00
// Wraps passport session creation for async usage
const login = (user, req) => {
2018-07-25 18:45:38 -04:00
return new Promise((resolve) => {
req.login(user, resolve);
});
};
2017-10-12 12:50:02 -04:00
2018-07-25 01:45:05 -04:00
// Query the database for a valid invite code. An error message property is set if invalid.
const validateInvite = wrap(async (req, res, next) => {
const invite = await Invite.findOne({code: req.body.invite}).catch(next);
2018-07-25 01:45:05 -04:00
2018-12-26 19:02:59 -05:00
if (!invite) {
// Log failure
await fs.appendFile('auth.log', `${new Date().toISOString()} register ${req.ip}\n`);
return res.status(422).json({message: 'Invalid invite code.'});
2018-12-26 19:02:59 -05:00
}
2018-07-25 01:45:05 -04:00
if (invite.used)
return res.status(422).json({message: 'Invite already used.'});
2018-07-25 01:45:05 -04:00
2018-07-27 14:23:23 -04:00
if (invite.expires != null && invite.expires < Date.now())
return res.status(422).json({message: 'Invite expired.'});
2017-10-11 12:55:46 -04:00
req.invite = invite;
next();
});
2018-07-25 01:45:05 -04:00
// Check if the requested username is valid
const validateUsername = wrap(async (req, res, next) => {
const username = req.body.username;
const count = await User.countDocuments({username: username}).catch(next);
if (count !== 0)
return res.status(422).json({message: 'Username in use.'});
2018-07-25 01:45:05 -04:00
next();
});
2018-07-25 01:45:05 -04:00
2018-12-26 20:47:04 -05:00
const registerLimiter = config.get('RateLimit.enable')
? rateLimit({
windowMs: config.get('RateLimit.register.window') * 1000,
max: config.get('RateLimit.register.max'),
skipSuccessfulRequests: true
})
: (req, res, next) => { next(); };
const registerProps = [
{
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',
2018-12-26 20:47:04 -05:00
registerLimiter,
bodyVerifier(registerProps), canonicalizeRequest,
validateInvite, validateUsername,
wrap(async (req, res, next) => {
2018-07-25 18:45:38 -04:00
// Update the database
await Promise.all([
User.register({
username: req.body.username,
displayname: req.body.displayname,
scope: req.invite.scope,
2018-07-25 18:45:38 -04:00
date: Date.now()
}, req.body.password).catch(next),
Invite.updateOne({code: req.invite.code}, {recipient: req.body.username, used: Date.now()}).catch(next)
2018-07-25 18:45:38 -04:00
]);
2018-07-25 01:45:05 -04:00
res.status(200).json({'message': 'Registration successful.'});
}));
2017-10-11 10:15:19 -04:00
2018-12-26 20:47:04 -05:00
const loginLimiter = config.get('RateLimit.enable')
? rateLimit({
2018-12-26 21:03:41 -05:00
windowMs: config.get('RateLimit.login.window') * 1000,
max: config.get('RateLimit.login.max'),
skipSuccessfulRequests: true
2018-12-26 20:47:04 -05:00
})
: (req, res, next) => { next(); };
2018-07-29 11:00:14 -04:00
const loginProps = [
{name: 'username', type: 'string', optional: true},
{name: 'displayname', type: 'string', optional: true},
{name: 'password', type: 'string'}];
2018-12-26 20:47:04 -05:00
router.post('/login',
2018-12-26 21:01:15 -05:00
loginLimiter,
2018-12-26 20:47:04 -05:00
bodyVerifier(loginProps),
canonicalizeRequest,
wrap(async (req, res, next) => {
2018-07-25 18:45:38 -04:00
// Authenticate
const user = await authenticate(req, res, next);
2018-12-26 19:02:59 -05:00
if (!user) {
// Log failure
await fs.appendFile('auth.log', `${new Date().toISOString()} login ${req.ip}\n`);
2018-07-25 18:45:38 -04:00
return res.status(401).json({'message': 'Unauthorized.'});
2018-12-26 19:02:59 -05:00
}
2018-07-25 18:45:38 -04:00
// Create session
await login(user, req);
// Set session vars
req.session.passport.displayname = user.displayname;
2018-07-25 18:45:38 -04:00
req.session.passport.scope = user.scope;
res.status(200).json({'message': 'Logged in.'});
}));
2017-10-11 10:15:19 -04:00
router.post('/logout', function (req, res) {
if (!req.isAuthenticated())
return res.status(400).json({message: 'Not logged in.'});
2017-10-18 13:31:08 -04:00
req.logout();
res.status(200).json({'message': 'Logged out.'});
2017-10-14 17:49:11 -04:00
});
router.get('/whoami', requireAuth(), (req, res) => {
2018-07-25 18:45:38 -04:00
res.status(200).json({
2018-08-12 05:30:50 -04:00
username: req.username,
displayname: req.displayname,
scope: req.scope,
key: req.key
2018-07-25 18:45:38 -04:00
});
2017-10-18 13:31:08 -04:00
});
2017-10-11 10:15:19 -04:00
module.exports = router;