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

Cannot set @hasOne fields to null with GraphQL #2120

Open DarylBeattie opened 9 months ago

DarylBeattie commented 9 months ago

How did you install the Amplify CLI?

npm

If applicable, what version of Node.js are you using?

v18.15.0

Amplify CLI Version

12.8.2

What operating system are you using?

Windows

Did you make any manual changes to the cloud resources managed by Amplify? Please describe the changes made.

No.

Describe the bug

I am unable to set a related-object's ID field to null even though the authorization rules state that I have delete access to the field. When I try to do so, I get an error Unauthorized on [profileOrganizationId]

I believe this is due to a mismatch in the permissions between the auto-generated ID field for the @hasOne, and the object itself.

For example, let's say I have the following schema:

type Profile @model @auth(rules: [{ allow: owner }, { allow: private, operations: [create, read] }]) {
  id: ID! @primaryKey
  name: String
  organization: Organization @hasOne
  owner: String @auth(rules: [{ allow: owner, operations: [read, delete] }])
}

type Organization @model @auth(rules: [{ allow: private, operations: [read, create, update] }]) {
  id: ID! @primaryKey
  name: String
}

This generates an update-mutation for the profile that looks like this:

export const updateProfile = /* GraphQL */ `mutation UpdateProfile(
  $input: UpdateProfileInput!
  $condition: ModelProfileConditionInput
) {
  updateProfile(input: $input, condition: $condition) {
    id
    name
    organization {
      id
      name
      owner
      __typename
    }
    owner
    createdAt
    updatedAt
    profileOrganizationId
    __typename
  }
}
` as GeneratedMutation<
  APITypes.UpdateProfileMutationVariables,
  APITypes.UpdateProfileMutation
>;

(Note the organization and the generated profileOrganizationId.)

With an input like this:

export type UpdateProfileInput = {
  id: string,
  name?: string | null,
  owner?: string | null,
  profileOrganizationId?: string | null,
};

(Note the generated profileOrganizationId, but I cannot set an organization into the input.)

And resulting owner permissions like this:

┌────────────────────────────────┬────────┬────────┬────────┬──────┬──────┬────────┬────────┬──────┐
 │            (index)             │ create │ update │ delete │ get  │ list │ search │ listen │ sync │
 ├────────────────────────────────┼────────┼────────┼────────┼──────┼──────┼────────┼────────┼──────┤
 │               id               │  true  │  true  │  true  │ true │ true │  true  │  true  │ true │
 │              name              │  true  │  true  │  true  │ true │ true │  true  │  true  │ true │
 │          organization          │  true  │  true  │  true  │ true │ true │  true  │  true  │ true │
 │             owner              │ false  │ false  │  true  │ true │ true │  true  │  true  │ true │
 └────────────────────────────────┴────────┴────────┴────────┴──────┴──────┴────────┴────────┴──────┘

(Note the permissions only specify a field-level permission for a field called organization.)

However, when you execute this command:

const profileId = [[some valid profile id]];
const profileDataToUpdate: UpdateProfileInput = { profileOrganizationId: null, id: profileId };
await API.graphql<GraphQLQuery<UpdateProfileMutation>>({
  ...graphqlOperation(updateProfile, { input: profileDataToUpdate }),
  authMode: GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS,
})

It fails with:

Unauthorized on [profileOrganizationId]

I can update all other fields without issue, just not that one.

This happens even if I set field-level authorization-rules onto the organization field in the GraphQL to explicitly allow anyone access to "delete" the field.

Expected behavior

It should allow me to update a generated ID that I have permissions to update.

Reproduction steps

Outlined in the description.

Project Identifier

No response

Log output

``` ERROR {"data":{"updateProfile":null},"errors":[{"path":["updateProfile"],"data":null,"errorType":"Unauthorized","errorInfo":null,"locations":[{"line":2,"column":3,"sourceName":null}],"message":"Unauthorized on [profileOrganizationId]"}]} ```

Additional information

This schema was mocked, because I do not want to share parts of my proprietary schema. However, it's a simplified example of exactly what I am really doing with objects in my project.

Before submitting, please confirm:

DarylBeattie commented 9 months ago

This may at first seem similar to https://github.com/aws-amplify/amplify-category-api/issues/75, however, I have tried to manually add the delete operation and that did not work in this case.

DarylBeattie commented 9 months ago

If I change the schema to specify the field, then it does work as intended:

type Profile @model @auth(rules: [{ allow: owner }, { allow: private, operations: [create, read] }]) {
  id: ID! @primaryKey
  name: String
  profileOrganizationId: ID
  organization: Organization @hasOne(fields: ["profileOrganizationId"])
  owner: String @auth(rules: [{ allow: owner, operations: [read, delete] }])
}

So this seems to be an issue with the generated ID permissions.

As a user, I shouldn't have to specify the ID for every @hasOne, it should work out of the box with the generated IDs.