Pardoner
Pardoner

Reputation: 1111

What are best practices for authorizing and sharing API resources across accounts?

I have a simple REST API built in Node.js, Express and MongoDB—technologies really don't matter though since I'm looking more at design than anything. A typical route looks something like this:

router.get('/doc/:id', async function(req, res, next) {

  const loggedInUserId = req.session.user.id;

  const doc = await db.docs.findOne({_id:req.params.id, user:loggedInUserId});

  if (!doc) {
    return next( new Error('not found') )
  }

  return res.json(doc);

});

I know the user is the owner of the document since I use the logged in user's id in the document query. However, if it's a shared document this approach won't works. I need to think of a better way to authorize a "team".

My initial thought on this is to allow the owner of a "document" to share it with another user by adding them to a teams collection. In this case the route might look something like this:

router.get('/doc/:id', async function(req, res, next) {

  const loggedInUserId = req.session.user.id;

  // here I check the teams collection to see if the logged
  // in user has been given access the document.
  const hasAccess = await db.teams.exists({
    doc:res.params.id,
    users:loggedInUserId
  });

  if (!hasAccess) {
    return next( new Error('forbidden') )
  }

  const doc = await db.docs.findOne({ _id:req.params.id });

  if (!doc) {
    return next( new Error('not found') )
  }

  return res.json(doc);

});

This approach may work for simple scenarios but I'm worried it could get messy when adding additional features like read/write/delete access, pagination, concurrency control, etc. Not to mention this approach may considerably slow down my API since most users will be accessing their own resources and will not be part of a team.

I'm still in the germination phase for this and I'd like to have a solid plan in place before I move forward. Does this seem like a good path or am I missing something? Is there a "best practice" for authorizing resources across accounts?

Thanks

Upvotes: 0

Views: 136

Answers (1)

rolodex
rolodex

Reputation: 530

Decided to give this problem a go. Have a look at my approach, hope it helps. I think directly using query is a much faster approach, besides, it's only a single query.


Pseudo models

users
- id
- name

teams
- id
- name
- owner_id > user_teams.id

user_teams
- id
- teams_id
- users_id > users.id

permissions
- id
- name

user_permissions
- users_id > users.id
- permissions_id > permissions.id

document_permissions
- documents_id > documents.id
- permissions_id > permissions.id

documents
- id
- file_url
- owner_id > users.id
- teams_id > teams.id nullable

Make Believe Queries

User's documents


SELECT file_url FROM documents WHERE owner_id = <current_user_id>

Shared With Me documents

SELECT
    doc.file_url,
    p.name as access_type,
    owner.name AS owner
FROM documents doc

LEFT JOIN users owner ON doc.owner_id = owner.id
LEFT JOIN permissions p ON dp.permissions_id = p.id
LEFT JOIN document_permissions dp ON dp.documents_id = doc.id
LEFT JOIN teams t ON doc.teams_id = t.id

WHERE t.users_id = <current_user_id>

-- If the document's permission is not available for the user means it's not assigned
AND dp.permissions_id IN (
    SELECT permissions_id FROM user_permissions up
    WHERE up.users_id = <current_user_id>
)

The query above can then be translated (hopefully) into whatever ORM you're using to be used in the code.

Upvotes: 2

Related Questions