mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
test(login): avoid assert.Never race in forged registration check (#12548)
# Which Problems Are Solved - `TestExternalNotFoundOption_ForgedRegistration_IsRejected` flakes across unrelated PRs: the test reaches PASS, then panics with `Log in goroutine after Test... has completed` and `rpc error: code = Canceled desc = context canceled`. # How the Problems Are Solved - Replaces `assert.Never` with a synchronous poll helper (`requireNeverUserByEmail`) so `ListUsers` and `require` run on the test goroutine and finish before `TestMain` cancels `CTX`. - Keeps the same “must stay absent for a window” polarity (not `Eventually`), so a late-projected forged user still fails the test. # Additional Changes - None. # Additional Context - CI examples: [run 30980062306](https://github.com/zitadel/zitadel/actions/runs/30980062306), [run 30837302974](https://github.com/zitadel/zitadel/actions/runs/30837302974) Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user