You can use IAM to restrict access to DynamoDB items but for those unfamiliar with the intricacies of IAM roles or wishing for more dynamic permissions on the application level we can use a second table instead.
If we have a set of entities we want to control access to, we can use the pattern actor controlType entity
. For example:
user 2 is-owner document 1user 5 is-editor document 1user 6 is-writer document 1user 9 is-audience document 1
So for example let's say the following steps happen.
user 2
creates document 1
user 2
adds user 6
as a writer so they can start writing.user 2
adds user 5
as an editor, so that they can make changes and suggestions to the documentuser 5
sends the document to user 9
, who can now read it but not make any changesWe would end up with the following DynamoDB table items
[{pk: 'access#user#2',sk: 'is-owner#doc#1'},{pk: 'access#user#6',sk: 'is-writer#doc#1'},{pk: 'access#user#5',sk: 'is-editor#doc#1'},{pk: 'access#user#9',sk: 'is-audience#doc#1'}]
With an index of pk+sk on the table, this allows us to query for any roles a user has.
const params = {TableName: "access-control",KeyConditionExpression: "pk = :userid and begins_with(sk, :control)",ExpressionAttributeValues: {":userid": `access#user#${userid}`,":control": "is-"}};
and then we can use the Items
returned from our query to determine if a user has a sufficient role for a given entity.
Here's an implementation that uses dynamodb-toolbox to create the AccessControl
entity.
const AccessControl = new Entity({name: "access-control",timestamps: true,attributes: {pk: { hidden: true, partitionKey: true },sk: { hidden: true, sortKey: true },access: ["pk", 0, { default: "access", save: false }],// useractorType: ["pk", 1, { required: true, save: false }],actorId: ["pk", 2, { required: true, save: false }],// can-access, is-admincontrol: ["sk", 0, { required: true, save: false }],entityType: ["sk", 1, { required: true, save: false }],entityId: ["sk", 2, { required: true, save: false }],},table: AccessControlTable,});
hidden
means the serialized object won't have that key, and save
means that field won't be saved in the DynamoDB table as an attribute. So what we end up with is two fields, built up from the other 6.
await AccessControl.put({actorType: "user",actorId: userId,control: "is-admin",entityType: "mdx",entityId: mdxId,});