mirror of https://gitee.com/bigwinds/arangodb
260 lines
7.9 KiB
JavaScript
260 lines
7.9 KiB
JavaScript
'use strict';
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / DISCLAIMER
|
|
// /
|
|
// / Copyright 2013-2014 triAGENS GmbH, Cologne, Germany
|
|
// / Copyright 2015 ArangoDB GmbH, Cologne, Germany
|
|
// /
|
|
// / 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.
|
|
// /
|
|
// / Copyright holder is ArangoDB GmbH, Cologne, Germany
|
|
// /
|
|
// / @author Jan Steemann
|
|
// / @author Lucas Dohmen
|
|
// / @author Alan Plum
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
const joi = require('joi');
|
|
const Foxx = require('@arangodb/foxx/legacy');
|
|
const crypto = require('@arangodb/crypto');
|
|
const paramSchema = joi.string().optional().description('Foxx session ID');
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief decorates the controller with session logic
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function decorateController (auth, controller) {
|
|
var cfg = auth.configuration;
|
|
|
|
if (cfg.param) {
|
|
controller.allRoutes.queryParam(cfg.param, paramSchema);
|
|
}
|
|
|
|
controller.before('/*', function (req) {
|
|
var sessions = auth.getSessionStorage();
|
|
var sid;
|
|
if (cfg.param) {
|
|
sid = req.params(cfg.param);
|
|
}
|
|
if (!sid && cfg.cookie) {
|
|
sid = req.cookie(cfg.cookie.name, cfg.cookie.secret ? {
|
|
signed: {
|
|
secret: cfg.cookie.secret,
|
|
algorithm: cfg.cookie.algorithm
|
|
}
|
|
} : undefined);
|
|
}
|
|
if (!sid && cfg.header) {
|
|
sid = req.headers[cfg.header.toLowerCase()];
|
|
}
|
|
if (sid) {
|
|
if (cfg.jwt) {
|
|
sid = crypto.jwtDecode(cfg.jwt.secret, sid, !cfg.jwt.verify);
|
|
}
|
|
try {
|
|
req.session = sessions.get(sid);
|
|
} catch (e) {
|
|
if (!(e instanceof sessions.errors.SessionNotFound)) {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
if (!req.session && cfg.autoCreateSession) {
|
|
req.session = sessions.create();
|
|
}
|
|
});
|
|
|
|
controller.after('/*', function (req, res) {
|
|
if (req.session) {
|
|
var sid = req.session.forClient();
|
|
if (cfg.jwt) {
|
|
sid = crypto.jwtEncode(cfg.jwt.secret, sid, cfg.jwt.algorithm);
|
|
}
|
|
if (cfg.cookie) {
|
|
res.cookie(cfg.cookie.name, sid, {
|
|
ttl: req.session.getTTL() / 1000,
|
|
signed: cfg.cookie.secret ? {
|
|
secret: cfg.cookie.secret,
|
|
algorithm: cfg.cookie.algorithm
|
|
} : undefined
|
|
});
|
|
}
|
|
if (cfg.header) {
|
|
res.set(cfg.header, sid);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief convenience wrapper for session destruction logic
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
function createDestroySessionHandler (auth, opts) {
|
|
if (!opts) {
|
|
opts = {};
|
|
}
|
|
if (typeof opts === 'function') {
|
|
opts = {after: opts};
|
|
}
|
|
if (!opts.after) {
|
|
opts.after = function (req, res) {
|
|
res.json({message: 'logged out'});
|
|
};
|
|
}
|
|
var cfg = auth.configuration;
|
|
return function (req, res, injected) {
|
|
if (typeof opts.before === 'function') {
|
|
opts.before(req, res, injected);
|
|
}
|
|
if (req.session && typeof req.session.delete === 'function') {
|
|
req.session.delete();
|
|
}
|
|
if (cfg.autoCreateSession) {
|
|
req.session = auth.getSessionStorage().create();
|
|
} else {
|
|
if (cfg.cookie) {
|
|
res.cookie(cfg.cookie.name, '', {
|
|
ttl: -(7 * 24 * 60 * 60),
|
|
sign: cfg.cookie.secret ? {
|
|
secret: cfg.cookie.secret,
|
|
algorithm: cfg.cookie.algorithm
|
|
} : undefined
|
|
});
|
|
}
|
|
delete req.session;
|
|
}
|
|
if (typeof opts.after === 'function') {
|
|
opts.after(req, res, injected);
|
|
}
|
|
};
|
|
}
|
|
|
|
var sessionTypes = ['cookie', 'header'];
|
|
|
|
class Sessions {
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief constructor
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
constructor (opts) {
|
|
if (!opts) {
|
|
opts = {};
|
|
}
|
|
|
|
if (opts.type) {
|
|
if (opts.type === 'cookie') {
|
|
delete opts.header;
|
|
opts.cookie = opts.cookie || true;
|
|
} else if (opts.type === 'header') {
|
|
delete opts.cookie;
|
|
opts.header = opts.header || true;
|
|
} else {
|
|
throw new Error('Only the following session types are supported at this time: ' + sessionTypes.join(', '));
|
|
}
|
|
}
|
|
|
|
if (opts.cookie) {
|
|
if (opts.cookie === true) {
|
|
opts.cookie = {};
|
|
} else if (typeof opts.cookie === 'string') {
|
|
opts.cookie = {name: opts.cookie};
|
|
} else if (typeof opts.cookie !== 'object') {
|
|
throw new Error('Expected cookie settings to be an object, boolean or string.');
|
|
}
|
|
if (!opts.cookie.name) {
|
|
opts.cookie.name = 'sid';
|
|
} else if (typeof opts.cookie.name !== 'string') {
|
|
throw new Error('Cookie name must be a string or empty.');
|
|
}
|
|
if (opts.cookie.secret && typeof opts.cookie.secret !== 'string') {
|
|
throw new Error('Cookie secret must be a string or empty.');
|
|
}
|
|
}
|
|
if (opts.header) {
|
|
if (opts.header === true) {
|
|
opts.header = 'X-Session-Id';
|
|
} else if (typeof opts.header !== 'string') {
|
|
throw new Error('Header name must be true, a string or empty.');
|
|
}
|
|
}
|
|
if (opts.param) {
|
|
if (opts.param === true) {
|
|
opts.param = 'FOXXSID';
|
|
} else if (typeof opts.param !== 'string') {
|
|
throw new Error('Param name must be true, a string or empty.');
|
|
}
|
|
}
|
|
if (opts.jwt) {
|
|
if (opts.jwt === true) {
|
|
opts.jwt = {};
|
|
} else if (typeof opts.jwt === 'string') {
|
|
opts.jwt = {secret: opts.jwt};
|
|
} else if (typeof opts.jwt !== 'object') {
|
|
throw new Error('Expected JWT settings to be an object, boolean or string.');
|
|
}
|
|
if (opts.jwt.verify !== false) {
|
|
opts.jwt.verify = true;
|
|
}
|
|
if (!opts.jwt.secret) {
|
|
opts.jwt.secret = '';
|
|
if (!opts.jwt.algorithm) {
|
|
opts.jwt.algorithm = 'none';
|
|
} else if (opts.jwt.algorithm !== 'none') {
|
|
throw new Error('Must provide a JWT secret to use any algorithm other than "none".');
|
|
}
|
|
} else {
|
|
if (typeof opts.jwt.secret !== 'string') {
|
|
throw new Error('Header JWT secret must be a string or empty.');
|
|
}
|
|
if (!opts.jwt.algorithm) {
|
|
opts.jwt.algorithm = 'HS256';
|
|
} else {
|
|
opts.jwt.algorithm = crypto.jwtCanonicalAlgorithmName(opts.jwt.algorithm);
|
|
}
|
|
}
|
|
}
|
|
if (opts.autoCreateSession !== false) {
|
|
opts.autoCreateSession = true;
|
|
}
|
|
|
|
if (!opts.sessionStorage) {
|
|
if (opts.sessionStorageApp) {
|
|
opts.sessionStorage = opts.sessionStorageApp;
|
|
} else {
|
|
opts.sessionStorage = '/_system/sessions';
|
|
}
|
|
}
|
|
|
|
this.configuration = opts;
|
|
}
|
|
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
// / @brief fetches the session storage
|
|
// //////////////////////////////////////////////////////////////////////////////
|
|
|
|
getSessionStorage () {
|
|
if (typeof this.configuration.sessionStorage !== 'string') {
|
|
return this.configuration.sessionStorage;
|
|
}
|
|
return Foxx.getExports(this.configuration.sessionStorage).sessionStorage;
|
|
}
|
|
}
|
|
|
|
exports.sessionTypes = sessionTypes;
|
|
exports.Sessions = Sessions;
|
|
exports.decorateController = decorateController;
|
|
exports.createDestroySessionHandler = createDestroySessionHandler;
|