This Turborepo contains the source code for the XGS platform.
-
pnpm package manager
-
Rust with the
wasm32-unknown-unknown
target:rustup target add wasm32-unknown-unknown
-
dfx (better if installed with the dfx version manager -
dfxvm
) -
an Auth0 account
-
an Android/iOS device or simulator
Follow these steps to configure Auth0 for this project:
- Create a Tenant and get your Auth0 Tenant domain, which looks like
<TENANT_NAME>.<TENANT_REGION>.auth0.com
After creating the tenant, you can configure it by using the configuration available at apps/auth0/config/tenant.yaml. See the Auth0 docs for more details.
Below are the steps to configure the tenant manually.
The Auth0 Native Application is used to authenticated users from the Mobile app. Follow these steps:
-
In the Dashboard > Applications > YOUR_APP > Settings tab, set the Allowed Callback URLs and Allowed Logout URLs to:
io.icp0.jwtauthdemo.auth0://<YOUR_AUTH0_TENANT_DOMAIN>/ios/io.icp0.jwtauthdemo/callback
io.icp0.jwtauthdemo.auth0://<YOUR_AUTH0_TENANT_DOMAIN>/android/io.icp0.jwtauthdemo/callback
Where
<YOUR_AUTH0_TENANT_DOMAIN>
is the Auth0 Tenant domain andio.icp0.jwtauthdemo
is both the Android Package Name and iOS Bundle Identifier, as configured in the app.config.js file. -
In the Dashboard > Applications > YOUR_APP > Credentials tab, set the Authentication Method to None (instead of Client Secret (Post))
The 1st step of the Auth0 React Native Quickstart interactive guide can be helpful too.
-
Configure the Login Flow in order to add custom JWT claims to the token minted by Auth0 and sync the user with Hasura. See this guide for more details. Write the following Login / Post Login Custom Action (select the Node.js 18 runtime):
Hasura Sync User and JWT Claims
const fetch = require("node-fetch"); const HASURA_GRAPHQL_URL = "https://alive-glowworm-caring.ngrok-free.app//v1/graphql"; /** * Handler that will be called during the execution of a PostLogin flow. * * @param {Event} event - Details about the user and the context in which they are logging in. * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. */ exports.onExecutePostLogin = async (event, api) => { const auth0Id = event.user.user_id; const email = event.user.email ?? null; const lastLoginAt = event.session?.authenticated_at ?? new Date(); const admin_secret = event.secrets.HASURA_GRAPHQL_ADMIN_SECRET; const query = `mutation UpsertUser($auth0Id: String!, $email: citext!, $lastLoginAt: timestamptz!, $isPending: Boolean!) { insert_users_one( object: {auth0_id: $auth0Id, email: $email, last_login_at: $lastLoginAt, is_pending: $isPending}, on_conflict: {constraint: users_email_key, update_columns: [auth0_id, is_pending, last_login_at]} ) { id } } `; const variables = { auth0Id, email, lastLoginAt, isPending: false }; const res = await fetch(HASURA_GRAPHQL_URL, { method: "POST", body: JSON.stringify({ query: query, variables: variables, }), headers: { "content-type": "application/json", "x-hasura-admin-secret": admin_secret, }, }); const graphqlRes = await res.json(); console.log("GraphQL Response:", graphqlRes); if (graphqlRes.errors && graphqlRes.errors.length > 0) { const err = graphqlRes.errors[0]; api.access.deny(err?.message || "GraphQL API returned an error."); return; } if (!graphqlRes.data) { api.access.deny("No data from GraphQL API."); return; } if (event.authorization) { const userId = graphqlRes.data.insert_users_one.id; const roles = ["user"]; const customClaimKey = "https://hasura.io/jwt/claims"; const customClaimValue = { "x-hasura-default-role": "user", "x-hasura-allowed-roles": roles, "x-hasura-user-id": userId, }; api.idToken.setCustomClaim(customClaimKey, customClaimValue); api.accessToken.setCustomClaim(customClaimKey, customClaimValue); } };
Finally, add this action to the Login Flow in the Auth0 Dashboard.
In order for the Auth0 Login Action to access the local Hasura API, you need to run a tunnel like ngrok. See the backend's README tunnel for more details.
The Next.js Quickstart guide can be helpful too.
To install the dependencies, run:
pnpm install
This will install the dependencies for all the apps and packages in the monorepo.
In every app, there's a .env.example
file that you can use as reference to create the relative .env
file.
The backend canister is located in the apps/ssp_backend
folder.
To deploy the backend canister on the local dfx replica, run the following commands:
-
Start the local replica from the
apps/ssp_backend
folder:pnpm dev
Keep this terminal open.
-
In another terminal, deploy the
ssp_backend
canister from the root of the monorepo:ENV_FILE_PATH=apps/ssp_backend/.env && pnpm run deploy --filter=ssp_backend
An example of mobile app that works using the authentication flow implemented in the SSP canister is available at ilbertt/ic-react-native-jwt-auth/src/app, and is built with Expo. You can copy that app and place it in the apps/mobile
folder, then follow the instructions below to run it.
To start the mobile app in dev mode, run the following commands from the apps/mobile
folder:
-
Make sure you've deployed the backend canister, see the SwissSportPass backend canister section. This generates a
.env
file in thepackages/config
folder with the necessary environment variables to connect to the backend canister from the mobile app. -
Prebuild the mobile app:
pnpm expo prebuild
Note: you should only do this the first time you build the mobile app.
-
Start the Expo development server:
pnpm dev
More info on how to use Expo dev server on their docs: https://docs.expo.dev/more/expo-cli/#develop.
Run the tests in all packages with:
pnpm test
This script uses Turborepo's dependencies to run the pretest
script in all packages before running the tests. Use the pretest
script to prepare the environment before running the tests (e.g. building the binaries, etc.).
If you want to skip Turborepo's cache, run:
pnpm test -- --force
Run the linters in all packages with:
pnpm lint
Run the formatters in all packages with:
pnpm format
Turborepo doesn't officially support Rust yet, but we have a workaround to make it work with the cache system of Turborepo.
When you want to add a Rust app to the monorepo:
- Add the Rust app in the
apps
folder - Add a
package.json
file with the<app-name>
into the app folder - Add any script needed for the app to build, lint, etc. in the
package.json
file
When you want to add a Rust package to the monorepo:
- Add the Rust package in the
packages
folder - Add a
package.json
file with the<package-name>
name into the package folder - Add any script needed for the package to build, lint, etc. in the
package.json
file - Import the Rust package in the app's
Cargo.toml
file where needed - Import the workspace package in the package.json of the app, so that Turbo recognizes it as a dependency and handles cache properly
See apps/ssp_backend
and packages/ssp_backend_types
for reference.