mirror of https://gitee.com/bigwinds/arangodb
Added bundled apps as system apps.
This commit is contained in:
parent
66068924e1
commit
af2b1c1ff6
|
@ -59,6 +59,10 @@ JAVASCRIPT_JSLINT = \
|
|||
\
|
||||
`find @srcdir@/js/apps/system/cerberus -name "*.js"` \
|
||||
`find @srcdir@/js/apps/system/gharial -name "*.js"` \
|
||||
`find @srcdir@/js/apps/system/oauth2 -name "*.js"` \
|
||||
`find @srcdir@/js/apps/system/sessions -name "*.js"` \
|
||||
`find @srcdir@/js/apps/system/simple-auth -name "*.js"` \
|
||||
`find @srcdir@/js/apps/system/users -name "*.js"` \
|
||||
\
|
||||
`find @srcdir@/js/apps/system/aardvark/frontend/js/models -name "*.js"` \
|
||||
`find @srcdir@/js/apps/system/aardvark/frontend/js/views -name "*.js"` \
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
/*jslint indent: 2, nomen: true, maxlen: 120, white: true, plusplus: true, unparam: true, regexp: true, vars: true */
|
||||
/*global require, exports */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function ProviderNotFound(key) {
|
||||
this.message = 'Provider with key ' + key + ' not found.';
|
||||
}
|
||||
ProviderNotFound.prototype = new Error();
|
||||
|
||||
exports.ProviderNotFound = ProviderNotFound;
|
||||
}());
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "oauth2",
|
||||
"author": "Alan Plum",
|
||||
"version": "0.1",
|
||||
"isSystem": true,
|
||||
"description": "OAuth2 authentication for Foxx.",
|
||||
|
||||
"exports": {
|
||||
"providers": "providers.js"
|
||||
},
|
||||
|
||||
"defaultDocument": "",
|
||||
|
||||
"lib": ".",
|
||||
|
||||
"setup": "setup.js"
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
/*jslint indent: 2, nomen: true, maxlen: 120, vars: true, es5: true */
|
||||
/*global require, exports, applicationContext */
|
||||
(function () {
|
||||
'use strict';
|
||||
var _ = require('underscore');
|
||||
var url = require('url');
|
||||
var querystring = require('querystring');
|
||||
var internal = require('internal');
|
||||
var arangodb = require('org/arangodb');
|
||||
var db = arangodb.db;
|
||||
var Foxx = require('org/arangodb/foxx');
|
||||
var errors = require('./errors');
|
||||
|
||||
var Provider = Foxx.Model.extend({}, {
|
||||
attributes: {
|
||||
_key: {type: 'string', required: true},
|
||||
label: {type: 'string', required: true},
|
||||
authEndpoint: {type: 'string', required: true},
|
||||
tokenEndpoint: {type: 'string', required: true},
|
||||
refreshEndpoint: {type: 'string', required: false},
|
||||
activeUserEndpoint: {type: 'string', required: false},
|
||||
usernameTemplate: {type: 'string', required: false},
|
||||
clientId: {type: 'string', required: true},
|
||||
clientSecret: {type: 'string', required: true}
|
||||
}
|
||||
});
|
||||
|
||||
var providers = new Foxx.Repository(
|
||||
applicationContext.collection('providers'),
|
||||
{model: Provider}
|
||||
);
|
||||
|
||||
function listProviders() {
|
||||
return providers.collection.all().toArray().forEach(function (provider) {
|
||||
return _.pick(provider, '_key', 'label', 'clientId');
|
||||
});
|
||||
}
|
||||
|
||||
function createProvider(data) {
|
||||
var provider = new Provider(data);
|
||||
providers.save(provider);
|
||||
return provider;
|
||||
}
|
||||
|
||||
function getProvider(key) {
|
||||
var provider;
|
||||
try {
|
||||
provider = providers.byId(key);
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof arangodb.ArangoError &&
|
||||
err.errorNum === arangodb.ERROR_ARANGO_DOCUMENT_NOT_FOUND
|
||||
) {
|
||||
throw new errors.ProviderNotFound(key);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
function deleteProvider(key) {
|
||||
try {
|
||||
providers.removeById(key);
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof arangodb.ArangoError
|
||||
&& err.errorNum === arangodb.ERROR_ARANGO_DOCUMENT_NOT_FOUND
|
||||
) {
|
||||
throw new errors.ProviderNotFound(key);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_.extend(Provider.prototype, {
|
||||
getAuthUrl: function (redirect_uri, args) {
|
||||
if (redirect_uri && typeof redirect_uri === 'object') {
|
||||
args = redirect_uri;
|
||||
redirect_uri = undefined;
|
||||
}
|
||||
var endpoint = url.parse(this.get('authEndpoint'));
|
||||
args = _.extend(querystring.parse(endpoint.query), args);
|
||||
if (redirect_uri) {
|
||||
args.redirect_uri = redirect_uri;
|
||||
}
|
||||
if (!args.response_type) {
|
||||
args.response_type = 'code';
|
||||
}
|
||||
args.client_id = this.get('clientId');
|
||||
endpoint.search = '?' + querystring.stringify(args);
|
||||
return url.format(endpoint);
|
||||
},
|
||||
_getTokenRequest: function (code, redirect_uri, args) {
|
||||
if (code && typeof code === 'object') {
|
||||
args = code;
|
||||
code = undefined;
|
||||
redirect_uri = undefined;
|
||||
} else if (redirect_uri && typeof redirect_uri === 'object') {
|
||||
args = redirect_uri;
|
||||
redirect_uri = undefined;
|
||||
}
|
||||
var endpoint = url.parse(this.get('tokenEndpoint'));
|
||||
args = _.extend(querystring.parse(endpoint.query), args);
|
||||
if (code) {
|
||||
args.code = code;
|
||||
}
|
||||
if (redirect_uri) {
|
||||
args.redirect_uri = redirect_uri;
|
||||
}
|
||||
if (!args.grant_type) {
|
||||
args.grant_type = 'authorization_code';
|
||||
}
|
||||
args.client_id = this.get('clientId');
|
||||
args.client_secret = this.get('clientSecret');
|
||||
delete endpoint.search;
|
||||
delete endpoint.query;
|
||||
return {url: url.format(endpoint), body: args};
|
||||
},
|
||||
getActiveUserUrl: function (access_token, args) {
|
||||
var endpoint = this.get('activeUserEndpoint');
|
||||
if (!endpoint) {
|
||||
return null;
|
||||
}
|
||||
if (access_token && typeof access_token === 'object') {
|
||||
args = access_token;
|
||||
access_token = undefined;
|
||||
}
|
||||
args = _.extend(querystring.parse(endpoint.query), args);
|
||||
if (access_token) {
|
||||
args.access_token = access_token;
|
||||
}
|
||||
endpoint = url.parse(endpoint);
|
||||
args = _.extend(querystring.parse(endpoint.query), args);
|
||||
endpoint.search = '?' + querystring.stringify(args);
|
||||
return url.format(endpoint);
|
||||
},
|
||||
getUsername: function (obj) {
|
||||
var tpl = this.get('usernameTemplate');
|
||||
if (!tpl) {
|
||||
tpl = '<%= id %>';
|
||||
}
|
||||
return _.template(tpl)(obj);
|
||||
},
|
||||
exchangeGrantToken: function (code, redirect_uri) {
|
||||
var request = this._getTokenRequest(code, redirect_uri);
|
||||
var response = internal.download(
|
||||
request.url,
|
||||
querystring.stringify(request.body),
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
accept: 'application/json',
|
||||
'content-type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
}
|
||||
);
|
||||
if (!response.body) {
|
||||
throw new Error('OAuth provider ' + this.get('_key') + ' returned HTTP ' + response.code);
|
||||
}
|
||||
try {
|
||||
return JSON.parse(response.body);
|
||||
} catch (err) {
|
||||
if (err instanceof SyntaxError) {
|
||||
return querystring.parse(response.body);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
fetchActiveUser: function (access_token) {
|
||||
var url = this.getActiveUserUrl(access_token);
|
||||
if (!url) {
|
||||
throw new Error('Provider ' + this.get('_key') + ' does not support active user lookup');
|
||||
}
|
||||
var response = internal.download(url);
|
||||
if (!response.body) {
|
||||
throw new Error('OAuth provider ' + this.get('_key') + ' returned HTTP ' + response.code);
|
||||
}
|
||||
try {
|
||||
return JSON.parse(response.body);
|
||||
} catch (err) {
|
||||
if (err instanceof SyntaxError) {
|
||||
return querystring.parse(response.body);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
save: function () {
|
||||
var provider = this;
|
||||
providers.replace(provider);
|
||||
return provider;
|
||||
},
|
||||
delete: function () {
|
||||
try {
|
||||
deleteProvider(this.get('_key'));
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (e instanceof errors.ProviderNotFound) {
|
||||
return false;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
exports.list = listProviders;
|
||||
exports.create = createProvider;
|
||||
exports.get = getProvider;
|
||||
exports.delete = deleteProvider;
|
||||
exports.errors = errors;
|
||||
exports.repository = providers;
|
||||
}());
|
|
@ -0,0 +1,47 @@
|
|||
/*jslint indent: 2, nomen: true, maxlen: 120, white: true, plusplus: true, unparam: true, regexp: true, vars: true */
|
||||
/*global require, applicationContext */
|
||||
(function () {
|
||||
'use strict';
|
||||
var db = require('org/arangodb').db;
|
||||
var providersName = applicationContext.collectionName('providers');
|
||||
|
||||
if (db._collection(providersName) === null) {
|
||||
db._create(providersName);
|
||||
}
|
||||
|
||||
var providers = db._collection(providersName);
|
||||
[
|
||||
{
|
||||
_key: 'github',
|
||||
label: 'GitHub',
|
||||
authEndpoint: 'https://github.com/login/oauth/authorize?scope=user',
|
||||
tokenEndpoint: 'https://github.com/login/oauth/access_token',
|
||||
activeUserEndpoint: 'https://api.github.com/user',
|
||||
clientId: null,
|
||||
clientSecret: null
|
||||
},
|
||||
{
|
||||
_key: 'facebook',
|
||||
label: 'Facebook',
|
||||
authEndpoint: 'https://www.facebook.com/dialog/oauth',
|
||||
tokenEndpoint: 'https://graph.facebook.com/oauth/access_token',
|
||||
activeUserEndpoint: 'https://graph.facebook.com/v2.0/me',
|
||||
clientId: null,
|
||||
clientSecret: null
|
||||
},
|
||||
{
|
||||
_key: 'google',
|
||||
label: 'Google',
|
||||
authEndpoint: 'https://accounts.google.com/o/oauth2/auth?access_type=offline&scope=profile',
|
||||
tokenEndpoint: 'https://accounts.google.com/o/oauth2/token',
|
||||
refreshEndpoint: 'https://accounts.google.com/o/oauth2/token',
|
||||
activeUserEndpoint: 'https://www.googleapis.com/plus/v1/people/me',
|
||||
clientId: null,
|
||||
clientSecret: null
|
||||
}
|
||||
].forEach(function(provider) {
|
||||
if (!providers.exists(provider._key)) {
|
||||
providers.save(provider);
|
||||
}
|
||||
});
|
||||
}());
|
|
@ -0,0 +1,78 @@
|
|||
/*jslint indent: 2, nomen: true, maxlen: 120, vars: true, es5: true */
|
||||
/*global require, applicationContext */
|
||||
(function () {
|
||||
'use strict';
|
||||
var _ = require('underscore');
|
||||
var Foxx = require('org/arangodb/foxx');
|
||||
var errors = require('./errors');
|
||||
var controller = new Foxx.Controller(applicationContext);
|
||||
var api = Foxx.requireApp('/sessions').sessionStorage;
|
||||
|
||||
controller.post('/', function (req, res) {
|
||||
var session = api.create(req.body());
|
||||
res.status(201);
|
||||
res.json(session.forClient());
|
||||
})
|
||||
.errorResponse(SyntaxError, 400, 'Malformed or non-JSON session data.')
|
||||
.summary('Create session')
|
||||
.notes('Stores the given sessionData in a new session.');
|
||||
|
||||
controller.get('/:sid', function (req, res) {
|
||||
var session = api.get(req.urlParameters.sid);
|
||||
res.json(session.forClient());
|
||||
})
|
||||
.pathParam('sid', {
|
||||
description: 'Session ID',
|
||||
type: 'string'
|
||||
})
|
||||
.errorResponse(errors.SessionExpired, 404, 'Session has expired')
|
||||
.errorResponse(errors.SessionNotFound, 404, 'Session does not exist')
|
||||
.summary('Read session')
|
||||
.notes('Fetches the session with the given sid.');
|
||||
|
||||
controller.put('/:sid', function (req, res) {
|
||||
var body = JSON.parse(req.rawBody());
|
||||
var session = api.get(req.urlParameters.sid);
|
||||
session.set('sessionData', body);
|
||||
session.save();
|
||||
res.json(session.forClient());
|
||||
})
|
||||
.pathParam('sid', {
|
||||
description: 'Session ID',
|
||||
type: 'string'
|
||||
})
|
||||
.errorResponse(errors.SessionExpired, 404, 'Session has expired')
|
||||
.errorResponse(errors.SessionNotFound, 404, 'Session does not exist')
|
||||
.errorResponse(SyntaxError, 400, 'Malformed or non-JSON session data.')
|
||||
.summary('Update session (replace)')
|
||||
.notes('Updates the session with the given sid by replacing the sessionData.');
|
||||
|
||||
controller.patch('/:sid', function (req, res) {
|
||||
var body = JSON.parse(req.rawBody());
|
||||
var session = api.get(req.urlParameters.sid);
|
||||
_.extend(session.get('sessionData'), body);
|
||||
session.save();
|
||||
res.json(session.forClient());
|
||||
})
|
||||
.pathParam('sid', {
|
||||
description: 'Session ID',
|
||||
type: 'string'
|
||||
})
|
||||
.errorResponse(errors.SessionExpired, 404, 'Session has expired')
|
||||
.errorResponse(errors.SessionNotFound, 404, 'Session does not exist')
|
||||
.errorResponse(SyntaxError, 400, 'Malformed or non-JSON session data.')
|
||||
.summary('Update session')
|
||||
.notes('Updates the session with the given sid by merging its sessionData.');
|
||||
|
||||
controller.delete('/:sid', function (req, res) {
|
||||
api.destroy(req.urlParameters.sid);
|
||||
res.status(204);
|
||||
})
|
||||
.pathParam('sid', {
|
||||
description: 'Session ID',
|
||||
type: 'string'
|
||||
})
|
||||
.errorResponse(errors.SessionNotFound, 404, 'Session does not exist')
|
||||
.summary('Delete session')
|
||||
.notes('Removes the session with the given sid from the database.');
|
||||
}());
|
|
@ -0,0 +1,18 @@
|
|||
/*jslint indent: 2, nomen: true, maxlen: 120, vars: true, es5: true */
|
||||
/*global require, exports */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function SessionNotFound(sid) {
|
||||
this.message = 'Session with session id ' + sid + ' not found.';
|
||||
}
|
||||
SessionNotFound.prototype = new Error();
|
||||
|
||||
function SessionExpired(sid) {
|
||||
this.message = 'Session with session id ' + sid + ' has expired.';
|
||||
}
|
||||
SessionExpired.prototype = Object.create(SessionNotFound.prototype);
|
||||
|
||||
exports.SessionNotFound = SessionNotFound;
|
||||
exports.SessionExpired = SessionExpired;
|
||||
}());
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "sessions",
|
||||
"author": "Alan Plum",
|
||||
"version": "0.1",
|
||||
"isSystem": true,
|
||||
"description": "Session storage for Foxx.",
|
||||
|
||||
"controllers": {
|
||||
"/": "app.js"
|
||||
},
|
||||
|
||||
"exports": {
|
||||
"sessionStorage": "storage.js"
|
||||
},
|
||||
|
||||
"defaultDocument": "",
|
||||
|
||||
"lib": ".",
|
||||
|
||||
"setup": "setup.js",
|
||||
|
||||
"configuration": {
|
||||
"timeToLive": {
|
||||
"description": "Session expiry timeout in milliseconds.",
|
||||
"type": "integer",
|
||||
"default": 604800000
|
||||
},
|
||||
"ttlType": {
|
||||
"description": "Timestamp session expiry should be checked against.",
|
||||
"type": "string",
|
||||
"default": "created"
|
||||
},
|
||||
"sidTimestamp": {
|
||||
"description": "Append a timestamp to the session id.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"sidLength": {
|
||||
"description": "Length of the random part of the session id",
|
||||
"type": "integer",
|
||||
"default": 20
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
/*jslint indent: 2, nomen: true, maxlen: 120, vars: true, es5: true */
|
||||
/*global require, applicationContext */
|
||||
(function () {
|
||||
'use strict';
|
||||
var db = require('org/arangodb').db;
|
||||
var sessionsName = applicationContext.collectionName('sessions');
|
||||
|
||||
if (db._collection(sessionsName) === null) {
|
||||
db._create(sessionsName);
|
||||
}
|
||||
}());
|
|
@ -0,0 +1,201 @@
|
|||
/*jslint indent: 2, nomen: true, maxlen: 120, vars: true, es5: true */
|
||||
/*global require, exports, applicationContext */
|
||||
(function () {
|
||||
'use strict';
|
||||
var _ = require('underscore');
|
||||
var internal = require('internal');
|
||||
var arangodb = require('org/arangodb');
|
||||
var db = arangodb.db;
|
||||
var addCookie = require('org/arangodb/actions').addCookie;
|
||||
var crypto = require('org/arangodb/crypto');
|
||||
var Foxx = require('org/arangodb/foxx');
|
||||
var errors = require('./errors');
|
||||
|
||||
var cfg = applicationContext.configuration;
|
||||
|
||||
var Session = Foxx.Model.extend({}, {
|
||||
attributes: {
|
||||
_key: {type: 'string', required: true},
|
||||
uid: {type: 'string', required: false},
|
||||
sessionData: {type: 'object', required: true},
|
||||
userData: {type: 'object', required: true},
|
||||
created: {type: 'integer', required: true},
|
||||
lastAccess: {type: 'integer', required: true},
|
||||
lastUpdate: {type: 'integer', required: true}
|
||||
}
|
||||
});
|
||||
|
||||
var sessions = new Foxx.Repository(
|
||||
applicationContext.collection('sessions'),
|
||||
{model: Session}
|
||||
);
|
||||
|
||||
function generateSessionId() {
|
||||
var sid = '';
|
||||
if (cfg.sidTimestamp) {
|
||||
sid = internal.base64Encode(Number(new Date()));
|
||||
if (cfg.sidLength === 0) {
|
||||
return sid;
|
||||
}
|
||||
sid += '-';
|
||||
}
|
||||
return sid + internal.genRandomAlphaNumbers(cfg.sidLength || 10);
|
||||
}
|
||||
|
||||
function createSession(sessionData) {
|
||||
var sid = generateSessionId(cfg);
|
||||
var now = Number(new Date());
|
||||
var session = new Session({
|
||||
_key: sid,
|
||||
sid: sid,
|
||||
sessionData: sessionData || {},
|
||||
userData: {},
|
||||
created: now,
|
||||
lastAccess: now,
|
||||
lastUpdate: now
|
||||
});
|
||||
sessions.save(session);
|
||||
return session;
|
||||
}
|
||||
|
||||
function getSession(sid) {
|
||||
var session;
|
||||
db._executeTransaction({
|
||||
collections: {
|
||||
read: [sessions.collection.name()],
|
||||
write: [sessions.collection.name()]
|
||||
},
|
||||
action: function () {
|
||||
try {
|
||||
session = sessions.byId(sid);
|
||||
session.enforceTimeout();
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof arangodb.ArangoError
|
||||
&& err.errorNum === arangodb.ERROR_ARANGO_DOCUMENT_NOT_FOUND
|
||||
) {
|
||||
throw new errors.SessionNotFound(sid);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
var now = Number(new Date());
|
||||
sessions.collection.update(session.forDB(), {
|
||||
lastAccess: now
|
||||
});
|
||||
session.set('lastAccess', now);
|
||||
}
|
||||
});
|
||||
return session;
|
||||
}
|
||||
|
||||
function deleteSession(sid) {
|
||||
try {
|
||||
sessions.removeById(sid);
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof arangodb.ArangoError
|
||||
&& err.errorNum === arangodb.ERROR_ARANGO_DOCUMENT_NOT_FOUND
|
||||
) {
|
||||
throw new errors.SessionNotFound(sid);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function fromCookie(req, cookieName, secret) {
|
||||
var session = null;
|
||||
var value = req.cookies[cookieName];
|
||||
if (value) {
|
||||
if (secret) {
|
||||
var signature = req.cookies[cookieName + '_sig'] || '';
|
||||
if (!crypto.constantEquals(signature, crypto.hmac(secret, value))) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
try {
|
||||
session = getSession(value);
|
||||
} catch (e) {
|
||||
if (!(e instanceof errors.SessionNotFound)) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
_.extend(Session.prototype, {
|
||||
enforceTimeout: function () {
|
||||
if (!cfg.timeToLive) {
|
||||
return;
|
||||
}
|
||||
var now = Number(new Date());
|
||||
var prop = cfg.ttlType;
|
||||
if (!prop || !this.get(prop)) {
|
||||
prop = 'created';
|
||||
}
|
||||
if (cfg.timeToLive < (now - this.get(prop))) {
|
||||
throw new errors.SessionExpired(this.get('_key'));
|
||||
}
|
||||
},
|
||||
addCookie: function (res, cookieName, secret) {
|
||||
var value = this.get('_key');
|
||||
var ttl = cfg.timeToLive;
|
||||
ttl = ttl ? Math.floor(ttl / 1000) : undefined;
|
||||
addCookie(res, cookieName, value, ttl);
|
||||
if (secret) {
|
||||
addCookie(res, cookieName + '_sig', crypto.hmac(secret, value), ttl);
|
||||
}
|
||||
},
|
||||
clearCookie: function (res, cookieName, secret) {
|
||||
addCookie(res, cookieName, '', -(7 * 24 * 60 * 60));
|
||||
if (secret) {
|
||||
addCookie(res, cookieName + '_sig', '', -(7 * 24 * 60 * 60));
|
||||
}
|
||||
},
|
||||
setUser: function (user) {
|
||||
var session = this;
|
||||
if (user) {
|
||||
session.set('uid', user.get('_key'));
|
||||
session.set('userData', user.get('userData'));
|
||||
} else {
|
||||
delete session.attributes.uid;
|
||||
session.set('userData', {});
|
||||
}
|
||||
return session;
|
||||
},
|
||||
save: function () {
|
||||
var session = this;
|
||||
var now = Number(new Date());
|
||||
session.set('lastAccess', now);
|
||||
session.set('lastUpdate', now);
|
||||
sessions.replace(session);
|
||||
return session;
|
||||
},
|
||||
delete: function () {
|
||||
var session = this;
|
||||
var now = Number(new Date());
|
||||
session.set('lastAccess', now);
|
||||
session.set('lastUpdate', now);
|
||||
try {
|
||||
deleteSession(session.get('_key'));
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (e instanceof errors.SessionNotFound) {
|
||||
return false;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
exports.fromCookie = fromCookie;
|
||||
exports.create = createSession;
|
||||
exports.get = getSession;
|
||||
exports.delete = deleteSession;
|
||||
exports.errors = errors;
|
||||
exports.repository = sessions;
|
||||
exports._generateSessionId = generateSessionId;
|
||||
}());
|
|
@ -0,0 +1,29 @@
|
|||
/*jslint indent: 2, nomen: true, maxlen: 120, vars: true, es5: true */
|
||||
/*global require, exports, applicationContext */
|
||||
(function () {
|
||||
'use strict';
|
||||
var crypto = require('org/arangodb/crypto');
|
||||
var cfg = applicationContext.configuration;
|
||||
|
||||
function verifyPassword(authData, password) {
|
||||
if (!authData) {
|
||||
authData = {};
|
||||
}
|
||||
var hashMethod = authData.method || cfg.hashMethod;
|
||||
var salt = authData.salt || '';
|
||||
var storedHash = authData.hash || '';
|
||||
var generatedHash = crypto[hashMethod](salt + password);
|
||||
// non-lazy comparison to avoid timing attacks
|
||||
return crypto.constantEquals(storedHash, generatedHash);
|
||||
}
|
||||
|
||||
function hashPassword(password) {
|
||||
var hashMethod = cfg.hashMethod;
|
||||
var salt = crypto.genRandomAlphaNumbers(cfg.saltLength);
|
||||
var hash = crypto[hashMethod](salt + password);
|
||||
return {method: hashMethod, salt: salt, hash: hash};
|
||||
}
|
||||
|
||||
exports.verifyPassword = verifyPassword;
|
||||
exports.hashPassword = hashPassword;
|
||||
}());
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "simple-auth",
|
||||
"author": "Alan Plum",
|
||||
"version": "0.1",
|
||||
"isSystem": true,
|
||||
"description": "Simple password-based authentication for Foxx.",
|
||||
|
||||
"exports": {
|
||||
"auth": "auth.js"
|
||||
},
|
||||
|
||||
"defaultDocument": "",
|
||||
|
||||
"lib": ".",
|
||||
|
||||
"configuration": {
|
||||
"hashMethod": {
|
||||
"description": "Cryptographic hash function to use for new passwords.",
|
||||
"type": "string",
|
||||
"default": "sha256"
|
||||
},
|
||||
"saltLength": {
|
||||
"description": "Length of new salts.",
|
||||
"type": "integer",
|
||||
"default": 16
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/*jslint indent: 2, nomen: true, maxlen: 120, vars: true, es5: true */
|
||||
/*global require, exports */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function UserNotFound(uid) {
|
||||
this.message = 'User with user id ' + uid + ' not found.';
|
||||
}
|
||||
UserNotFound.prototype = new Error();
|
||||
|
||||
function UsernameNotAvailable(username) {
|
||||
this.message = 'The username ' + username + ' is not available or already taken.';
|
||||
}
|
||||
UsernameNotAvailable.prototype = new Error();
|
||||
|
||||
exports.UserNotFound = UserNotFound;
|
||||
exports.UsernameNotAvailable = UsernameNotAvailable;
|
||||
}());
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "users",
|
||||
"author": "Alan Plum",
|
||||
"version": "0.1",
|
||||
"isSystem": true,
|
||||
"description": "Username-based user storage for Foxx.",
|
||||
|
||||
"exports": {
|
||||
"userStorage": "storage.js"
|
||||
},
|
||||
|
||||
"defaultDocument": "",
|
||||
|
||||
"lib": ".",
|
||||
|
||||
"setup": "setup.js"
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
/*jslint indent: 2, nomen: true, maxlen: 120, vars: true, es5: true */
|
||||
/*global require, applicationContext */
|
||||
(function () {
|
||||
'use strict';
|
||||
var db = require('org/arangodb').db;
|
||||
var usersName = applicationContext.collectionName('users');
|
||||
|
||||
if (db._collection(usersName) === null) {
|
||||
db._create(usersName);
|
||||
}
|
||||
}());
|
|
@ -0,0 +1,121 @@
|
|||
/*jslint indent: 2, nomen: true, maxlen: 120, vars: true, es5: true */
|
||||
/*global require, exports, applicationContext */
|
||||
(function () {
|
||||
'use strict';
|
||||
var _ = require('underscore');
|
||||
var arangodb = require('org/arangodb');
|
||||
var db = arangodb.db;
|
||||
var Foxx = require('org/arangodb/foxx');
|
||||
var errors = require('./errors');
|
||||
|
||||
var User = Foxx.Model.extend({}, {
|
||||
attributes: {
|
||||
authData: {type: 'object', required: true},
|
||||
userData: {type: 'object', required: true}
|
||||
}
|
||||
});
|
||||
|
||||
var users = new Foxx.Repository(
|
||||
applicationContext.collection('users'),
|
||||
{model: User}
|
||||
);
|
||||
|
||||
function resolve(username) {
|
||||
var user = users.firstExample({'userData.username': username});
|
||||
if (!user.get('_key')) {
|
||||
return null;
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
function listUsers() {
|
||||
return users.collection.all().toArray().map(function (user) {
|
||||
return user.userData ? user.userData.username : null;
|
||||
}).filter(Boolean);
|
||||
}
|
||||
|
||||
function createUser(userData) {
|
||||
if (!userData) {
|
||||
userData = {};
|
||||
}
|
||||
if (!userData.username) {
|
||||
throw new Error('Must provide username!');
|
||||
}
|
||||
var user;
|
||||
db._executeTransaction({
|
||||
collections: {
|
||||
read: [users.collection.name()],
|
||||
write: [users.collection.name()]
|
||||
},
|
||||
action: function () {
|
||||
if (resolve(userData.username)) {
|
||||
throw new errors.UsernameNotAvailable(userData.username);
|
||||
}
|
||||
user = new User({
|
||||
userData: userData,
|
||||
authData: {}
|
||||
});
|
||||
users.save(user);
|
||||
}
|
||||
});
|
||||
return user;
|
||||
}
|
||||
|
||||
function getUser(uid) {
|
||||
var user;
|
||||
try {
|
||||
user = users.byId(uid);
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof arangodb.ArangoError
|
||||
&& err.errorNum === arangodb.ERROR_ARANGO_DOCUMENT_NOT_FOUND
|
||||
) {
|
||||
throw new errors.UserNotFound(uid);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
function deleteUser(uid) {
|
||||
try {
|
||||
users.removeById(uid);
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof arangodb.ArangoError
|
||||
&& err.errorNum === arangodb.ERROR_ARANGO_DOCUMENT_NOT_FOUND
|
||||
) {
|
||||
throw new errors.UserNotFound(uid);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_.extend(User.prototype, {
|
||||
save: function () {
|
||||
var user = this;
|
||||
users.replace(user);
|
||||
return user;
|
||||
},
|
||||
delete: function () {
|
||||
try {
|
||||
deleteUser(this.get('_key'));
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (e instanceof errors.UserNotFound) {
|
||||
return false;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
exports.resolve = resolve;
|
||||
exports.list = listUsers;
|
||||
exports.create = createUser;
|
||||
exports.get = getUser;
|
||||
exports.delete = deleteUser;
|
||||
exports.errors = errors;
|
||||
exports.repository = users;
|
||||
}());
|
Loading…
Reference in New Issue