django-clerk-users 0.0.1__py3-none-any.whl → 0.1.0__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.
- django_clerk_users/__init__.py +78 -7
- django_clerk_users/apps.py +20 -0
- django_clerk_users/authentication/__init__.py +24 -0
- django_clerk_users/authentication/backends.py +89 -0
- django_clerk_users/authentication/drf.py +111 -0
- django_clerk_users/authentication/utils.py +171 -0
- django_clerk_users/caching.py +161 -0
- django_clerk_users/checks.py +127 -0
- django_clerk_users/client.py +32 -0
- django_clerk_users/decorators.py +181 -0
- django_clerk_users/exceptions.py +51 -0
- django_clerk_users/management/__init__.py +0 -0
- django_clerk_users/management/commands/__init__.py +0 -0
- django_clerk_users/management/commands/migrate_users_to_clerk.py +223 -0
- django_clerk_users/management/commands/sync_clerk_organizations.py +191 -0
- django_clerk_users/management/commands/sync_clerk_users.py +114 -0
- django_clerk_users/managers.py +120 -0
- django_clerk_users/middleware/__init__.py +9 -0
- django_clerk_users/middleware/auth.py +230 -0
- django_clerk_users/migrations/0001_initial.py +174 -0
- django_clerk_users/migrations/0002_make_clerk_id_nullable.py +24 -0
- django_clerk_users/migrations/__init__.py +0 -0
- django_clerk_users/models.py +180 -0
- django_clerk_users/organizations/__init__.py +8 -0
- django_clerk_users/organizations/admin.py +81 -0
- django_clerk_users/organizations/apps.py +8 -0
- django_clerk_users/organizations/middleware.py +130 -0
- django_clerk_users/organizations/migrations/0001_initial.py +349 -0
- django_clerk_users/organizations/migrations/__init__.py +0 -0
- django_clerk_users/organizations/models.py +314 -0
- django_clerk_users/organizations/webhooks.py +417 -0
- django_clerk_users/settings.py +37 -0
- django_clerk_users/testing.py +381 -0
- django_clerk_users/utils.py +210 -0
- django_clerk_users/webhooks/__init__.py +26 -0
- django_clerk_users/webhooks/handlers.py +346 -0
- django_clerk_users/webhooks/security.py +108 -0
- django_clerk_users/webhooks/signals.py +42 -0
- django_clerk_users/webhooks/views.py +76 -0
- django_clerk_users-0.1.0.dist-info/METADATA +311 -0
- django_clerk_users-0.1.0.dist-info/RECORD +43 -0
- {django_clerk_users-0.0.1.dist-info → django_clerk_users-0.1.0.dist-info}/WHEEL +1 -2
- django_clerk_users/main.py +0 -2
- django_clerk_users-0.0.1.dist-info/METADATA +0 -24
- django_clerk_users-0.0.1.dist-info/RECORD +0 -7
- django_clerk_users-0.0.1.dist-info/top_level.txt +0 -1
- {django_clerk_users-0.0.1.dist-info → django_clerk_users-0.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Webhook handlers for organization events.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from django.db import transaction
|
|
11
|
+
|
|
12
|
+
from django_clerk_users.caching import invalidate_organization_cache
|
|
13
|
+
from django_clerk_users.webhooks.handlers import parse_clerk_timestamp
|
|
14
|
+
from django_clerk_users.webhooks.signals import (
|
|
15
|
+
clerk_invitation_accepted,
|
|
16
|
+
clerk_invitation_created,
|
|
17
|
+
clerk_invitation_revoked,
|
|
18
|
+
clerk_membership_created,
|
|
19
|
+
clerk_membership_deleted,
|
|
20
|
+
clerk_membership_updated,
|
|
21
|
+
clerk_organization_created,
|
|
22
|
+
clerk_organization_deleted,
|
|
23
|
+
clerk_organization_updated,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def update_or_create_organization(org_id: str) -> tuple:
|
|
30
|
+
"""
|
|
31
|
+
Update or create an Organization from Clerk data.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
org_id: The Clerk organization ID.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
A tuple of (organization, created).
|
|
38
|
+
"""
|
|
39
|
+
from django_clerk_users.client import get_clerk_client
|
|
40
|
+
from django_clerk_users.organizations.models import Organization
|
|
41
|
+
|
|
42
|
+
clerk = get_clerk_client()
|
|
43
|
+
clerk_org = clerk.organizations.get(organization_id=org_id)
|
|
44
|
+
|
|
45
|
+
if not clerk_org:
|
|
46
|
+
logger.error(f"Organization not found in Clerk: {org_id}")
|
|
47
|
+
return None, False
|
|
48
|
+
|
|
49
|
+
org_data = {
|
|
50
|
+
"name": getattr(clerk_org, "name", ""),
|
|
51
|
+
"slug": getattr(clerk_org, "slug", ""),
|
|
52
|
+
"image_url": getattr(clerk_org, "image_url", "") or "",
|
|
53
|
+
"public_metadata": getattr(clerk_org, "public_metadata", {}) or {},
|
|
54
|
+
"private_metadata": getattr(clerk_org, "private_metadata", {}) or {},
|
|
55
|
+
"members_count": getattr(clerk_org, "members_count", 0) or 0,
|
|
56
|
+
"pending_invitations_count": getattr(clerk_org, "pending_invitations_count", 0) or 0,
|
|
57
|
+
"max_allowed_memberships": getattr(clerk_org, "max_allowed_memberships", 0) or 0,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
created_at = parse_clerk_timestamp(getattr(clerk_org, "created_at", None))
|
|
61
|
+
if created_at:
|
|
62
|
+
org_data["created_at"] = created_at
|
|
63
|
+
|
|
64
|
+
organization, created = Organization.objects.update_or_create(
|
|
65
|
+
clerk_id=org_id,
|
|
66
|
+
defaults=org_data,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return organization, created
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@transaction.atomic
|
|
73
|
+
def handle_organization_created(data: dict[str, Any]) -> bool:
|
|
74
|
+
"""Handle organization.created webhook event."""
|
|
75
|
+
from django_clerk_users.organizations.models import Organization
|
|
76
|
+
|
|
77
|
+
org_id = data.get("id")
|
|
78
|
+
if not org_id:
|
|
79
|
+
logger.error("organization.created webhook missing organization ID")
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
organization, created = update_or_create_organization(org_id)
|
|
84
|
+
if organization:
|
|
85
|
+
clerk_organization_created.send(
|
|
86
|
+
sender=Organization,
|
|
87
|
+
organization=organization,
|
|
88
|
+
clerk_data=data,
|
|
89
|
+
)
|
|
90
|
+
logger.info(f"Organization created: {organization.name}")
|
|
91
|
+
return True
|
|
92
|
+
return False
|
|
93
|
+
except Exception as e:
|
|
94
|
+
logger.error(f"Failed to handle organization.created: {e}")
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@transaction.atomic
|
|
99
|
+
def handle_organization_updated(data: dict[str, Any]) -> bool:
|
|
100
|
+
"""Handle organization.updated webhook event."""
|
|
101
|
+
from django_clerk_users.organizations.models import Organization
|
|
102
|
+
|
|
103
|
+
org_id = data.get("id")
|
|
104
|
+
if not org_id:
|
|
105
|
+
logger.error("organization.updated webhook missing organization ID")
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
invalidate_organization_cache(org_id)
|
|
110
|
+
organization, created = update_or_create_organization(org_id)
|
|
111
|
+
if organization:
|
|
112
|
+
clerk_organization_updated.send(
|
|
113
|
+
sender=Organization,
|
|
114
|
+
organization=organization,
|
|
115
|
+
clerk_data=data,
|
|
116
|
+
)
|
|
117
|
+
logger.info(f"Organization updated: {organization.name}")
|
|
118
|
+
return True
|
|
119
|
+
return False
|
|
120
|
+
except Exception as e:
|
|
121
|
+
logger.error(f"Failed to handle organization.updated: {e}")
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@transaction.atomic
|
|
126
|
+
def handle_organization_deleted(data: dict[str, Any]) -> bool:
|
|
127
|
+
"""Handle organization.deleted webhook event."""
|
|
128
|
+
from django_clerk_users.organizations.models import Organization
|
|
129
|
+
|
|
130
|
+
org_id = data.get("id")
|
|
131
|
+
if not org_id:
|
|
132
|
+
logger.error("organization.deleted webhook missing organization ID")
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
invalidate_organization_cache(org_id)
|
|
137
|
+
organization = Organization.objects.filter(clerk_id=org_id).first()
|
|
138
|
+
if organization:
|
|
139
|
+
organization.is_active = False
|
|
140
|
+
organization.save(update_fields=["is_active", "updated_at"])
|
|
141
|
+
clerk_organization_deleted.send(
|
|
142
|
+
sender=Organization,
|
|
143
|
+
organization=organization,
|
|
144
|
+
clerk_data=data,
|
|
145
|
+
)
|
|
146
|
+
logger.info(f"Organization deleted: {organization.name}")
|
|
147
|
+
return True
|
|
148
|
+
except Exception as e:
|
|
149
|
+
logger.error(f"Failed to handle organization.deleted: {e}")
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@transaction.atomic
|
|
154
|
+
def handle_membership_created(data: dict[str, Any]) -> bool:
|
|
155
|
+
"""Handle organizationMembership.created webhook event."""
|
|
156
|
+
from django.contrib.auth import get_user_model
|
|
157
|
+
|
|
158
|
+
from django_clerk_users.organizations.models import Organization, OrganizationMember
|
|
159
|
+
|
|
160
|
+
User = get_user_model()
|
|
161
|
+
|
|
162
|
+
membership_id = data.get("id")
|
|
163
|
+
org_data = data.get("organization", {})
|
|
164
|
+
user_data = data.get("public_user_data", {})
|
|
165
|
+
|
|
166
|
+
org_id = org_data.get("id")
|
|
167
|
+
user_id = user_data.get("user_id")
|
|
168
|
+
|
|
169
|
+
if not all([membership_id, org_id, user_id]):
|
|
170
|
+
logger.error("organizationMembership.created webhook missing required fields")
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
organization = Organization.objects.filter(clerk_id=org_id).first()
|
|
175
|
+
if not organization:
|
|
176
|
+
organization, _ = update_or_create_organization(org_id)
|
|
177
|
+
|
|
178
|
+
user = User.objects.filter(clerk_id=user_id).first()
|
|
179
|
+
if not user:
|
|
180
|
+
from django_clerk_users.utils import update_or_create_clerk_user
|
|
181
|
+
user, _ = update_or_create_clerk_user(user_id)
|
|
182
|
+
|
|
183
|
+
membership, created = OrganizationMember.objects.update_or_create(
|
|
184
|
+
clerk_membership_id=membership_id,
|
|
185
|
+
defaults={
|
|
186
|
+
"organization": organization,
|
|
187
|
+
"user": user,
|
|
188
|
+
"role": data.get("role", "member"),
|
|
189
|
+
"public_metadata": data.get("public_metadata", {}),
|
|
190
|
+
"private_metadata": data.get("private_metadata", {}),
|
|
191
|
+
},
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
joined_at = parse_clerk_timestamp(data.get("created_at"))
|
|
195
|
+
if joined_at:
|
|
196
|
+
membership.joined_at = joined_at
|
|
197
|
+
membership.save(update_fields=["joined_at"])
|
|
198
|
+
|
|
199
|
+
clerk_membership_created.send(
|
|
200
|
+
sender=OrganizationMember,
|
|
201
|
+
membership=membership,
|
|
202
|
+
clerk_data=data,
|
|
203
|
+
)
|
|
204
|
+
logger.info(f"Membership created: {user.email} in {organization.name}")
|
|
205
|
+
return True
|
|
206
|
+
|
|
207
|
+
except Exception as e:
|
|
208
|
+
logger.error(f"Failed to handle organizationMembership.created: {e}")
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@transaction.atomic
|
|
213
|
+
def handle_membership_updated(data: dict[str, Any]) -> bool:
|
|
214
|
+
"""Handle organizationMembership.updated webhook event."""
|
|
215
|
+
from django_clerk_users.organizations.models import OrganizationMember
|
|
216
|
+
|
|
217
|
+
membership_id = data.get("id")
|
|
218
|
+
if not membership_id:
|
|
219
|
+
logger.error("organizationMembership.updated webhook missing membership ID")
|
|
220
|
+
return False
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
membership = OrganizationMember.objects.filter(
|
|
224
|
+
clerk_membership_id=membership_id
|
|
225
|
+
).first()
|
|
226
|
+
if membership:
|
|
227
|
+
membership.role = data.get("role", membership.role)
|
|
228
|
+
membership.public_metadata = data.get("public_metadata", membership.public_metadata)
|
|
229
|
+
membership.private_metadata = data.get("private_metadata", membership.private_metadata)
|
|
230
|
+
membership.save()
|
|
231
|
+
|
|
232
|
+
clerk_membership_updated.send(
|
|
233
|
+
sender=OrganizationMember,
|
|
234
|
+
membership=membership,
|
|
235
|
+
clerk_data=data,
|
|
236
|
+
)
|
|
237
|
+
logger.info(f"Membership updated: {membership}")
|
|
238
|
+
return True
|
|
239
|
+
|
|
240
|
+
except Exception as e:
|
|
241
|
+
logger.error(f"Failed to handle organizationMembership.updated: {e}")
|
|
242
|
+
return False
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@transaction.atomic
|
|
246
|
+
def handle_membership_deleted(data: dict[str, Any]) -> bool:
|
|
247
|
+
"""Handle organizationMembership.deleted webhook event."""
|
|
248
|
+
from django_clerk_users.organizations.models import OrganizationMember
|
|
249
|
+
|
|
250
|
+
membership_id = data.get("id")
|
|
251
|
+
if not membership_id:
|
|
252
|
+
logger.error("organizationMembership.deleted webhook missing membership ID")
|
|
253
|
+
return False
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
membership = OrganizationMember.objects.filter(
|
|
257
|
+
clerk_membership_id=membership_id
|
|
258
|
+
).first()
|
|
259
|
+
if membership:
|
|
260
|
+
clerk_membership_deleted.send(
|
|
261
|
+
sender=OrganizationMember,
|
|
262
|
+
membership=membership,
|
|
263
|
+
clerk_data=data,
|
|
264
|
+
)
|
|
265
|
+
membership.delete()
|
|
266
|
+
logger.info(f"Membership deleted: {membership_id}")
|
|
267
|
+
return True
|
|
268
|
+
|
|
269
|
+
except Exception as e:
|
|
270
|
+
logger.error(f"Failed to handle organizationMembership.deleted: {e}")
|
|
271
|
+
return False
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
@transaction.atomic
|
|
275
|
+
def handle_invitation_created(data: dict[str, Any]) -> bool:
|
|
276
|
+
"""Handle organizationInvitation.created webhook event."""
|
|
277
|
+
from django.contrib.auth import get_user_model
|
|
278
|
+
|
|
279
|
+
from django_clerk_users.organizations.models import Organization, OrganizationInvitation
|
|
280
|
+
|
|
281
|
+
User = get_user_model()
|
|
282
|
+
|
|
283
|
+
invitation_id = data.get("id")
|
|
284
|
+
org_id = data.get("organization_id")
|
|
285
|
+
email = data.get("email_address")
|
|
286
|
+
|
|
287
|
+
if not all([invitation_id, org_id, email]):
|
|
288
|
+
logger.error("organizationInvitation.created webhook missing required fields")
|
|
289
|
+
return False
|
|
290
|
+
|
|
291
|
+
try:
|
|
292
|
+
organization = Organization.objects.filter(clerk_id=org_id).first()
|
|
293
|
+
if not organization:
|
|
294
|
+
organization, _ = update_or_create_organization(org_id)
|
|
295
|
+
|
|
296
|
+
inviter = None
|
|
297
|
+
inviter_id = data.get("inviter_user_id")
|
|
298
|
+
if inviter_id:
|
|
299
|
+
inviter = User.objects.filter(clerk_id=inviter_id).first()
|
|
300
|
+
|
|
301
|
+
invitation, created = OrganizationInvitation.objects.update_or_create(
|
|
302
|
+
clerk_invitation_id=invitation_id,
|
|
303
|
+
defaults={
|
|
304
|
+
"organization": organization,
|
|
305
|
+
"inviter": inviter,
|
|
306
|
+
"email_address": email,
|
|
307
|
+
"role": data.get("role", "member"),
|
|
308
|
+
"status": OrganizationInvitation.Status.PENDING,
|
|
309
|
+
"public_metadata": data.get("public_metadata", {}),
|
|
310
|
+
"private_metadata": data.get("private_metadata", {}),
|
|
311
|
+
},
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
clerk_invitation_created.send(
|
|
315
|
+
sender=OrganizationInvitation,
|
|
316
|
+
invitation=invitation,
|
|
317
|
+
clerk_data=data,
|
|
318
|
+
)
|
|
319
|
+
logger.info(f"Invitation created: {email} to {organization.name}")
|
|
320
|
+
return True
|
|
321
|
+
|
|
322
|
+
except Exception as e:
|
|
323
|
+
logger.error(f"Failed to handle organizationInvitation.created: {e}")
|
|
324
|
+
return False
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
@transaction.atomic
|
|
328
|
+
def handle_invitation_accepted(data: dict[str, Any]) -> bool:
|
|
329
|
+
"""Handle organizationInvitation.accepted webhook event."""
|
|
330
|
+
from django_clerk_users.organizations.models import OrganizationInvitation
|
|
331
|
+
|
|
332
|
+
invitation_id = data.get("id")
|
|
333
|
+
if not invitation_id:
|
|
334
|
+
logger.error("organizationInvitation.accepted webhook missing invitation ID")
|
|
335
|
+
return False
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
invitation = OrganizationInvitation.objects.filter(
|
|
339
|
+
clerk_invitation_id=invitation_id
|
|
340
|
+
).first()
|
|
341
|
+
if invitation:
|
|
342
|
+
invitation.status = OrganizationInvitation.Status.ACCEPTED
|
|
343
|
+
invitation.save(update_fields=["status", "updated_at"])
|
|
344
|
+
|
|
345
|
+
clerk_invitation_accepted.send(
|
|
346
|
+
sender=OrganizationInvitation,
|
|
347
|
+
invitation=invitation,
|
|
348
|
+
clerk_data=data,
|
|
349
|
+
)
|
|
350
|
+
logger.info(f"Invitation accepted: {invitation.email_address}")
|
|
351
|
+
return True
|
|
352
|
+
|
|
353
|
+
except Exception as e:
|
|
354
|
+
logger.error(f"Failed to handle organizationInvitation.accepted: {e}")
|
|
355
|
+
return False
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
@transaction.atomic
|
|
359
|
+
def handle_invitation_revoked(data: dict[str, Any]) -> bool:
|
|
360
|
+
"""Handle organizationInvitation.revoked webhook event."""
|
|
361
|
+
from django_clerk_users.organizations.models import OrganizationInvitation
|
|
362
|
+
|
|
363
|
+
invitation_id = data.get("id")
|
|
364
|
+
if not invitation_id:
|
|
365
|
+
logger.error("organizationInvitation.revoked webhook missing invitation ID")
|
|
366
|
+
return False
|
|
367
|
+
|
|
368
|
+
try:
|
|
369
|
+
invitation = OrganizationInvitation.objects.filter(
|
|
370
|
+
clerk_invitation_id=invitation_id
|
|
371
|
+
).first()
|
|
372
|
+
if invitation:
|
|
373
|
+
invitation.status = OrganizationInvitation.Status.REVOKED
|
|
374
|
+
invitation.save(update_fields=["status", "updated_at"])
|
|
375
|
+
|
|
376
|
+
clerk_invitation_revoked.send(
|
|
377
|
+
sender=OrganizationInvitation,
|
|
378
|
+
invitation=invitation,
|
|
379
|
+
clerk_data=data,
|
|
380
|
+
)
|
|
381
|
+
logger.info(f"Invitation revoked: {invitation.email_address}")
|
|
382
|
+
return True
|
|
383
|
+
|
|
384
|
+
except Exception as e:
|
|
385
|
+
logger.error(f"Failed to handle organizationInvitation.revoked: {e}")
|
|
386
|
+
return False
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def process_organization_event(event_type: str, data: dict[str, Any]) -> bool:
|
|
390
|
+
"""
|
|
391
|
+
Process an organization-related webhook event.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
event_type: The Clerk event type.
|
|
395
|
+
data: The event data.
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
True if handled successfully, False otherwise.
|
|
399
|
+
"""
|
|
400
|
+
handlers = {
|
|
401
|
+
"organization.created": handle_organization_created,
|
|
402
|
+
"organization.updated": handle_organization_updated,
|
|
403
|
+
"organization.deleted": handle_organization_deleted,
|
|
404
|
+
"organizationMembership.created": handle_membership_created,
|
|
405
|
+
"organizationMembership.updated": handle_membership_updated,
|
|
406
|
+
"organizationMembership.deleted": handle_membership_deleted,
|
|
407
|
+
"organizationInvitation.created": handle_invitation_created,
|
|
408
|
+
"organizationInvitation.accepted": handle_invitation_accepted,
|
|
409
|
+
"organizationInvitation.revoked": handle_invitation_revoked,
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
handler = handlers.get(event_type)
|
|
413
|
+
if handler:
|
|
414
|
+
return handler(data)
|
|
415
|
+
|
|
416
|
+
logger.debug(f"Unhandled organization event type: {event_type}")
|
|
417
|
+
return True
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Settings for django-clerk-users package.
|
|
3
|
+
|
|
4
|
+
All settings are prefixed with CLERK_ and can be set in Django's settings.py.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from django.conf import settings
|
|
8
|
+
|
|
9
|
+
# Required settings
|
|
10
|
+
CLERK_SECRET_KEY: str | None = getattr(settings, "CLERK_SECRET_KEY", None)
|
|
11
|
+
CLERK_WEBHOOK_SIGNING_KEY: str | None = getattr(
|
|
12
|
+
settings, "CLERK_WEBHOOK_SIGNING_KEY", None
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
# Authorized frontend hosts for JWT validation (authorized_parties)
|
|
16
|
+
CLERK_FRONTEND_HOSTS: list[str] = getattr(settings, "CLERK_FRONTEND_HOSTS", [])
|
|
17
|
+
|
|
18
|
+
# Alias for CLERK_FRONTEND_HOSTS for consistency with existing implementations
|
|
19
|
+
CLERK_AUTH_PARTIES: list[str] = getattr(
|
|
20
|
+
settings, "CLERK_AUTH_PARTIES", CLERK_FRONTEND_HOSTS
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Session revalidation interval in seconds (default: 5 minutes)
|
|
24
|
+
CLERK_SESSION_REVALIDATION_SECONDS: int = getattr(
|
|
25
|
+
settings, "CLERK_SESSION_REVALIDATION_SECONDS", 300
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Cache timeout for JWT payloads and user lookups (default: 5 minutes)
|
|
29
|
+
CLERK_CACHE_TIMEOUT: int = getattr(settings, "CLERK_CACHE_TIMEOUT", 300)
|
|
30
|
+
|
|
31
|
+
# Cache timeout for organization lookups (default: 15 minutes)
|
|
32
|
+
CLERK_ORG_CACHE_TIMEOUT: int = getattr(settings, "CLERK_ORG_CACHE_TIMEOUT", 900)
|
|
33
|
+
|
|
34
|
+
# Webhook deduplication cache timeout (default: 45 seconds)
|
|
35
|
+
CLERK_WEBHOOK_DEDUP_TIMEOUT: int = getattr(
|
|
36
|
+
settings, "CLERK_WEBHOOK_DEDUP_TIMEOUT", 45
|
|
37
|
+
)
|