aws-amplify / amplify-cli

The AWS Amplify CLI is a toolchain for simplifying serverless web and mobile development.
Apache License 2.0
2.81k stars 821 forks source link

Getting a permission error - migration from old @auth directive #1825

Closed apertureless closed 5 years ago

apertureless commented 5 years ago

Which Category is your question related to?

The issue is based on the fact that @connections are protected by default.

My old schema (simplified)

type User @model @auth (rules: [{ allow: owner }]) {
    id: ID!
    username: String!
    createdAt: String
    updatedAt: String
    entries: [DiaryEntry] @connection(name:"UserEntries" sortField: "date")
    cars: [Car] @connection(name: "UserCars")
}

type Car @model @versioned @auth (rules: [{ allow: owner }]) {
  id: ID!
  name: String!
  # some fields....
  user: User @connection(name: "UserCars")
  entry: [DiaryEntry] @connection(name: "DiaryEntryCar")
}

type DiaryEntry @model @versioned @auth (rules: [
  { allow: owner },
  { allow: owner, ownerField: "shared", queries: [get, list], mutations: []}
])  @searchable {
  id: ID!
  track: Track! @connection(name: "DiaryEntryTrack")
  date: AWSDate
  updatedAt: String
  createdAt: String
  videos: [String]
  car: Car @connection(name: "DiaryEntryCar")
  notes: AWSJSON
  owner: String
  user: User! @connection(name: "UserEntries")
  shared: [String]!
}

So this was working with the old @auth directive. A user could share an entry with other users and they could see the entry, car etc.

Migrated

Here is the new migrated schema

type User @model @auth (rules: [{ allow: owner }]) {
    id: ID!
    username: String!
    createdAt: String
    updatedAt: String
    entries: [DiaryEntry] @connection(name: "UserEntries" sortField: "date")
    cars: [Car] @connection(name: "UserCars")
    favorites: [Favorite] @connection(name: "UserFavorites")
}

type Car @model @versioned @auth (rules: [{ allow: owner }]) {
  id: ID!
  name: String!
  ....
  user: User @connection(name: "UserCars")
  entry: [DiaryEntry] @connection(name: "DiaryEntryCar")
}

type DiaryEntry @model @versioned @auth (rules: [
  { allow: owner },
  { allow: owner, ownerField: "shared", operations: [read] }
])  @searchable {
  id: ID!
  track: Track! @connection(name: "DiaryEntryTrack")
  date: AWSDate
  updatedAt: String
  createdAt: String
  videos: [String]
  car: Car @connection(name: "DiaryEntryCar") @auth(rules: [{ allow: owner }, { allow: owner, ownerField: "shared", operations: [read] }])
  notes: AWSJSON
  owner: String
  user: User! @connection(name: "UserEntries")
  shared: [String]!
}

So I am trying to list them with this query:

export const ListEntriesSharedForMe = gql`
  query DiarySharedForMe($username: String!) {
    listDiaryEntrys(filter: { shared: { contains: $username } }) {
      items {
        id
        car {
          id
          name
          favorite
        }
        track {
          id
          name
        }
        date
        shared
      }
    }
  }
`;

However, I am receiving the error Not Authorized to access car on type DiaryEntry

In the first iteration the car connection on DiaryEntry had no auth rules. But that also throw an not authorized error.

So as far as I understood the new field level and connection auth rules, if no rule is set, it would go over the parent auth rule of the car model. But as the car only is available for owner I added the auth rule on the diary entry to accept also the shared owner field.

But I could not find a way to get around the Not Authorized error.

mikeparisstuff commented 5 years ago

In order you allow a Car object to be accessible via DiaryEntry.car when the logged in user is not the owner of the Car itself you need to opt-out of the @connection protection that is added by default. You currently have a rule @auth (rules: [{ allow: owner }]) on type Car that conceptually means that no one other than the owner of the car should be able to interact with this car. You are able to tweak this behavior such that top level queries/mutations are protected by this rule while connections are not. This allows you to use field level @auth rules to protect the connection itself. To do so you can change your Car type to this:

# Only owners of car may use Query.getCar, Query.listCars, Mutation.createCar, Mutation.updateCar, Mutation.deleteCar
type Car @model @versioned @auth (rules: [{ allow: owner, queries: [get, list], mutations: [create, update, delete] }]) {
  id: ID!
  name: String!
  entry: [DiaryEntry] @connection(name: "DiaryEntryCar")
}

type DiaryEntry ... {
  # Allow users that are in the `DiaryEntry.shared` or equal `DiaryEntry.owner` can access the
  # `DiaryEntry.car` resolver
  car: Car @connection(name: "DiaryEntryCar") @auth(rules: [{ allow: owner }, { allow: owner, ownerField: "shared", operations: [read] }])
}

By adding the queries & mutations arguments you are opting in to the old behavior where @connection fields are not protected. This means that logic will not be injected into DiaryEntry.car and thus people that are not the owner of the Car will be able to access it via that path. The Query.getCar, Query.listCars, Mutation.createCar, Mutation.updateCar, and Mutation.deleteCar will still be protected such that only owners of the car can access them.

github-actions[bot] commented 3 years ago

This issue has been automatically locked since there hasn't been any recent activity after it was closed. Please open a new issue for related bugs.

Looking for a help forum? We recommend joining the Amplify Community Discord server *-help channels for those types of questions.