timescale.info
Sign inSign up

How OpenChronology was built

Every merged pull request of openchronology/timeline-app, its first week of commits and its releases; and before them the 2020 PureScript editor, from snapshots of its deleted repositories in the Wayback Machine. Written to this server through the HTTP API.

327 moments · 2020-03-09T00:15:08Z CE – 2026-11-02T20:11:21Z CE · by @athan · @athan

#406 · Limit two-factor answers per account; keep directory accounts under the directory

  • timeline-app
  • pull request

## Two-factor step: a limit per account Each sign-in's challenge already closes after five wrong answers. Wrong answers now also count per account across challenges: - **Limit:** 10 wrong answers in a 15-minute window that starts at the first one. The count is a row in `oc_auth_attempts`, like the existing sign-in failure counters in `challenge.mjs`. It is updated inside the transaction that holds the account lock, so concurrent answers are counted one at a time. - **Once reached:** authenticator and recovery codes are refused with 429 until the window ends. They are not checked, so a recovery code is not used up. - **Passkeys:** a passkey is still checked during that time, because its answer is a signature and cannot be guessed. The owner can still sign in while someone else is entering wrong codes. Wrong passkey answers count like wrong codes. - **Email:** reaching the limit emails the account (`AccountSecurity.notify`), at most once a day. A second `oc_auth_attempts` row lasting a day records that the email was sent. - **Reset:** changing or resetting the password clears the count. ## Directory-only accounts An account made by a directory (LDAP/AD) sign-in has no password, and `ldap` is its only identity. It can no longer gain a way to sign in that doesn't go through the directory. These are now refused with 403: - setting a password (`POST /api/auth/password/change`); - linking another provider (`POST /api/auth/{provider}/start` with `link: true`, checked again when the provider returns, under the account lock); - signing in with a passkey alone. Its passkeys still answer the two-factor step. - **Forgot password:** it already sent such an account nothing, because the account has no password. It now says so explicitly and still gives the same generic answer. As a second check, `reset` refuses a reset link for a directory-only account. - **Earlier accounts:** accounts that had a password or another provider before they linked a directory account keep those, as the docs describe. - **Account page:** `GET /api/auth/account` reports the new state as `security.directory_only`, and the account page hides the password and provider-link options for those accounts. I refused new sign-in methods rather than checking the directory on every sign-in. Directory identities are stored like social identities, `docs/authentication.md` describes them that way, and a check on every sign-in would depend on the directory being reachable for sign-ins that don't otherwise use it. ## Releasing an account from the directory A directory-only account is stranded when directory sign-in is turned off, or when an operator moves the person off the directory. In **/admin → Users**, such an account is marked **Signs in only through the directory** and offers **Release from directory**. The API is `release_directory: true` on `PATCH /api/admin/users/{id}`, and it needs the usual confirmation (password plus a code or passkey). The release: - unlinks the account's `ldap` identity, so it is no longer directory-only; - ends its sessions, sign-in challenges and pending desktop sign-ins (`endAccess`), and revokes its API keys; - emails a password-reset link to its confirmed address; - is recorded in the audit as `{release_directory, reset_link_sent}`. The release is never automatic, so a directory that is unconfigured by mistake doesn't release anyone. It returns 409 for any other kind of account. If the person signs in through the directory again afterwards, that creates a new account. The user list now reports `directory_only`. `forgot` now also sends a link to an account with neither a password nor a linked sign-in, which is what a released account is. It still sends nothing to accounts that sign in through a provider or the directory, and the answer is the same in every case. I didn't add a command-line equivalent. A directory-only administrator who is locked out runs the existing `openchronology-reset-admin-password`. That gives the account a password, so it…

Sources

Open #406 · Limit two-factor answers per account; keep directory accounts under the directory in the timeline