Skip to content

Commit c3ff5f7

Browse files
committed
Update documentation and added validation
1 parent 98554a5 commit c3ff5f7

File tree

3 files changed

+121
-11
lines changed

3 files changed

+121
-11
lines changed

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ The Firebase Admin Go SDK enables server-side (backend) applications to interact
6464

6565
- **DO:** Use the centralized HTTP client in `internal/http_client.go` for all network calls.
6666
- **DO:** Pass `context.Context` as the first argument to all functions that perform I/O or other blocking operations.
67+
- **DO:** Run `go fmt` after implementing a change and fix any linting errors.
6768
- **DON'T:** Expose types or functions from the `internal/` directory in the public API.
6869
- **DON'T:** Introduce new third-party dependencies without a strong, documented justification and team consensus.
6970

auth/user_mgt.go

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,7 +1048,7 @@ func (c *baseClient) GetUsers(
10481048
return &GetUsersResult{userRecords, notFound}, nil
10491049
}
10501050

1051-
// QueryUserInfoResponse is the response structure for the accounts:query endpoint.
1051+
// QueryUserInfoResponse is the response structure for the QueryUsers function.
10521052
type QueryUserInfoResponse struct {
10531053
Users []*UserRecord
10541054
Count int64
@@ -1060,21 +1060,38 @@ type queryUsersResponse struct {
10601060
}
10611061

10621062
// Expression is a query condition used to filter results.
1063+
//
1064+
// Only one of Email, PhoneNumber, or UID should be specified.
1065+
// If more than one is specified, only the first (in the order of Email, PhoneNumber, UID) will be applied.
10631066
type Expression struct {
1064-
Email string `json:"email,omitempty"`
1065-
UID string `json:"userId,omitempty"`
1067+
// Email is a case insensitive string that the account's email should match.
1068+
Email string `json:"email,omitempty"`
1069+
// PhoneNumber is a string that the account's phone number should match.
10661070
PhoneNumber string `json:"phoneNumber,omitempty"`
1071+
// UID is a string that the account's local ID should match.
1072+
UID string `json:"userId,omitempty"`
10671073
}
10681074

1069-
// QueryUsersRequest is the request structure for the accounts:query endpoint.
1075+
// QueryUsersRequest is the request structure for the QueryUsers function.
10701076
type QueryUsersRequest struct {
1071-
ReturnUserInfo *bool `json:"returnUserInfo,omitempty"`
1072-
Limit int64 `json:"limit,string,omitempty"`
1073-
Offset int64 `json:"offset,string,omitempty"`
1074-
SortBy SortBy `json:"-"`
1075-
Order Order `json:"-"`
1076-
TenantID string `json:"tenantId,omitempty"`
1077-
Expression []*Expression `json:"expression,omitempty"`
1077+
// ReturnUserInfo indicates whether to return the accounts matching the query.
1078+
// If false, only the count of accounts matching the query will be returned.
1079+
// Defaults to true.
1080+
ReturnUserInfo *bool `json:"returnUserInfo,omitempty"`
1081+
// Limit is the maximum number of accounts to return with an upper limit of 500.
1082+
// Defaults to 500. Only valid when ReturnUserInfo is set to true.
1083+
Limit int64 `json:"limit,string,omitempty"`
1084+
// Offset is the number of accounts to skip from the beginning of matching records.
1085+
// Only valid when ReturnUserInfo is set to true.
1086+
Offset int64 `json:"offset,string,omitempty"`
1087+
// SortBy is the field to use for sorting user accounts.
1088+
SortBy SortBy `json:"-"`
1089+
// Order is the order for sorting query results.
1090+
Order Order `json:"-"`
1091+
// TenantID is the ID of the tenant to which the result is scoped.
1092+
TenantID string `json:"tenantId,omitempty"`
1093+
// Expression is a list of query conditions used to filter results.
1094+
Expression []*Expression `json:"expression,omitempty"`
10781095
}
10791096

10801097
// build builds the query request (for internal use only).
@@ -1118,6 +1135,33 @@ func (q *QueryUsersRequest) build() interface{} {
11181135
}
11191136
}
11201137

