Skip to content

Stop accepting a code once the address changes (3.3) - #217

Open
nursoda wants to merge 2 commits into
release/3.3from
security/drop-the-code-when-the-address-changes-3.3
Open

Stop accepting a code once the address changes (3.3)#217
nursoda wants to merge 2 commits into
release/3.3from
security/drop-the-code-when-the-address-changes-3.3

Conversation

@nursoda

@nursoda nursoda commented Aug 26, 2026

Copy link
Copy Markdown
Member

The 3.3 twin of #214. Same change, same reasoning; this text
repeats what matters so the pull request stands on its own.

A one-time code is mailed to whatever address the account carries at that moment.
If the address then changes, the mailbox holding that code may belong to someone
else — and until now the code stayed valid, so whoever held it could finish the
login. Changing or clearing the address now drops any pending code.

The app listens for Nextcloud's UserChangedEvent, which every documented way of
writing the address emits. An unchanged value is ignored so that rewriting the same
address cannot break a login in progress; that comparison casts both sides to
string, because for an address that was never set the emitter compares against
null, and clearing an already empty address would otherwise drop a code that is
still deliverable through the account's primary address.

Carried along is a database write that was never needed: every login challenge asks
for the current code, and a user who has none reads a timestamp of 0, which always
counts as expired — so two DELETE statements went out for rows that do not exist.
They are skipped now, while a timestamp left behind without a code is still removed,
because the cleanup command finds such users by their timestamp alone.

What is deliberately missing here: the smoke test. This line has no
tests/smoke/, so the end-to-end proof exists only on the main line.

Checked

  • unit tests per path, php-cs-fixer clean
  • psalm clean — on this line it needs php-legacy, because Psalm 6.8.2 refuses to
    run on PHP 8.5

🤖 Generated with Claude Code, verified, tweaked and approved by @nursoda.

@nursoda nursoda added security Related to a security vulnerability php Pull requests that update php code labels Aug 26, 2026
@nursoda nursoda self-assigned this Aug 26, 2026
@nursoda
nursoda requested a review from seyfahni August 26, 2026 18:20
@codecov

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

nursoda and others added 2 commits August 26, 2026 20:27
A code stays valid for minutes, and it was mailed to the address in force when
it was issued. Nothing dropped it when that address changed, so the code stayed
acceptable while the mailbox holding it had already moved on — correcting a
typo is enough for it to belong to someone else, and a group subadmin or a
directory sync can make that change. The window is the remaining validity of
the code, and whoever holds the old mailbox needs the password as well, so this
is narrow rather than urgent.

A listener on the event EMailDeleted already uses now drops the stored code
whenever the address for delivery changes, clearing included. The user gets a
new code at the new address on the next attempt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Olav Seyfarth <olav@seyfarth.de>
Every login challenge asks CodeStorage for the current code, and a user who has
none reads a timestamp of 0 — which always counts as expired, so readCode()
called deleteCode(), which issued two DELETE statements against oc_preferences
for rows that do not exist. Nextcloud's deleteUserConfig() always sends the
statement; it does not check its own cache first. The new listener makes this
weigh more: a directory sync that touches many addresses now runs it per account,
including accounts that never used this app.

The deletes are skipped when neither key holds anything. A timestamp without a
code still has to go, because deleteExpired() finds users by their timestamp
alone and would otherwise count a removal that never happened.

One existing test claimed to cover an expired code but described a user who never
had one: a timestamp of 0 with no code stored. It now sets a real stored code with
an hour-old timestamp, which is what its name says.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Olav Seyfarth <olav@seyfarth.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

php Pull requests that update php code security Related to a security vulnerability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant