Email & password

Email and password sign-in for self-hosted Proliferate.

Email and password is the default sign-in method for self-hosted Proliferate. It is on out of the box and needs no OAuth app and no identity provider: you claim the server in a browser, invite your team from the desktop app, and everyone signs in with an email and a password.

It works because self-hosted servers run in single-org mode (SINGLE_ORG_MODE defaults to true whenever PROLIFERATE_TELEMETRY_MODE is not hosted_product, so no configuration is needed): every account belongs to the one instance organization, and an invitation to that organization doubles as permission to register.

How the desktop app picks it

There is nothing to enable. On launch the desktop app probes GET /auth/desktop/methods; the email and password form is the default sign-in screen whenever GitHub OAuth is not configured on the server.

curl -fsS https://proliferate.company.com/auth/desktop/methods
# {"password_login": true, "github": false}

Screenshot: Desktop sign-in screen, email and password form against a self-hosted server

The desktop sign-in screen showing the email/password form served when GitHub OAuth is not configured.

The first account: claim the server

A fresh install has no accounts and no open signup. ./bootstrap.sh prints a one-time setup token and a claim URL when it finishes:

First-run setup is pending. Claim this instance in a browser:

  Setup token: 7f3a...c91b
  Claim URL:   https://proliferate.company.com/setup

Open https://<site>/setup in a browser and enter an email, a password, an optional organization name (blank derives a name from your email domain; the derived default shows as the placeholder), and the setup token. That account becomes the owner of the instance organization. The /setup page returns 404 forever after the claim; there is no open registration for anyone else. The full bootstrap walkthrough is on the Docker page.

Screenshot: the /setup claim page with email, password, optional organization name, and setup token fields

The server-rendered first-run claim form in a browser, with the derived organization name shown as the placeholder.

Info:

The owner is an ordinary organization owner, not a permanent superuser: the account can be promoted, demoted, or deleted like any other, within the invariants below (the organization always keeps at least one admin).

Invite the rest of the team

Proliferate uses an invite-as-allowlist model: an invitation allowlists an email address, and the invited person creates their own account with their own password. Registering takes proof of the invitation, not just knowledge of an invited email: the request must carry the invitation token from the invite link. Nobody ever sets a password for someone else, and revoking an invitation closes registration for that email again.

In the desktop app, open Settings, then Members, and invite the person's email address. Every pending invitation in the list has a Copy invite link action that copies a link to your server's registration page with the token and email prefilled:

https://proliferate.company.com/register?token=<invitation-id>&email=<email>

Share that link with the person. Invitations work without any email provider configured; if you set the optional RESEND_API_KEY (and RESEND_FROM_EMAIL) variables, the server emails the same link through Resend for you.

Screenshot: Desktop settings, Members pane with a pending invitation and its Copy invite link action

The organization members settings pane showing a pending invitation with the Copy invite link menu item.

Info:

Inviting the same email again replaces the pending invitation with a fresh token, so previously shared links stop working. Copy the new link and share it again.

The invitee registers

The invited person opens the link in any browser. The server renders a plain registration form with their email and invitation token prefilled; they choose a password and submit. The same thing is available as an API call to POST /auth/password/register, which requires all three fields:

curl -fsS https://proliferate.company.com/auth/password/register \
  -H 'Content-Type: application/json' \
  -d '{"email": "dev@company.com", "password": "a-strong-password", "invitationToken": "<invitation-id>"}'
# 201 {"email": "dev@company.com", "organizationName": "Company"}

An unknown, revoked, expired, or mismatched token gets one uniform 403, on purpose: responses never reveal whether an email is invited. An email that already has an account gets a 409 and should sign in instead.

Sign in

The new user points the desktop app at your server (Connect the desktop app) and signs in with the email and password they just registered. They land in the instance organization with the role they were invited with, so inviting someone as an admin makes them an admin from their first sign-in; admins adjust roles in the Members pane afterwards.

Restrict registration to your domains

ALLOWED_EMAIL_DOMAINS is an optional, comma-separated list of email domains that invited users must belong to before they can self-register:

# .env.static
ALLOWED_EMAIL_DOMAINS=corp.example.com,subsidiary.example.com

It is a gate on top of invitations, never a grant: nobody registers without an invitation, and the list assigns no roles. Leaving it empty means invited emails at any domain may register.

Removing a member

Removal is durable. When an admin removes someone in the Members pane, that person stays removed: signing in again, through any method including SSO, never silently reactivates the membership. They get a clear 403 instead.

The one deliberate exception is ADMIN_EMAILS: emails on that list are reinstated as admin at their next sign-in, because that is the lockout recovery path (below). So offboarding someone who is listed takes two steps: remove them from the organization, and remove them from ADMIN_EMAILS and restart the server. Also revoke any pending invitation for their email, since a live invitation would let them register again.

ADMIN_EMAILS: the admin floor

ADMIN_EMAILS is a comma-separated list of emails that must always hold at least the admin role in the instance organization:

# .env.static
ADMIN_EMAILS=ops@corp.example.com,pablo@corp.example.com

Its semantics are deliberately strong:

  • The floor is asserted at account creation and again at every sign-in, whichever sign-in method the user came in through.
  • It is a floor, not a ceiling: removing an email from the list never demotes anyone. Day-to-day role management happens in the product.
  • A listed user cannot be demoted below admin while listed, and the instance organization must always keep at least one active admin. Both invariants are enforced server-side.
  • A listed user who was removed from the organization is reinstated as admin at their next sign-in. That is deliberate (it is the lockout recovery path), and it means offboarding a listed user requires removing them from ADMIN_EMAILS too. See Removing a member.

Lockout recovery

Because the floor is asserted at every sign-in, ADMIN_EMAILS is also the recovery path when in-product role management has locked every admin out:

Add the email

Add your email to ADMIN_EMAILS in .env.static on the server host.

Restart the server

Apply the change by running ./update.sh in the deploy bundle (or restart the stack with your usual Compose commands).

Sign in

Sign in from the desktop app. The server asserts the floor during login: you hold at least the admin role again, and if your membership in the organization had been removed entirely, it is reinstated at admin.

Disabling password sign-in

PASSWORD_AUTH_ENABLED is the kill switch for the whole password surface. It defaults to true; setting it to false disables password login, credential management, and account creation in one move: the /register page and POST /auth/password/register return 404, exactly like password login itself. Use it when your install runs OIDC SSO or GitHub sign-in and you want no password path at all. Weigh it against the break-glass advice on the SSO page: with password auth off, a broken identity provider has no password fallback.

Coexistence with other methods

Email and password sign-in coexists with the other methods. If you later enable GitHub sign-in, a GitHub account whose email matches an existing password account is linked to it rather than creating a duplicate. Note that OAuth and SSO sign-ins are not gated by the invitation allowlist; see the method pages for who each one admits.