Skip to content

Commit 29a84db

Browse files
committed
fix: persist identity chip fallback for new passwordless-OTP users
Root cause: UserController.php:410-414 (emitOTP()) gated Session::put('user_fullname', ...) behind an existing-user check, so a not-yet-registered email never got a persisted display name - but login.js:165-167 (emitOtpAction()) already falls back to the submitted email as the chip's display name in live client state. This asymmetry made the identity chip visible right after opting into OTP, then vanish entirely on a page refresh. Moves the user_fullname Session::put() outside the existing-user conditional, using the same email fallback the client already applies. user_pic/user_is_active remain conditional - confirmed login.js has no equivalent avatar fallback, so no client/server asymmetry existed there. Inverts the existing (bug-encoding) assertion in testEmitOtpForNewUserStillPersistsRefreshState rather than adding a new test - it covers the exact same code path.
1 parent 3fcd5fa commit 29a84db

2 files changed

Lines changed: 14 additions & 4 deletions

File tree

app/Http/Controllers/UserController.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,11 +404,16 @@ public function emitOTP()
404404
Session::put('flow', IAuthService::AuthenticationFlowPasswordless);
405405
Session::put('username', $username);
406406
Session::put('user_verified', true);
407+
// Mirrors login.js's emitOtpAction(), which falls back to the
408+
// submitted email as the chip's display name when there's no real
409+
// full name yet - persisting the same fallback here keeps the
410+
// identity chip (visible right after opting into OTP) from
411+
// vanishing on a refresh for a not-yet-registered email.
412+
Session::put('user_fullname', !is_null($existing_user) ? $existing_user->getFullName() : $username);
407413
Session::put('otp_length', $otp->getLength());
408414
Session::put('otp_lifetime', $otp->getLifetime());
409415
Session::put('otp_issued_at', $otp->getCreatedAt()?->getTimestamp() ?? time());
410416
if (!is_null($existing_user)) {
411-
Session::put('user_fullname', $existing_user->getFullName());
412417
Session::put('user_pic', $existing_user->getPic());
413418
Session::put('user_is_active', $existing_user->isActive() ? 1 : 0);
414419
}

tests/TwoFactorLoginFlowTest.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,9 +271,14 @@ public function testEmitOtpForNewUserStillPersistsRefreshState(): void
271271
$this->assertNotNull(Session::get('otp_issued_at'));
272272
$this->assertSame($email, Session::get('username'));
273273

274-
$this->assertNull(Session::get('user_fullname'), 'no identity to persist for a not-yet-registered user');
275-
$this->assertNull(Session::get('user_pic'));
276-
$this->assertNull(Session::get('user_is_active'));
274+
// login.js's emitOtpAction() falls back to the submitted email as the
275+
// chip's display name when there's no real full name yet (login.js:165-167) -
276+
// the persisted session state must match that same fallback, or the
277+
// identity chip (visible right after opting into OTP) vanishes on refresh
278+
// instead of being restored identically.
279+
$this->assertSame($email, Session::get('user_fullname'), 'must fall back to the submitted email, matching emitOtpAction()\'s client-side fallback');
280+
$this->assertNull(Session::get('user_pic'), 'no picture to persist for a not-yet-registered user');
281+
$this->assertNull(Session::get('user_is_active'), 'no active-status to persist for a not-yet-registered user');
277282
}
278283

279284
public function testSuccessfulPasswordlessLoginClearsOtpSessionState(): void

0 commit comments

Comments
 (0)