Skip to content

Commit b4dd686

Browse files
feat: Implement support for accounts:query
Adds the `QueryUsers` function to the Auth client, allowing users to query for user accounts with various filters. - Defines `QueryUsersRequest` and `QueryUsersResponse` structs. - Implements the `QueryUsers` function to call the `accounts:query` endpoint. - Includes support for tenant-specific queries. - Adds comprehensive unit tests for the new functionality.
1 parent da7068d commit b4dd686

File tree

2 files changed

+216
-0
lines changed

2 files changed

+216
-0
lines changed

auth/user_mgt.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,61 @@ type getAccountInfoResponse struct {
838838
Users []*userQueryResponse `json:"users"`
839839
}
840840

841+
// QueryUserInfoResponse is the response structure for the accounts:query endpoint.
842+
type QueryUserInfoResponse struct {
843+
Users []*UserRecord
844+
Count string
845+
}
846+
847+
type queryUsersResponse struct {
848+
Users []*userQueryResponse `json:"usersInfo,omitempty"`
849+
Count string `json:"recordsCount,omitempty"`
850+
}
851+
852+
// SqlExpression is a query condition used to filter results.
853+
type SqlExpression struct {
854+
Email string `json:"email,omitempty"`
855+
UserID string `json:"userId,omitempty"`
856+
PhoneNumber string `json:"phoneNumber,omitempty"`
857+
}
858+
859+
// QueryUsersRequest is the request structure for the accounts:query endpoint.
860+
type QueryUsersRequest struct {
861+
ReturnUserInfo bool `json:"returnUserInfo"`
862+
Limit string `json:"limit,omitempty"`
863+
Offset string `json:"offset,omitempty"`
864+
SortBy string `json:"sortBy,omitempty"`
865+
Order string `json:"order,omitempty"`
866+
TenantID string `json:"tenantId,omitempty"`
867+
Expression []*SqlExpression `json:"expression,omitempty"`
868+
}
869+
870+
// SortByField is a field to use for sorting user accounts.
871+
type SortByField string
872+
873+
const (
874+
// UserID sorts results by userId.
875+
UserID SortByField = "USER_ID"
876+
// Name sorts results by name.
877+
Name SortByField = "NAME"
878+
// CreatedAt sorts results by createdAt.
879+
CreatedAt SortByField = "CREATED_AT"
880+
// LastLoginAt sorts results by lastLoginAt.
881+
LastLoginAt SortByField = "LAST_LOGIN_AT"
882+
// UserEmail sorts results by userEmail.
883+
UserEmail SortByField = "USER_EMAIL"
884+
)
885+
886+
// Order is an order for sorting query results.
887+
type Order string
888+
889+
const (
890+
// Asc sorts in ascending order.
891+
Asc Order = "ASC"
892+
// Desc sorts in descending order.
893+
Desc Order = "DESC"
894+
)
895+
841896
func (c *baseClient) getUser(ctx context.Context, query *userQuery) (*UserRecord, error) {
842897
var parsed getAccountInfoResponse
843898
resp, err := c.post(ctx, "/accounts:lookup", query.build(), &parsed)
@@ -1311,6 +1366,33 @@ type DeleteUsersErrorInfo struct {
13111366
// array of errors that correspond to the failed deletions. An error is
13121367
// returned if any of the identifiers are invalid or if more than 1000
13131368
// identifiers are specified.
1369+
// QueryUsers queries for user accounts based on the provided query configuration.
1370+
func (c *baseClient) QueryUsers(ctx context.Context, query *QueryUsersRequest) (*QueryUserInfoResponse, error) {
1371+
if query == nil {
1372+
return nil, fmt.Errorf("query request must not be nil")
1373+
}
1374+
1375+
var parsed queryUsersResponse
1376+
_, err := c.post(ctx, "/accounts:query", query, &parsed)
1377+
if err != nil {
1378+
return nil, err
1379+
}
1380+
1381+
var userRecords []*UserRecord
1382+
for _, user := range parsed.Users {
1383+
userRecord, err := user.makeUserRecord()
1384+
if err != nil {
1385+
return nil, fmt.Errorf("error while parsing response: %w", err)
1386+
}
1387+
userRecords = append(userRecords, userRecord)
1388+
}
1389+
1390+
return &QueryUserInfoResponse{
1391+
Users: userRecords,
1392+
Count: parsed.Count,
1393+
}, nil
1394+
}
1395+
13141396
func (c *baseClient) DeleteUsers(ctx context.Context, uids []string) (*DeleteUsersResult, error) {
13151397
if len(uids) == 0 {
13161398
return &DeleteUsersResult{}, nil

auth/user_mgt_test.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1899,6 +1899,140 @@ func TestDeleteUsers(t *testing.T) {
18991899
})
19001900
}
19011901

1902+
func TestQueryUsers(t *testing.T) {
1903+
resp := `{
1904+
"usersInfo": [{
1905+
"localId": "testuser",
1906+
"email": "[email protected]",
1907+
"phoneNumber": "+1234567890",
1908+
"emailVerified": true,
1909+
"displayName": "Test User",
1910+
"photoUrl": "http://www.example.com/testuser/photo.png",
1911+
"validSince": "1494364393",
1912+
"disabled": false,
1913+
"createdAt": "1234567890000",
1914+
"lastLoginAt": "1233211232000",
1915+
"customAttributes": "{\"admin\": true, \"package\": \"gold\"}",
1916+
"tenantId": "testTenant",
1917+
"providerUserInfo": [{
1918+
"providerId": "password",
1919+
"displayName": "Test User",
1920+
"photoUrl": "http://www.example.com/testuser/photo.png",
1921+
"email": "[email protected]",
1922+
"rawId": "testuid"
1923+
}, {
1924+
"providerId": "phone",
1925+
"phoneNumber": "+1234567890",
1926+
"rawId": "testuid"
1927+
}],
1928+
"mfaInfo": [{
1929+
"phoneInfo": "+1234567890",
1930+
"mfaEnrollmentId": "enrolledPhoneFactor",
1931+
"displayName": "My MFA Phone",
1932+
"enrolledAt": "2021-03-03T13:06:20.542896Z"
1933+
}, {
1934+
"totpInfo": {},
1935+
"mfaEnrollmentId": "enrolledTOTPFactor",
1936+
"displayName": "My MFA TOTP",
1937+
"enrolledAt": "2021-03-03T13:06:20.542896Z"
1938+
}]
1939+
}],
1940+
"recordsCount": "1"
1941+
}`
1942+
s := echoServer([]byte(resp), t)
1943+
defer s.Close()
1944+
1945+
query := &QueryUsersRequest{
1946+
ReturnUserInfo: true,
1947+
Limit: "1",
1948+
SortBy: string(UserEmail),
1949+
Order: string(Asc),
1950+
Expression: []*SqlExpression{
1951+
{
1952+
1953+
},
1954+
},
1955+
}
1956+
1957+
result, err := s.Client.QueryUsers(context.Background(), query)
1958+
if err != nil {
1959+
t.Fatalf("QueryUsers() = %v", err)
1960+
}
1961+
1962+
if len(result.Users) != 1 {
1963+
t.Fatalf("QueryUsers() returned %d users; want 1", len(result.Users))
1964+
}
1965+
1966+
if result.Count != "1" {
1967+
t.Errorf("QueryUsers() returned count %q; want '1'", result.Count)
1968+
}
1969+
1970+
if !reflect.DeepEqual(result.Users[0], testUser) {
1971+
t.Errorf("QueryUsers() = %#v; want = %#v", result.Users[0], testUser)
1972+
}
1973+
1974+
wantPath := "/projects/mock-project-id/accounts:query"
1975+
if s.Req[0].RequestURI != wantPath {
1976+
t.Errorf("QueryUsers() URL = %q; want = %q", s.Req[0].RequestURI, wantPath)
1977+
}
1978+
}
1979+
1980+
func TestQueryUsersError(t *testing.T) {
1981+
resp := `{
1982+
"error": {
1983+
"message": "INVALID_QUERY"
1984+
}
1985+
}`
1986+
s := echoServer([]byte(resp), t)
1987+
defer s.Close()
1988+
s.Status = http.StatusBadRequest
1989+
1990+
query := &QueryUsersRequest{
1991+
ReturnUserInfo: true,
1992+
Limit: "1",
1993+
SortBy: "USER_EMAIL",
1994+
Order: "ASC",
1995+
Expression: []*SqlExpression{
1996+
{
1997+
1998+
},
1999+
},
2000+
}
2001+
2002+
result, err := s.Client.QueryUsers(context.Background(), query)
2003+
if result != nil || err == nil {
2004+
t.Fatalf("QueryUsers() = (%v, %v); want = (nil, error)", result, err)
2005+
}
2006+
}
2007+
2008+
func TestQueryUsersWithTenant(t *testing.T) {
2009+
resp := `{
2010+
"usersInfo": [],
2011+
"recordsCount": "0"
2012+
}`
2013+
s := echoServer([]byte(resp), t)
2014+
defer s.Close()
2015+
2016+
query := &QueryUsersRequest{
2017+
ReturnUserInfo: true,
2018+
TenantID: "test-tenant",
2019+
}
2020+
2021+
_, err := s.Client.QueryUsers(context.Background(), query)
2022+
if err != nil {
2023+
t.Fatalf("QueryUsers() = %v", err)
2024+
}
2025+
2026+
var req map[string]interface{}
2027+
if err := json.Unmarshal(s.Rbody, &req); err != nil {
2028+
t.Fatal(err)
2029+
}
2030+
2031+
if req["tenantId"] != "test-tenant" {
2032+
t.Errorf("QueryUsers() tenantId = %q; want = %q", req["tenantId"], "test-tenant")
2033+
}
2034+
}
2035+
19022036
func TestMakeExportedUser(t *testing.T) {
19032037
queryResponse := &userQueryResponse{
19042038
UID: "testuser",

0 commit comments

Comments
 (0)