Reputation: 1111
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
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.
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
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