|
- const express = require('express');
- const router = express.Router();
- const config = require('config');
- const fs = require('fs').promises;
-
- const ModelPath = '../../models/';
- const User = require(ModelPath + 'User.js');
- const Invite = require(ModelPath + 'Invite.js');
-
- const passport = require('passport');
-
- const canonicalizeRequest = require('../../util/canonicalize').canonicalizeRequest;
- const requireAuth = require('../../util/auth').requireAuth;
- const wrap = require('../../util/wrap.js');
- const bodyVerifier = require('../../util/verifyBody').bodyVerifier;
-
- // 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 for async usage
- const login = (user, req) => {
- return new Promise((resolve) => {
- req.login(user, resolve);
- });
- };
-
- // 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);
-
- if (!invite) {
- // Log failure
- await fs.appendFile('auth.log', `${new Date().toISOString()} register ${req.connection.remoteAddress}`);
- 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.'});
-
- req.invite = invite;
- next();
- });
-
- // 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.'});
-
- 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',
- bodyVerifier(registerProps), canonicalizeRequest,
- validateInvite, validateUsername,
- wrap(async (req, res, next) => {
- // Update the database
- await Promise.all([
- User.register({
- username: req.body.username,
- displayname: req.body.displayname,
- scope: req.invite.scope,
- date: Date.now()
- }, req.body.password).catch(next),
- Invite.updateOne({code: req.invite.code}, {recipient: req.body.username, used: Date.now()}).catch(next)
- ]);
-
- res.status(200).json({'message': 'Registration successful.'});
- }));
-
- const loginProps = [
- {name: 'username', type: 'string', optional: true},
- {name: 'displayname', type: 'string', optional: true},
- {name: 'password', type: 'string'}];
- router.post('/login', bodyVerifier(loginProps), canonicalizeRequest, wrap(async (req, res, next) => {
- // Authenticate
- const user = await authenticate(req, res, next);
- if (!user) {
- // Log failure
- await fs.appendFile('auth.log', `${new Date().toISOString()} login ${req.connection.remoteAddress}`);
- 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', function (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;
|