django-clerk-users 0.0.1__tar.gz → 0.0.2__tar.gz
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-0.0.2/PKG-INFO +228 -0
- django_clerk_users-0.0.2/README.md +203 -0
- {django_clerk_users-0.0.1 → django_clerk_users-0.0.2}/pyproject.toml +12 -5
- django_clerk_users-0.0.2/src/django_clerk_users/__init__.py +93 -0
- django_clerk_users-0.0.2/src/django_clerk_users/apps.py +20 -0
- django_clerk_users-0.0.2/src/django_clerk_users/authentication/__init__.py +24 -0
- django_clerk_users-0.0.2/src/django_clerk_users/authentication/backends.py +89 -0
- django_clerk_users-0.0.2/src/django_clerk_users/authentication/drf.py +111 -0
- django_clerk_users-0.0.2/src/django_clerk_users/authentication/utils.py +171 -0
- django_clerk_users-0.0.2/src/django_clerk_users/caching.py +161 -0
- django_clerk_users-0.0.2/src/django_clerk_users/checks.py +127 -0
- django_clerk_users-0.0.2/src/django_clerk_users/client.py +32 -0
- django_clerk_users-0.0.2/src/django_clerk_users/decorators.py +181 -0
- django_clerk_users-0.0.2/src/django_clerk_users/exceptions.py +51 -0
- django_clerk_users-0.0.2/src/django_clerk_users/management/__init__.py +0 -0
- django_clerk_users-0.0.2/src/django_clerk_users/management/commands/__init__.py +0 -0
- django_clerk_users-0.0.2/src/django_clerk_users/management/commands/migrate_users_to_clerk.py +223 -0
- django_clerk_users-0.0.2/src/django_clerk_users/management/commands/sync_clerk_organizations.py +191 -0
- django_clerk_users-0.0.2/src/django_clerk_users/management/commands/sync_clerk_users.py +114 -0
- django_clerk_users-0.0.2/src/django_clerk_users/managers.py +121 -0
- django_clerk_users-0.0.2/src/django_clerk_users/middleware/__init__.py +9 -0
- django_clerk_users-0.0.2/src/django_clerk_users/middleware/auth.py +201 -0
- django_clerk_users-0.0.2/src/django_clerk_users/migrations/0001_initial.py +174 -0
- django_clerk_users-0.0.2/src/django_clerk_users/migrations/__init__.py +0 -0
- django_clerk_users-0.0.2/src/django_clerk_users/models.py +174 -0
- django_clerk_users-0.0.2/src/django_clerk_users/organizations/__init__.py +8 -0
- django_clerk_users-0.0.2/src/django_clerk_users/organizations/admin.py +81 -0
- django_clerk_users-0.0.2/src/django_clerk_users/organizations/apps.py +8 -0
- django_clerk_users-0.0.2/src/django_clerk_users/organizations/middleware.py +130 -0
- django_clerk_users-0.0.2/src/django_clerk_users/organizations/models.py +316 -0
- django_clerk_users-0.0.2/src/django_clerk_users/organizations/webhooks.py +417 -0
- django_clerk_users-0.0.2/src/django_clerk_users/settings.py +37 -0
- django_clerk_users-0.0.2/src/django_clerk_users/testing.py +381 -0
- django_clerk_users-0.0.2/src/django_clerk_users/utils.py +210 -0
- django_clerk_users-0.0.2/src/django_clerk_users/webhooks/__init__.py +26 -0
- django_clerk_users-0.0.2/src/django_clerk_users/webhooks/handlers.py +346 -0
- django_clerk_users-0.0.2/src/django_clerk_users/webhooks/security.py +108 -0
- django_clerk_users-0.0.2/src/django_clerk_users/webhooks/signals.py +42 -0
- django_clerk_users-0.0.2/src/django_clerk_users/webhooks/views.py +76 -0
- django_clerk_users-0.0.2/src/django_clerk_users.egg-info/PKG-INFO +228 -0
- django_clerk_users-0.0.2/src/django_clerk_users.egg-info/SOURCES.txt +55 -0
- django_clerk_users-0.0.2/src/django_clerk_users.egg-info/requires.txt +6 -0
- django_clerk_users-0.0.2/tests/test_authentication.py +134 -0
- django_clerk_users-0.0.2/tests/test_caching.py +181 -0
- django_clerk_users-0.0.2/tests/test_decorators.py +274 -0
- django_clerk_users-0.0.2/tests/test_drf.py +194 -0
- django_clerk_users-0.0.2/tests/test_import.py +221 -0
- django_clerk_users-0.0.2/tests/test_middleware.py +307 -0
- django_clerk_users-0.0.2/tests/test_models.py +224 -0
- django_clerk_users-0.0.2/tests/test_organizations.py +343 -0
- django_clerk_users-0.0.2/tests/test_testing.py +234 -0
- django_clerk_users-0.0.2/tests/test_utils.py +343 -0
- django_clerk_users-0.0.2/tests/test_webhooks.py +363 -0
- django_clerk_users-0.0.1/PKG-INFO +0 -24
- django_clerk_users-0.0.1/README.md +0 -3
- django_clerk_users-0.0.1/src/django_clerk_users/__init__.py +0 -22
- django_clerk_users-0.0.1/src/django_clerk_users/main.py +0 -2
- django_clerk_users-0.0.1/src/django_clerk_users.egg-info/PKG-INFO +0 -24
- django_clerk_users-0.0.1/src/django_clerk_users.egg-info/SOURCES.txt +0 -11
- django_clerk_users-0.0.1/src/django_clerk_users.egg-info/requires.txt +0 -1
- django_clerk_users-0.0.1/tests/test_import.py +0 -19
- {django_clerk_users-0.0.1 → django_clerk_users-0.0.2}/LICENSE +0 -0
- {django_clerk_users-0.0.1 → django_clerk_users-0.0.2}/setup.cfg +0 -0
- {django_clerk_users-0.0.1 → django_clerk_users-0.0.2}/src/django_clerk_users.egg-info/dependency_links.txt +0 -0
- {django_clerk_users-0.0.1 → django_clerk_users-0.0.2}/src/django_clerk_users.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-clerk-users
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Integrate Clerk with Django
|
|
5
|
+
Project-URL: Changelog, https://github.com/jmitchel3/django-clerk-users
|
|
6
|
+
Project-URL: Documentation, https://github.com/jmitchel3/django-clerk-users
|
|
7
|
+
Project-URL: Funding, https://github.com/jmitchel3/django-clerk-users
|
|
8
|
+
Project-URL: Repository, https://github.com/jmitchel3/django-clerk-users
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Framework :: Django
|
|
11
|
+
Classifier: Framework :: Django :: 4.2
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Requires-Python: >=3.12
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: django>=4.2
|
|
20
|
+
Requires-Dist: clerk-backend-api>=1.0.0
|
|
21
|
+
Requires-Dist: svix>=1.0.0
|
|
22
|
+
Provides-Extra: drf
|
|
23
|
+
Requires-Dist: djangorestframework>=3.14; extra == "drf"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# Django Clerk Users
|
|
27
|
+
|
|
28
|
+
Integrate [Clerk](https://clerk.com) authentication with Django.
|
|
29
|
+
|
|
30
|
+
> **Note:** This package is in early development (v0.0.2). APIs may change.
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- Custom user model (`ClerkUser`) with Clerk integration
|
|
35
|
+
- JWT token validation via Clerk SDK
|
|
36
|
+
- Session-based authentication middleware (validates once, caches in session)
|
|
37
|
+
- Webhook handling with Svix signature verification
|
|
38
|
+
- Optional organizations support (separate sub-app)
|
|
39
|
+
- Django REST Framework authentication (optional)
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install django-clerk-users
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
For Django REST Framework support:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install django-clerk-users[drf]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
### 1. Add to installed apps
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
INSTALLED_APPS = [
|
|
59
|
+
# ...
|
|
60
|
+
"django_clerk_users",
|
|
61
|
+
# Optional: for organization support
|
|
62
|
+
# "django_clerk_users.organizations",
|
|
63
|
+
]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2. Configure settings
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
# Required
|
|
70
|
+
CLERK_SECRET_KEY = "sk_live_..." # From Clerk Dashboard
|
|
71
|
+
CLERK_WEBHOOK_SIGNING_KEY = "whsec_..." # From Clerk Webhooks
|
|
72
|
+
CLERK_FRONTEND_HOSTS = ["https://your-app.com"] # Your frontend URLs
|
|
73
|
+
|
|
74
|
+
# Optional
|
|
75
|
+
CLERK_SESSION_REVALIDATION_SECONDS = 300 # Re-validate JWT every 5 minutes
|
|
76
|
+
CLERK_CACHE_TIMEOUT = 300 # Cache timeout for user lookups
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 3. Set the user model
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
AUTH_USER_MODEL = "django_clerk_users.ClerkUser"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Or extend the abstract model for custom fields:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
# myapp/models.py
|
|
89
|
+
from django_clerk_users.models import AbstractClerkUser
|
|
90
|
+
|
|
91
|
+
class CustomUser(AbstractClerkUser):
|
|
92
|
+
company = models.CharField(max_length=255, blank=True)
|
|
93
|
+
|
|
94
|
+
class Meta(AbstractClerkUser.Meta):
|
|
95
|
+
swappable = "AUTH_USER_MODEL"
|
|
96
|
+
|
|
97
|
+
# settings.py
|
|
98
|
+
AUTH_USER_MODEL = "myapp.CustomUser"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 4. Add middleware
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
MIDDLEWARE = [
|
|
105
|
+
"django.middleware.security.SecurityMiddleware",
|
|
106
|
+
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
107
|
+
"django.middleware.common.CommonMiddleware",
|
|
108
|
+
"django.middleware.csrf.CsrfViewMiddleware",
|
|
109
|
+
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
|
110
|
+
"django_clerk_users.middleware.ClerkAuthMiddleware", # Add after AuthenticationMiddleware
|
|
111
|
+
# ...
|
|
112
|
+
]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 5. Add authentication backend
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
AUTHENTICATION_BACKENDS = [
|
|
119
|
+
"django_clerk_users.authentication.ClerkBackend",
|
|
120
|
+
]
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 6. Run migrations
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
python manage.py migrate
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 7. Configure webhooks
|
|
130
|
+
|
|
131
|
+
Add the webhook URL to your `urls.py`:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from django_clerk_users.webhooks import clerk_webhook_view
|
|
135
|
+
|
|
136
|
+
urlpatterns = [
|
|
137
|
+
# ...
|
|
138
|
+
path("webhooks/clerk/", clerk_webhook_view, name="clerk_webhook"),
|
|
139
|
+
]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Then configure your Clerk Dashboard to send webhooks to `https://your-app.com/webhooks/clerk/`.
|
|
143
|
+
|
|
144
|
+
## Usage
|
|
145
|
+
|
|
146
|
+
### Accessing the user in views
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
def my_view(request):
|
|
150
|
+
if request.user.is_authenticated:
|
|
151
|
+
# Access Clerk user attributes
|
|
152
|
+
print(request.user.clerk_id)
|
|
153
|
+
print(request.user.email)
|
|
154
|
+
print(request.user.full_name)
|
|
155
|
+
|
|
156
|
+
# Access organization (if using organizations)
|
|
157
|
+
print(request.org) # Organization ID from JWT
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Decorators
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from django_clerk_users.decorators import clerk_user_required
|
|
164
|
+
|
|
165
|
+
@clerk_user_required
|
|
166
|
+
def protected_view(request):
|
|
167
|
+
# Only authenticated Clerk users can access
|
|
168
|
+
return HttpResponse(f"Hello, {request.user.email}")
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Django REST Framework
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
# settings.py
|
|
175
|
+
REST_FRAMEWORK = {
|
|
176
|
+
"DEFAULT_AUTHENTICATION_CLASSES": [
|
|
177
|
+
"django_clerk_users.authentication.ClerkAuthentication",
|
|
178
|
+
],
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Organizations (Optional)
|
|
183
|
+
|
|
184
|
+
For Clerk organization support:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
# settings.py
|
|
188
|
+
INSTALLED_APPS = [
|
|
189
|
+
# ...
|
|
190
|
+
"django_clerk_users",
|
|
191
|
+
"django_clerk_users.organizations",
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
MIDDLEWARE = [
|
|
195
|
+
# ...
|
|
196
|
+
"django_clerk_users.middleware.ClerkAuthMiddleware",
|
|
197
|
+
"django_clerk_users.organizations.middleware.ClerkOrganizationMiddleware",
|
|
198
|
+
]
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Management Commands
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
# Sync users from Clerk
|
|
205
|
+
python manage.py sync_clerk_users
|
|
206
|
+
|
|
207
|
+
# Sync organizations from Clerk
|
|
208
|
+
python manage.py sync_clerk_organizations
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Configuration Reference
|
|
212
|
+
|
|
213
|
+
| Setting | Required | Default | Description |
|
|
214
|
+
|---------|----------|---------|-------------|
|
|
215
|
+
| `CLERK_SECRET_KEY` | Yes | - | Your Clerk secret key |
|
|
216
|
+
| `CLERK_WEBHOOK_SIGNING_KEY` | Yes* | - | Webhook signing secret (*required for webhooks) |
|
|
217
|
+
| `CLERK_FRONTEND_HOSTS` | Yes | `[]` | Authorized frontend URLs |
|
|
218
|
+
| `CLERK_SESSION_REVALIDATION_SECONDS` | No | `300` | JWT revalidation interval |
|
|
219
|
+
| `CLERK_CACHE_TIMEOUT` | No | `300` | User cache timeout |
|
|
220
|
+
| `CLERK_ORG_CACHE_TIMEOUT` | No | `900` | Organization cache timeout |
|
|
221
|
+
|
|
222
|
+
## License
|
|
223
|
+
|
|
224
|
+
MIT
|
|
225
|
+
|
|
226
|
+
## Contributing
|
|
227
|
+
|
|
228
|
+
Contributions are welcome! Please open an issue or PR on [GitHub](https://github.com/jmitchel3/django-clerk-users).
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Django Clerk Users
|
|
2
|
+
|
|
3
|
+
Integrate [Clerk](https://clerk.com) authentication with Django.
|
|
4
|
+
|
|
5
|
+
> **Note:** This package is in early development (v0.0.2). APIs may change.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Custom user model (`ClerkUser`) with Clerk integration
|
|
10
|
+
- JWT token validation via Clerk SDK
|
|
11
|
+
- Session-based authentication middleware (validates once, caches in session)
|
|
12
|
+
- Webhook handling with Svix signature verification
|
|
13
|
+
- Optional organizations support (separate sub-app)
|
|
14
|
+
- Django REST Framework authentication (optional)
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install django-clerk-users
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
For Django REST Framework support:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install django-clerk-users[drf]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
### 1. Add to installed apps
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
INSTALLED_APPS = [
|
|
34
|
+
# ...
|
|
35
|
+
"django_clerk_users",
|
|
36
|
+
# Optional: for organization support
|
|
37
|
+
# "django_clerk_users.organizations",
|
|
38
|
+
]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. Configure settings
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
# Required
|
|
45
|
+
CLERK_SECRET_KEY = "sk_live_..." # From Clerk Dashboard
|
|
46
|
+
CLERK_WEBHOOK_SIGNING_KEY = "whsec_..." # From Clerk Webhooks
|
|
47
|
+
CLERK_FRONTEND_HOSTS = ["https://your-app.com"] # Your frontend URLs
|
|
48
|
+
|
|
49
|
+
# Optional
|
|
50
|
+
CLERK_SESSION_REVALIDATION_SECONDS = 300 # Re-validate JWT every 5 minutes
|
|
51
|
+
CLERK_CACHE_TIMEOUT = 300 # Cache timeout for user lookups
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 3. Set the user model
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
AUTH_USER_MODEL = "django_clerk_users.ClerkUser"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Or extend the abstract model for custom fields:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
# myapp/models.py
|
|
64
|
+
from django_clerk_users.models import AbstractClerkUser
|
|
65
|
+
|
|
66
|
+
class CustomUser(AbstractClerkUser):
|
|
67
|
+
company = models.CharField(max_length=255, blank=True)
|
|
68
|
+
|
|
69
|
+
class Meta(AbstractClerkUser.Meta):
|
|
70
|
+
swappable = "AUTH_USER_MODEL"
|
|
71
|
+
|
|
72
|
+
# settings.py
|
|
73
|
+
AUTH_USER_MODEL = "myapp.CustomUser"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 4. Add middleware
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
MIDDLEWARE = [
|
|
80
|
+
"django.middleware.security.SecurityMiddleware",
|
|
81
|
+
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
82
|
+
"django.middleware.common.CommonMiddleware",
|
|
83
|
+
"django.middleware.csrf.CsrfViewMiddleware",
|
|
84
|
+
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
|
85
|
+
"django_clerk_users.middleware.ClerkAuthMiddleware", # Add after AuthenticationMiddleware
|
|
86
|
+
# ...
|
|
87
|
+
]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 5. Add authentication backend
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
AUTHENTICATION_BACKENDS = [
|
|
94
|
+
"django_clerk_users.authentication.ClerkBackend",
|
|
95
|
+
]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 6. Run migrations
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
python manage.py migrate
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 7. Configure webhooks
|
|
105
|
+
|
|
106
|
+
Add the webhook URL to your `urls.py`:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from django_clerk_users.webhooks import clerk_webhook_view
|
|
110
|
+
|
|
111
|
+
urlpatterns = [
|
|
112
|
+
# ...
|
|
113
|
+
path("webhooks/clerk/", clerk_webhook_view, name="clerk_webhook"),
|
|
114
|
+
]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Then configure your Clerk Dashboard to send webhooks to `https://your-app.com/webhooks/clerk/`.
|
|
118
|
+
|
|
119
|
+
## Usage
|
|
120
|
+
|
|
121
|
+
### Accessing the user in views
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
def my_view(request):
|
|
125
|
+
if request.user.is_authenticated:
|
|
126
|
+
# Access Clerk user attributes
|
|
127
|
+
print(request.user.clerk_id)
|
|
128
|
+
print(request.user.email)
|
|
129
|
+
print(request.user.full_name)
|
|
130
|
+
|
|
131
|
+
# Access organization (if using organizations)
|
|
132
|
+
print(request.org) # Organization ID from JWT
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Decorators
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from django_clerk_users.decorators import clerk_user_required
|
|
139
|
+
|
|
140
|
+
@clerk_user_required
|
|
141
|
+
def protected_view(request):
|
|
142
|
+
# Only authenticated Clerk users can access
|
|
143
|
+
return HttpResponse(f"Hello, {request.user.email}")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Django REST Framework
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
# settings.py
|
|
150
|
+
REST_FRAMEWORK = {
|
|
151
|
+
"DEFAULT_AUTHENTICATION_CLASSES": [
|
|
152
|
+
"django_clerk_users.authentication.ClerkAuthentication",
|
|
153
|
+
],
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Organizations (Optional)
|
|
158
|
+
|
|
159
|
+
For Clerk organization support:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
# settings.py
|
|
163
|
+
INSTALLED_APPS = [
|
|
164
|
+
# ...
|
|
165
|
+
"django_clerk_users",
|
|
166
|
+
"django_clerk_users.organizations",
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
MIDDLEWARE = [
|
|
170
|
+
# ...
|
|
171
|
+
"django_clerk_users.middleware.ClerkAuthMiddleware",
|
|
172
|
+
"django_clerk_users.organizations.middleware.ClerkOrganizationMiddleware",
|
|
173
|
+
]
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Management Commands
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# Sync users from Clerk
|
|
180
|
+
python manage.py sync_clerk_users
|
|
181
|
+
|
|
182
|
+
# Sync organizations from Clerk
|
|
183
|
+
python manage.py sync_clerk_organizations
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Configuration Reference
|
|
187
|
+
|
|
188
|
+
| Setting | Required | Default | Description |
|
|
189
|
+
|---------|----------|---------|-------------|
|
|
190
|
+
| `CLERK_SECRET_KEY` | Yes | - | Your Clerk secret key |
|
|
191
|
+
| `CLERK_WEBHOOK_SIGNING_KEY` | Yes* | - | Webhook signing secret (*required for webhooks) |
|
|
192
|
+
| `CLERK_FRONTEND_HOSTS` | Yes | `[]` | Authorized frontend URLs |
|
|
193
|
+
| `CLERK_SESSION_REVALIDATION_SECONDS` | No | `300` | JWT revalidation interval |
|
|
194
|
+
| `CLERK_CACHE_TIMEOUT` | No | `300` | User cache timeout |
|
|
195
|
+
| `CLERK_ORG_CACHE_TIMEOUT` | No | `900` | Organization cache timeout |
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
MIT
|
|
200
|
+
|
|
201
|
+
## Contributing
|
|
202
|
+
|
|
203
|
+
Contributions are welcome! Please open an issue or PR on [GitHub](https://github.com/jmitchel3/django-clerk-users).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "django-clerk-users"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.2"
|
|
4
4
|
description = "Integrate Clerk with Django"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -15,19 +15,26 @@ classifiers = [
|
|
|
15
15
|
]
|
|
16
16
|
dependencies = [
|
|
17
17
|
"django>=4.2",
|
|
18
|
+
"clerk-backend-api>=1.0.0",
|
|
19
|
+
"svix>=1.0.0",
|
|
18
20
|
]
|
|
19
21
|
|
|
22
|
+
[project.optional-dependencies]
|
|
23
|
+
drf = ["djangorestframework>=3.14"]
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
urls
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Changelog = "https://github.com/jmitchel3/django-clerk-users"
|
|
28
|
+
Documentation = "https://github.com/jmitchel3/django-clerk-users"
|
|
29
|
+
Funding = "https://github.com/jmitchel3/django-clerk-users"
|
|
30
|
+
Repository = "https://github.com/jmitchel3/django-clerk-users"
|
|
25
31
|
|
|
26
32
|
[dependency-groups]
|
|
27
33
|
dev = [
|
|
28
34
|
"pip>=25.3",
|
|
29
35
|
"pre-commit>=4.4.0",
|
|
30
36
|
"pytest>=9.0.0",
|
|
37
|
+
"pytest-django>=4.9",
|
|
31
38
|
"rav>=0.1.1",
|
|
32
39
|
"tox>=4.32.0",
|
|
33
40
|
"tox-uv>=1.29.0",
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
django-clerk-users: Integrate Clerk authentication with Django.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
__version__ = version("django-clerk-users")
|
|
9
|
+
except PackageNotFoundError:
|
|
10
|
+
__version__ = "unknown"
|
|
11
|
+
|
|
12
|
+
# Re-export default app config
|
|
13
|
+
default_app_config = "django_clerk_users.apps.DjangoClerkUsersConfig"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def __getattr__(name: str):
|
|
17
|
+
"""Lazy import to avoid loading Django models before apps are ready."""
|
|
18
|
+
# Models
|
|
19
|
+
if name == "AbstractClerkUser":
|
|
20
|
+
from django_clerk_users.models import AbstractClerkUser
|
|
21
|
+
|
|
22
|
+
return AbstractClerkUser
|
|
23
|
+
if name == "ClerkUser":
|
|
24
|
+
from django_clerk_users.models import ClerkUser
|
|
25
|
+
|
|
26
|
+
return ClerkUser
|
|
27
|
+
if name == "ClerkUserManager":
|
|
28
|
+
from django_clerk_users.models import ClerkUserManager
|
|
29
|
+
|
|
30
|
+
return ClerkUserManager
|
|
31
|
+
|
|
32
|
+
# Client
|
|
33
|
+
if name == "get_clerk_client":
|
|
34
|
+
from django_clerk_users.client import get_clerk_client
|
|
35
|
+
|
|
36
|
+
return get_clerk_client
|
|
37
|
+
|
|
38
|
+
# Exceptions
|
|
39
|
+
if name in (
|
|
40
|
+
"ClerkError",
|
|
41
|
+
"ClerkConfigurationError",
|
|
42
|
+
"ClerkAuthenticationError",
|
|
43
|
+
"ClerkTokenError",
|
|
44
|
+
"ClerkWebhookError",
|
|
45
|
+
"ClerkAPIError",
|
|
46
|
+
"ClerkUserNotFoundError",
|
|
47
|
+
"ClerkOrganizationNotFoundError",
|
|
48
|
+
):
|
|
49
|
+
from django_clerk_users import exceptions
|
|
50
|
+
|
|
51
|
+
return getattr(exceptions, name)
|
|
52
|
+
|
|
53
|
+
# Testing utilities
|
|
54
|
+
if name in (
|
|
55
|
+
"ClerkTestClient",
|
|
56
|
+
"ClerkTestMixin",
|
|
57
|
+
"TestUserData",
|
|
58
|
+
"make_test_email",
|
|
59
|
+
"make_test_phone",
|
|
60
|
+
"TEST_OTP_CODE",
|
|
61
|
+
):
|
|
62
|
+
from django_clerk_users import testing
|
|
63
|
+
|
|
64
|
+
return getattr(testing, name)
|
|
65
|
+
|
|
66
|
+
raise AttributeError(f"Module 'django_clerk_users' has no attribute '{name}'")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
__all__ = [
|
|
70
|
+
"__version__",
|
|
71
|
+
# Models
|
|
72
|
+
"AbstractClerkUser",
|
|
73
|
+
"ClerkUser",
|
|
74
|
+
"ClerkUserManager",
|
|
75
|
+
# Client
|
|
76
|
+
"get_clerk_client",
|
|
77
|
+
# Exceptions
|
|
78
|
+
"ClerkError",
|
|
79
|
+
"ClerkConfigurationError",
|
|
80
|
+
"ClerkAuthenticationError",
|
|
81
|
+
"ClerkTokenError",
|
|
82
|
+
"ClerkWebhookError",
|
|
83
|
+
"ClerkAPIError",
|
|
84
|
+
"ClerkUserNotFoundError",
|
|
85
|
+
"ClerkOrganizationNotFoundError",
|
|
86
|
+
# Testing utilities
|
|
87
|
+
"ClerkTestClient",
|
|
88
|
+
"ClerkTestMixin",
|
|
89
|
+
"TestUserData",
|
|
90
|
+
"make_test_email",
|
|
91
|
+
"make_test_phone",
|
|
92
|
+
"TEST_OTP_CODE",
|
|
93
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from django.apps import AppConfig
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DjangoClerkUsersConfig(AppConfig):
|
|
5
|
+
name = "django_clerk_users"
|
|
6
|
+
verbose_name = "Django Clerk Users"
|
|
7
|
+
default_auto_field = "django.db.models.BigAutoField"
|
|
8
|
+
|
|
9
|
+
def ready(self):
|
|
10
|
+
# Import checks to register them with Django
|
|
11
|
+
from django_clerk_users import checks # noqa: F401
|
|
12
|
+
|
|
13
|
+
# Disconnect Django's update_last_login signal
|
|
14
|
+
# Clerk manages authentication externally
|
|
15
|
+
from django.contrib.auth import get_user_model
|
|
16
|
+
from django.contrib.auth.models import update_last_login
|
|
17
|
+
from django.contrib.auth.signals import user_logged_in
|
|
18
|
+
|
|
19
|
+
User = get_user_model()
|
|
20
|
+
user_logged_in.disconnect(update_last_login, sender=User)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Authentication backends and utilities for django-clerk-users.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from django_clerk_users.authentication.backends import ClerkBackend
|
|
6
|
+
from django_clerk_users.authentication.utils import (
|
|
7
|
+
get_clerk_payload_from_request,
|
|
8
|
+
get_or_create_user_from_payload,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"ClerkBackend",
|
|
13
|
+
"get_clerk_payload_from_request",
|
|
14
|
+
"get_or_create_user_from_payload",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
# Conditionally export DRF authentication if available
|
|
18
|
+
try:
|
|
19
|
+
from django_clerk_users.authentication.drf import ClerkAuthentication
|
|
20
|
+
|
|
21
|
+
__all__.append("ClerkAuthentication")
|
|
22
|
+
except ImportError:
|
|
23
|
+
# DRF is not installed
|
|
24
|
+
pass
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django authentication backend for Clerk.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
from django.contrib.auth import get_user_model
|
|
11
|
+
from django.contrib.auth.backends import BaseBackend
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from django.http import HttpRequest
|
|
15
|
+
|
|
16
|
+
from django_clerk_users.models import AbstractClerkUser
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ClerkBackend(BaseBackend):
|
|
22
|
+
"""
|
|
23
|
+
Django authentication backend for Clerk.
|
|
24
|
+
|
|
25
|
+
This backend authenticates users by their Clerk ID rather than
|
|
26
|
+
username/password. It's designed to work with Clerk's JWT-based
|
|
27
|
+
authentication.
|
|
28
|
+
|
|
29
|
+
To use this backend, add it to AUTHENTICATION_BACKENDS in settings:
|
|
30
|
+
|
|
31
|
+
AUTHENTICATION_BACKENDS = [
|
|
32
|
+
'django_clerk_users.authentication.ClerkBackend',
|
|
33
|
+
]
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def authenticate(
|
|
37
|
+
self,
|
|
38
|
+
request: "HttpRequest | None" = None,
|
|
39
|
+
clerk_id: str | None = None,
|
|
40
|
+
**kwargs: Any,
|
|
41
|
+
) -> "AbstractClerkUser | None":
|
|
42
|
+
"""
|
|
43
|
+
Authenticate a user by their Clerk ID.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
request: The current HTTP request (optional).
|
|
47
|
+
clerk_id: The Clerk user ID to authenticate.
|
|
48
|
+
**kwargs: Additional keyword arguments (ignored).
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
The authenticated user or None if authentication fails.
|
|
52
|
+
"""
|
|
53
|
+
if not clerk_id:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
User = get_user_model()
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
user = User.objects.get(clerk_id=clerk_id)
|
|
60
|
+
if user.is_active:
|
|
61
|
+
return user
|
|
62
|
+
logger.debug(f"User {clerk_id} is inactive")
|
|
63
|
+
return None
|
|
64
|
+
except User.DoesNotExist:
|
|
65
|
+
logger.debug(f"No user found with clerk_id: {clerk_id}")
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
def get_user(self, user_id: int) -> "AbstractClerkUser | None":
|
|
69
|
+
"""
|
|
70
|
+
Get a user by their Django primary key.
|
|
71
|
+
|
|
72
|
+
This method is called by Django's authentication middleware
|
|
73
|
+
to restore the user from the session.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
user_id: The user's primary key.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
The user instance or None if not found.
|
|
80
|
+
"""
|
|
81
|
+
User = get_user_model()
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
user = User.objects.get(pk=user_id)
|
|
85
|
+
if user.is_active:
|
|
86
|
+
return user
|
|
87
|
+
return None
|
|
88
|
+
except User.DoesNotExist:
|
|
89
|
+
return None
|