test(webauthn): check a fresh assertion still succeeds after each refusal case
- commit
- 633b248e5e36cde966ad6c43d093e1bdea55f236
- author
- brice · · 2026-10-12 15:46 UTC
- parent
- 24ccd67
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
1 file · +13 -0
M internal/adapters/webauthn/webauthn_test.go +13 -0
| @@ -5,6 +5,7 @@ package webauthn_test | |||
| 5 | 5 | import ( | |
| 6 | 6 | "encoding/json" | |
| 7 | 7 | "errors" | |
| 8 | + | "maps" | |
| 8 | 9 | "testing" | |
| 9 | 10 | "time" | |
| 10 | 11 | ||
| @@ -274,10 +275,22 @@ func TestLoginRefused(t *testing.T) { | |||
| 274 | 275 | t.Run(tc.name, func(t *testing.T) { | |
| 275 | 276 | t.Parallel() | |
| 276 | 277 | d := register(t, v, s, virtualwebauthn.AuthenticatorOptions{}) | |
| 278 | + | opts := d.auth.Options | |
| 279 | + | stored := maps.Clone(d.store) | |
| 277 | 280 | err := tc.run(t, d) | |
| 278 | 281 | require.Error(t, err) | |
| 279 | 282 | assert.ErrorIs(t, err, domain.ErrUnauthenticated) | |
| 280 | 283 | assert.NotErrorIs(t, err, domain.ErrPasskeyCloned) | |
| 284 | + | ||
| 285 | + | // Control: the refusal was for the reason of the case. With the device | |
| 286 | + | // and the store as they were, a fresh assertion still succeeds, so the | |
| 287 | + | // case did not fail by something that breaks every login. | |
| 288 | + | d.auth.Options = opts | |
| 289 | + | d.store = stored | |
| 290 | + | ceremony, options := d.assertion(t, v, s) | |
| 291 | + | got, err := v.FinishLogin(userHandle, ceremony, d.respond(s, options), d.lookup) | |
| 292 | + | require.NoError(t, err, "a fresh, untouched assertion must succeed") | |
| 293 | + | assert.Equal(t, d.cred.ID, got.ID) | |
| 281 | 294 | }) | |
| 282 | 295 | } | |
| 283 | 296 | } | |
| @@ -5,6 +5,7 @@ package webauthn_test | |||||
| 5 | import ( | 5 | import ( | ||
| 6 | "encoding/json" | 6 | "encoding/json" | ||
| 7 | "errors" | 7 | "errors" | ||
| 8 | + | "maps" | |||
| 8 | "testing" | 9 | "testing" | ||
| 9 | "time" | 10 | "time" | ||
| 10 | 11 | ||||
| @@ -274,10 +275,22 @@ func TestLoginRefused(t *testing.T) { | |||||
| 274 | t.Run(tc.name, func(t *testing.T) { | 275 | t.Run(tc.name, func(t *testing.T) { | ||
| 275 | t.Parallel() | 276 | t.Parallel() | ||
| 276 | d := register(t, v, s, virtualwebauthn.AuthenticatorOptions{}) | 277 | d := register(t, v, s, virtualwebauthn.AuthenticatorOptions{}) | ||
| 278 | + | opts := d.auth.Options | |||
| 279 | + | stored := maps.Clone(d.store) | |||
| 277 | err := tc.run(t, d) | 280 | err := tc.run(t, d) | ||
| 278 | require.Error(t, err) | 281 | require.Error(t, err) | ||
| 279 | assert.ErrorIs(t, err, domain.ErrUnauthenticated) | 282 | assert.ErrorIs(t, err, domain.ErrUnauthenticated) | ||
| 280 | assert.NotErrorIs(t, err, domain.ErrPasskeyCloned) | 283 | assert.NotErrorIs(t, err, domain.ErrPasskeyCloned) | ||
| 284 | + | ||||
| 285 | + | // Control: the refusal was for the reason of the case. With the device | |||
| 286 | + | // and the store as they were, a fresh assertion still succeeds, so the | |||
| 287 | + | // case did not fail by something that breaks every login. | |||
| 288 | + | d.auth.Options = opts | |||
| 289 | + | d.store = stored | |||
| 290 | + | ceremony, options := d.assertion(t, v, s) | |||
| 291 | + | got, err := v.FinishLogin(userHandle, ceremony, d.respond(s, options), d.lookup) | |||
| 292 | + | require.NoError(t, err, "a fresh, untouched assertion must succeed") | |||
| 293 | + | assert.Equal(t, d.cred.ID, got.ID) | |||
| 281 | }) | 294 | }) | ||
| 282 | } | 295 | } | ||
| 283 | } | 296 | } | ||