UNPKG

cloud-red

Version:

Harnessing Serverless for your cloud integration needs

280 lines (259 loc) 7.87 kB
/** * Copyright JS Foundation and other contributors, http://js.foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ /** * @mixin @node-red/editor-api_auth */ var passport = require('passport'); var oauth2orize = require('oauth2orize'); var strategies = require('./strategies'); var Tokens = require('./tokens'); var Users = require('./users'); var permissions = require('./permissions'); var theme = require('../editor/theme'); var settings = null; var log = require('../../../util').log; // TODO: separate module passport.use(strategies.bearerStrategy.BearerStrategy); passport.use(strategies.clientPasswordStrategy.ClientPasswordStrategy); passport.use(strategies.anonymousStrategy); var server = oauth2orize.createServer(); server.exchange( oauth2orize.exchange.password(strategies.passwordTokenExchange) ); function init(_settings, storage) { settings = _settings; if (settings.adminAuth) { var mergedAdminAuth = Object.assign( {}, settings.adminAuth, settings.adminAuth.module ); Users.init(mergedAdminAuth); Tokens.init(mergedAdminAuth, storage); } } /** * Returns an Express middleware function that ensures the user making a request * has the necessary permission. * * @param {String} permission - the permission required for the request, such as `flows.write` * @return {Function} - an Express middleware * @memberof @node-red/editor-api_auth */ function needsPermission(permission) { return function(req, res, next) { if (settings && settings.adminAuth) { return passport.authenticate(['bearer', 'anon'], { session: false })( req, res, function() { if (!req.user) { return next(); } if (permissions.hasPermission(req.authInfo.scope, permission)) { return next(); } log.audit({ event: 'permission.fail', permissions: permission }, req); return res.status(401).end(); } ); } else { next(); } }; } function ensureClientSecret(req, res, next) { if (!req.body.client_secret) { req.body.client_secret = 'not_available'; } next(); } function authenticateClient(req, res, next) { return passport.authenticate(['oauth2-client-password'], { session: false })( req, res, next ); } function getToken(req, res, next) { return server.token()(req, res, next); } function login(req, res) { var response = {}; if (settings.adminAuth) { var mergedAdminAuth = Object.assign( {}, settings.adminAuth, settings.adminAuth.module ); if (mergedAdminAuth.type === 'credentials') { response = { type: 'credentials', prompts: [ { id: 'username', type: 'text', label: 'user.username' }, { id: 'password', type: 'password', label: 'user.password' } ] }; } else if (mergedAdminAuth.type === 'strategy') { var urlPrefix = settings.httpAdminRoot === '/' ? '' : settings.httpAdminRoot; response = { type: 'strategy', prompts: [ { type: 'button', label: mergedAdminAuth.strategy.label, url: urlPrefix + 'auth/strategy' } ] }; if (mergedAdminAuth.strategy.icon) { response.prompts[0].icon = mergedAdminAuth.strategy.icon; } if (mergedAdminAuth.strategy.image) { response.prompts[0].image = theme.serveFile( '/login/', mergedAdminAuth.strategy.image ); } } if (theme.context().login && theme.context().login.image) { response.image = theme.context().login.image; } } res.json(response); } function revoke(req, res) { var token = req.body.token; // TODO: audit log Tokens.revoke(token).then(function() { log.audit({ event: 'auth.login.revoke' }, req); if ( settings.editorTheme && settings.editorTheme.logout && settings.editorTheme.logout.redirect ) { res.json({ redirect: settings.editorTheme.logout.redirect }); } else { res.status(200).end(); } }); } function completeVerify(profile, done) { Users.authenticate(profile).then(function(user) { if (user) { Tokens.create(user.username, 'node-red-editor', user.permissions).then( function(tokens) { log.audit({ event: 'auth.login', username: user.username, scope: user.permissions }); user.tokens = tokens; done(null, user); } ); } else { log.audit({ event: 'auth.login.fail.oauth', username: typeof profile === 'string' ? profile : profile.username }); done(null, false); } }); } function genericStrategy(adminApp, strategy) { var crypto = require('crypto'); var session = require('express-session'); var MemoryStore = require('memorystore')(session); adminApp.use( session({ // As the session is only used across the life-span of an auth // hand-shake, we can use a instance specific random string secret: crypto.randomBytes(20).toString('hex'), resave: false, saveUninitialized: false, store: new MemoryStore({ checkPeriod: 86400000 // prune expired entries every 24h }) }) ); //TODO: all passport references ought to be in ./auth adminApp.use(passport.initialize()); adminApp.use(passport.session()); var options = strategy.options; passport.use( new strategy.strategy(options, function() { var originalDone = arguments[arguments.length - 1]; if (options.verify) { var args = Array.from(arguments); args[args.length - 1] = function(err, profile) { if (err) { return originalDone(err); } else { return completeVerify(profile, originalDone); } }; options.verify.apply(null, args); } else { var profile = arguments[arguments.length - 2]; return completeVerify(profile, originalDone); } }) ); adminApp.get( '/auth/strategy', passport.authenticate(strategy.name, { session: false, failureRedirect: settings.httpAdminRoot }), completeGenerateStrategyAuth ); var callbackMethodFunc = adminApp.get; if (/^post$/i.test(options.callbackMethod)) { callbackMethodFunc = adminApp.post; } callbackMethodFunc.call( adminApp, '/auth/strategy/callback', passport.authenticate(strategy.name, { session: false, failureRedirect: settings.httpAdminRoot }), completeGenerateStrategyAuth ); } function completeGenerateStrategyAuth(req, res) { var tokens = req.user.tokens; delete req.user.tokens; // Successful authentication, redirect home. res.redirect(settings.httpAdminRoot + '?access_token=' + tokens.accessToken); } module.exports = { init: init, needsPermission: needsPermission, ensureClientSecret: ensureClientSecret, authenticateClient: authenticateClient, getToken: getToken, errorHandler: function(err, req, res, next) { //TODO: audit log statment //console.log(err.stack); //log.log({level:"audit",type:"auth",msg:err.toString()}); return server.errorHandler()(err, req, res, next); }, login: login, revoke: revoke, genericStrategy: genericStrategy };