aws-amplify / amplify-category-api

The AWS Amplify CLI is a toolchain for simplifying serverless web and mobile development. This plugin provides functionality for the API category, allowing for the creation and management of GraphQL and REST based backends for your amplify project.
https://docs.amplify.aws/
Apache License 2.0
88 stars 76 forks source link

How do we point two different types to the same DynamoDB table? #647

Open skryshi opened 2 years ago

skryshi commented 2 years ago

For new Amplify (>=7.5.0), how do we point two different types to the same DynamoDB table? Couldn't find this anywhere in the docs.

For example, how can I have both User and UserPublic read/mutate the same DynamoDB table? I tried mucking around with CustomResources.json, but couldn't get it to work:

type User @model {
   name: String
   ssn: String
}

type UserPublic @model {
   name: String
}

Thank you!

cjihrig commented 2 years ago

Can you explain your use case a bit more. It seems like this problem could be solved by using auth to control access to a single table.

skryshi commented 2 years ago

@cjihrig There are plenty of use cases that cannot be solved with @auth. Whenever my signed-in users need more than 2 groups of access, I am stuck.

Example 1:

Take a User model in a basic social network. Consider User A:

  1. User A needs full access to User A model: @auth(rules: [{ provider: userPools, allow: owner }])
  2. Friends of User A need partial access to User A model
  3. Non-friends of User A need light access to User A model: @auth(rules: [{ provider: userPools, allow: private, operations: [read] }])
  4. Guest users have no access to User A model: @auth(rules: [{ allow: public, operations: [] }])

There is no way for me to setup permissions for (2) using @auth. But with a secondary type UserForFriends pointing at Users table, it's trivial:

type User @model @auth(rules: [{ provider: userPools, allow: owner }]) {
   name: String
   favoriteMovie: String
   ssn: String
}

type UserForFriends @model @auth(rules: [{ provider: userPools, allow: private, operations: [read] }]) {
   name: String
   favoriteMovie: String
}

type UserPublic @model @auth(rules: [{ provider: userPools, allow: private, operations: [read] }]) {
   name: String
}

Example 2:

Take a User model in a basic dating app (e.g. Tinder). Consider User A:

  1. User A needs full access to User A model: @auth(rules: [{ provider: userPools, allow: owner }])
  2. Matched users need partial access to User A model
  3. Potential matches need light access to User A model: @auth(rules: [{ provider: userPools, allow: private, operations: [read] }])
  4. Guest users have no access to User A model: @auth(rules: [{ allow: public, operations: [] }])

Same as above, there is no way for me to setup permissions for (2) using @auth. But with a secondary type UserForMatched pointing at Users table, it's trivial.


I just tried another approach, using amplify override api with override.ts, but it didn't work either:

import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper';

export function override(resources: AmplifyApiGraphQlResourceStackTemplate) {
  resources.models["UserPublic"].modelDDBTable = resources.models["User"].modelDDBTable
  resources.models["UserPublic"].modelIamRole = resources.models["User"].modelIamRole
  resources.models["UserPublic"].dynamoDBAccess = resources.models["User"].dynamoDBAccess
  resources.models["UserPublic"].modelDatasource = resources.models["User"].modelDatasource
}

How do I do this? Please, help.

cjihrig commented 2 years ago

There are plenty of use cases that cannot be solved with @auth. Whenever my signed-in users need more than 2 groups of access

Sorry, I was referring to field level auth. Have you looked into that at all?

skryshi commented 2 years ago

@cjihrig Yes, I have looked at field level auth. Whether I use model- or field-level auth, it doesn't help when I have 3+ groups of signed-in users.

Taking example (1) above, I have the owner with allow: owner permissions, a signed-in user with allow: private permissions, but I don't have allow: friends permissions for owner's friends.

Right now, to create allow:friends permission, I have to

  1. Create a custom authorizer that checks whether two users are friends.
  2. Create a custom allow/deny fields resolver.

If I could point two types at the same AppSync datasource, then I could skip step (2) while making the interface for allow: friends permission explicit in schema.graphql.

ggorge-etiqa commented 5 months ago

I'm landing here because I'm trying to implement a dual approval strategy where only one user can initiate the approval process.

My idea of schema.graphql is:

type sendApproval @model(subscriptions: null, timestamps: {createdAt: "createdAt", updatedAt: "sendUpdatedAt" }) @auth(rules: [{ allow: owner, ownerField: "sendOwner"}]) {
    id: ID!
    sendOwner: String!
    rcptOwner: String!
    contractId: String! 
    sendStatus: sendStatusType!
    createdAt: AWSDateTime
    sendUpdatedAt: AWSDateTime
}

type rcptApproval @model(subscriptions: null, timestamps: {createdAt: "rcptCreatedAt", updatedAt: "rcptUpdatedAt" }) @auth(rules: [{ allow: owner, operations: [read], ownerField: "rcptOwner"}]) {
    id: ID! @auth(rules: [{ allow: owner, operations: [read]}])
    rcptOwner: String! @auth(rules: [{ allow: owner, operations: [read]}])
    rcptStatus: String @auth(rules: [{ allow: owner, operations: [read,update]}])
    rcptUpdatedAt: AWSDateTime @auth(rules: [{ allow: owner, operations: [read,update]}])
    sendOwner: String @auth(rules: [{ allow: owner, operations: [read]}])
}

What I've tried is to tweak the table name with override.ts, but deploy fails with "table already exists" error because amplify is trying to create two tables with the same name.

export function override(resources: AmplifyApiGraphQlResourceStackTemplate, amplifyProjectInfo: AmplifyProjectInfo) {
    resources.models["sendApproval"].modelDDBTable.tableName = ApprovalTableName;
    resources.models["rcptApproval"].modelDDBTable.tableName = ApprovalTableName;
...
}

Having the possibility to use the same table would be great.