django-clerk-users 0.0.1__py3-none-any.whl → 0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. django_clerk_users/__init__.py +78 -7
  2. django_clerk_users/apps.py +20 -0
  3. django_clerk_users/authentication/__init__.py +24 -0
  4. django_clerk_users/authentication/backends.py +89 -0
  5. django_clerk_users/authentication/drf.py +111 -0
  6. django_clerk_users/authentication/utils.py +171 -0
  7. django_clerk_users/caching.py +161 -0
  8. django_clerk_users/checks.py +127 -0
  9. django_clerk_users/client.py +32 -0
  10. django_clerk_users/decorators.py +181 -0
  11. django_clerk_users/exceptions.py +51 -0
  12. django_clerk_users/management/__init__.py +0 -0
  13. django_clerk_users/management/commands/__init__.py +0 -0
  14. django_clerk_users/management/commands/migrate_users_to_clerk.py +223 -0
  15. django_clerk_users/management/commands/sync_clerk_organizations.py +191 -0
  16. django_clerk_users/management/commands/sync_clerk_users.py +114 -0
  17. django_clerk_users/managers.py +121 -0
  18. django_clerk_users/middleware/__init__.py +9 -0
  19. django_clerk_users/middleware/auth.py +201 -0
  20. django_clerk_users/migrations/0001_initial.py +174 -0
  21. django_clerk_users/migrations/__init__.py +0 -0
  22. django_clerk_users/models.py +174 -0
  23. django_clerk_users/organizations/__init__.py +8 -0
  24. django_clerk_users/organizations/admin.py +81 -0
  25. django_clerk_users/organizations/apps.py +8 -0
  26. django_clerk_users/organizations/middleware.py +130 -0
  27. django_clerk_users/organizations/models.py +316 -0
  28. django_clerk_users/organizations/webhooks.py +417 -0
  29. django_clerk_users/settings.py +37 -0
  30. django_clerk_users/testing.py +381 -0
  31. django_clerk_users/utils.py +210 -0
  32. django_clerk_users/webhooks/__init__.py +26 -0
  33. django_clerk_users/webhooks/handlers.py +346 -0
  34. django_clerk_users/webhooks/security.py +108 -0
  35. django_clerk_users/webhooks/signals.py +42 -0
  36. django_clerk_users/webhooks/views.py +76 -0
  37. django_clerk_users-0.0.2.dist-info/METADATA +228 -0
  38. django_clerk_users-0.0.2.dist-info/RECORD +41 -0
  39. django_clerk_users/main.py +0 -2
  40. django_clerk_users-0.0.1.dist-info/METADATA +0 -24
  41. django_clerk_users-0.0.1.dist-info/RECORD +0 -7
  42. {django_clerk_users-0.0.1.dist-info → django_clerk_users-0.0.2.dist-info}/WHEEL +0 -0
  43. {django_clerk_users-0.0.1.dist-info → django_clerk_users-0.0.2.dist-info}/licenses/LICENSE +0 -0
  44. {django_clerk_users-0.0.1.dist-info → django_clerk_users-0.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,191 @@
1
+ """
2
+ Management command to sync organizations from Clerk to Django.
3
+ """
4
+
5
+ from django.core.management.base import BaseCommand
6
+
7
+ from django_clerk_users.client import get_clerk_client
8
+
9
+
10
+ class Command(BaseCommand):
11
+ help = "Sync organizations from Clerk to Django database"
12
+
13
+ def add_arguments(self, parser):
14
+ parser.add_argument(
15
+ "--limit",
16
+ type=int,
17
+ default=100,
18
+ help="Maximum number of organizations to sync per batch (default: 100)",
19
+ )
20
+ parser.add_argument(
21
+ "--offset",
22
+ type=int,
23
+ default=0,
24
+ help="Offset to start syncing from (default: 0)",
25
+ )
26
+ parser.add_argument(
27
+ "--all",
28
+ action="store_true",
29
+ help="Sync all organizations (paginate through all results)",
30
+ )
31
+ parser.add_argument(
32
+ "--sync-members",
33
+ action="store_true",
34
+ help="Also sync organization members",
35
+ )
36
+ parser.add_argument(
37
+ "--dry-run",
38
+ action="store_true",
39
+ help="Show what would be synced without making changes",
40
+ )
41
+
42
+ def handle(self, *args, **options):
43
+ # Check if organizations app is installed
44
+ try:
45
+ from django_clerk_users.organizations.models import Organization
46
+ from django_clerk_users.organizations.webhooks import (
47
+ update_or_create_organization,
48
+ )
49
+ except ImportError:
50
+ self.stderr.write(
51
+ self.style.ERROR(
52
+ "Organizations app is not installed. "
53
+ "Add 'django_clerk_users.organizations' to INSTALLED_APPS."
54
+ )
55
+ )
56
+ return
57
+
58
+ limit = options["limit"]
59
+ offset = options["offset"]
60
+ sync_all = options["all"]
61
+ sync_members = options["sync_members"]
62
+ dry_run = options["dry_run"]
63
+
64
+ clerk = get_clerk_client()
65
+
66
+ created_count = 0
67
+ updated_count = 0
68
+ error_count = 0
69
+ total_count = 0
70
+
71
+ self.stdout.write("Starting organization sync from Clerk...")
72
+
73
+ if dry_run:
74
+ self.stdout.write(self.style.WARNING("DRY RUN - No changes will be made"))
75
+
76
+ while True:
77
+ self.stdout.write(
78
+ f"Fetching organizations (offset={offset}, limit={limit})..."
79
+ )
80
+
81
+ try:
82
+ response = clerk.organizations.list(limit=limit, offset=offset)
83
+ orgs = response.data if hasattr(response, "data") else response
84
+ except Exception as e:
85
+ self.stderr.write(
86
+ self.style.ERROR(f"Failed to fetch organizations: {e}")
87
+ )
88
+ break
89
+
90
+ if not orgs:
91
+ self.stdout.write("No more organizations to sync.")
92
+ break
93
+
94
+ for clerk_org in orgs:
95
+ total_count += 1
96
+ org_id = getattr(clerk_org, "id", None)
97
+ name = getattr(clerk_org, "name", "Unknown")
98
+
99
+ if not org_id:
100
+ error_count += 1
101
+ continue
102
+
103
+ if dry_run:
104
+ self.stdout.write(f" Would sync: {name} ({org_id})")
105
+ continue
106
+
107
+ try:
108
+ organization, created = update_or_create_organization(org_id)
109
+ if created:
110
+ created_count += 1
111
+ self.stdout.write(
112
+ self.style.SUCCESS(f" Created: {organization.name}")
113
+ )
114
+ else:
115
+ updated_count += 1
116
+ self.stdout.write(f" Updated: {organization.name}")
117
+
118
+ if sync_members and organization:
119
+ self._sync_organization_members(organization, org_id, dry_run)
120
+
121
+ except Exception as e:
122
+ error_count += 1
123
+ self.stderr.write(
124
+ self.style.ERROR(f" Failed to sync {name}: {e}")
125
+ )
126
+
127
+ if not sync_all:
128
+ break
129
+
130
+ offset += limit
131
+
132
+ self.stdout.write("")
133
+ self.stdout.write(self.style.SUCCESS("Sync complete!"))
134
+ self.stdout.write(f" Total processed: {total_count}")
135
+ if not dry_run:
136
+ self.stdout.write(f" Created: {created_count}")
137
+ self.stdout.write(f" Updated: {updated_count}")
138
+ self.stdout.write(f" Errors: {error_count}")
139
+
140
+ def _sync_organization_members(self, organization, org_id, dry_run):
141
+ """Sync members for a specific organization."""
142
+ from django.contrib.auth import get_user_model
143
+
144
+ from django_clerk_users.client import get_clerk_client
145
+ from django_clerk_users.organizations.models import OrganizationMember
146
+ from django_clerk_users.utils import update_or_create_clerk_user
147
+
148
+ User = get_user_model()
149
+ clerk = get_clerk_client()
150
+
151
+ try:
152
+ response = clerk.organizations.get_membership_list(
153
+ organization_id=org_id, limit=100
154
+ )
155
+ memberships = response.data if hasattr(response, "data") else response
156
+ except Exception as e:
157
+ self.stderr.write(
158
+ self.style.WARNING(f" Failed to fetch members: {e}")
159
+ )
160
+ return
161
+
162
+ for membership in memberships or []:
163
+ membership_id = getattr(membership, "id", None)
164
+ user_data = getattr(membership, "public_user_data", None)
165
+ user_id = getattr(user_data, "user_id", None) if user_data else None
166
+
167
+ if not membership_id or not user_id:
168
+ continue
169
+
170
+ if dry_run:
171
+ self.stdout.write(f" Would sync member: {user_id}")
172
+ continue
173
+
174
+ try:
175
+ user = User.objects.filter(clerk_id=user_id).first()
176
+ if not user:
177
+ user, _ = update_or_create_clerk_user(user_id)
178
+
179
+ OrganizationMember.objects.update_or_create(
180
+ clerk_membership_id=membership_id,
181
+ defaults={
182
+ "organization": organization,
183
+ "user": user,
184
+ "role": getattr(membership, "role", "member"),
185
+ },
186
+ )
187
+ self.stdout.write(f" Synced member: {user.email}")
188
+ except Exception as e:
189
+ self.stderr.write(
190
+ self.style.WARNING(f" Failed to sync member {user_id}: {e}")
191
+ )
@@ -0,0 +1,114 @@
1
+ """
2
+ Management command to sync users from Clerk to Django.
3
+ """
4
+
5
+ from django.core.management.base import BaseCommand
6
+
7
+ from django_clerk_users.client import get_clerk_client
8
+ from django_clerk_users.utils import update_or_create_clerk_user
9
+
10
+
11
+ class Command(BaseCommand):
12
+ help = "Sync users from Clerk to Django database"
13
+
14
+ def add_arguments(self, parser):
15
+ parser.add_argument(
16
+ "--limit",
17
+ type=int,
18
+ default=100,
19
+ help="Maximum number of users to sync per batch (default: 100)",
20
+ )
21
+ parser.add_argument(
22
+ "--offset",
23
+ type=int,
24
+ default=0,
25
+ help="Offset to start syncing from (default: 0)",
26
+ )
27
+ parser.add_argument(
28
+ "--all",
29
+ action="store_true",
30
+ help="Sync all users (paginate through all results)",
31
+ )
32
+ parser.add_argument(
33
+ "--dry-run",
34
+ action="store_true",
35
+ help="Show what would be synced without making changes",
36
+ )
37
+
38
+ def handle(self, *args, **options):
39
+ limit = options["limit"]
40
+ offset = options["offset"]
41
+ sync_all = options["all"]
42
+ dry_run = options["dry_run"]
43
+
44
+ clerk = get_clerk_client()
45
+
46
+ created_count = 0
47
+ updated_count = 0
48
+ error_count = 0
49
+ total_count = 0
50
+
51
+ self.stdout.write("Starting user sync from Clerk...")
52
+
53
+ if dry_run:
54
+ self.stdout.write(self.style.WARNING("DRY RUN - No changes will be made"))
55
+
56
+ while True:
57
+ self.stdout.write(f"Fetching users (offset={offset}, limit={limit})...")
58
+
59
+ try:
60
+ response = clerk.users.list(limit=limit, offset=offset)
61
+ users = response.data if hasattr(response, "data") else response
62
+ except Exception as e:
63
+ self.stderr.write(self.style.ERROR(f"Failed to fetch users: {e}"))
64
+ break
65
+
66
+ if not users:
67
+ self.stdout.write("No more users to sync.")
68
+ break
69
+
70
+ for clerk_user in users:
71
+ total_count += 1
72
+ clerk_id = getattr(clerk_user, "id", None)
73
+
74
+ if not clerk_id:
75
+ error_count += 1
76
+ continue
77
+
78
+ email = None
79
+ email_addresses = getattr(clerk_user, "email_addresses", []) or []
80
+ if email_addresses:
81
+ email = getattr(email_addresses[0], "email_address", None)
82
+
83
+ if dry_run:
84
+ self.stdout.write(f" Would sync: {email} ({clerk_id})")
85
+ continue
86
+
87
+ try:
88
+ user, created = update_or_create_clerk_user(clerk_id)
89
+ if created:
90
+ created_count += 1
91
+ self.stdout.write(
92
+ self.style.SUCCESS(f" Created: {user.email}")
93
+ )
94
+ else:
95
+ updated_count += 1
96
+ self.stdout.write(f" Updated: {user.email}")
97
+ except Exception as e:
98
+ error_count += 1
99
+ self.stderr.write(
100
+ self.style.ERROR(f" Failed to sync {clerk_id}: {e}")
101
+ )
102
+
103
+ if not sync_all:
104
+ break
105
+
106
+ offset += limit
107
+
108
+ self.stdout.write("")
109
+ self.stdout.write(self.style.SUCCESS("Sync complete!"))
110
+ self.stdout.write(f" Total processed: {total_count}")
111
+ if not dry_run:
112
+ self.stdout.write(f" Created: {created_count}")
113
+ self.stdout.write(f" Updated: {updated_count}")
114
+ self.stdout.write(f" Errors: {error_count}")
@@ -0,0 +1,121 @@
1
+ """
2
+ Custom managers for django-clerk-users models.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ from django.contrib.auth.models import BaseUserManager
10
+
11
+ if TYPE_CHECKING:
12
+ from django_clerk_users.models import AbstractClerkUser
13
+
14
+
15
+ class ClerkUserManager(BaseUserManager["AbstractClerkUser"]):
16
+ """
17
+ Custom manager for ClerkUser model.
18
+
19
+ Handles user creation with clerk_id as the primary identifier.
20
+ """
21
+
22
+ def create_user(
23
+ self,
24
+ clerk_id: str,
25
+ email: str,
26
+ password: str | None = None,
27
+ **extra_fields: Any,
28
+ ) -> "AbstractClerkUser":
29
+ """
30
+ Create and save a user with the given clerk_id and email.
31
+
32
+ Args:
33
+ clerk_id: The Clerk user ID.
34
+ email: The user's email address.
35
+ password: Optional password (not used for Clerk auth, but required
36
+ for Django admin compatibility).
37
+ **extra_fields: Additional fields for the user model.
38
+
39
+ Returns:
40
+ The created user instance.
41
+
42
+ Raises:
43
+ ValueError: If clerk_id or email is not provided.
44
+ """
45
+ if not clerk_id:
46
+ raise ValueError("The clerk_id must be set")
47
+ if not email:
48
+ raise ValueError("The email must be set")
49
+
50
+ email = self.normalize_email(email)
51
+ extra_fields.setdefault("is_active", True)
52
+ extra_fields.setdefault("is_staff", False)
53
+ extra_fields.setdefault("is_superuser", False)
54
+
55
+ user = self.model(clerk_id=clerk_id, email=email, **extra_fields)
56
+ if password:
57
+ user.set_password(password)
58
+ else:
59
+ user.set_unusable_password()
60
+ user.save(using=self._db)
61
+ return user
62
+
63
+ def create_superuser(
64
+ self,
65
+ clerk_id: str,
66
+ email: str,
67
+ password: str | None = None,
68
+ **extra_fields: Any,
69
+ ) -> "AbstractClerkUser":
70
+ """
71
+ Create and save a superuser with the given clerk_id and email.
72
+
73
+ Args:
74
+ clerk_id: The Clerk user ID.
75
+ email: The user's email address.
76
+ password: Optional password.
77
+ **extra_fields: Additional fields for the user model.
78
+
79
+ Returns:
80
+ The created superuser instance.
81
+ """
82
+ extra_fields.setdefault("is_staff", True)
83
+ extra_fields.setdefault("is_superuser", True)
84
+ extra_fields.setdefault("is_active", True)
85
+
86
+ if extra_fields.get("is_staff") is not True:
87
+ raise ValueError("Superuser must have is_staff=True.")
88
+ if extra_fields.get("is_superuser") is not True:
89
+ raise ValueError("Superuser must have is_superuser=True.")
90
+
91
+ return self.create_user(clerk_id, email, password, **extra_fields)
92
+
93
+ def get_by_clerk_id(self, clerk_id: str) -> "AbstractClerkUser | None":
94
+ """
95
+ Get a user by their Clerk ID.
96
+
97
+ Args:
98
+ clerk_id: The Clerk user ID.
99
+
100
+ Returns:
101
+ The user instance or None if not found.
102
+ """
103
+ try:
104
+ return self.get(clerk_id=clerk_id)
105
+ except self.model.DoesNotExist:
106
+ return None
107
+
108
+ def get_by_email(self, email: str) -> "AbstractClerkUser | None":
109
+ """
110
+ Get a user by their email address.
111
+
112
+ Args:
113
+ email: The user's email address.
114
+
115
+ Returns:
116
+ The user instance or None if not found.
117
+ """
118
+ try:
119
+ return self.get(email=self.normalize_email(email))
120
+ except self.model.DoesNotExist:
121
+ return None
@@ -0,0 +1,9 @@
1
+ """
2
+ Middleware for django-clerk-users.
3
+ """
4
+
5
+ from django_clerk_users.middleware.auth import ClerkAuthMiddleware
6
+
7
+ __all__ = [
8
+ "ClerkAuthMiddleware",
9
+ ]
@@ -0,0 +1,201 @@
1
+ """
2
+ Clerk authentication middleware.
3
+
4
+ This middleware validates Clerk JWT tokens and creates Django sessions
5
+ for authenticated users. It uses manual session handling instead of
6
+ Django's login() to avoid triggering signals that may conflict with Clerk.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ import time
13
+ from typing import TYPE_CHECKING, Callable
14
+
15
+ from django.conf import settings
16
+ from django.contrib.auth.models import AnonymousUser
17
+
18
+ from django_clerk_users.authentication.utils import (
19
+ get_clerk_payload_from_request,
20
+ get_or_create_user_from_payload,
21
+ )
22
+ from django_clerk_users.exceptions import ClerkAuthenticationError, ClerkTokenError
23
+ from django_clerk_users.settings import CLERK_SESSION_REVALIDATION_SECONDS
24
+
25
+ if TYPE_CHECKING:
26
+ from django.http import HttpRequest, HttpResponse
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ # Authentication backend path
31
+ CLERK_BACKEND = "django_clerk_users.authentication.ClerkBackend"
32
+
33
+
34
+ class ClerkAuthMiddleware:
35
+ """
36
+ Middleware that authenticates users via Clerk JWT tokens.
37
+
38
+ This middleware implements a session-based optimization strategy:
39
+ 1. On first request: Validates JWT, creates Django session
40
+ 2. On subsequent requests: Uses session (avoids repeated validation)
41
+ 3. Periodically re-validates to detect token expiration
42
+
43
+ After processing, the middleware sets:
44
+ - request.user: The authenticated Django user (or AnonymousUser)
45
+ - request.clerk_user: Same as request.user (for explicit Clerk access)
46
+ - request.clerk_payload: The decoded JWT payload (if authenticated)
47
+ - request.org: The organization ID from the token (if present)
48
+
49
+ Note: This middleware manually sets session data instead of using Django's
50
+ login() function to avoid triggering signals that may conflict with Clerk's
51
+ authentication flow.
52
+ """
53
+
54
+ def __init__(self, get_response: Callable[["HttpRequest"], "HttpResponse"]):
55
+ self.get_response = get_response
56
+ self.debug = getattr(settings, "DEBUG", False)
57
+
58
+ def __call__(self, request: "HttpRequest") -> "HttpResponse":
59
+ # Process authentication before the view
60
+ self.process_request(request)
61
+
62
+ # Call the next middleware/view
63
+ response = self.get_response(request)
64
+
65
+ return response
66
+
67
+ def process_request(self, request: "HttpRequest") -> None:
68
+ """
69
+ Process the request and authenticate the user.
70
+
71
+ Sets request.user, request.clerk_user, request.clerk_payload,
72
+ and request.org based on the authentication result.
73
+ """
74
+ # Initialize attributes
75
+ request.clerk_user = None # type: ignore
76
+ request.clerk_payload = None # type: ignore
77
+ request.org = None # type: ignore
78
+
79
+ # Check if user is already authenticated via session
80
+ if self._is_session_valid(request):
81
+ # User is already authenticated, just set clerk attributes
82
+ request.clerk_user = request.user # type: ignore
83
+ request.org = request.session.get("clerk_org_id") # type: ignore
84
+ return
85
+
86
+ # Try to authenticate via JWT token
87
+ try:
88
+ payload = get_clerk_payload_from_request(request)
89
+ except ClerkTokenError as e:
90
+ logger.debug(f"Token validation failed: {e}")
91
+ self._set_anonymous(request)
92
+ return
93
+
94
+ if not payload:
95
+ # No token provided - anonymous user
96
+ self._set_anonymous(request)
97
+ return
98
+
99
+ # Get or create the Django user
100
+ try:
101
+ user, created = get_or_create_user_from_payload(payload)
102
+ except ClerkAuthenticationError as e:
103
+ logger.warning(f"Failed to get/create user: {e}")
104
+ self._set_anonymous(request)
105
+ return
106
+ except Exception as e:
107
+ logger.error(f"ClerkAuthMiddleware error: {e}", exc_info=True)
108
+ if self.debug:
109
+ raise
110
+ self._set_anonymous(request)
111
+ return
112
+
113
+ if not user.is_active:
114
+ logger.debug(f"User {user.clerk_id} is inactive")
115
+ self._set_anonymous(request)
116
+ return
117
+
118
+ # Create Django session (without calling login())
119
+ self._create_session(request, user, payload)
120
+
121
+ # Set request attributes
122
+ user.backend = CLERK_BACKEND
123
+ request.user = user
124
+ request.clerk_user = user # type: ignore
125
+ request.clerk_payload = payload # type: ignore
126
+ request.org = payload.get("org_id") # type: ignore
127
+
128
+ if created:
129
+ logger.info(f"Created new user: {user.email} ({user.clerk_id})")
130
+
131
+ def _is_session_valid(self, request: "HttpRequest") -> bool:
132
+ """
133
+ Check if the current session is valid and doesn't need revalidation.
134
+
135
+ Returns True if:
136
+ 1. User is authenticated in session
137
+ 2. The session hasn't expired
138
+ 3. The re-validation interval hasn't passed
139
+ """
140
+ if not hasattr(request, "user") or not request.user.is_authenticated:
141
+ return False
142
+
143
+ # Check if re-validation is needed
144
+ last_check = request.session.get("last_clerk_check", 0)
145
+ now = int(time.time())
146
+
147
+ if now - last_check > CLERK_SESSION_REVALIDATION_SECONDS:
148
+ # Re-validation needed - try to validate the token
149
+ try:
150
+ payload = get_clerk_payload_from_request(request)
151
+ if payload:
152
+ # Token is still valid, update session
153
+ request.session["last_clerk_check"] = now
154
+ request.session["clerk_org_id"] = payload.get("org_id")
155
+ request.clerk_payload = payload # type: ignore
156
+ return True
157
+
158
+ # Missing token or invalid payload - end the session
159
+ self._clear_session(request)
160
+ return False
161
+ except ClerkTokenError:
162
+ # Token is invalid - invalidate session
163
+ self._clear_session(request)
164
+ return False
165
+
166
+ return True
167
+
168
+ def _create_session(self, request: "HttpRequest", user, payload: dict) -> None:
169
+ """
170
+ Create a Django session for the authenticated user.
171
+
172
+ Note: We manually set session data instead of calling login() to avoid
173
+ triggering Django signals (like user_logged_in) that may conflict with
174
+ Clerk's authentication flow.
175
+ """
176
+ # Manually set session auth data (what login() would do internally)
177
+ request.session["_auth_user_id"] = str(user.pk)
178
+ request.session["_auth_user_backend"] = CLERK_BACKEND
179
+ request.session["_auth_user_hash"] = ""
180
+
181
+ # Store Clerk-specific session data
182
+ request.session["last_clerk_check"] = int(time.time())
183
+ request.session["clerk_org_id"] = payload.get("org_id")
184
+
185
+ logger.debug(f"Created session for user {user.email}")
186
+
187
+ def _clear_session(self, request: "HttpRequest") -> None:
188
+ """
189
+ Clear the Django session.
190
+ """
191
+ request.session.flush()
192
+
193
+ def _set_anonymous(self, request: "HttpRequest") -> None:
194
+ """
195
+ Set the request user to anonymous.
196
+ """
197
+ if not hasattr(request, "user") or request.user.is_authenticated:
198
+ request.user = AnonymousUser()
199
+ request.clerk_user = None # type: ignore
200
+ request.clerk_payload = None # type: ignore
201
+ request.org = None # type: ignore