The DTP Sites web app development engine.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

199 lines
5.4 KiB

// auth.js
// Copyright (C) 2022 DTP Technologies, LLC
// License: Apache-2.0
'use strict';
const DTP_COMPONENT_NAME = 'auth';
const express = require('express');
const mongoose = require('mongoose');
const multer = require('multer');
const passport = require('passport');
const uuidv4 = require('uuid').v4;
const { SiteController, SiteError } = require('../../lib/site-lib');
const ConnectToken = mongoose.model('ConnectToken');
class AuthController extends SiteController {
constructor (dtp) {
super(dtp, DTP_COMPONENT_NAME);
}
async start ( ) {
const { limiter: limiterService } = this.dtp.services;
const upload = multer({ dest: `/tmp/${this.dtp.config.site.domainKey}/uploads/${DTP_COMPONENT_NAME}` });
const router = express.Router();
this.dtp.app.use('/auth', router);
const authRequired = this.dtp.services.session.authCheckMiddleware({ requireLogin: true });
router.post('/otp/enable',
limiterService.create(limiterService.config.auth.postOtpEnable),
this.postOtpEnable.bind(this),
);
router.post('/otp/auth',
limiterService.create(limiterService.config.auth.postOtpAuthenticate),
this.postOtpAuthenticate.bind(this),
);
router.post('/login',
limiterService.create(limiterService.config.auth.postLogin),
upload.none(),
this.postLogin.bind(this),
);
router.get('/api-token/personal',
authRequired,
limiterService.create(limiterService.config.auth.getPersonalApiToken),
this.getPersonalApiToken.bind(this),
);
router.get('/socket-token',
authRequired,
limiterService.create(limiterService.config.auth.getSocketToken),
this.getSocketToken.bind(this),
);
router.get('/logout',
authRequired,
limiterService.create(limiterService.config.auth.getLogout),
this.getLogout.bind(this),
);
return router;
}
async postOtpEnable (req, res, next) {
const { otpAuth: otpAuthService } = this.dtp.services;
const service = req.body['otp-service'];
const secret = req.body['otp-secret'];
const token = req.body['otp-token'];
const otpRedirectURL = req.body['otp-redirect'] || '/';
try {
this.log.info('enabling OTP protections', { service, secret, token });
res.locals.otpAccount = await otpAuthService.createOtpAccount(req, service, secret, token);
res.locals.otpRedirectURL = otpRedirectURL;
res.render('otp/new-account');
} catch (error) {
this.log.error('failed to enable OTP protections', {
service, error,
});
return next(error);
}
}
async postOtpAuthenticate (req, res, next) {
const { otpAuth: otpAuthService } = this.dtp.services;
if (!req.user) {
return res.status(403).json({
success: false,
message: 'Must be logged in',
});
}
const service = req.body['otp-service'];
if (!service) {
return res.status(400).json({
success: false,
message: 'Must specify OTP service name',
});
}
const passcode = req.body['otp-passcode'];
if (!passcode || (typeof passcode !== 'string') || (passcode.length !== 6)) {
return res.status(400).json({
success: false,
message: 'Must include a valid passcode',
});
}
try {
await otpAuthService.startOtpSession(req, service, passcode);
return res.redirect(req.body['otp-redirect']);
} catch (error) {
this.log.error('failed to verify one-time password for 2FA', {
service, error,
}, req.user);
return next(error);
}
}
async postLogin (req, res, next) {
passport.authenticate('dtp-local', (error, user/*, info*/) => {
if (error) {
req.session.loginResult = error.toString();
return next(error);
}
if (!user) {
req.session.loginResult = 'Username or email address is unknown.';
return res.redirect('/welcome/login');
}
this.log.info('user logging in', { user: user.username });
req.login(user, (error) => {
if (error) {
return next(error);
}
return res.redirect('/');
});
})(req, res, next);
}
async getPersonalApiToken (req, res, next) {
try {
const { apiGuard: apiGuardService } = this.dtp.platform.services;
res.locals.apiToken = await apiGuardService.createApiToken(req.user, [
'account-read',
// additional scopes go here
]);
res.render('api-token/view');
} catch (error) {
this.log.error('failed to generate API token', { error });
return next(error);
}
}
async getSocketToken (req, res, next) {
try {
const token = await ConnectToken.create({
created: new Date(),
user: req.user._id,
token: uuidv4(),
});
res.status(200).json({
success: true,
token: token.token
});
} catch (error) {
this.log.error('failed to create Socket.io connect token', { error });
return next(error);
}
}
async getLogout (req, res, next) {
if (!req.user) {
return next(new SiteError(403, 'You are not signed in'));
}
req.logout();
req.session.destroy((err) => {
if (err) {
this.log.error('failed to destroy browser session', { err });
return next(err);
}
res.redirect('/');
});
}
}
module.exports = {
slug: 'auth',
name: 'auth',
create: async (dtp) => {
let controller = new AuthController(dtp);
return controller;
},
};