From ed39577cea942dac90df83a3681d21ef50843340 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 7 Aug 2026 17:53:37 +0530 Subject: [PATCH 1/3] fix!: delete_user takes id, not email MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors authorizerdev/authorizer#753. Email is not an identifier every account has — a phone-only signup has none — so an email-keyed delete could not reach those accounts at all, and there was no second way in. Both integration call sites already had the id in scope (user_id and signup.user.id), so no extra lookup was needed to migrate them. BREAKING CHANGE: DeleteUserRequest.email is replaced by DeleteUserRequest.id. Requires server 2.4.0+ and authorizer-proto 0.2.0rc1+. --- src/authorizer/types.py | 5 ++++- tests/integration/test_live.py | 4 ++-- tests/test_admin_client.py | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/authorizer/types.py b/src/authorizer/types.py index efb2b6f..73de56e 100644 --- a/src/authorizer/types.py +++ b/src/authorizer/types.py @@ -660,7 +660,10 @@ class UpdateUserRequest(_Request): @dataclass class DeleteUserRequest(_Request): - email: str + # BREAKING (server 2.4.0): this took `email` and now takes `id`. Email is + # not an identifier every account has — a phone-only signup has none — so + # an email-keyed delete could not reach those accounts at all. + id: str @dataclass diff --git a/tests/integration/test_live.py b/tests/integration/test_live.py index 9084269..517fd15 100644 --- a/tests/integration/test_live.py +++ b/tests/integration/test_live.py @@ -550,7 +550,7 @@ def test_admin_org_member_lifecycle( ).message finally: admin.delete_organization(t.OrganizationRequest(id=org.id)) - admin.delete_user(t.DeleteUserRequest(email=email)) + admin.delete_user(t.DeleteUserRequest(id=user_id)) def test_admin_org_domain_lifecycle(admin: AuthorizerAdminClient, protocol: str) -> None: @@ -691,4 +691,4 @@ def test_webauthn_options_and_credentials( assert isinstance(creds, list) assert creds == [] # nothing registered yet finally: - admin.delete_user(t.DeleteUserRequest(email=email)) + admin.delete_user(t.DeleteUserRequest(id=signup.user.id)) diff --git a/tests/test_admin_client.py b/tests/test_admin_client.py index 7964cd0..54bb769 100644 --- a/tests/test_admin_client.py +++ b/tests/test_admin_client.py @@ -81,7 +81,7 @@ def test_delete_user_destructive_message() -> None: return_value=Response(200, json={"data": {"_delete_user": {"message": "deleted"}}}) ) with _admin() as c: - assert c.delete_user(t.DeleteUserRequest(email="a@b.com")).message == "deleted" + assert c.delete_user(t.DeleteUserRequest(id="usr_123")).message == "deleted" @respx.mock From 581077e7e53300c38548bd884b455cd09f148227 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 7 Aug 2026 18:29:09 +0530 Subject: [PATCH 2/3] chore: require authorizer-proto>=0.2.0rc1 rc1 replaced DeleteUserRequest.email with .id and this SDK builds the id form, so the floor moves with the break. The previous >=0.2.0rc0 floated across it: an older SDK release would resolve rc1 and fail at the delete call. --- pyproject.toml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0b1d183..47b00ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,10 +28,15 @@ classifiers = [ # grpc-gateway responses with protojson (int64-as-string + single-field wrappers) # using those message types. grpcio is only needed for the optional ``grpc`` # protocol (pulled via authorizer-proto[grpc]). -dependencies = ["httpx>=0.24,<1", "protobuf>=4", "authorizer-proto>=0.2.0rc0"] +# +# authorizer-proto is pinned at >=0.2.0rc1: rc1 replaced DeleteUserRequest.email +# with .id, and this SDK's delete_user builds the id form. An older SDK paired +# with rc1 (or this SDK with rc0) breaks at that call, so the floor moves with +# the break rather than floating across it. +dependencies = ["httpx>=0.24,<1", "protobuf>=4", "authorizer-proto>=0.2.0rc1"] [project.optional-dependencies] -grpc = ["authorizer-proto[grpc]>=0.2.0rc0", "grpcio>=1.60", "protobuf>=4"] +grpc = ["authorizer-proto[grpc]>=0.2.0rc1", "grpcio>=1.60", "protobuf>=4"] dev = [ "pytest>=7", "pytest-asyncio>=0.23", From bb18433b7326278e585cce106a6a991467eefb34 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 7 Aug 2026 21:07:58 +0530 Subject: [PATCH 3/3] ci: run live tests against 2.4.0-rc.16 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The live job pinned rc.13, which still has DeleteUserRequest.email. Since delete_user now sends id, that server rejects it with 'unknown field' — real version skew, not a test bug. rc.16 is the first RC with the id-only field. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1302a3e..5941a8f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,7 @@ jobs: run: | docker run -d --name authorizer \ -p 8080:8080 -p 9091:9091 \ - quay.io/authorizer/authorizer:2.4.0-rc.13 \ + quay.io/authorizer/authorizer:2.4.0-rc.16 \ --database-type=sqlite --database-url=test.db \ --jwt-type=HS256 --jwt-secret=test \ --admin-secret=admin --client-id=ci-client --client-secret=secret \