feat(session/v2): user password lockout error response (#9233)

# Which Problems Are Solved

Adds `failed attempts` field to the grpc response when a user enters
wrong password when logging in

FYI:

this only covers the senario above; other senarios where this is not
applied are:
SetPasswordWithVerifyCode
setPassword
ChangPassword
setPasswordWithPermission

# How the Problems Are Solved 

Created new grpc message `CredentialsCheckError` -
`proto/zitadel/message.proto` to include `failed_attempts` field.

Had to create a new package -
`github.com/zitadel/zitadel/internal/command/errors` to resolve cycle
dependency between `github.com/zitadel/zitadel/internal/command` and
`github.com/zitadel/zitadel/internal/command`.

# Additional Changes

- none

# Additional Context

- Closes https://github.com/zitadel/zitadel/issues/9198

---------

Co-authored-by: Iraq Jaber <IraqJaber@gmail.com>
This commit is contained in:
kkrime
2025-01-29 10:29:00 +00:00
committed by GitHub
parent 21f00c1e6b
commit 5eeff97ffe
6 changed files with 118 additions and 11 deletions

View File

@@ -2,11 +2,16 @@ package gerrors
import (
"errors"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/protoadapt"
commandErrors "github.com/zitadel/zitadel/internal/command/errors"
"github.com/zitadel/zitadel/internal/zerrors"
"github.com/zitadel/zitadel/pkg/grpc/message"
)
func TestCaosToGRPCError(t *testing.T) {
@@ -43,6 +48,54 @@ func TestCaosToGRPCError(t *testing.T) {
}
}
func Test_getErrorInfo(t *testing.T) {
tests := []struct {
name string
id string
key string
err error
result protoadapt.MessageV1
}{
{
name: "parent error nil, return message.ErrorDetail{}",
id: "id",
key: "key",
result: &message.ErrorDetail{Id: "id", Message: "key"},
},
{
name: "parent error nil not commandErrors.WrongPasswordError{}, return message.ErrorDetail{}",
id: "id",
key: "key",
err: fmt.Errorf("normal error not commandErrors.WrongPasswordError{}"),
result: &message.ErrorDetail{Id: "id", Message: "key"},
},
{
name: "parent error not nil type commandErrors.WrongPasswordError{}, return message.CredentialsCheckError{}",
id: "id",
key: "key",
err: &commandErrors.WrongPasswordError{FailedAttempts: 22},
result: &message.CredentialsCheckError{Id: "id", Message: "key", FailedAttempts: 22},
},
{
name: "parent error not nil wrapped commandErrors.WrongPasswordError{}, return message.CredentialsCheckError{}",
id: "id",
key: "key",
err: func() error {
err := fmt.Errorf("normal error")
wpa := &commandErrors.WrongPasswordError{FailedAttempts: 26}
return fmt.Errorf("%w: %w", err, wpa)
}(),
result: &message.CredentialsCheckError{Id: "id", Message: "key", FailedAttempts: 26},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
errorInfo := getErrorInfo(tt.id, tt.key, tt.err)
assert.Equal(t, tt.result, errorInfo)
})
}
}
func Test_Extract(t *testing.T) {
type args struct {
err error