Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.

Commit db3153a

Browse files
authored
Merge 3dda96c into 485d7ff
2 parents 485d7ff + 3dda96c commit db3153a

File tree

5 files changed

+145
-4
lines changed

5 files changed

+145
-4
lines changed

supabase_auth/_async/gotrue_admin_api.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
from functools import partial
44
from typing import Dict, List, Optional
55

6-
from ..helpers import model_validate, parse_link_response, parse_user_response
6+
from ..helpers import (
7+
is_valid_uuid,
8+
model_validate,
9+
parse_link_response,
10+
parse_user_response,
11+
)
712
from ..http_clients import AsyncClient
813
from ..types import (
914
AdminUserAttributes,
@@ -131,6 +136,8 @@ async def get_user_by_id(self, uid: str) -> UserResponse:
131136
This function should only be called on a server.
132137
Never expose your `service_role` key in the browser.
133138
"""
139+
self._validate_uuid(uid)
140+
134141
return await self._request(
135142
"GET",
136143
f"admin/users/{uid}",
@@ -148,6 +155,7 @@ async def update_user_by_id(
148155
This function should only be called on a server.
149156
Never expose your `service_role` key in the browser.
150157
"""
158+
self._validate_uuid(uid)
151159
return await self._request(
152160
"PUT",
153161
f"admin/users/{uid}",
@@ -162,13 +170,15 @@ async def delete_user(self, id: str, should_soft_delete: bool = False) -> None:
162170
This function should only be called on a server.
163171
Never expose your `service_role` key in the browser.
164172
"""
173+
self._validate_uuid(id)
165174
body = {"should_soft_delete": should_soft_delete}
166175
return await self._request("DELETE", f"admin/users/{id}", body=body)
167176

168177
async def _list_factors(
169178
self,
170179
params: AuthMFAAdminListFactorsParams,
171180
) -> AuthMFAAdminListFactorsResponse:
181+
self._validate_uuid(params.get("user_id"))
172182
return await self._request(
173183
"GET",
174184
f"admin/users/{params.get('user_id')}/factors",
@@ -179,8 +189,14 @@ async def _delete_factor(
179189
self,
180190
params: AuthMFAAdminDeleteFactorParams,
181191
) -> AuthMFAAdminDeleteFactorResponse:
192+
self._validate_uuid(params.get("user_id"))
193+
self._validate_uuid(params.get("id"))
182194
return await self._request(
183195
"DELETE",
184-
f"admin/users/{params.get('user_id')}/factors/{params.get('factor_id')}",
196+
f"admin/users/{params.get('user_id')}/factors/{params.get('id')}",
185197
xform=partial(model_validate, AuthMFAAdminDeleteFactorResponse),
186198
)
199+
200+
def _validate_uuid(self, id: str) -> None:
201+
if not is_valid_uuid(id):
202+
raise ValueError(f"Invalid id, '{id}' is not a valid uuid")

supabase_auth/_sync/gotrue_admin_api.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
from functools import partial
44
from typing import Dict, List, Optional
55

6-
from ..helpers import model_validate, parse_link_response, parse_user_response
6+
from ..helpers import (
7+
is_valid_uuid,
8+
model_validate,
9+
parse_link_response,
10+
parse_user_response,
11+
)
712
from ..http_clients import SyncClient
813
from ..types import (
914
AdminUserAttributes,
@@ -131,6 +136,8 @@ def get_user_by_id(self, uid: str) -> UserResponse:
131136
This function should only be called on a server.
132137
Never expose your `service_role` key in the browser.
133138
"""
139+
self._validate_uuid(uid)
140+
134141
return self._request(
135142
"GET",
136143
f"admin/users/{uid}",
@@ -148,6 +155,7 @@ def update_user_by_id(
148155
This function should only be called on a server.
149156
Never expose your `service_role` key in the browser.
150157
"""
158+
self._validate_uuid(uid)
151159
return self._request(
152160
"PUT",
153161
f"admin/users/{uid}",
@@ -162,13 +170,15 @@ def delete_user(self, id: str, should_soft_delete: bool = False) -> None:
162170
This function should only be called on a server.
163171
Never expose your `service_role` key in the browser.
164172
"""
173+
self._validate_uuid(id)
165174
body = {"should_soft_delete": should_soft_delete}
166175
return self._request("DELETE", f"admin/users/{id}", body=body)
167176

168177
def _list_factors(
169178
self,
170179
params: AuthMFAAdminListFactorsParams,
171180
) -> AuthMFAAdminListFactorsResponse:
181+
self._validate_uuid(params.get("user_id"))
172182
return self._request(
173183
"GET",
174184
f"admin/users/{params.get('user_id')}/factors",
@@ -179,8 +189,14 @@ def _delete_factor(
179189
self,
180190
params: AuthMFAAdminDeleteFactorParams,
181191
) -> AuthMFAAdminDeleteFactorResponse:
192+
self._validate_uuid(params.get("user_id"))
193+
self._validate_uuid(params.get("id"))
182194
return self._request(
183195
"DELETE",
184-
f"admin/users/{params.get('user_id')}/factors/{params.get('factor_id')}",
196+
f"admin/users/{params.get('user_id')}/factors/{params.get('id')}",
185197
xform=partial(model_validate, AuthMFAAdminDeleteFactorResponse),
186198
)
199+
200+
def _validate_uuid(self, id: str) -> None:
201+
if not is_valid_uuid(id):
202+
raise ValueError(f"Invalid id, '{id}' is not a valid uuid")

supabase_auth/helpers.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import re
66
import secrets
77
import string
8+
import uuid
89
from base64 import urlsafe_b64decode
910
from datetime import datetime
1011
from json import loads
@@ -317,3 +318,11 @@ def validate_exp(exp: int) -> None:
317318
time_now = datetime.now().timestamp()
318319
if exp <= time_now:
319320
raise AuthInvalidJwtError("JWT has expired")
321+
322+
323+
def is_valid_uuid(value: str) -> bool:
324+
try:
325+
uuid.UUID(value)
326+
return True
327+
except ValueError:
328+
return False

tests/_async/test_gotrue_admin_api.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import uuid
2+
13
import pytest
24

35
from supabase_auth.errors import (
@@ -588,3 +590,51 @@ async def test_weak_phone_password_error():
588590
)
589591
except (AuthWeakPasswordError, AuthApiError) as e:
590592
assert e.to_dict()
593+
594+
595+
async def test_get_user_by_id_invalid_id_raises_error():
596+
with pytest.raises(
597+
ValueError, match=r"Invalid id, 'invalid_id' is not a valid uuid"
598+
):
599+
await service_role_api_client().get_user_by_id("invalid_id")
600+
601+
602+
async def test_update_user_by_id_invalid_id_raises_error():
603+
with pytest.raises(
604+
ValueError, match=r"Invalid id, 'invalid_id' is not a valid uuid"
605+
):
606+
await service_role_api_client().update_user_by_id(
607+
"invalid_id", {"email": "test@test.com"}
608+
)
609+
610+
611+
async def test_delete_user_invalid_id_raises_error():
612+
with pytest.raises(
613+
ValueError, match=r"Invalid id, 'invalid_id' is not a valid uuid"
614+
):
615+
await service_role_api_client().delete_user("invalid_id")
616+
617+
618+
async def test_list_factors_invalid_id_raises_error():
619+
with pytest.raises(
620+
ValueError, match=r"Invalid id, 'invalid_id' is not a valid uuid"
621+
):
622+
await service_role_api_client()._list_factors({"user_id": "invalid_id"})
623+
624+
625+
async def test_delete_factor_invalid_id_raises_error():
626+
# invalid user id
627+
with pytest.raises(
628+
ValueError, match=r"Invalid id, 'invalid_id' is not a valid uuid"
629+
):
630+
await service_role_api_client()._delete_factor(
631+
{"user_id": "invalid_id", "id": "invalid_id"}
632+
)
633+
634+
# valid user id, invalid factor id
635+
with pytest.raises(
636+
ValueError, match=r"Invalid id, 'invalid_id' is not a valid uuid"
637+
):
638+
await service_role_api_client()._delete_factor(
639+
{"user_id": str(uuid.uuid4()), "id": "invalid_id"}
640+
)

tests/_sync/test_gotrue_admin_api.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import uuid
2+
13
import pytest
24

35
from supabase_auth.errors import (
@@ -590,3 +592,51 @@ def test_weak_phone_password_error():
590592
)
591593
except (AuthWeakPasswordError, AuthApiError) as e:
592594
assert e.to_dict()
595+
596+
597+
def test_get_user_by_id_invalid_id_raises_error():
598+
with pytest.raises(
599+
ValueError, match=r"Invalid id, 'invalid_id' is not a valid uuid"
600+
):
601+
service_role_api_client().get_user_by_id("invalid_id")
602+
603+
604+
def test_update_user_by_id_invalid_id_raises_error():
605+
with pytest.raises(
606+
ValueError, match=r"Invalid id, 'invalid_id' is not a valid uuid"
607+
):
608+
service_role_api_client().update_user_by_id(
609+
"invalid_id", {"email": "test@test.com"}
610+
)
611+
612+
613+
def test_delete_user_invalid_id_raises_error():
614+
with pytest.raises(
615+
ValueError, match=r"Invalid id, 'invalid_id' is not a valid uuid"
616+
):
617+
service_role_api_client().delete_user("invalid_id")
618+
619+
620+
def test_list_factors_invalid_id_raises_error():
621+
with pytest.raises(
622+
ValueError, match=r"Invalid id, 'invalid_id' is not a valid uuid"
623+
):
624+
service_role_api_client()._list_factors({"user_id": "invalid_id"})
625+
626+
627+
def test_delete_factor_invalid_id_raises_error():
628+
# invalid user id
629+
with pytest.raises(
630+
ValueError, match=r"Invalid id, 'invalid_id' is not a valid uuid"
631+
):
632+
service_role_api_client()._delete_factor(
633+
{"user_id": "invalid_id", "id": "invalid_id"}
634+
)
635+
636+
# valid user id, invalid factor id
637+
with pytest.raises(
638+
ValueError, match=r"Invalid id, 'invalid_id' is not a valid uuid"
639+
):
640+
service_role_api_client()._delete_factor(
641+
{"user_id": str(uuid.uuid4()), "id": "invalid_id"}
642+
)

0 commit comments

Comments
 (0)