feat(admin): challenge TOTP only when the login IP changes - #28
Conversation
Optional authenticator-app 2FA for oc-admin. The same IP signs in with password alone; a new IP, a blank last IP, or a password change must present a TOTP or backup code before the session is established. Co-authored-by: Cursor <cursoragent@cursor.com>
The TOTP CI pin expected the folklore Google-demo string JBSWY3DPEHPK3PXP, which is not the RFC 4648 encoding of Hello!. Co-authored-by: Cursor <cursoragent@cursor.com>
The decoder stripped lowercase letters as invalid, so a pasted secret with spaces or lowercase decoded to empty. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Thanks for putting this together — the TOTP implementation itself is clean, the throttle is correctly wired into the second factor under its own 1. The encryption dependency no longer exists on develop
This is easy to miss because the branch still carries its own copy of the vendor directory, so it works when you check the branch out. Test-merging into develop is what surfaces it — after the merge, 2. The secret cannot be stored in the column the migration createsThis one is independent of the above, and is currently masked by it, so fixing only the fatal will not surface it.
s_secret VARCHAR(255) NOT NULL DEFAULT ''on a utf8mb4 table. Random ciphertext is effectively never valid UTF-8, so the insert fails: On an install without strict mode this is worse than an error — the value is silently mangled on write and the admin is locked out of their own panel immediately after enrolling, with no way back in short of database surgery. Suggested fix covering both points: use 3. Migration numbering and schema driftThe new migration is Related: 4. The design premise — worth deciding on purposeThe mechanics are sound; my question is about the security property being chosen. Only one address is remembered, and Second, there is no replay tracking. Neither is a coding mistake — they follow from the feature as designed. But "TOTP only when the IP changes" is a materially weaker guarantee than what people generally assume 2FA gives them, and I would rather it be a stated, documented decision than an implicit one. Options, roughly in order of preference:
Happy to discuss — I do not want to send you rewriting things if you had a threat model in mind that I have not accounted for. Points 1 to 3 are the blocking ones. Also note the branch is currently conflicting with develop, mostly in CHANGELOG.md. |
Drop OpensslCryptor (gone on develop). Put the table in struct.sql as 0036. Keep the IP gate, document the proxy hole, and reject a reused TOTP time-step.
|
Thanks — 1 to 3 are done on the branch. Crypto / column: Migration / schema: file is Design (your point 4): I am keeping the IP gate on purpose, not as an accident. The feature we wanted is “password-only on the usual desk, code when the address moves” — not a prompt on every login. I agree that is a weaker property than people assume when they hear 2FA, so it is now spelled out in the class docblock and on Your Profile, including the Cloudflare/proxy hole: if REMOTE_ADDR is the edge, the first success stamps that address and later password theft from the same edge is never challenged. A remember-this-device cookie would be a better exemption; I did not rewrite the gate in this pass because that is a different product. Happy to do that as a follow-up if you would rather merge always-challenge + signed cookie than ship the documented IP gate. What I did add in this pass: reject a TOTP time-step that was already consumed in the window (replay inside ±1 step). Also still conflicting with develop on CHANGELOG; merge from this side is ready once you want it pushed. |
…tp-ip Keep the admin TOTP notes in the single 6.2.0 New section alongside billing and personal-data download.
|
I am keep this pending till 6.2.0 release, once that is out we will integrate this. it will also give us time to properly test this feature. |
Summary
otpauth://link, backup codes shown once). Remember-me from a new IP takes the same challenge path. Changing the password clears the stored IP so the next login must 2FA.t_admin_2fatable (migration 0029 + struct.sql). Secrets are encrypted with the install signing key. 2FA attempts use LoginThrottle under a separateadmin-2facontext. If the table is missing (upgrade not run yet), lookups fail open so the admin who has to start the upgrade is not locked out.Test plan
db:upgradecreatest_admin_2faphp tests/security-totp.php(RFC 4226 / window pins)NOTE: I have been running a heavily customized version of Osclass for 12 years volunteersbase.com - Thank you for keeping the project alive! Here is my humble contribution, hope you can find it useful :)