diff --git a/localenv/README.md b/localenv/README.md index 98860cb907..11910e44d3 100644 --- a/localenv/README.md +++ b/localenv/README.md @@ -255,11 +255,9 @@ Note that you have to go through an interaction flow by clicking on the `redirec #### Admin UI -In order to manage, and view information about the Rafiki instance(s) you can use the [Rafiki Admin](../packages/frontend/README.md) UI. We have secured access to Rafiki Admin using [Ory Kratos](https://www.ory.sh/docs/kratos/ory-kratos-intro). Since access to the UI is on an invitation-only basis the registration flow is not publicly available. As such, in order to access Rafiki Admin you can click the registration link provided in the logs during `localenv` startup or you can manually add a new user with the invite-user script. Run `docker exec -it npm run invite-user -- example@mail.com` and it will output a link to the terminal. Copy and paste this link in your browser and you will automatically be logged in and directed to the account settings page. The next step is changing your password. We're using a simple email and password authentication method. +In order to manage and view information about the Rafiki instance(s) you can use the [Rafiki Admin](https://rafiki.dev/rafikiadmin/overview/) UI. We have secured access to Rafiki Admin using [Ory Kratos](https://www.ory.sh/docs/kratos/ory-kratos-intro); however, in our local playground setup we've chosen to disable authorization for easier development and testing interactions. -Note that a separate registration is required for Cloud Nine Wallet's Rafiki Admin and Happy Life Bank's Rafiki Admin, since they are each designed to run as separate mock account servicing entities. Once you've registered, you can always come back to your Rafiki Admin account by navigating to [`localhost:3010`](http://localhost:3010) (Cloud Nine Wallet) or [`localhost:4010`](http://localhost:4010) (Happy Life Bank) and logging in. - -You can test the account recovery flow by clicking "Forgot pasword?" on the login page and by navigating to [`localhost:4436`](http://localhost:4436) (Mailslurper interface). +If you'd like to enable authorization locally you can run `pnpm localenv:compose:adminauth up` and check out the setup in the [`admin-auth`](./admin-auth/) subdirectory. Note that, if authorization is enabled, you must register separately for Cloud Nine Wallet's Rafiki Admin and Happy Life Bank's Rafiki Admin, as they are intended to operate as distinct mock account servicing entities. Once you've registered, you can always come back to your Rafiki Admin account by navigating to [`localhost:3010`](http://localhost:3010) (Cloud Nine Wallet) or [`localhost:4010`](http://localhost:4010) (Happy Life Bank) and logging in. Since access to the UI is on an invitation-only basis the registration flow is not publicly available. As such, in order to access Rafiki Admin you can manually add a new user with the invite-user script. Run `docker exec -it npm run invite-user -- example@mail.com`, and it will output a link to the terminal. Copy and paste this link in your browser and you will automatically be logged in and directed to the account settings page. The next step is changing your password. We are using a simple email and password authentication method. #### Admin APIs diff --git a/localenv/admin-auth/cloud-nine-kratos.yml b/localenv/admin-auth/cloud-nine-kratos.yml new file mode 100644 index 0000000000..65be26b711 --- /dev/null +++ b/localenv/admin-auth/cloud-nine-kratos.yml @@ -0,0 +1,91 @@ +version: v0.13.0 + +dsn: postgres://cloud_nine_kratos:kratos_password@shared-database:5432/cloud_nine_kratos?sslmode=disable&max_conns=20&max_idle_conns=4 + +serve: + public: + base_url: http://localhost:4433/ + cors: + enabled: true + admin: + base_url: http://cloud-nine-kratos:4434/ + +selfservice: + default_browser_return_url: http://localhost:3010/ + allowed_return_urls: + - http://localhost:3010 + + methods: + link: + config: + lifespan: 1h + base_url: http://localhost:4433 + enabled: true + password: + enabled: true + + flows: + error: + ui_url: http://localhost:3010/error + + settings: + ui_url: http://localhost:3010/settings + privileged_session_max_age: 15m + required_aal: highest_available + + recovery: + enabled: true + ui_url: http://localhost:3010/auth/recovery + use: link + after: + hooks: + - hook: revoke_active_sessions + + verification: + enabled: false + + logout: + after: + default_browser_return_url: http://localhost:3010/auth + + login: + ui_url: http://localhost:3010/auth/login + lifespan: 10m + + registration: + enabled: false + +log: + level: debug + format: json + leak_sensitive_values: true + +secrets: + cookie: + - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE + cipher: + - 32-LONG-SECRET-NOT-SECURE-AT-ALL + +ciphers: + algorithm: xchacha20-poly1305 + +hashers: + algorithm: bcrypt + bcrypt: + cost: 8 + +identity: + schemas: + - id: default + url: file:///etc/config/kratos/identity.schema.json + +courier: + smtp: + connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true + +session: + lifespan: 1h + cookie: + persistent: false + same_site: Strict + path: / diff --git a/localenv/admin-auth/dbinit.sql b/localenv/admin-auth/dbinit.sql new file mode 100644 index 0000000000..1052df19a3 --- /dev/null +++ b/localenv/admin-auth/dbinit.sql @@ -0,0 +1,23 @@ +CREATE USER cloud_nine_wallet_backend WITH PASSWORD 'cloud_nine_wallet_backend'; +CREATE DATABASE cloud_nine_wallet_backend; +ALTER DATABASE cloud_nine_wallet_backend OWNER TO cloud_nine_wallet_backend; + +CREATE USER cloud_nine_wallet_auth WITH PASSWORD 'cloud_nine_wallet_auth'; +CREATE DATABASE cloud_nine_wallet_auth; +ALTER DATABASE cloud_nine_wallet_auth OWNER TO cloud_nine_wallet_auth; + +CREATE USER happy_life_bank_backend WITH PASSWORD 'happy_life_bank_backend'; +CREATE DATABASE happy_life_bank_backend; +ALTER DATABASE happy_life_bank_backend OWNER TO happy_life_bank_backend; + +CREATE USER happy_life_bank_auth WITH PASSWORD 'happy_life_bank_auth'; +CREATE DATABASE happy_life_bank_auth; +ALTER DATABASE happy_life_bank_auth OWNER TO happy_life_bank_auth; + +CREATE USER happy_life_kratos WITH PASSWORD 'kratos_password'; +CREATE DATABASE happy_life_kratos; +ALTER DATABASE happy_life_kratos OWNER TO happy_life_kratos; + +CREATE USER cloud_nine_kratos WITH PASSWORD 'kratos_password'; +CREATE DATABASE cloud_nine_kratos; +ALTER DATABASE cloud_nine_kratos OWNER TO cloud_nine_kratos; diff --git a/localenv/admin-auth/docker-compose.yml b/localenv/admin-auth/docker-compose.yml new file mode 100644 index 0000000000..192a812a41 --- /dev/null +++ b/localenv/admin-auth/docker-compose.yml @@ -0,0 +1,65 @@ +services: + cloud-nine-admin: + environment: + AUTH_ENABLED: true + KRATOS_CONTAINER_PUBLIC_URL: 'http://cloud-nine-kratos:4433' + KRATOS_BROWSER_PUBLIC_URL: 'http://localhost:4433' + KRATOS_ADMIN_URL: 'http://cloud-nine-kratos:4434/admin' + depends_on: + - cloud-nine-backend + - cloud-nine-kratos + + happy-life-admin: + environment: + AUTH_ENABLED: true + KRATOS_CONTAINER_PUBLIC_URL: 'http://happy-life-kratos:4433' + KRATOS_BROWSER_PUBLIC_URL: 'http://localhost:4432' + KRATOS_ADMIN_URL: 'http://happy-life-kratos:4434/admin' + depends_on: + - cloud-nine-admin + - happy-life-backend + - happy-life-kratos + + cloud-nine-kratos: + build: + context: ../.. + dockerfile: ./packages/frontend/kratos/Dockerfile + args: + PATH_TO_KRATOS_CONFIG: ./localenv/admin-auth/cloud-nine-kratos.yml + depends_on: + - shared-database + - mailslurper + environment: + DEV_MODE: true + ports: + - "4433:4433" + networks: + - rafiki + + happy-life-kratos: + build: + context: ../.. + dockerfile: ./packages/frontend/kratos/Dockerfile + args: + PATH_TO_KRATOS_CONFIG: ./localenv/admin-auth/happy-life-kratos.yml + depends_on: + - shared-database + - mailslurper + environment: + DEV_MODE: true + ports: + - "4432:4433" + networks: + - rafiki + + mailslurper: + image: oryd/mailslurper:latest-smtps + ports: + - "4436:4436" + - "4437:4437" + networks: + - rafiki + + shared-database: + volumes: + - ../admin-auth/dbinit.sql:/docker-entrypoint-initdb.d/init.sql diff --git a/localenv/admin-auth/happy-life-kratos.yml b/localenv/admin-auth/happy-life-kratos.yml new file mode 100644 index 0000000000..fbd1d28103 --- /dev/null +++ b/localenv/admin-auth/happy-life-kratos.yml @@ -0,0 +1,91 @@ +version: v0.13.0 + +dsn: postgres://happy_life_kratos:kratos_password@shared-database:5432/happy_life_kratos?sslmode=disable&max_conns=20&max_idle_conns=4 + +serve: + public: + base_url: http://localhost:4432/ + cors: + enabled: true + admin: + base_url: http://happy-life-kratos:4434/ + +selfservice: + default_browser_return_url: http://localhost:4010/ + allowed_return_urls: + - http://localhost:4010 + + methods: + link: + config: + lifespan: 1h + base_url: http://localhost:4432 + enabled: true + password: + enabled: true + + flows: + error: + ui_url: http://localhost:4010/error + + settings: + ui_url: http://localhost:4010/settings + privileged_session_max_age: 15m + required_aal: highest_available + + recovery: + enabled: true + ui_url: http://localhost:4010/auth/recovery + use: link + after: + hooks: + - hook: revoke_active_sessions + + verification: + enabled: false + + logout: + after: + default_browser_return_url: http://localhost:4010/auth + + login: + ui_url: http://localhost:4010/auth/login + lifespan: 10m + + registration: + enabled: false + +log: + level: debug + format: json + leak_sensitive_values: true + +secrets: + cookie: + - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE + cipher: + - 32-LONG-SECRET-NOT-SECURE-AT-ALL + +ciphers: + algorithm: xchacha20-poly1305 + +hashers: + algorithm: bcrypt + bcrypt: + cost: 8 + +identity: + schemas: + - id: default + url: file:///etc/config/kratos/identity.schema.json + +courier: + smtp: + connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true + +session: + lifespan: 1h + cookie: + persistent: false + same_site: Strict + path: / diff --git a/localenv/cloud-nine-wallet/dbinit.sql b/localenv/cloud-nine-wallet/dbinit.sql index 1052df19a3..cb8895204f 100644 --- a/localenv/cloud-nine-wallet/dbinit.sql +++ b/localenv/cloud-nine-wallet/dbinit.sql @@ -9,15 +9,3 @@ ALTER DATABASE cloud_nine_wallet_auth OWNER TO cloud_nine_wallet_auth; CREATE USER happy_life_bank_backend WITH PASSWORD 'happy_life_bank_backend'; CREATE DATABASE happy_life_bank_backend; ALTER DATABASE happy_life_bank_backend OWNER TO happy_life_bank_backend; - -CREATE USER happy_life_bank_auth WITH PASSWORD 'happy_life_bank_auth'; -CREATE DATABASE happy_life_bank_auth; -ALTER DATABASE happy_life_bank_auth OWNER TO happy_life_bank_auth; - -CREATE USER happy_life_kratos WITH PASSWORD 'kratos_password'; -CREATE DATABASE happy_life_kratos; -ALTER DATABASE happy_life_kratos OWNER TO happy_life_kratos; - -CREATE USER cloud_nine_kratos WITH PASSWORD 'kratos_password'; -CREATE DATABASE cloud_nine_kratos; -ALTER DATABASE cloud_nine_kratos OWNER TO cloud_nine_kratos; diff --git a/localenv/cloud-nine-wallet/docker-compose.yml b/localenv/cloud-nine-wallet/docker-compose.yml index 9d2921bd89..99239ccecf 100644 --- a/localenv/cloud-nine-wallet/docker-compose.yml +++ b/localenv/cloud-nine-wallet/docker-compose.yml @@ -151,36 +151,11 @@ services: GRAPHQL_URL: http://cloud-nine-wallet-backend:3001/graphql OPEN_PAYMENTS_URL: https://cloud-nine-wallet-backend/ ENABLE_INSECURE_MESSAGE_COOKIE: true - KRATOS_CONTAINER_PUBLIC_URL: 'http://cloud-nine-kratos:4433' - KRATOS_BROWSER_PUBLIC_URL: 'http://localhost:4433' - KRATOS_ADMIN_URL: 'http://cloud-nine-kratos:4434/admin' + AUTH_ENABLED: false SIGNATURE_VERSION: 1 SIGNATURE_SECRET: iyIgCprjb9uL8wFckR+pLEkJWMB7FJhgkvqhTQR/964= depends_on: - cloud-nine-backend - - cloud-nine-kratos - cloud-nine-kratos: - build: - context: ../.. - dockerfile: ./packages/frontend/kratos/Dockerfile - args: - PATH_TO_KRATOS_CONFIG: ./localenv/cloud-nine-wallet/kratos.yml - depends_on: - - shared-database - - mailslurper - environment: - DEV_MODE: true - ports: - - "4433:4433" - networks: - - rafiki - mailslurper: - image: oryd/mailslurper:latest-smtps - ports: - - "4436:4436" - - "4437:4437" - networks: - - rafiki volumes: database-data: # named volumes can be managed easier using docker-compose diff --git a/localenv/happy-life-bank/docker-compose.yml b/localenv/happy-life-bank/docker-compose.yml index 465bd030ff..5a3c37daa4 100644 --- a/localenv/happy-life-bank/docker-compose.yml +++ b/localenv/happy-life-bank/docker-compose.yml @@ -118,27 +118,9 @@ services: GRAPHQL_URL: http://happy-life-bank-backend:3001/graphql OPEN_PAYMENTS_URL: https://happy-life-bank-backend/ ENABLE_INSECURE_MESSAGE_COOKIE: true - KRATOS_CONTAINER_PUBLIC_URL: 'http://happy-life-kratos:4433' - KRATOS_BROWSER_PUBLIC_URL: 'http://localhost:4432' - KRATOS_ADMIN_URL: 'http://happy-life-kratos:4434/admin' + AUTH_ENABLED: false SIGNATURE_VERSION: 1 SIGNATURE_SECRET: iyIgCprjb9uL8wFckR+pLEkJWMB7FJhgkvqhTQR/964= depends_on: - cloud-nine-admin - happy-life-backend - - happy-life-kratos - happy-life-kratos: - build: - context: ../.. - dockerfile: ./packages/frontend/kratos/Dockerfile - args: - PATH_TO_KRATOS_CONFIG: ./localenv/happy-life-bank/kratos.yml - depends_on: - - shared-database - - mailslurper - environment: - DEV_MODE: true - ports: - - "4432:4433" - networks: - - rafiki diff --git a/package.json b/package.json index ba8fcc7282..323e41cb4f 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "localenv:compose": "docker compose -f ./localenv/cloud-nine-wallet/docker-compose.yml -f ./localenv/happy-life-bank/docker-compose.yml -f ./localenv/merged/docker-compose.yml -f ./localenv/tigerbeetle/docker-compose.yml --env-file ./localenv/tigerbeetle/.env.tigerbeetle", "localenv:compose:psql:telemetry": "docker compose -f ./localenv/cloud-nine-wallet/docker-compose.yml -f ./localenv/happy-life-bank/docker-compose.yml -f ./localenv/merged/docker-compose.yml -f ./localenv/telemetry/docker-compose.yml", "localenv:compose:telemetry": "docker compose -f ./localenv/cloud-nine-wallet/docker-compose.yml -f ./localenv/happy-life-bank/docker-compose.yml -f ./localenv/merged/docker-compose.yml -f ./localenv/tigerbeetle/docker-compose.yml -f ./localenv/telemetry/docker-compose.yml --env-file ./localenv/tigerbeetle/.env.tigerbeetle", + "localenv:compose:adminauth": "docker compose -f ./localenv/cloud-nine-wallet/docker-compose.yml -f ./localenv/happy-life-bank/docker-compose.yml -f ./localenv/merged/docker-compose.yml -f ./localenv/admin-auth/docker-compose.yml", "localenv:seed:auth": "pnpm -C ./packages/auth knex seed:run --env=development && pnpm -C ./packages/auth knex seed:run --env=peerdevelopment", "sanity": "pnpm -r build && pnpm -r test", "localenv:compose:autopeer": "run-p tunnel:start wait-tunnel:localenv:compose", diff --git a/packages/frontend/README.md b/packages/frontend/README.md index a19d312547..ea8ee3f366 100644 --- a/packages/frontend/README.md +++ b/packages/frontend/README.md @@ -1,10 +1,12 @@ # Rafiki Admin +Rafiki Admin provides a user-friendly administrative interface for interacting with the `backend` admin APIs. In this web application, you'll be able to view and manage peering relationships, assets, and wallet addresses, among other settings. These commands access sensitive information and, therefore, must be secured. This is why we have enabled authentication by default, so only authenticated users can be granted access to Rafiki Admin by an administrator. However, authentication can be disabled in instances where the environment variable `AUTH_ENABLED` is set to `false`. **Disabling authentication should only be considered in testing environments, or if Rafiki Admin is not exposed externally and your system ensures access is securely locked down.** + ## Setup -Rafiki Admin functions as an interface to the Rafiki backend service and relies on the [Ory Kratos](https://www.ory.sh/docs/kratos/ory-kratos-intro) identity and user management solution, and an SMTP mail server for sending account recovery emails. It doesn’t operate independently; all actions performed in the Rafiki Admin interface, such as fetching data or executing commands, are passed to the Rafiki `backend` service. +Rafiki Admin always relies on the Rafiki `backend` service, and when authentication is enabled, it also relies on the [Ory Kratos](https://www.ory.sh/docs/kratos/ory-kratos-intro) identity and user management solution and an SMTP mail server. Ory Kratos is a secure, open-source identity and user management solution that handles authentication (login) and user management (account creation and password recovery). Check it out on [GitHub](https://github.com/ory/kratos). -Ory Kratos, a secure and open-source identity and user management solution, handles authentication (login) and user management (account creation and password recovery). Check it out on [GitHub](https://github.com/ory/kratos). For an example of how to get these services up and running, see our [local environment](../../localenv/cloud-nine-wallet/docker-compose.yml) setup. TLDR: to get the whole environment up, run the command `pnpm localenv:compose up` from the root of the project. Once all the Docker containers are up, you can interact with Rafiki Admin either through the local Cloud Nine Wallet instance at http://localhost:3010, or through the Happy Life Bank instance at http://localhost:4010. +For an example of how to get these services up and running, see our [local environment](../../localenv/README.md) setup and look at the [`admin-auth`](../../localenv/admin-auth/) subdirectory for authentication implementation instructions. TLDR: to get the whole environment up, run the command `pnpm localenv:compose:adminauth up` from the root of the project. Once all the Docker containers are up, you can interact with Rafiki Admin through the local Cloud Nine Wallet instance at http://localhost:3010, or through the Happy Life Bank instance at http://localhost:4010. > **Note: Ory Kratos and Rafiki Admin must be hosted on the same top-level domain. Hosting Kratos on a subdomain is generally not recommended by Ory, but if you choose this approach, ensure you follow the guidelines provided in the Kratos documentation.** @@ -12,25 +14,25 @@ Ory Kratos, a secure and open-source identity and user management solution, hand Access to Rafiki Admin is restricted to ensure that only authorized users can register. This is achieved by using an invitation-only system, where new users are invited by an administrator. The registration flow is not public, so users cannot sign up on their own. Instead, administrators create accounts using the `invite-user` script. -An administrator (someone with backend interface system access) can run the invite-user script in one of two ways, either from outside the container, on the host machine where Docker is running, `docker exec -it npm run invite-user -- example@mail.com`, or directly inside the Rafiki Admin Docker container `npm run invite-user -- example@mail.com`. +An administrator (someone with backend interface system access) can run the invite-user script in one of two ways, either from outside the container on the host machine where Docker is running: `docker exec -it npm run invite-user -- example@mail.com`, or directly inside the Rafiki Admin Docker container: `npm run invite-user -- example@mail.com`. After running the invite-user script, it generates a recovery link that also serves as an invitation link. This link is output to the terminal, and the administrator can send it to the user. When the user opens the link in their browser, they are automatically logged in and taken to the account settings page, where they can set a new password. Afterward, they can log in normally via the Rafiki Admin URL. > **Note**: The invitation link is single-use for security purposes. Once accessed, it becomes invalid. If sending the link through Slack, ensure you format it as code by placing it inside backticks (\`) to prevent Slack from automatically previewing the link, which would invalidate it. Example: \``http://localhost:4433/self-service/recovery?flow=116250ee-07bd-4b5c-a98e-87406192bb4b&token=miv0yZ7DFKKw8RyBBQvWoOsTRa2TVuZm`\`. -There is an automated account recovery flow which is triggered by clicking "Forgot pasword?" on the login page. This functionality requires an SMTP mail server for sending recovery links to users. Alternatively, an administrator may generate a recovery link using the same `invite-user` script. +There is an automated account recovery flow which is triggered by clicking "Forgot password?" on the login page. This functionality requires an SMTP mail server for sending recovery links to users. Alternatively, an administrator may generate a recovery link using the same `invite-user` script. To remove a user, administrators can use the following script: `docker exec -it npm run delete-user -- example@mail.com`. ### Why Ory Kratos? -We chose Kratos for its open-source nature, lightweight design, and robust security features. It eliminates the need to manage password hashing, storage, or account recovery flows ourselves, allowing us to focus on what we do best. Kratos also enhances security with features like built-in breach detection, secure session management, and regular security updates. +We chose Kratos for its open-source nature, lightweight design, and robust security features. It eliminates the need to manage password hashing, storage, or account recovery flows ourselves, allowing us to focus on what we do best. -## Development +Kratos also enhances security with features like built-in breach detection, secure session management, and regular security updates. -You can get a local development environment up by running the command `pnpm localenv:compose up` from the root of the project. Once all the Docker containers are up, you can interact with Rafiki Admin either through the local Cloud Nine Wallet instance at http://localhost:3010, or through the Happy Life Bank instance at http://localhost:4010.. For more information see the local environment [README](../../localenv/README). +## Development -We've made development smoother by attaching our Docker containers to the current code with a bind mount. This allows for live development changes with a simple page refresh while running locally. This is not suitable for production setups. +We've made development smoother by attaching our Docker containers to the current code with a bind mount. This allows for live development changes with a simple page refresh while running locally. Additionally, we've disabled authentication to simplify access. This is not suitable for production setups. You can get a local development environment up by running the command `pnpm localenv:compose up` (if you want to test without authentication), or `pnpm localenv:compose:adminauth up` (if you want the full experience). Once all the Docker containers are up, you can interact with Rafiki Admin through the local Cloud Nine Wallet instance at http://localhost:3010, or through the Happy Life Bank instance at http://localhost:4010.. For more information see the local environment [README](../../localenv/README). Ory Kratos provides frontend components (such as forms and buttons) for identity management flows like login, and account settings. These components are not fixed in design; they are fetched via API calls based on the specific flow (e.g., login, recovery). Kratos then returns the necessary UI elements, which you can organize, place, and style within the Rafiki Admin interface. This flexibility allows you to match the identity management components with Rafiki Admin's overall look and feel. @@ -68,7 +70,7 @@ To add a new typed Apollo request, you will need to add an untyped request and r - `lib`: business logic - `api`: GraphQL queries and mutations - `routes`: outer layer of the application - - `shared`: utilility functions or types + - `shared`: utility functions or types - `styles`: CSS files - `utils`: serverside utilities - `kratos`: Dockerfile and setup files diff --git a/packages/frontend/app/components/Sidebar.tsx b/packages/frontend/app/components/Sidebar.tsx index 2138f46ef8..5b140a25ca 100644 --- a/packages/frontend/app/components/Sidebar.tsx +++ b/packages/frontend/app/components/Sidebar.tsx @@ -8,6 +8,7 @@ import { Button } from '~/components/ui' interface SidebarProps { logoutUrl: string + authEnabled: boolean } const navigation = [ @@ -34,14 +35,10 @@ const navigation = [ { name: 'Payments', href: '/payments' - }, - { - name: 'Account Settings', - href: '/settings' } ] -export const Sidebar: FC = ({ logoutUrl }) => { +export const Sidebar: FC = ({ logoutUrl, authEnabled }) => { const [sidebarIsOpen, setSidebarIsOpen] = useState(false) return ( @@ -101,6 +98,22 @@ export const Sidebar: FC = ({ logoutUrl }) => { {name} ))} + {authEnabled && ( + + cx( + isActive + ? 'bg-mercury' + : 'text-tealish/70 hover:bg-mercury/70', + 'flex p-2 font-medium rounded-md' + ) + } + > + Account Settings + + )} {logoutUrl && (