Feature | UserController MFA Integration, Device Trust Cookie Management, Audit Wiring, and 2FA Rate Limiting#136
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
📘 OpenAPI / Swagger preview ➡️ https://OpenStackweb.github.io/openstackid/openapi/pr-136/ This page is automatically updated on each push to this PR. |
77c0c4f to
0fb9adf
Compare
|
📘 OpenAPI / Swagger preview ➡️ https://OpenStackweb.github.io/openstackid/openapi/pr-136/ This page is automatically updated on each push to this PR. |
0fb9adf to
84252f7
Compare
|
📘 OpenAPI / Swagger preview ➡️ https://OpenStackweb.github.io/openstackid/openapi/pr-136/ This page is automatically updated on each push to this PR. |
84252f7 to
56e1468
Compare
|
📘 OpenAPI / Swagger preview ➡️ https://OpenStackweb.github.io/openstackid/openapi/pr-136/ This page is automatically updated on each push to this PR. |
caseylocker
left a comment
There was a problem hiding this comment.
Reviewed against ClickUp 86ba2zc6p and SDS idp-mfa.md (§4.4 controller flow, §4.5 strategies, §4.9.1 AuthService MFA methods).
The transaction-context design rule is correctly implemented — the controller orchestrates only, all strategy DB-mutating calls go through AuthService::*MFA* wrappers, and no add(entity, true) self-flush remains. Every acceptance criterion is met on the happy path, and verifyChallenge now correctly loads the persisted OTP and compares its value.
However, five issues should be addressed before merge. All five are consequences of work first made live in this PR (the MFA strategies and loginUser() were previously unwired/dead code), so they are in scope here rather than the base PR. The first is a critical regression in the shared password-login path. Inline comments below.
Also, a few test gaps versus the ticket's TESTS list: concurrent OTP redeem, concurrent recovery-code redeem, client-bound MFA verification, recovery rate-limit blocking, and rollback/partial-failure after a successful redeem.
|
📘 OpenAPI / Swagger preview ➡️ https://OpenStackweb.github.io/openstackid/openapi/pr-136/ This page is automatically updated on each push to this PR. |
smarcet
left a comment
There was a problem hiding this comment.
@matiasperrone-exo review
|
📘 OpenAPI / Swagger preview ➡️ https://OpenStackweb.github.io/openstackid/openapi/pr-136/ This page is automatically updated on each push to this PR. |
6d66d2d to
f253e7f
Compare
|
📘 OpenAPI / Swagger preview ➡️ https://OpenStackweb.github.io/openstackid/openapi/pr-136/ This page is automatically updated on each push to this PR. |
a58c823 to
50f4ad0
Compare
… Audit Wiring, and 2FA Rate Limiting
f253e7f to
5fc47a4
Compare
|
📘 OpenAPI / Swagger preview ➡️ https://OpenStackweb.github.io/openstackid/openapi/pr-136/ This page is automatically updated on each push to this PR. |
Task:
Ref: https://app.clickup.com/t/9014802374/86ba2zc6p
What Changed
UserController(app/Http/Controllers/UserController.php)IDeviceTrustService,ITwoFactorAuditService, andITwoFactorGateServicevia constructor.MFAGateService::requiresChallenge()returnstrue, issues a challenge (viaAuthService::issueMFAChallenge) and returns{ error_code: "mfa_required", ... }— no session created yet.resolveClientFromMemento()private helper to extract the OAuth2 client from a pending session memento (deduplicates logic previously repeated in password and OTP flows).postVerify2FA(),postResend2FA(), andpostRecovery2FA()action methods to handle the full MFA step-up login lifecycle.MFACookieManagertrait for reading/writing the trusted-device cookie.MFACookieManagertrait (app/Http/Controllers/Traits/MFACookieManager.php) — new filegetCookieToken(): ?string(reads raw token from cookie).queueDeviceTrustCookie(User $user): void(callsIDeviceTrustService::trustDeviceand queues a secure, HttpOnly cookie).TwoFactorRateLimitMiddleware(app/Http/Middleware/TwoFactorRateLimitMiddleware.php) — new fileverify,recovery, andresendMFA endpoints.verify/recovery: increments only on failed response (mfa_verification_failed,mfa_invalid_recovery).resend: increments on every request.HTTP 429witherror_code: mfa_rate_limitwhen the limit is exceeded.app/Http/Kernel.phpand wired to the new routes inroutes/web.php.AuthService(app/libs/Auth/AuthService.php) andIAuthService(app/libs/Utils/Services/IAuthService.php)validateCredentials(string $username, string $password): User— validates without creating a session.loginUser(User $user, bool $remember): void— establishes the session for an already-validated user.issueMFAChallenge(User $user, MFAChallengeStrategy $strategy, ?Client $client, bool $remember): array— triggers the challenge strategy and stores pending state.DeviceTrustServiceandTwoFactorAuditService(minor fixes)config/two_factor.php— new filecookie_name,device_trust_lifetime_days, rate-limit thresholds per action.EncryptCookiesmiddlewaredevice_trust_tokencookie from encryption (raw HMAC token must be read as-is byIDeviceTrustService).Tests
tests/TwoFactorLoginFlowTest.php(new)tests/DeviceTrustServiceTest.phptests/Unit/TwoFactorAuditServiceTest.phptests/Unit/MFA/EmailOTPMFAChallengeStrategyTest.phpissueMFAChallengepath.phpunit.xml