Skip to content

Commit c434072

Browse files
0x62mandarini
authored andcommitted
feat(auth): add filter support to listUsers
1 parent 2d0bd77 commit c434072

File tree

2 files changed

+48
-19
lines changed

2 files changed

+48
-19
lines changed

packages/core/auth-js/src/GoTrueAdminApi.ts

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
OAuthClientResponse,
2727
OAuthClientListResponse,
2828
} from './lib/types'
29-
import { AuthError, isAuthError } from './lib/errors'
29+
import { AuthError, isAuthError, AuthApiError } from './lib/errors'
3030

3131
export default class GoTrueAdminApi {
3232
/** Contains all MFA administration methods. */
@@ -194,22 +194,31 @@ export default class GoTrueAdminApi {
194194
* Get a list of users.
195195
*
196196
* This function should only be called on a server. Never expose your `service_role` key in the browser.
197-
* @param params An object which supports `page` and `perPage` as numbers, to alter the paginated results.
197+
* @param params An object which supports `page` and `perPage` as numbers, to alter the paginated results,
198+
* and `filter` string to search for users by email address.
199+
*
200+
* @warning The filter parameter is provided for convenience but may have performance implications on large databases.
201+
* Consider using pagination without filters for projects with many users.
198202
*/
203+
199204
async listUsers(
200-
params?: PageParams
205+
params?: PageParams & { filter?: string }
201206
): Promise<
202207
| { data: { users: User[]; aud: string } & Pagination; error: null }
203208
| { data: { users: [] }; error: AuthError }
204209
> {
205210
try {
211+
if (params?.filter && params.filter.trim().length < 3) {
212+
throw new AuthApiError('Filter must be at least 3 characters', 400, 'invalid_request')
213+
}
206214
const pagination: Pagination = { nextPage: null, lastPage: 0, total: 0 }
207215
const response = await _request(this.fetch, 'GET', `${this.url}/admin/users`, {
208216
headers: this.headers,
209217
noResolveJson: true,
210218
query: {
211219
page: params?.page?.toString() ?? '',
212220
per_page: params?.perPage?.toString() ?? '',
221+
...(params?.filter ? { filter: params.filter } : {}),
213222
},
214223
xform: _noResolveJsonResponse,
215224
})
@@ -220,8 +229,13 @@ export default class GoTrueAdminApi {
220229
const links = response.headers.get('link')?.split(',') ?? []
221230
if (links.length > 0) {
222231
links.forEach((link: string) => {
223-
const page = parseInt(link.split(';')[0].split('=')[1].substring(0, 1))
224-
const rel = JSON.parse(link.split(';')[1].split('=')[1])
232+
const [urlPart, relPart] = link.split(';').map((part) => part.trim())
233+
const url = urlPart.slice(1, -1) // Remove the leading "</" and trailing ">"
234+
235+
const searchParams = new URLSearchParams(url.split('?')[1])
236+
const page = parseInt(searchParams.get('page') || '1', 10)
237+
const rel = relPart.split('=')[1].replace(/"/g, '')
238+
225239
pagination[`${rel}Page`] = page
226240
})
227241

@@ -414,9 +428,7 @@ export default class GoTrueAdminApi {
414428
*
415429
* This function should only be called on a server. Never expose your `service_role` key in the browser.
416430
*/
417-
private async _createOAuthClient(
418-
params: CreateOAuthClientParams
419-
): Promise<OAuthClientResponse> {
431+
private async _createOAuthClient(params: CreateOAuthClientParams): Promise<OAuthClientResponse> {
420432
try {
421433
return await _request(this.fetch, 'POST', `${this.url}/admin/oauth/clients`, {
422434
body: params,
@@ -465,17 +477,12 @@ export default class GoTrueAdminApi {
465477
*/
466478
private async _deleteOAuthClient(clientId: string): Promise<OAuthClientResponse> {
467479
try {
468-
return await _request(
469-
this.fetch,
470-
'DELETE',
471-
`${this.url}/admin/oauth/clients/${clientId}`,
472-
{
473-
headers: this.headers,
474-
xform: (client: any) => {
475-
return { data: client, error: null }
476-
},
477-
}
478-
)
480+
return await _request(this.fetch, 'DELETE', `${this.url}/admin/oauth/clients/${clientId}`, {
481+
headers: this.headers,
482+
xform: (client: any) => {
483+
return { data: client, error: null }
484+
},
485+
})
479486
} catch (error) {
480487
if (isAuthError(error)) {
481488
return { data: null, error }

packages/core/auth-js/test/GoTrueApi.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,28 @@ describe('GoTrueAdminApi', () => {
114114
expect(emails).toContain(email)
115115
})
116116

117+
test('listUsers() should support filter property', async () => {
118+
const { email } = mockUserCredentials()
119+
const { error: createError, data: createdUser } = await createNewUserWithEmail({ email })
120+
expect(createError).toBeNull()
121+
expect(createdUser.user).not.toBeUndefined()
122+
123+
const { error: listUserError, data: userList } = await serviceRoleApiClient.listUsers({
124+
filter: email,
125+
})
126+
expect(listUserError).toBeNull()
127+
expect(userList).toHaveProperty('users')
128+
expect(userList).toHaveProperty('aud')
129+
130+
const emails =
131+
userList.users?.map((user: User) => {
132+
return user.email
133+
}) || []
134+
135+
expect(emails.length).toEqual(1)
136+
expect(emails).toContain(email)
137+
})
138+
117139
test('listUsers() returns AuthError when page is invalid', async () => {
118140
const { error, data } = await serviceRoleApiClient.listUsers({
119141
page: -1,

0 commit comments

Comments
 (0)