diff --git a/internal/api/ui/login/integration_test/external_login_test.go b/internal/api/ui/login/integration_test/external_login_test.go index 32bbd9fc0..ed763ce9f 100644 --- a/internal/api/ui/login/integration_test/external_login_test.go +++ b/internal/api/ui/login/integration_test/external_login_test.go @@ -190,13 +190,10 @@ func TestExternalNotFoundOption_ForgedRegistration_IsRejected(t *testing.T) { assert.Contains(t, body, "LOGIN-Dju3f", "forged registration should be rejected by the LinkingUsers guard; got a page without that rejection (attack likely succeeded)") - // The load-bearing assertion: no account may exist for the attacker's forged identity. We use - // assert.Never (not Eventually) so that if the account IS created but its projection lags, the user - // still surfaces within the window and fails the test - which is exactly the unpatched behavior. - assert.Never(t, func() bool { - return userCountByEmail(t, forgedEmail) > 0 - }, 5*time.Second, 250*time.Millisecond, - "a user was created for the attacker-forged external identity %q - account pre-hijack succeeded", forgedEmail) + // No account may exist for the forged identity. Poll on the test goroutine for a window so a + // late-projected user still fails the test (assert.Never is unsafe here: its condition runs in a + // background goroutine that can outlive the test and panic on CTX cancel from TestMain). + requireNeverUserByEmail(t, forgedEmail, 5*time.Second, 250*time.Millisecond) } // --- helpers ------------------------------------------------------------------------------------ @@ -248,14 +245,22 @@ func doPostForm(t *testing.T, c *http.Client, rawURL string, form url.Values) st return string(b) } -// userCountByEmail returns how many users match the given email address (via the User v2 API). -func userCountByEmail(t *testing.T, email string) int { +// requireNeverUserByEmail fails if a user with the given email appears within waitFor. +func requireNeverUserByEmail(t *testing.T, email string, waitFor, tick time.Duration) { t.Helper() - resp, err := Instance.Client.UserV2.ListUsers(CTX, &user.ListUsersRequest{ - Queries: []*user.SearchQuery{ - {Query: &user.SearchQuery_EmailQuery{EmailQuery: &user.EmailQuery{EmailAddress: email}}}, - }, - }) - require.NoError(t, err) - return len(resp.GetResult()) + deadline := time.Now().Add(waitFor) + for { + resp, err := Instance.Client.UserV2.ListUsers(CTX, &user.ListUsersRequest{ + Queries: []*user.SearchQuery{ + {Query: &user.SearchQuery_EmailQuery{EmailQuery: &user.EmailQuery{EmailAddress: email}}}, + }, + }) + require.NoError(t, err) + require.Empty(t, resp.GetResult(), + "a user was created for the attacker-forged external identity %q - account pre-hijack succeeded", email) + if !time.Now().Before(deadline) { + return + } + time.Sleep(tick) + } }