#293 · Sign in with a directory account: LDAP and Active Directory
- timeline-app
- pull request
- security
Closes #286. Schools and organisations with a domain can let people sign in with the accounts they already have. It is off unless `LDAP_URL` is set. ## How it works - **Sign-in page:** `/login` shows **Sign in with School account** (the text is `LDAP_LABEL`). It opens a username-and-password form that posts to `POST /api/auth/ldap`. - **Checking the password:** the server searches as its service account (`LDAP_BIND_DN`), and exactly one entry must match. It then binds as that entry's DN with the typed password. - An empty password is refused before anything is sent, since many directories treat it as an anonymous bind. - The password is never stored or logged. - Unknown names, wrong passwords and people outside the required group all get the same 401. An unreachable directory or a refused service account gets 502. - **Checks shared with password sign-in:** the CSRF and login nonce, the rate limits (per address and per directory name), and the human-verification challenge after repeated failures. - **Identity:** a directory account becomes provider `ldap` in `oc_identities`. Its subject is AD's `objectGUID` (in GUID text form), else `entryUUID`, else the lowercased DN, so renaming or moving an AD account keeps the same OpenChronology account. - **First sign-in:** `OAuth.identity` creates the account, named after the directory account when that username is free and allowed (`ana.lopez` becomes `ana-lopez`). Like a new social account, it then goes through `finishPrimary`: confirm an email, give a year of birth, and enter two-factor codes when on. No account-creation code was duplicated, so #277's terms acceptance can hook in at the same place. - **Linking:** **Account → Sign-in methods** can link a directory account within five minutes of signing in, with its own username and password (`POST /api/auth/ldap/link`; 409 if it belongs to another account). Unlinking uses the existing `identities/unlink`, which now accepts `ldap`. - **Desktop app:** signs in through **Sign in using your browser**, as with social providers. ## Configuration Every setting is documented in `docs/authentication.md` ("Directory sign-in"), `.env.example`, `compose.yml` and the packaged `openchronology.env`. Because the package's file lists them, the upgrade notice will show them as new settings. | Setting | | | --- | --- | | `LDAP_URL` | `ldaps://…`, or `ldap://…` with `LDAP_STARTTLS=1`. Plain LDAP is refused unless `LDAP_ALLOW_INSECURE=1`. | | `LDAP_TLS_CA` | PEM file for an internal certificate authority. Certificates are always verified, with TLS 1.2 as the minimum. | | `LDAP_BIND_DN` / `LDAP_BIND_PASSWORD` | The service account (both, or neither for anonymous search). | | `LDAP_BASE_DN` | Required. | | `LDAP_USER_FILTER` | Default `(&(objectClass=user)(sAMAccountName={username}))`; for OpenLDAP, `(uid={username})`. | | `LDAP_USERNAME_ATTRIBUTE` | Default `sAMAccountName`. | | `LDAP_REQUIRED_GROUP` | Membership through `memberOf`. | | `LDAP_NESTED_GROUPS` | AD's nested-group matching rule. | | `LDAP_LABEL` | The sign-in button's text. | The server refuses to start with half a configuration, a filter without `{username}`, an unreadable CA file, or plain LDAP without the explicit opt-in. ## Other changes - **Schema:** the `oc_identities_provider_check` constraint is replaced to allow `ldap`, idempotently. - **OpenAPI:** documents `POST /api/auth/ldap`, `POST /api/auth/ldap/link`, `directory` in `GET /api/session`, and a new `IdentityProvider` enum. - **Dependency:** **ldapts 9.2.0** (MIT), with its dependency strict-event-emitter-types (ISC). It is a pure-JS client, kept as a `serverExternalPackages` entry so it's traced into the standalone server packages. Its license is in `THIRD_PARTY.md` and the platform's `THIRD_PARTY.txt`. - **Lockfiles:** all three are updated. The pnpm and yarn ones were checked with `--frozen-lockfile` and `--immutable`. - **Not built:** schools on Microsoft 365 / Entra ID have no LDAP to point at. They need a Microsoft OIDC provi…
Sources
Open #293 · Sign in with a directory account: LDAP and Active Directory in the timeline