aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.43k stars 2.13k forks source link

A model with rules 'owner' able to be accessed by unauthorized / guest user #9791

Closed techandmedia closed 2 years ago

techandmedia commented 2 years ago

Before opening, please confirm:

JavaScript Framework

React

Amplify APIs

DataStore

Amplify Categories

Not applicable

Environment information

``` # Put output below this line System: OS: Windows 10 10.0.19043 CPU: (12) x64 AMD Ryzen 5 3600 6-Core Processor Memory: 12.07 GB / 31.92 GB Binaries: Node: 16.13.2 - C:\Program Files\nodejs\node.EXE Yarn: 1.22.10 - ~\AppData\Roaming\npm\yarn.CMD npm: 8.5.5 - C:\Program Files\nodejs\npm.CMD Browsers: Chrome: 100.0.4896.75 Edge: Spartan (44.19041.1266.0), Chromium (100.0.1185.29) Internet Explorer: 11.0.19041.1566 npmPackages: @ampproject/toolbox-optimizer: undefined () @ant-design/icons: ^4.7.0 => 4.7.0 @aws-amplify/ui-react: ^2.15.1 => 2.15.1 @aws-amplify/ui-react-internal: undefined () @aws-amplify/ui-react-legacy: undefined () @babel/core: undefined () @babel/runtime: 7.15.4 @hapi/accept: undefined () @napi-rs/triples: undefined () @next/bundle-analyzer: ^12.1.4 => 12.1.4 @next/react-dev-overlay: undefined () @next/react-refresh-utils: 12.1.4 @peculiar/webcrypto: undefined () @types/node: ^17.0.23 => 17.0.23 @types/react: ^18.0.0 => 18.0.0 @types/react-dom: ^18.0.0 => 18.0.0 @vercel/nft: undefined () @wsh4and/antd: ^0.0.7 => 0.0.7 abort-controller: undefined () acorn: undefined () amphtml-validator: undefined () antd: ^4.19.5 => 4.19.5 arg: undefined () assert: undefined () async-retry: undefined () async-sema: undefined () aws-amplify: ^4.3.19 => 4.3.19 aws-amplify-react: ^5.1.9 => 5.1.9 aws-sdk: ^2.1111.0 => 2.1111.0 babel-packages: undefined () browserify-zlib: undefined () browserslist: undefined () buffer: undefined () bytes: undefined () chalk: undefined () ci-info: undefined () cli-select: undefined () comment-json: undefined () compression: undefined () conf: undefined () constants-browserify: undefined () content-disposition: undefined () content-type: undefined () cookie: undefined () cross-spawn: undefined () crypto-browserify: undefined () cssnano-simple: undefined () dayjs: 1.11.0 => 1.11.0 debug: undefined () devalue: undefined () domain-browser: undefined () eslint: 8.13.0 => 8.13.0 eslint-config-next: 12.1.4 => 12.1.4 eslint-config-prettier: ^8.5.0 => 8.5.0 etag: undefined () events: undefined () find-cache-dir: undefined () find-up: undefined () formdata-node: undefined () fresh: undefined () get-orientation: undefined () glob: undefined () gzip-size: undefined () http-proxy: undefined () https-browserify: undefined () husky: ^7.0.4 => 7.0.4 icss-utils: undefined () ignore-loader: undefined () image-size: undefined () is-animated: undefined () is-docker: undefined () is-wsl: undefined () jest-worker: undefined () json5: undefined () jsonwebtoken: undefined () lint-staged: ^12.3.7 => 12.3.7 loader-utils: undefined () lodash.curry: undefined () lru-cache: undefined () micromatch: undefined () mini-css-extract-plugin: undefined () nanoid: undefined () native-url: undefined () neo-async: undefined () next: 12.1.4 => 12.1.4 node-fetch: undefined () node-html-parser: undefined () ora: undefined () os-browserify: undefined () p-limit: undefined () path-browserify: undefined () postcss-flexbugs-fixes: undefined () postcss-modules-extract-imports: undefined () postcss-modules-local-by-default: undefined () postcss-modules-scope: undefined () postcss-modules-values: undefined () postcss-preset-env: ^7.4.3 => 7.4.3 () postcss-safe-parser: undefined () postcss-scss: undefined () postcss-value-parser: undefined () prettier: ^2.6.2 => 2.6.2 process: undefined () punycode: undefined () querystring-es3: undefined () raw-body: undefined () react: ^18.0.0 => 18.0.0 react-dom: ^18.0.0 => 18.0.0 react-is: 17.0.2 react-refresh: 0.12.0 react-server-dom-webpack: undefined () recoil: ^0.7.0 => 0.7.0 regenerator-runtime: 0.13.4 sass-loader: undefined () schema-utils: undefined () semver: undefined () send: undefined () setimmediate: undefined () source-map: undefined () stream-browserify: undefined () stream-http: undefined () string-hash: undefined () string_decoder: undefined () strip-ansi: undefined () terser: undefined () text-table: undefined () timers-browserify: undefined () tty-browserify: undefined () typescript: ^4.6.3 => 4.6.3 ua-parser-js: undefined () unistore: undefined () use-subscription: undefined () util: undefined () uuid: undefined () vm-browserify: undefined () watchpack: undefined () web-streams-polyfill: undefined () web-vitals: undefined () webpack: undefined () webpack-sources: undefined () ws: undefined () npmGlobalPackages: @aws-amplify/cli: 7.6.5 @ionic/cli: 6.18.1 @nestjs/cli: 8.2.0 amplify: 0.0.11 artillery: 1.6.2 cordova-res: 0.15.4 docker-compose: 0.23.6 expo-cli: 4.0.15 http-server: 0.12.3 lerna: 4.0.0 loadtest: 5.1.2 npm: 8.5.5 npx: 10.2.2 nx: 13.9.3 pm2: 4.5.6 vercel: 21.3.3 yarn: 1.22.10 ```

