Search code examples
authenticationarangodbfoxx

How to have a Foxx service use base collection instead of mount specific ones


How can I have a Foxx service use base collections for auth operations? For example I want the User management tutorial at https://docs.arangodb.com/3.11/develop/foxx-microservices/guides/authentication-and-sessions/ to use collections "users" and "sessions" instead of "test_users" and "test_sessions", where "test" is the name of my mountpoint.

I want to run multiple services all working off the same base collections. But if I go with whats given in the tutorials, I end up with auth collections and routes which are specific to a service, which doesnt males much sense to me.

My setup.js is;

'use strict';
const db = require('@arangodb').db;
const sessions = module.context.collectionName('sessions');
const users = module.context.collectionName('users');

if (!db._collection(sessions)) {
  db._createDocumentCollection(sessions);
}

if (!db._collection(users)) {
  db._createDocumentCollection(users);
}

db._collection(users).ensureIndex({
  type: 'hash',
  fields: ['username'],
  unique: true
});

and my index.js is;

'use strict';

const joi = require('joi');
const createAuth = require('@arangodb/foxx/auth');
const createRouter = require('@arangodb/foxx/router');
const sessionsMiddleware = require('@arangodb/foxx/sessions');
// const db = require('@arangodb').db;

const auth = createAuth();
const router = createRouter();
const users = db._collection('users');
const sessions = sessionsMiddleware({
storage: module.context.collection('sessions'),
transport: 'cookie'
});

module.context.use(sessions);
module.context.use(router);

// continued
router.post('/signup', function (req, res) {
    const user = {};
    try {
      user.authData = auth.create(req.body.password);
      user.username = req.body.username;
      user.perms = [];
      const meta = users.save(user);
      Object.assign(user, meta);
    } catch (e) {
      // Failed to save the user
      // We'll assume the uniqueness constraint has been violated
      res.throw('bad request', 'Username already taken', e);
    }
    req.session.uid = user._key;
    req.sessionStorage.save(req.session);
    res.send({success: true});
  })
  .body(joi.object({
    username: joi.string().required(),
    password: joi.string().required()
  }).required(), 'Credentials')
  .description('Creates a new user and logs them in.');

I tried using const users = db._collection('users'); instead of const users = module.context.collection('users'); but that throws swagger api errors.


Solution

  • to achieve that you need to change the assignment of collection names from module.context.collectionName('nameOfCollection') to 'nameOfCollection' in all files, because module.context.collectionName prefixes string with name of service

    so

    setup.js

    const sessions = 'sessions';
    const users = 'users';
    

    index.js

    const users = db._collection('users');
    const sessions = sessionsMiddleware({
        storage: 'sessions',
        transport: 'cookie'
    });
    

    however, that approach is antipattern for case when more services need access to same underlying collections (for example teardown of one service can delete those collections for other services).

    for that case you should utilize dependencies, only your auth service should have access to its own collections and other services should have auth service as dependency and access auth data through auth service.

    auth service needs to have

    in manifest.json

    "provides": {
        "myauth": "1.0.0"
    }
    

    in index.js or what file you pointing as main in manifest.json

    module.exports = {
        isAuthorized (id) {
            return false; // your code for validating if user is authorized
        }
    };
    

    consuming service needs to have

    in manifest.json

    "dependencies": {
        "myauth": {
            "name": "myauth",
            "version": "^1.0.0",
            "description": "Auth service.",
            "required": true
        }
    }
    

    and then you can register it and call it

    const myauth = module.context.dependencies.myauth;
    if (myauth.isAuthorized()) {
        // your code
    } else {
        res.throw(401);
    }
    

    for further steps in terms of authorization of requests check how to use Middleware and Session middleware

    god speed