Skip to content

Commit 4d1a87b

Browse files
committed
fix: skip empty optional fields in create_user
The form submits empty strings for unfilled optional schema fields (e.g. givenName, dateOfBirth on the register step). The user_attributes DB check constraint rejects empty values, so submit returns 500 and the register flow stalls before passkey-upsell. Skip empty-string values when building attributes, matching how the flow field validator treats them as "no value provided".
1 parent 73b0c97 commit 4d1a87b

2 files changed

Lines changed: 80 additions & 0 deletions

File tree

internal/domain/flow_on_success_create_user.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ func (h *FlowCreateUserHandler) Handle(ctx context.Context, client database.Quer
5757
if !known || field.Challenge == FlowFieldChallengePassword {
5858
continue
5959
}
60+
if s, ok := value.(string); ok && s == "" {
61+
continue
62+
}
6063
uniqueScope := attributeUniquenessFor(name, identifierName, field.Unique)
6164
attr, err := NewCreateAttribute(name, value, uniqueScope)
6265
if err != nil {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package domain_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
10+
"github.com/zitadel/nextgen/internal/domain"
11+
"github.com/zitadel/nextgen/internal/storage/database"
12+
)
13+
14+
type fixedIDGenerator struct{ id string }
15+
16+
func (f fixedIDGenerator) New(string) (string, error) { return f.id, nil }
17+
18+
type recordingUserRepo struct{ created []*domain.CreateUser }
19+
20+
func (r *recordingUserRepo) Create(_ context.Context, _ database.QueryExecutor, u *domain.CreateUser) error {
21+
r.created = append(r.created, u)
22+
return nil
23+
}
24+
25+
type recordingUserPasswordRepo struct{ created []*domain.CreateUserPassword }
26+
27+
func (r *recordingUserPasswordRepo) Create(_ context.Context, _ database.QueryExecutor, p *domain.CreateUserPassword) error {
28+
r.created = append(r.created, p)
29+
return nil
30+
}
31+
32+
type prefixHasher struct{}
33+
34+
func (prefixHasher) Hash(plain string) (string, error) { return "h:" + plain, nil }
35+
36+
// Empty-string values from optional fields the user left blank must be
37+
// dropped before insert. The user_attributes DB check constraint rejects
38+
// "" values, so submitting a register step with unfilled optional inputs
39+
// would otherwise 500.
40+
func TestFlowCreateUserHandler_Handle_SkipsEmptyOptionalAttributes(t *testing.T) {
41+
users := &recordingUserRepo{}
42+
pws := &recordingUserPasswordRepo{}
43+
h := domain.NewFlowCreateUserHandler(fixedIDGenerator{id: "user_TEST"}, users, pws, prefixHasher{})
44+
45+
resolved := domain.FlowResolvedFields{
46+
Fields: map[string]domain.FlowField{
47+
"email": {Challenge: domain.FlowFieldChallengeIdentifier},
48+
"givenName": {},
49+
"familyName": {},
50+
"password": {Challenge: domain.FlowFieldChallengePassword},
51+
},
52+
}
53+
54+
res, err := h.Handle(t.Context(), nil, domain.FlowOnSuccessInput{
55+
ProjectID: "proj_X",
56+
UserSchemaURL: "https://example.test/schema.json",
57+
Resolved: resolved,
58+
State: &domain.FlowState{FlowProgress: domain.FlowProgress{CollectedData: map[string]any{
59+
"email": "alice@example.com",
60+
"givenName": "Alice",
61+
"familyName": "",
62+
"password": "secret-pass",
63+
}}},
64+
})
65+
require.NoError(t, err)
66+
assert.Equal(t, "user_TEST", res.UserID)
67+
68+
require.Len(t, users.created, 1)
69+
got := make(map[string]any, len(users.created[0].Attributes))
70+
for _, a := range users.created[0].Attributes {
71+
got[a.Key] = a.Value
72+
}
73+
assert.Equal(t, map[string]any{
74+
"email": "alice@example.com",
75+
"givenName": "Alice",
76+
}, got)
77+
}

0 commit comments

Comments
 (0)