Describe the bug

An unauthorized / guest user able to access model with rules "owner"

Expected behavior

Only an owner of the record able to access the data

Reproduction steps

Setup Amplify project Add multiple models as below code snippet Then fetch the data using Datastore

Code Snippet

// schema
type PrivateBlog @model @auth(rules: [{ allow: owner }]) {
  id: ID!
  name: String!
  posts: [PrivatePost] @hasMany(indexName: "byPrivateBlog", fields: ["id"])
}

type PrivatePost @model @auth(rules: [{ allow: owner }]) {
  id: ID!
  blogID: ID! @index(name: "byPrivateBlog")
  comments: [PrivateComment] @hasMany(indexName: "byPrivatePost", fields: ["id"])
  title: String!
  PrivateBlog: PrivateBlog @belongsTo
  content: String
}

type PrivateComment @model @auth(rules: [{ allow: owner }]) {
  id: ID!
  postID: ID! @index(name: "byPrivatePost")
  comment: String!
  PrivatePost: PrivatePost @belongsTo
}
// React component
  async function onFinishPrivate(values: any) {
    console.log('Received values of form: ', values);
    const blog = await DataStore.save(
      new PrivateBlog({
        name: values.name,
      })
    );

    await DataStore.save(
      new PrivatePost({
        blogID: blog.id,
        title: values.title,
        content: values.content,
      })
    );
  }

  useEffect(() => {
    const subscriptionPrivate2 = DataStore.observeQuery(PrivateBlog).subscribe(snapshot => {
      const { items, isSynced } = snapshot;
      console.log('subscriptionPrivate2', snapshot);
    });

    return () => {
      subscriptionPrivate2.unsubscribe();
    };
  }, []);

Log output

``` // return data from Datastore { "items": [ { "id": "d7ba31df-5d75-49a0-bd06-bda9e37698e8", "name": "PRIVATE BLOG", "createdAt": "2022-04-09T13:34:06.204Z", "updatedAt": "2022-04-09T13:34:06.204Z", "owner": some email", "_version": 1, "_lastChangedAt": 1649511246228, "_deleted": null }, { "id": "d3966800-b877-41f4-a7a5-44cb91b0bd6e", "name": "PRIVATE BLOG", "createdAt": "2022-04-09T13:35:41.397Z", "updatedAt": "2022-04-09T13:35:41.397Z", "owner": some email", "_version": 1, "_lastChangedAt": 1649511341420, "_deleted": null } ], "isSynced": true } ```

aws-exports.js

