Commit graph

2 commits

Author SHA1 Message Date
Daniel
e58aa1b996 refactor: sign-in codes and registration invitations leave; the SSO has both
Some checks failed
Forgejo Docker Build / Root app tests (push) Successful in 49s
Forgejo Docker Build / Build Docker image (push) Successful in 7s
Forgejo Docker Build / End-to-end (browser) (push) Failing after 6s
Sign-in is email → code at sso.pedshub.com, and new accounts come from an
invitation link minted there, so the app's own code emails and invite codes
recorded a path nobody can take. Gone: the login-code routes and their rate
limiters, the invite admin API and card, the invite field on the register
form, the "email me a code / use my password" choice on the sign-in screen
(an email now leads straight to the password), both utility modules, and
the invite-only setting. A migration drops login_codes and
registration_invites.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-13 06:11:00 +02:00
Daniel
a7132c218e feat: a ciphertext now only decrypts in the row it was written for
enc1 said what a value was but not where it belonged. The blob in one
user's nextcloud_token would decrypt perfectly if copied onto another
user's row, and that account's exports would then go into someone else's
storage. Nothing about the ciphertext objected.

Adds enc2, which binds a context string — 'users:nextcloud_token:41' —
as AES-GCM additional authenticated data. The auth tag covers it, so a
moved ciphertext fails to open rather than opening somewhere wrong. The
context is authenticated, not encrypted: it is not a secret, it is a
claim about location.

enc1 is still read, unchanged and forever — every existing row is enc1.
Bound rows are distinguishable (isBound), so the Nextcloud token is
rebound in place the next time it is successfully used, the same way a
legacy plaintext one was already upgraded. That gate moved from
isEncrypted to isBound; isEncrypted is true for enc1, so it would have
skipped exactly the rows that need rebinding.

Bound so far: Nextcloud app passwords, and invite codes — which bind to
their own code_hash, since unlike the row id that exists at INSERT time.
Decrypting an invite moved into list(), so the cipher and the hash that
opens it no longer leave the module together.

Mutation-tested: dropping the AAD, dropping the guard, gating on
isEncrypted, or dropping a context at a call site each fail a test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-12 21:25:24 +02:00