diff --git a/src/routes/cloudron.js b/src/routes/cloudron.js index d65cb8309..b23bd43ac 100644 --- a/src/routes/cloudron.js +++ b/src/routes/cloudron.js @@ -1,6 +1,8 @@ 'use strict'; exports = module.exports = { + login: login, + logout: logout, reboot: reboot, isRebootRequired: isRebootRequired, getConfig: getConfig, @@ -22,16 +24,66 @@ let assert = require('assert'), async = require('async'), auditSource = require('../auditsource.js'), BoxError = require('../boxerror.js'), + clients = require('../clients.js'), cloudron = require('../cloudron.js'), custom = require('../custom.js'), externalLdap = require('../externalldap.js'), HttpError = require('connect-lastmile').HttpError, HttpSuccess = require('connect-lastmile').HttpSuccess, + passport = require('passport'), + speakeasy = require('speakeasy'), sysinfo = require('../sysinfo.js'), system = require('../system.js'), + tokendb = require('../tokendb.js'), updater = require('../updater.js'), updateChecker = require('../updatechecker.js'); +function login(req, res, next) { + passport.authenticate('local', function (error, user) { + if (error) return next(new HttpError(500, error)); + if (!user) return next(new HttpError(401, 'Invalid credentials')); + + var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || null; + + if (!user.ghost && !user.appPassword && user.twoFactorAuthenticationEnabled) { + if (!req.body.totpToken) return next(new HttpError(401, 'A totpToken must be provided')); + + let verified = speakeasy.totp.verify({ secret: user.twoFactorAuthenticationSecret, encoding: 'base32', token: req.body.totpToken, window: 2 }); + if (!verified) return next(new HttpError(401, 'Invalid totpToken')); + } + + const auditSource = { authType: 'cli', ip: ip }; + clients.issueDeveloperToken(user, auditSource, function (error, result) { + if (error) return next(new HttpError(500, error)); + + next(new HttpSuccess(200, result)); + }); + })(req, res, next); +} + +function logout(req, res) { + var token; + + // this determines the priority + if (req.body && req.body.access_token) token = req.body.access_token; + if (req.query && req.query.access_token) token = req.query.access_token; + if (req.headers && req.headers.authorization) { + var parts = req.headers.authorization.split(' '); + if (parts.length == 2) { + var scheme = parts[0]; + var credentials = parts[1]; + + if (/^Bearer$/i.test(scheme)) token = credentials; + } + } + + if (!token) return res.redirect('/login.html'); + + tokendb.delByAccessToken(token, function () { + res.redirect('/index.html'); + }); +} + function reboot(req, res, next) { // Finish the request, to let the appstore know we triggered the reboot next(new HttpSuccess(202, {})); diff --git a/src/server.js b/src/server.js index f0e0f6743..80df070bd 100644 --- a/src/server.js +++ b/src/server.js @@ -139,6 +139,10 @@ function initializeExpressSync() { router.get ('/api/v1/cloudron/avatar', routes.settings.getCloudronAvatar); // this is a public alias for /api/v1/settings/cloudron_avatar + // login/logout routes + router.post('/api/v1/cloudron/login', routes.cloudron.login); + router.get ('/api/v1/cloudron/logout', routes.cloudron.logout); // this will invalidate the token if any and redirect to /login.html always + // developer routes router.post('/api/v1/developer/login', routes.developer.login); diff --git a/src/tokendb.js b/src/tokendb.js index f9fa11a00..164b3240b 100644 --- a/src/tokendb.js +++ b/src/tokendb.js @@ -5,6 +5,7 @@ exports = module.exports = { get: get, getByAccessToken: getByAccessToken, + delByAccessToken: delByAccessToken, add: add, del: del, delByClientId: delByClientId, @@ -35,6 +36,18 @@ function getByAccessToken(accessToken, callback) { }); } +function delByAccessToken(accessToken, callback) { + assert.strictEqual(typeof accessToken, 'string'); + assert.strictEqual(typeof callback, 'function'); + + database.query('DELETE FROM tokens WHERE accessToken = ?', [ accessToken ], function (error, result) { + if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); + if (result.affectedRows !== 1) return callback(new BoxError(BoxError.NOT_FOUND, 'Token not found')); + + return callback(null); + }); +} + function get(id, callback) { assert.strictEqual(typeof id, 'string'); assert.strictEqual(typeof callback, 'function');