1138+
func (q *QueryUsersRequest) validate() error {
1139+
if q.Limit != 0 && (q.Limit < 1 || q.Limit > 500) {
1140+
return fmt.Errorf("limit must be between 1 and 500")
1141+
}
1142+
if q.Offset < 0 {
1143+
return fmt.Errorf("offset must be non-negative")
1144+
}
1145+
for _, exp := range q.Expression {
1146+
if exp.Email != "" {
1147+
if err := validateEmail(exp.Email); err != nil {
1148+
return err
1149+
}
1150+
}
1151+
if exp.PhoneNumber != "" {
1152+
if err := validatePhone(exp.PhoneNumber); err != nil {
1153+
return err
1154+
}
1155+
}
1156+
if exp.UID != "" {
1157+
if err := validateUID(exp.UID); err != nil {
1158+
return err
1159+
}
1160+
}
1161+
}
1162+
return nil
1163+
}
1164+
11211165
// SortBy is a field to use for sorting user accounts.
11221166
type SortBy int
11231167

@@ -1151,6 +1195,9 @@ func (c *baseClient) QueryUsers(ctx context.Context, query *QueryUsersRequest) (
11511195
if query == nil {
11521196
return nil, fmt.Errorf("query request must not be nil")
11531197
}
1198+
if err := query.validate(); err != nil {
1199+
return nil, err
1200+
}
11541201

11551202
var parsed queryUsersResponse
11561203
_, err := c.post(ctx, "/accounts:query", query.build(), &parsed)

auth/user_mgt_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2078,6 +2078,68 @@ func TestQueryUsersDefaultReturnUserInfo(t *testing.T) {
20782078
}
20792079
}
20802080

2081+
func TestQueryUsersValidation(t *testing.T) {
2082+
s := echoServer([]byte("{}"), t)
2083+
defer s.Close()
2084+
2085+
tests := []struct {
2086+
name string
2087+
query *QueryUsersRequest
2088+
}{
2089+
{
2090+
name: "Invalid Limit Low",
2091+
query: &QueryUsersRequest{
2092+
Limit: -1,
2093+
},
2094+
},
2095+
{
2096+
name: "Invalid Limit High",
2097+
query: &QueryUsersRequest{
2098+
Limit: 501,
2099+
},
2100+
},
2101+
{
2102+
name: "Invalid Offset",
2103+
query: &QueryUsersRequest{
2104+
Offset: -1,
2105+
},
2106+
},
2107+
{
2108+
name: "Invalid Email in Expression",
2109+
query: &QueryUsersRequest{
2110+
Expression: []*Expression{
2111+
{Email: "invalid-email"},
2112+
},
2113+
},
2114+
},
2115+
{
2116+
name: "Invalid Phone in Expression",
2117+
query: &QueryUsersRequest{
2118+
Expression: []*Expression{
2119+
{PhoneNumber: "invalid-phone"},
2120+
},
2121+
},
2122+
},
2123+
{
2124+
name: "Invalid UID in Expression",
2125+
query: &QueryUsersRequest{
2126+
Expression: []*Expression{
2127+
{UID: string(make([]byte, 129))},
2128+
},
2129+
},
2130+
},
2131+
}
2132+
2133+
for _, tt := range tests {
2134+
t.Run(tt.name, func(t *testing.T) {
2135+
_, err := s.Client.QueryUsers(context.Background(), tt.query)
2136+
if err == nil {
2137+
t.Errorf("QueryUsers() with %s; want error, got nil", tt.name)
2138+
}
2139+
})
2140+
}
2141+
}
2142+
20812143
func TestMakeExportedUser(t *testing.T) {
20822144
queryResponse := &userQueryResponse{
20832145
UID: "testuser",

0 commit comments

Comments
 (0)