mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
The Login V1 MFA init/enrollment handlers acted on authReq.UserID via setUserContext without verifying the auth request had completed its first factor. A request that only submitted a loginname could enroll TOTP/SMS/Email/U2F factors and overwrite the victim's phone number, and the discrepant errors bypassed IgnoreUnknownUsernames. Gate the enrollment handlers on PossibleSteps[0] == *domain.MFAPromptStep, which nextSteps only yields after firstFactorChecked passes. Co-authored-by: Claude Opus 4.8 <[email protected]>
76 lines
1.9 KiB
Go
76 lines
1.9 KiB
Go
package login
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
)
|
|
|
|
// Test_isMFAPromptStep guards the fix for the unauthenticated MFA-enrollment / phone-overwrite
|
|
// issue: MFA init/enrollment handlers must only proceed when the auth request has legitimately
|
|
// reached the MFA prompt step (i.e. after the first factor was checked). A request that only
|
|
// submitted a loginname carries a first-factor step, not an *domain.MFAPromptStep.
|
|
func Test_isMFAPromptStep(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
authReq *domain.AuthRequest
|
|
want bool
|
|
}{
|
|
{
|
|
name: "nil auth request",
|
|
authReq: nil,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "no possible steps",
|
|
authReq: &domain.AuthRequest{},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "loginname only - first factor (password) step",
|
|
authReq: &domain.AuthRequest{
|
|
PossibleSteps: []domain.NextStep{&domain.PasswordStep{}},
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "existing factor - MFA verification step, not enrollment",
|
|
authReq: &domain.AuthRequest{
|
|
PossibleSteps: []domain.NextStep{
|
|
&domain.MFAVerificationStep{MFAProviders: []domain.MFAType{domain.MFATypeTOTP}},
|
|
},
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "legitimate enrollment - MFA prompt step",
|
|
authReq: &domain.AuthRequest{
|
|
PossibleSteps: []domain.NextStep{
|
|
&domain.MFAPromptStep{Required: true, MFAProviders: []domain.MFAType{domain.MFATypeTOTP}},
|
|
},
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "MFA prompt step not first - only the current step counts",
|
|
authReq: &domain.AuthRequest{
|
|
PossibleSteps: []domain.NextStep{
|
|
&domain.PasswordStep{},
|
|
&domain.MFAPromptStep{Required: true, MFAProviders: []domain.MFAType{domain.MFATypeTOTP}},
|
|
},
|
|
},
|
|
want: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.Equal(t, tc.want, isMFAPromptStep(tc.authReq))
|
|
})
|
|
}
|
|
} |