From 2fc2ffd5424cf786567e46952814af38524d5532 Mon Sep 17 00:00:00 2001 From: LJ <81748770+elle-j@users.noreply.github.com> Date: Tue, 31 Oct 2023 13:57:33 +0100 Subject: [PATCH] Additions to custom user data (#6221) --- examples/rn-connection-and-error/README.md | 23 ++++++++++--- .../AuthExample/Users/rules.json | 22 +++++++++++++ .../backend/functions/config.json | 1 - .../backend/functions/onUserCreation.js | 4 +-- .../frontend/app/hooks/useDemoSyncTriggers.ts | 16 ++++++++- .../frontend/app/screens/StoreScreen.tsx | 33 ++++++++++++------- package-lock.json | 2 -- 7 files changed, 78 insertions(+), 23 deletions(-) create mode 100644 examples/rn-connection-and-error/backend/data_sources/mongodb-atlas/AuthExample/Users/rules.json diff --git a/examples/rn-connection-and-error/README.md b/examples/rn-connection-and-error/README.md index 55210981f49..a4b5888490e 100644 --- a/examples/rn-connection-and-error/README.md +++ b/examples/rn-connection-and-error/README.md @@ -71,7 +71,7 @@ It specifically addresses the following points: * With invalid password * With non-existent email * Listening (and triggering) when a user gets logged out. -* Listening (and triggering) when a user's tokens are refreshed. +* Listening (and triggering) when a user's tokens and custom data are refreshed. * Listening (and triggering) when the underlying sync session: * Tries to connect * Gets connected @@ -201,17 +201,21 @@ To sync data used in this app you must first: ### Add an Atlas Function -> If you set up your App Services App [via a CLI](#via-a-cli-recommended), you can **skip this step** as the function should already be defined for you. +> 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 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. To set this up via the App Services UI: -1. [Define a function](https://www.mongodb.com/docs/atlas/app-services/functions/#define-a-function) with the following configurations: +1. [Define two 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` + * Authentication: `Application Authentication` + * Private: `false` + * Code: See [backend function](./backend/functions/onUserCreation.js) ### Install Dependencies @@ -253,7 +257,16 @@ npm run android > If you set up your App Services App [via a CLI](#via-a-cli-recommended), you can **skip this step** as the permissions should already be defined for you. -After running the client app for the first time, [check the rules](https://www.mongodb.com/docs/atlas/app-services/rules/roles/#define-roles---permissions) for the collections in the App Services UI and make sure all collections have `readAndWriteAll` permissions (see [corresponding json](./backend/data_sources/mongodb-atlas/sync/Product/rules.json)). +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)) + * Explanation: + * All users will be able to read and write to the above collections. +* Collection: `Users` + * Permissions: `ThisUser` (see [corresponding json](./backend/data_sources/mongodb-atlas/AuthExample/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 === `), 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/). diff --git a/examples/rn-connection-and-error/backend/data_sources/mongodb-atlas/AuthExample/Users/rules.json b/examples/rn-connection-and-error/backend/data_sources/mongodb-atlas/AuthExample/Users/rules.json new file mode 100644 index 00000000000..0176ecaac03 --- /dev/null +++ b/examples/rn-connection-and-error/backend/data_sources/mongodb-atlas/AuthExample/Users/rules.json @@ -0,0 +1,22 @@ +{ + "collection": "Users", + "database": "AuthExample", + "roles": [ + { + "name": "ThisUser", + "apply_when": { + "user_id": "%%user.id%%" + }, + "document_filters": { + "write": {}, + "read": {} + }, + "read": true, + "write": true, + "insert": false, + "delete": false, + "search": false + } + ], + "filters": [] +} diff --git a/examples/rn-connection-and-error/backend/functions/config.json b/examples/rn-connection-and-error/backend/functions/config.json index 5d5b38f1bd1..526d5d60f2c 100644 --- a/examples/rn-connection-and-error/backend/functions/config.json +++ b/examples/rn-connection-and-error/backend/functions/config.json @@ -8,7 +8,6 @@ { "name": "onUserCreation", "private": false, - "run_as_system": true, "disable_arg_logs": true } ] diff --git a/examples/rn-connection-and-error/backend/functions/onUserCreation.js b/examples/rn-connection-and-error/backend/functions/onUserCreation.js index 0653782de0a..dc8b3633351 100644 --- a/examples/rn-connection-and-error/backend/functions/onUserCreation.js +++ b/examples/rn-connection-and-error/backend/functions/onUserCreation.js @@ -7,9 +7,9 @@ exports = async function onUserCreation(user) { const customUserDataCollection = context.services.get("mongodb-atlas").db("AuthExample").collection("Users"); try { await customUserDataCollection.insertOne({ - // Save the user's account ID to your configured user_id_field + // Save the user's account ID to your configured user_id field. user_id: user.id, - // Store any other user data you want + // Store any other user data you want. team: "service", }); } catch (e) { diff --git a/examples/rn-connection-and-error/frontend/app/hooks/useDemoSyncTriggers.ts b/examples/rn-connection-and-error/frontend/app/hooks/useDemoSyncTriggers.ts index 57cff39594f..91fbb088592 100644 --- a/examples/rn-connection-and-error/frontend/app/hooks/useDemoSyncTriggers.ts +++ b/examples/rn-connection-and-error/frontend/app/hooks/useDemoSyncTriggers.ts @@ -18,7 +18,7 @@ import {useCallback, useEffect, useState} from 'react'; import {BSON, ConnectionState, UserState} from 'realm'; -import {useRealm, useUser} from '@realm/react'; +import {useApp, useRealm, useUser} from '@realm/react'; import {Store} from '../models/Store'; import {logger} from '../utils/logger'; @@ -37,6 +37,7 @@ let mostRecentAccessToken: string | null = null; * You can also add a listener to the App (via `useApp()`). */ export function useDemoSyncTriggers() { + const app = useApp(); const realm = useRealm(); const currentUser = useUser(); const [isConnected, setIsConnected] = useState(true); @@ -147,6 +148,18 @@ export function useDemoSyncTriggers() { await currentUser.refreshCustomData(); }, [currentUser]); + /** + * Trigger the user event listener by removing the user from the app. + */ + const deleteUser = useCallback(() => { + // TODO: Update to use only `deleteUser`. + // We currently call both `deleteUser` (deletes from server and client) and + // `removeUser` (deletes from client) due to a bug in `deleteUser` where the + // `currentUser` is not updated to `null`. + app.deleteUser(currentUser); + app.removeUser(currentUser); + }, [app, currentUser]); + useEffect(() => { /** * The user listener - Will be invoked on various user related events including @@ -196,5 +209,6 @@ export function useDemoSyncTriggers() { triggerSyncError, triggerClientReset, refreshAccessToken, + deleteUser, }; } diff --git a/examples/rn-connection-and-error/frontend/app/screens/StoreScreen.tsx b/examples/rn-connection-and-error/frontend/app/screens/StoreScreen.tsx index c27b8140151..ed62cb9632c 100644 --- a/examples/rn-connection-and-error/frontend/app/screens/StoreScreen.tsx +++ b/examples/rn-connection-and-error/frontend/app/screens/StoreScreen.tsx @@ -18,7 +18,7 @@ import React from 'react'; import {Alert, FlatList, StyleSheet, Text, View} from 'react-native'; -import {useAuth, useApp, useUser} from '@realm/react'; +import {useAuth, useUser} from '@realm/react'; import {Button} from '../components/Button'; import {KioskItem} from '../components/KioskItem'; @@ -27,6 +27,18 @@ import {fonts} from '../styles/fonts'; import {useDemoSyncTriggers} from '../hooks/useDemoSyncTriggers'; import {useStore} from '../providers/StoreProvider'; +/** + * The properties used as custom user data. + * + * @note + * Our backend function `onUserCreation()` adds these fields + * when the user registers. + */ +type CustomUserData = { + user_id: string; + team: string; +}; + /** * Screen for showing the kiosks and products in the store, * as well as buttons for triggering various listeners. @@ -41,10 +53,10 @@ export function StoreScreen() { triggerSyncError, triggerClientReset, refreshAccessToken, + deleteUser, } = useDemoSyncTriggers(); const {logOut} = useAuth(); - const user = useUser(); - const app = useApp(); + const user = useUser<{}, CustomUserData, {}>(); return ( @@ -72,6 +84,11 @@ export function StoreScreen() { /> + + + Team: {user.customData.team} + + Status: {isConnected ? 'Connected 🟢' : 'Not connected 🔴'} @@ -82,11 +99,6 @@ export function StoreScreen() { text={isConnected ? 'Disconnect' : 'Connect'} /> - - - Team: {user.customData?.team || '-'} - -