const awsmobile = {
    "aws_project_region": "ap-southeast-1",
    "aws_cognito_identity_pool_id": "***",
    "aws_cognito_region": "ap-southeast-1",
    "aws_user_pools_id": "***",
    "aws_user_pools_web_client_id": "***",
    "oauth": {},
    "aws_cognito_username_attributes": [],
    "aws_cognito_social_providers": [],
    "aws_cognito_signup_attributes": [
        "EMAIL"
    ],
    "aws_cognito_mfa_configuration": "OFF",
    "aws_cognito_mfa_types": [
        "SMS"
    ],
    "aws_cognito_password_protection_settings": {
        "passwordPolicyMinLength": 8,
        "passwordPolicyCharacters": []
    },
    "aws_cognito_verification_mechanisms": [
        "EMAIL"
    ],
    "aws_appsync_graphqlEndpoint": "***",
    "aws_appsync_region": "ap-southeast-1",
    "aws_appsync_authenticationType": "API_KEY",
    "aws_appsync_apiKey": "***"
};

Manual configuration

No response

Additional configuration

No response

Mobile Device

No response

Mobile Operating System

No response

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

No response

chrisbonifacio commented 2 years ago

Hi @techandmedia 👋 thanks for raising this issue. Just a few things:

  1. Do you mean you are creating these records while logged in as a Cognito User, logging out and still able to query the records?

  2. Are you able to query and access these records as a guest or different user in another browser?

techandmedia commented 2 years ago

Hi @techandmedia 👋 thanks for raising this issue. Just a few things:

  1. Do you mean you are creating these records while logged in as a Cognito User, logging out and still able to query the records?
  2. Are you able to query and access these records as a guest or different user in another browser/private window?
  1. Yes
  2. I just checked using different browser / private window and only public model is returning the data, while private model return empty.

What happens? When I logged out I also cleared Datastore like so

const { user, signOut } = useAuthenticator(context => [context.user]);

function logout() {
  signOut(); // 
  DataStore.clear();
}
techandmedia commented 2 years ago

Here you can test this https://github.com/techandmedia/next-amplify-react18/tree/testing-blog

And here is the current situation which scenario as follow

  1. Login with user A in browser A, create 3 public records and 2 private records
  2. Logout and the user still can see 3 public records and 2 private records Logout and the user can see public records and not private records
  3. Open browser B and the user only see 3 public records and 0 private records
  4. Login with user B in browser B, create 2 public records and 2 private records
  5. User now able to see 5 public records and 2 private records
  6. Logout user B in browser B and the user only see 5 public records and 0 private records
  7. Guest user in browser A only see 5 public records and 0 private records
  8. Login with user A in browser A only see 5 public records and 0 private records
  9. Refresh browser A and now user A able see 5 public records and 2 private records

I am not sure if this is correct, like needing to refresh browser to see the current data, shouldn't it updated in real time? Otherwise the user will think they get false data

Update 1 The strange behaviour also happens in browser B, private blog wont show up until the user add a private blog or the user refresh the browser Browser A; Chrome Browser B: Firefox

Update 2 User B in browser B add a public blog, user B see their public blogs updated, but not in browser A, either logged in or not Moving some codes in useEffect, now works fine

Update 3 Error in private mode in Firefox [ERROR] 16:37.260 DataStore - IndexedDB not supported in this browser's private mode

Strange behaviour in Chrome, it doesn't matter how many times I logged in, logout, the number of data for public blog is incorrect, still showing old data (8 contents), while in Firefox, always correct Moving some codes in useEffect, now works fine

chrisbonifacio commented 2 years ago

Hey @techandmedia , sorry, I forgot DataStore won't work in a private window where IndexedDB API is not available. Just using another browser and logging in as another user should suffice. The goal was to simulate and test whether one user on one "device" (browser A) could query another user's data on another "device" (browser B), which doesn't seem to be the case.

DataStore only interacts with the local store (indexedDB in browser) so any data a user creates on a device will remain stored on the device and accessible to DataStore unless cleared. We recommend using DataStore.clear() on logout and/or when a different user logs in to remove any data you don't want a different user on the same device to access.

So, need to keep in mind that data stored on the device, as the main purpose of DataStore is offline capability, can be accessed or viewed in local storage by anyone with access to the device. Consider this when deciding what data you actually want to be stored locally. For data you'd rather keep only on the server you can use API.graphql instead as the auth rules will always be respected when trying to access data from the server.

This being said, it sounds like the current issue you're facing is that the observeQuery subscription (PrivateBlog model?) is not returning new data until the user creates another record or the page is refreshed. Is that correct?

chrisbonifacio commented 2 years ago

Hi 👋 Closing this as we have not heard back from you. If you are still experiencing this issue and in need of assistance, please feel free to comment and provide us with any information previously requested by our team members so we can re-open this issue and be better able to assist you.

Thank you!

github-actions[bot] commented 1 year 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 amplify-help forum.