> For the complete documentation index, see [llms.txt](https://mercure-technologies.gitbook.io/expo-open-ota/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mercure-technologies.gitbook.io/expo-open-ota/dashboard/users.md).

# Users

Every dashboard visitor signs in with an email address and a password. What happens behind that login form depends on the server mode: a stateless deployment has exactly one account, while the control plane manages a real user directory in PostgreSQL.

### One account or many

In **stateless mode** there is nothing to manage: the server compares the login form against `ADMIN_EMAIL` and `ADMIN_PASSWORD` from the environment, and that single account is always an administrator. To rotate the email or the password, change the variables and restart the server. The **Users** page does not exist in this mode.

On the **control plane**, accounts live in the `users` table of your PostgreSQL database. Administrators manage them at runtime from **Users** in the dashboard sidebar — the environment variables are only read once, to create the very first account.

### The first admin

On its first control-plane boot the server runs a migration that creates the `users` table, then seeds it with one administrator built from `ADMIN_EMAIL` and `ADMIN_PASSWORD`. Both variables must be set for that first boot: with an empty users table and nothing to seed it with, the dashboard would be a door nobody can ever open, so the server refuses to start instead.

`ADMIN_PASSWORD` is held to the same password policy as accounts created from the UI — at least 8 characters, with an uppercase letter, a lowercase letter, a digit and a special character. A non-compliant value fails the migration, and therefore the boot, with an error listing the missing rules; pick a compliant password and change it from the dashboard later if you wish.

Once that migration has run, the pair has done its job. You can remove both variables from the deployment — logins are checked against the database from then on, and changing the variables later has no effect on existing accounts. If you restore a database that already contains users, the seeding step notices and skips itself.

### Admins and members

An account is either an **admin** or a **member**, and the difference is simple: members are read-only. They can browse every app with its branches, channels, updates and API-key list, and they can change their own password from the **Account** page. Everything that mutates the server is reserved for admins:

| Action                                                 | Member | Admin |
| ------------------------------------------------------ | :----: | :---: |
| Browse apps, branches, channels and updates            |    ✅   |   ✅   |
| Change their own password                              |    ✅   |   ✅   |
| Create, edit or delete apps                            |    ❌   |   ✅   |
| Create or delete branches and channels, remap channels |    ❌   |   ✅   |
| Create or revoke API keys                              |    ❌   |   ✅   |
| Download an app's signing certificate                  |    ❌   |   ✅   |
| Create or delete users, grant or revoke admin          |    ❌   |   ✅   |

API keys and the signing certificate sit on the admin side because they are release power: an API key can publish updates to your users, and the certificate is the key material those updates are verified against.

An admin demotion takes effect immediately — the server re-checks the flag in the database on every admin action rather than trusting the session token — so revoking someone's access does not wait for their session to expire.

On Enterprise deployments this all-or-nothing split can be refined: user roles let you grant a member access to specific apps only, each with its own set of permissions, so a member can for example manage the rollouts of one app without being able to touch any other. See [User roles & permissions](/expo-open-ota/security/user-roles-and-rbac.md).

### Creating a user

From **Users**, select **Create user** and enter the person's email, an initial password, and whether the account is an admin. Passwords must be at least 8 characters long and mix an uppercase letter, a lowercase letter, a digit and a special character. The server stores only a bcrypt hash — the password itself cannot be recovered later, so pass it on to the person now; they can change it from their **Account** page afterwards.

### Safety rails

Three rules keep the dashboard from locking itself out:

* You cannot change your own admin flag.
* You cannot delete your own account.
* The last remaining admin can be neither demoted nor deleted — there is always at least one admin.

If you personally need to leave a deployment, promote someone else first, then have them remove your account.

### Changing your password

Open **Account** in the sidebar and enter your current password along with the new one. The current password is required even though you are already signed in — a session left open on a shared machine should not be enough to take the account over. In stateless mode there is no stored password to change: update `ADMIN_PASSWORD` in the environment and restart instead.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://mercure-technologies.gitbook.io/expo-open-ota/dashboard/users.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
