Skip to content

Commit

Permalink
Add Complex Custom User Data Example (#6251)
Browse files Browse the repository at this point in the history
Co-authored-by: Kenneth Geisshirt <[email protected]>
Co-authored-by: LJ <[email protected]>
  • Loading branch information
3 people authored Nov 24, 2023
1 parent 8a9b9b4 commit f3159ab
Show file tree
Hide file tree
Showing 41 changed files with 635 additions and 311 deletions.
74 changes: 63 additions & 11 deletions examples/rn-connection-and-error/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ It specifically addresses the following points:
* Initial subscriptions are added to allow syncing of a subset of data to the device (i.e. the kiosks and products belonging to a specific store).
* The Realm is opened immediately without waiting for downloads from the server.
* See [Offline Support](#note-offline-support) below.
* Tying custom user data to document permissions.
* See [upcoming section](#tying-custom-user-data-to-collection-rules).

### Note: Offline Support

Expand Down Expand Up @@ -142,8 +144,6 @@ The server will [reset the client](https://www.mongodb.com/docs/atlas/app-servic

In this demo app, a client reset is triggered by calling a [custom Atlas Function](#add-an-atlas-function) that deletes the client files for the current user. Another way to simulate a client reset is to terminate and re-enable Device Sync.

> ⚠️ At the time of writing (Realm JS version 12.2.0), pre and post client reset listeners are not fired as expected. Instead, the sync error callback is invoked with an error named `ClientReset`. This will be fixed as soon as possible.
### Logging and App Activity Monitoring

App Services logs all incoming requests and application events such as Device Sync operations and user authentication. In this demo app, we log messages to the `console` when certain changes and activities are detected, but you can replace the logger used with your preferred logging mechanism or service.
Expand All @@ -152,6 +152,19 @@ To modify the [log level and logger](https://www.mongodb.com/docs/realm/sdk/reac

For the App Services logs, you can also choose to [forward the logs to a service](https://www.mongodb.com/docs/atlas/app-services/activity/forward-logs/). To read more about monitoring app activity, please see the [docs](https://www.mongodb.com/docs/atlas/app-services/activity/).

### Tying Custom User Data to Collection Rules

When a new user is created, their [custom user data](https://www.mongodb.com/docs/realm/sdk/react-native/manage-users/custom-user-data/#std-label-react-native-access-custom-user-data) document automatically gets updated to include the ID of the first store in the collection (or a new store if it doesn't exist). The `Store`, `Kiosk`, and `Product` collections have [rules](#set-data-access-permissions) set to only be accessible if the user's `storeId` field matches the corresponding field in each model. There is a [function](#add-an-atlas-function) that can be triggered via the UI to switch the store for the current user. Updating the custom user data and refreshing the session will trigger a client reset, which will automatically update the UI with the newly selected store.

To switch store in the application, after creating a new user and viewing the store page, one can press `Trigger Store Change`, which will call the `switchStore` function. At this point the UI will not be updated, as a refresh of both the session and the current user's custom data must be performed. To do this, press `Refresh Access Token/User Data`. The refresh of the session will update the rules in the backend and the refresh of the custom user data will update the UI with the correct store ID.

If the refresh of the session does not happen automatically, one can either press `Refresh Session` or click `Disconnect` followed by `Reconnect`. If this is done without refreshing the user data, no store will be shown as the rules will be updated to the new store, but the UI will not be updated with the new store ID.

> Switching stores in this way (i.e. by modifying permissions via custom user data) is
> demonstrated here for developers who currently have such a use case. Normally, this
> can simply be achieved by updating the subscriptions used on the client (e.g. only
> subscribing to a store with a specific ID).
## Getting Started

### Prerequisites
Expand Down Expand Up @@ -183,7 +196,7 @@ To import and deploy changes from your local directory to App Services you can u
```sh
realm-cli push --local <path to backend directory>
```
4. Once pushed, verify that your App shows up in the App Services UI.
4. Once pushed, verify that your App shows up in the App Services UI and that the trigger has the status `Enabled`.
5. 🥳 You can now go ahead and [install dependencies and run the React Native app](#install-dependencies).

#### Via the App Services UI
Expand All @@ -204,19 +217,58 @@ To sync data used in this app you must first:

> If you set up your App Services App [via a CLI](#via-a-cli-recommended), you can **skip this step** as these functions should already be defined for you.
We will add a function for forcing a client reset. The function is solely used for demo purposes and should not be used in production. We will also add a function to be run on user creation that adds fields to the user's [custom user data](https://www.mongodb.com/docs/atlas/app-services/users/custom-metadata/) document.
We will add functions for the following:
* Forcing a client reset.
* This function is solely used for demo purposes and should **never** be used in production.
* Setting the default `storeId` for a user on creation on the associated [custom user data](https://www.mongodb.com/docs/atlas/app-services/users/custom-metadata/) document.
* An [authentication trigger](https://www.mongodb.com/docs/atlas/app-services/triggers/authentication-triggers/) will need to be created and configured to call this function (see below).
* Switching the associated Store for the current user.
* Creating a new store document and getting all store documents.
* These functions are used to check if the demo stores already exist and create them if not.
* The functions need to be system calls, as the associated user will not have permissions to read or write any other store.
* Deleting a custom user data document.
* If a user triggers `Delete User` from the client, we also remove the associated custom user data document from Atlas.

To set this up via the App Services UI:

1. [Define two functions](https://www.mongodb.com/docs/atlas/app-services/functions/#define-a-function) with the following configurations:
1. [Define functions](https://www.mongodb.com/docs/atlas/app-services/functions/#define-a-function) with the following configurations:
* Function name: `triggerClientReset`
* Authentication: `System`
* Private: `false`
* Code: See [backend function](./backend/functions/triggerClientReset.js)
* Function name: `onUserCreation`
* Function name: `setUserDefaultStoreId`
* Authentication: `Application Authentication`
* Private: `true`
* Code: See [backend function](./backend/functions/setUserDefaultStoreId.js)
* Function name: `switchStore`
* Authentication: `Application Authentication`
* Private: `false`
* Code: See [backend function](./backend/functions/onUserCreation.js)
* Code: See [backend function](./backend/functions/switchStore.js)
* Function name: `createNewStore`
* Authentication: `System`
* Private: `true`
* Code: See [backend function](./backend/functions/createNewStore.js)
* Function name: `getAllStores`
* Authentication: `System`
* Private: `true`
* Code: See [backend function](./backend/functions/getAllStores.js)
* Function name: `deleteCustomUserDataDoc`
* Authentication: `Application Authentication`
* Private: `true`
* Code: See [backend function](./backend/functions/deleteCustomUserDataDoc.js)
2. [Define authentication triggers](https://www.mongodb.com/docs/atlas/app-services/triggers/authentication-triggers/#create-an-authentication-trigger) with the following configurations:
* Trigger name: `onUserCreation`
* Trigger type: `Authentication`
* Action type: `Create`
* Providers: `Email/Password`
* EventType: `Function`
* Function: `setUserDefaultStoreId`
* Trigger name: `onDeletedUser`
* Trigger type: `Authentication`
* Action type: `Delete`
* Providers: `Email/Password`
* EventType: `Function`
* Function: `deleteCustomUserDataDoc`

### Install Dependencies

Expand Down Expand Up @@ -268,13 +320,13 @@ npm run android
After running the client app for the first time, [modify the rules](https://www.mongodb.com/docs/atlas/app-services/rules/roles/#define-roles---permissions) for the collections in the App Services UI.

* Collections: `Kiosk`, `Product`, `Store`
* Permissions: `readAndWriteAll` (see [corresponding json](./backend/data_sources/mongodb-atlas/sync/Product/rules.json))
* Permissions: `readAndWriteSpecificStore` (see [corresponding json](./backend/data_sources/mongodb-atlas/sync/Product/rules.json))
* Explanation:
* All users will be able to read and write to the above collections.
* The current user will be able to read and write to the above collections if the document's store ID matches the store ID in the user's custom user data document.
* Collection: `Users`
* Permissions: `ThisUser` (see [corresponding json](./backend/data_sources/mongodb-atlas/AuthExample/Users/rules.json))
* Permissions: `ThisUser` (see [corresponding json](./backend/data_sources/mongodb-atlas/StoreDemo/Users/rules.json))
* Explanation:
* Users who have registered each have a `Users` document as custom user data. A user will be able to read and write to their own document (i.e. when `Users.user_id === <App User ID>`), but not anyone else's (see [Secure Custom User Data](https://www.mongodb.com/docs/atlas/app-services/users/custom-metadata/#secure-custom-user-data)).
* Users who have registered each have a `Users` document as custom user data. A user will be able to read and write to their own document (i.e. when `Users.userId === <App User ID>`), but not anyone else's (see [Secure Custom User Data](https://www.mongodb.com/docs/atlas/app-services/users/custom-metadata/#secure-custom-user-data)).

> To learn more and see examples of permissions depending on a certain use case, see [Device Sync Permissions Guide](https://www.mongodb.com/docs/atlas/app-services/sync/app-builder/device-sync-permissions-guide/#std-label-flexible-sync-permissions-guide) and [Data Access Role Examples](https://www.mongodb.com/docs/atlas/app-services/rules/examples/).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
{
"enabled": true,
"mongo_service_name": "mongodb-atlas",
"database_name": "AuthExample",
"database_name": "StoreDemo",
"collection_name": "Users",
"user_id_field": "user_id",
"on_user_creation_function_name": "onUserCreation"
"user_id_field": "userId"
}
4 changes: 2 additions & 2 deletions examples/rn-connection-and-error/backend/auth/providers.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
"local-userpass": {
"name": "local-userpass",
"type": "local-userpass",
"disabled": false,
"config": {
"autoConfirm": true,
"resetPasswordUrl": "https://",
"runConfirmationFunction": false,
"runResetFunction": false
},
"disabled": false
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"products": {
"ref": "#/relationship/mongodb-atlas/sync/Product",
"ref": "#/relationship/mongodb-atlas/StoreDemo/Product",
"source_key": "products",
"foreign_key": "_id",
"is_list": true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"database": "StoreDemo",
"collection": "Kiosk",
"roles": [
{
"name": "readAndWriteSpecificStore",
"apply_when": {},
"document_filters": {
"read": {
"storeId": "%%user.custom_data.storeId"
},
"write": {
"storeId": "%%user.custom_data.storeId"
}
},
"insert": true,
"delete": true,
"search": true,
"read": true,
"write": true
}
]
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
{
"bsonType": "object",
"title": "Kiosk",
"type": "object",
"required": [
"_id",
"storeId"
],
"properties": {
"_id": {
"bsonType": "objectId"
Expand All @@ -13,10 +18,5 @@
"storeId": {
"bsonType": "objectId"
}
},
"required": [
"_id",
"storeId"
],
"title": "Kiosk"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"database": "StoreDemo",
"collection": "Product",
"roles": [
{
"name": "readAndWriteSpecificStore",
"apply_when": {},
"document_filters": {
"read": {
"storeId": "%%user.custom_data.storeId"
},
"write": {
"storeId": "%%user.custom_data.storeId"
}
},
"insert": true,
"delete": true,
"search": true,
"read": true,
"write": true
}
]
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
{
"bsonType": "object",
"title": "Product",
"type": "object",
"required": [
"_id",
"name",
"numInStock",
"price",
"storeId"
],
"properties": {
"_id": {
"bsonType": "objectId"
Expand All @@ -16,13 +24,5 @@
"storeId": {
"bsonType": "objectId"
}
},
"required": [
"_id",
"storeId",
"name",
"price",
"numInStock"
],
"title": "Product"
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"kiosks": {
"ref": "#/relationship/mongodb-atlas/sync/Kiosk",
"ref": "#/relationship/mongodb-atlas/StoreDemo/Kiosk",
"source_key": "kiosks",
"foreign_key": "_id",
"is_list": true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"database": "StoreDemo",
"collection": "Store",
"roles": [
{
"name": "readAndWriteSpecificStore",
"apply_when": {},
"document_filters": {
"read": {
"_id": "%%user.custom_data.storeId"
},
"write": {
"_id": "%%user.custom_data.storeId"
}
},
"insert": true,
"delete": true,
"search": true,
"read": true,
"write": true
}
]
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"bsonType": "object",
"title": "Store",
"type": "object",
"required": [
"_id"
],
"properties": {
"_id": {
"bsonType": "objectId"
Expand All @@ -10,9 +14,5 @@
"bsonType": "objectId"
}
}
},
"required": [
"_id"
],
"title": "Store"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"database": "StoreDemo",
"collection": "Users",
"roles": [
{
"name": "ThisUser",
"apply_when": {
"userId": "%%user.id"
},
"document_filters": {
"read": true,
"write": true
},
"insert": true,
"delete": true,
"search": true,
"read": true,
"write": true
}
]
}
Loading

0 comments on commit f3159ab

Please sign in to comment.