Skip to content

Commit 54fa592

Browse files
committed
feat: validate uuid and sign out scope parameters to functions
1 parent eff5048 commit 54fa592

File tree

4 files changed

+55
-19
lines changed

4 files changed

+55
-19
lines changed

src/GoTrueAdminApi.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
_request,
66
_userResponse,
77
} from './lib/fetch'
8-
import { resolveFetch } from './lib/helpers'
8+
import { resolveFetch, validateUUID } from './lib/helpers'
99
import {
1010
AdminUserAttributes,
1111
GenerateLinkParams,
@@ -19,6 +19,8 @@ import {
1919
AuthMFAAdminListFactorsParams,
2020
AuthMFAAdminListFactorsResponse,
2121
PageParams,
22+
SIGN_OUT_SCOPES,
23+
SignOutScope,
2224
} from './lib/types'
2325
import { AuthError, isAuthError } from './lib/errors'
2426

@@ -59,8 +61,14 @@ export default class GoTrueAdminApi {
5961
*/
6062
async signOut(
6163
jwt: string,
62-
scope: 'global' | 'local' | 'others' = 'global'
64+
scope: SignOutScope = SIGN_OUT_SCOPES[0]
6365
): Promise<{ data: null; error: AuthError | null }> {
66+
if (SIGN_OUT_SCOPES.indexOf(scope) < 0) {
67+
throw new Error(
68+
`@supabase/auth-js: Parameter scope must be one of ${SIGN_OUT_SCOPES.join(', ')}`
69+
)
70+
}
71+
6472
try {
6573
await _request(this.fetch, 'POST', `${this.url}/logout?scope=${scope}`, {
6674
headers: this.headers,
@@ -219,6 +227,8 @@ export default class GoTrueAdminApi {
219227
* This function should only be called on a server. Never expose your `service_role` key in the browser.
220228
*/
221229
async getUserById(uid: string): Promise<UserResponse> {
230+
validateUUID(uid)
231+
222232
try {
223233
return await _request(this.fetch, 'GET', `${this.url}/admin/users/${uid}`, {
224234
headers: this.headers,
@@ -241,6 +251,8 @@ export default class GoTrueAdminApi {
241251
* This function should only be called on a server. Never expose your `service_role` key in the browser.
242252
*/
243253
async updateUserById(uid: string, attributes: AdminUserAttributes): Promise<UserResponse> {
254+
validateUUID(uid)
255+
244256
try {
245257
return await _request(this.fetch, 'PUT', `${this.url}/admin/users/${uid}`, {
246258
body: attributes,
@@ -266,6 +278,8 @@ export default class GoTrueAdminApi {
266278
* This function should only be called on a server. Never expose your `service_role` key in the browser.
267279
*/
268280
async deleteUser(id: string, shouldSoftDelete = false): Promise<UserResponse> {
281+
validateUUID(id)
282+
269283
try {
270284
return await _request(this.fetch, 'DELETE', `${this.url}/admin/users/${id}`, {
271285
headers: this.headers,
@@ -286,6 +300,8 @@ export default class GoTrueAdminApi {
286300
private async _listFactors(
287301
params: AuthMFAAdminListFactorsParams
288302
): Promise<AuthMFAAdminListFactorsResponse> {
303+
validateUUID(params.userId)
304+
289305
try {
290306
const { data, error } = await _request(
291307
this.fetch,
@@ -311,6 +327,9 @@ export default class GoTrueAdminApi {
311327
private async _deleteFactor(
312328
params: AuthMFAAdminDeleteFactorParams
313329
): Promise<AuthMFAAdminDeleteFactorResponse> {
330+
validateUUID(params.userId)
331+
validateUUID(params.id)
332+
314333
try {
315334
const data = await _request(
316335
this.fetch,

src/lib/helpers.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { API_VERSION_HEADER_NAME, BASE64URL_REGEX } from './constants'
22
import { AuthInvalidJwtError } from './errors'
3-
import { base64UrlToUint8Array, stringFromBase64URL, stringToBase64URL } from './base64url'
3+
import { base64UrlToUint8Array, stringFromBase64URL } from './base64url'
44
import { JwtHeader, JwtPayload, SupportedStorage } from './types'
55

66
export function expiresAt(expiresIn: number) {
@@ -357,3 +357,11 @@ export function getAlgorithm(alg: 'RS256' | 'ES256'): RsaHashedImportParams | Ec
357357
throw new Error('Invalid alg claim')
358358
}
359359
}
360+
361+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
362+
363+
export function validateUUID(str: string) {
364+
if (!UUID_REGEX.test(str)) {
365+
throw new Error('@supabase/auth-js: Expected parameter to be UUID but is not')
366+
}
367+
}

src/lib/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,3 +1279,6 @@ export interface JWK {
12791279
kid?: string
12801280
[key: string]: any
12811281
}
1282+
1283+
export const SIGN_OUT_SCOPES = ['global', 'local', 'others'] as const
1284+
export type SignOutScope = typeof SIGN_OUT_SCOPES[number]

test/GoTrueApi.test.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
import type { GenerateLinkProperties, User } from '../src/lib/types'
1717

1818
const INVALID_EMAIL = 'xx:;[email protected]'
19-
const INVALID_USER_ID = 'invalid-uuid'
19+
const NON_EXISTANT_USER_ID = '83fd9e20-7a80-46e4-bf29-a86e3d6bbf66'
2020

2121
describe('GoTrueAdminApi', () => {
2222
describe('User creation', () => {
@@ -152,7 +152,7 @@ describe('GoTrueAdminApi', () => {
152152
})
153153

154154
test('getUserById() returns AuthError when user id is invalid', async () => {
155-
const { error, data } = await serviceRoleApiClient.getUserById(INVALID_USER_ID)
155+
const { error, data } = await serviceRoleApiClient.getUserById(NON_EXISTANT_USER_ID)
156156

157157
expect(error).not.toBeNull()
158158
expect(data.user).toBeNull()
@@ -283,7 +283,7 @@ describe('GoTrueAdminApi', () => {
283283
})
284284

285285
test('deleteUser() returns AuthError when user id is invalid', async () => {
286-
const { error, data } = await serviceRoleApiClient.deleteUser(INVALID_USER_ID)
286+
const { error, data } = await serviceRoleApiClient.deleteUser(NON_EXISTANT_USER_ID)
287287

288288
expect(error).not.toBeNull()
289289
expect(data.user).toBeNull()
@@ -479,7 +479,7 @@ describe('GoTrueAdminApi', () => {
479479
test('listUsers() returns AuthError when page is invalid', async () => {
480480
const { error, data } = await serviceRoleApiClient.listUsers({
481481
page: -1,
482-
perPage: 10
482+
perPage: 10,
483483
})
484484

485485
expect(error).not.toBeNull()
@@ -489,8 +489,8 @@ describe('GoTrueAdminApi', () => {
489489

490490
describe('Update User', () => {
491491
test('updateUserById() returns AuthError when user id is invalid', async () => {
492-
const { error, data } = await serviceRoleApiClient.updateUserById(INVALID_USER_ID, {
493-
492+
const { error, data } = await serviceRoleApiClient.updateUserById(NON_EXISTANT_USER_ID, {
493+
494494
})
495495

496496
expect(error).not.toBeNull()
@@ -513,7 +513,7 @@ describe('GoTrueAdminApi', () => {
513513
expect(uid).toBeTruthy()
514514

515515
const { error: enrollError } = await authClientWithSession.mfa.enroll({
516-
factorType: 'totp'
516+
factorType: 'totp',
517517
})
518518
expect(enrollError).toBeNull()
519519

@@ -526,35 +526,41 @@ describe('GoTrueAdminApi', () => {
526526

527527
const factorId = data?.factors[0].id
528528
expect(factorId).toBeDefined()
529-
const { data: deletedData, error: deletedError } = await serviceRoleApiClient.mfa.deleteFactor({
530-
userId: uid,
531-
id: factorId!
532-
})
529+
const { data: deletedData, error: deletedError } =
530+
await serviceRoleApiClient.mfa.deleteFactor({
531+
userId: uid,
532+
id: factorId!,
533+
})
533534
expect(deletedError).toBeNull()
534535
expect(deletedData).not.toBeNull()
535536
const deletedId = (deletedData as any)?.data?.id
536537
console.log('deletedId:', deletedId)
537538
expect(deletedId).toEqual(factorId)
538539

539-
const { data: latestData, error: latestError } = await serviceRoleApiClient.mfa.listFactors({ userId: uid })
540+
const { data: latestData, error: latestError } = await serviceRoleApiClient.mfa.listFactors({
541+
userId: uid,
542+
})
540543
expect(latestError).toBeNull()
541544
expect(latestData).not.toBeNull()
542545
expect(Array.isArray(latestData?.factors)).toBe(true)
543546
expect(latestData?.factors.length).toEqual(0)
544547
})
545548

546-
547549
test('mfa.listFactors returns AuthError for invalid user', async () => {
548-
const { data, error } = await serviceRoleApiClient.mfa.listFactors({ userId: INVALID_USER_ID })
550+
const { data, error } = await serviceRoleApiClient.mfa.listFactors({
551+
userId: NON_EXISTANT_USER_ID,
552+
})
549553
expect(data).toBeNull()
550554
expect(error).not.toBeNull()
551555
})
552556

553557
test('mfa.deleteFactors returns AuthError for invalid user', async () => {
554-
const { data, error } = await serviceRoleApiClient.mfa.deleteFactor({ userId: INVALID_USER_ID , id: '1' })
558+
const { data, error } = await serviceRoleApiClient.mfa.deleteFactor({
559+
userId: NON_EXISTANT_USER_ID,
560+
id: NON_EXISTANT_USER_ID,
561+
})
555562
expect(data).toBeNull()
556563
expect(error).not.toBeNull()
557564
})
558-
559565
})
560566
})

0 commit comments

Comments
 (0)