Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
054f567
docs: ADR 0014 push sign-in approval, accepted; plan M14
pierrejochem Nov 8, 2026
9ee5173
feat(mfa): [mfa] settings and AppState wiring (ADR 0014)
pierrejochem Nov 8, 2026
2af311b
feat(mfa): schema and repository (ADR 0014)
pierrejochem Nov 8, 2026
4c63708
feat(mfa): key attestation verification (ADR 0014)
pierrejochem Nov 8, 2026
00e7364
chore: ignore .openidea folder
pierrejochem Nov 8, 2026
f697bd7
feat(mfa): device enrollment
pierrejochem Nov 9, 2026
66bfdc3
feat(mfa): device request authentication
pierrejochem Nov 9, 2026
3341cd0
feat(mfa): challenges with number matching
pierrejochem Nov 9, 2026
e0addc6
feat(mfa): device management and audit view
pierrejochem Nov 9, 2026
c97b2c0
feat(mfa): device HTTP API
pierrejochem Nov 9, 2026
c92be81
deps: update zstd to 0.13.4 and zstd-safe to 7.4.0
pierrejochem Nov 10, 2026
e9aff3f
ci: tolerate litmus put_no_parent deviation
pierrejochem Nov 10, 2026
f641103
test(mfa): allow one second of clock skew in the lifetime test
pierrejochem Nov 10, 2026
048be57
docs(mfa): changelog, security, admin, state
pierrejochem Nov 10, 2026
2fbb20f
feat(mfa): an enrolled phone is a second factor
pierrejochem Nov 10, 2026
ceef962
build(mfa): pin rustmfa-contract by git revision
pierrejochem Nov 10, 2026
f0ee1f5
build(mfa): license the contract, accept RUSTSEC-2026-0387 for it
pierrejochem Nov 10, 2026
611e06c
feat(mfa): bind a challenge to one sign-in and release it once
pierrejochem Nov 10, 2026
3411813
feat(mfa): approve a browser sign-in on the phone
pierrejochem Nov 10, 2026
165394d
feat(mfa): approve a Login Flow v2 sign-in on the phone
pierrejochem Nov 10, 2026
03fd311
feat(mfa): enroll and revoke phones in the web interface
pierrejochem Nov 10, 2026
f4fa7fd
feat(mfa): FCM push adapter
pierrejochem Nov 10, 2026
10322b8
feat(mfa): phone blocks on the lockouts page
pierrejochem Nov 10, 2026
c35a8a0
fix(mfa): recovery of phone-only users, stronger checks on the only f…
pierrejochem Nov 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ graphify-out/
scripts/verify/node_modules/
scripts/verify/out/
.playwright-mcp/
.openidea/
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,32 @@ All notable changes are listed here. The format follows [Keep a Changelog](https

## [Unreleased]

### Changed

- **An enrolled RustMFA phone counts as a second factor**, like an authenticator app (ADR 0014). Once a user has an active phone, WebDAV and OCS no longer accept their account password (app passwords still work) and the browser sign-in asks for a second step.

### Fixed

- **Recovery of a user whose only second factor is a phone**: `reset-2fa` (command line and administration page) now revokes the user's RustMFA phones too, and the administration page sees a phone as a second factor. Before, such a user who lost the phone could not sign in and nobody could help.
- **Adding or removing the only second factor from the web needs more than a session**: enrolling a phone asks for a current authenticator code as well when codes are on, and revoking the phone that is the only second factor asks for the password.
- **Reloading the number page does not start a second approval**: the open approval of a sign-in is shown again (browser and Login Flow v2), so the phone gets one prompt.

### Added

- **Phone blocks on the lockouts page**: the rate-limit blocks of the phone features (enrollment per address and per user, signed requests per phone, sign-in approvals per user) are listed on Administration › Security and can be lifted there.
- **Push delivery through FCM** (`[mfa.fcm]`: `project_id`, `service_account_file`): when a sign-in approval waits, Rostdav wakes the phone with a data message that holds the challenge id only. Off unless both keys are set; a bad service-account file stops the server at start-up. A failed push is logged and never blocks the sign-in.
- **Phones in the web interface**: Devices & security lists the user's RustMFA phones (name, status, last seen) with revoke, and the last security events. "Add a phone" asks for the account password again and shows a single-use enrollment QR code (`rustmfa://enroll?server=…&token=…`). Five codes per user in 15 minutes. Revoking a phone ends its open sign-in approvals.
- **Sign in with the RustMFA phone in the browser**: the second step offers "Send to my phone" (next to the code form when authenticator codes are on). The page shows a number, the user picks it on the phone, and the page signs in once the phone approved. A denied or expired request goes back to the second step. The Login Flow v2 page (connecting a Nextcloud app) has the same step.
- **A sign-in approval belongs to one sign-in**: a challenge started for a sign-in can only be polled and turned into a session by the browser that passed the password step, and only once; a phone revoked after it approved releases nothing. Not reachable from the sign-in page yet.
- **Settings for push sign-in approval** (`[mfa]` section: challenge lifetime, enrollment lifetime, audience, attestation policy), the first step of ADR 0014. They have no effect yet.
- **Database tables for push sign-in approval** (migration 0013: phones, enrollment tokens, challenges, request nonces). Nothing uses them yet.
- **Key attestation check for push sign-in approval phones**: verifies that a phone's key lives in secure hardware (certificate chain to a pinned Google root, hardware security level, challenge, key match). Nothing calls it yet.
- **Enrollment service for the RustMFA app**: a signed-in user creates a single-use token (QR code), the phone registers its two public keys with the token and an attestation. Wrong tokens and failed attestations are rate limited per address. Not reachable over HTTP yet.
- **Request authentication for the RustMFA app**: checks the signed `Authorization: RustMFA-Sig` header of a phone (timestamp within 60 seconds, signature by the request key, nonce used once in 5 minutes, phone and account still active). Five failures for one phone in 15 minutes block it for 15 minutes. Not reachable over HTTP yet.
- **Sign-in challenges for the RustMFA app**: the server can start an approval for one phone (a two-digit number, two decoys, 90 seconds by default), list pending ones, and accept an approve or deny signed with the phone's biometric key. A wrong number denies the challenge. Only one answer can win. Five challenges per user in 15 minutes. Push delivery is a stub until the sign-in page work; not reachable over HTTP yet.
- **Phone management for the RustMFA app**: list, revoke (this also ends the phone's open sign-in approvals), change the push token, rotate both keys (the new approval key needs a fresh attestation), and a view of the user's own security events. A revoked phone can no longer approve, even an answer that was already in flight. Not reachable over HTTP yet.
- **HTTP API for the RustMFA app** (`/api/v1/devices/…`, `/api/v1/mfa/challenges/…`, `/api/v1/audit`): phones register and sign their requests; they list and revoke phones, change their push token, rotate keys, fetch and answer sign-in approvals, and read their own security events. Requests are signed (no cookie, no CSRF token), bodies are limited to 64 KiB, and errors follow the contract. Creating an enrollment token and the sign-in page integration follow separately.

## [0.10.0] - 2026-10-25

### Changed
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ The binding working rules are in [AGENTS.md](AGENTS.md). The full project instru
- M2 (WebDAV core) complete. M3: server side recorded (Nextcloud 35.0.2, synthetic requests), real-app capture open. M4 (sign-in) implemented; ADR 0003 (app passwords, Login Flow v2, rate limiting) is accepted (2026-10-23). M5 (Nextcloud WebDAV extensions, chunked upload v2) implemented. M7 (admin CLI, event log, fsck, import) implemented. M6 (real app on the Android emulator) is postponed to the end by the project owner. M8 (versioning), M9 (shares, ADR 0004 accepted) and M10 (trash, ADR 0005) implemented. M11 (web interface, ADR 0006 accepted): slice 11a implemented (sign-in, Files, Trash, Devices, Settings, public link page). 11b implemented (share dialog, versions panel, details, folder filter). Chunked browser upload done (JS not yet tested in a real browser). 11c implemented (administration pages). 11d done (profile, change password, data export, German/English). M11 complete. M12 (two-factor, ADR 0007 accepted) implemented. M13 (retention overview, `default_quota`, `upload_retention_hours`, streamed data export) done. Phase 5 done except coverage-guided fuzzing (authorization matrix, robustness tests, load tests, container image + Compose, backup/restore, operations docs). M6 complete for now (2026-10-23): verified with the real Android app (sign-in incl. 2FA, files, favorites, shares, public link, versions, trash, offline) and the desktop sync engine (`nextcloudcmd`); not tried: desktop GUI, iOS client. ADR 0003 accepted; cargo-fuzz targets in `fuzz/`. Web interface gaps from the mockups closed (maintenance mode, lockouts, shares, recent, sortable columns); left out: avatar upload, editable admin limits.

- About page, release notes, opt-in version check and the updater (ADR 0009) are built. The updater needs the maintainer's minisign key (`CONTRIBUTING.md`, "Release signing key"): until `crates/rostdav-app/update-key.pub` holds a key, it reports "no update signing key". The signing step of the release workflow has not run yet. Documentation site in `site/` (GitHub Pages, `just site`).
- M14 (push sign-in approval with the RustMFA app, ADR 0014 accepted 2026-11-08): backend implemented on branch `feat/push-mfa` (`[mfa]` settings, migration 0013, attestation, enrollment, request authentication, challenges, device management, HTTP API in `rostdav-mfa-api`). Plan 2b implemented: phone as second factor, browser and Login Flow v2 phone step, phone enrollment and revoke pages, FCM adapter (`[mfa.fcm]`). The admin lockouts page lists the MFA rate-limit blocks. Open: the Android app (Plan 3). `rustmfa-contract` is a git dependency pinned by revision (repository `pierrejochem/rostdav-mfa`); a contract change needs a commit there and a new pin here.

## Releases

Expand Down
Loading
Loading