User management

Foxx does not provide any user management out of the box but it is very easy to roll your own solution:

The session middleware provides mechanisms for adding session logic to your service, using e.g. a collection or JSON Web Tokens to store the sessions between requests.

The auth module provides utilities for basic password verification and hashing.

The following example service demonstrates how user management can be implemented using these basic building blocks.

Setting up the collections

Let’s say we want to store sessions and users in collections. We can use the setup script to make sure these collections are created before the service is mounted.

First add a setup script to your manifest if it isn’t already defined:

  1. "scripts": {
  2. "setup": "scripts/setup.js"
  3. }

Then create the setup script with the following content:

  1. 'use strict';
  2. const db = require('@arangodb').db;
  3. const sessions = module.context.collectionName('sessions');
  4. const users = module.context.collectionName('users');
  5. if (!db._collection(sessions)) {
  6. db._createDocumentCollection(sessions);
  7. }
  8. if (!db._collection(users)) {
  9. db._createDocumentCollection(users);
  10. }
  11. db._collection(users).ensureIndex({
  12. type: 'hash',
  13. fields: ['username'],
  14. unique: true
  15. });

Creating the router

The following main file demonstrates basic user management:

  1. 'use strict';
  2. const joi = require('joi');
  3. const createAuth = require('@arangodb/foxx/auth');
  4. const createRouter = require('@arangodb/foxx/router');
  5. const sessionsMiddleware = require('@arangodb/foxx/sessions');
  6. const auth = createAuth();
  7. const router = createRouter();
  8. const users = module.context.collection('users');
  9. const sessions = sessionsMiddleware({
  10. storage: module.context.collection('sessions'),
  11. transport: 'cookie'
  12. });
  13. module.context.use(sessions);
  14. module.context.use(router);
  15. router.get('/whoami', function (req, res) {
  16. try {
  17. const user = users.document(req.session.uid);
  18. res.send({username: user.username});
  19. } catch (e) {
  20. res.send({username: null});
  21. }
  22. })
  23. .description('Returns the currently active username.');
  24. router.post('/login', function (req, res) {
  25. // This may return a user object or null
  26. const user = users.firstExample({
  27. username: req.body.username
  28. });
  29. const valid = auth.verify(
  30. // Pretend to validate even if no user was found
  31. user ? user.authData : {},
  32. req.body.password
  33. );
  34. if (!valid) res.throw('unauthorized');
  35. // Log the user in
  36. req.session.uid = user._key;
  37. req.sessionStorage.save(req.session);
  38. res.send({sucess: true});
  39. })
  40. .body(joi.object({
  41. username: joi.string().required(),
  42. password: joi.string().required()
  43. }).required(), 'Credentials')
  44. .description('Logs a registered user in.');
  45. router.post('/logout', function (req, res) {
  46. if (req.session.uid) {
  47. req.session.uid = null;
  48. req.sessionStorage.save(req.session);
  49. }
  50. res.send({success: true});
  51. })
  52. .description('Logs the current user out.');
  53. router.post('/signup', function (req, res) {
  54. const user = req.body;
  55. try {
  56. // Create an authentication hash
  57. user.authData = auth.create(user.password);
  58. delete user.password;
  59. const meta = users.save(user);
  60. Object.assign(user, meta);
  61. } catch (e) {
  62. // Failed to save the user
  63. // We'll assume the UniqueConstraint has been violated
  64. res.throw('bad request', 'Username already taken', e);
  65. }
  66. // Log the user in
  67. req.session.uid = user._key;
  68. req.sessionStorage.save(req.session);
  69. res.send({success: true});
  70. })
  71. .body(joi.object({
  72. username: joi.string().required(),
  73. password: joi.string().required()
  74. }).required(), 'Credentials')
  75. .description('Creates a new user and logs them in.');