django-trusted-devices 1.1__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_trusted_devices-1.1/LICENSE +21 -0
- django_trusted_devices-1.1/PKG-INFO +277 -0
- django_trusted_devices-1.1/README.md +238 -0
- django_trusted_devices-1.1/pyproject.toml +68 -0
- django_trusted_devices-1.1/setup.cfg +4 -0
- django_trusted_devices-1.1/src/django_trusted_devices.egg-info/PKG-INFO +277 -0
- django_trusted_devices-1.1/src/django_trusted_devices.egg-info/SOURCES.txt +24 -0
- django_trusted_devices-1.1/src/django_trusted_devices.egg-info/dependency_links.txt +1 -0
- django_trusted_devices-1.1/src/django_trusted_devices.egg-info/requires.txt +19 -0
- django_trusted_devices-1.1/src/django_trusted_devices.egg-info/top_level.txt +1 -0
- django_trusted_devices-1.1/src/trusted_devices/__init__.py +0 -0
- django_trusted_devices-1.1/src/trusted_devices/admin.py +0 -0
- django_trusted_devices-1.1/src/trusted_devices/apps.py +9 -0
- django_trusted_devices-1.1/src/trusted_devices/authentication.py +74 -0
- django_trusted_devices-1.1/src/trusted_devices/migrations/0001_initial.py +116 -0
- django_trusted_devices-1.1/src/trusted_devices/migrations/__init__.py +0 -0
- django_trusted_devices-1.1/src/trusted_devices/models.py +100 -0
- django_trusted_devices-1.1/src/trusted_devices/permissions.py +127 -0
- django_trusted_devices-1.1/src/trusted_devices/serializers.py +183 -0
- django_trusted_devices-1.1/src/trusted_devices/settings.py +36 -0
- django_trusted_devices-1.1/src/trusted_devices/signals.py +15 -0
- django_trusted_devices-1.1/src/trusted_devices/tests.py +0 -0
- django_trusted_devices-1.1/src/trusted_devices/tokens.py +17 -0
- django_trusted_devices-1.1/src/trusted_devices/urls.py +30 -0
- django_trusted_devices-1.1/src/trusted_devices/utils.py +76 -0
- django_trusted_devices-1.1/src/trusted_devices/views.py +73 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jakhongir Ganiev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-trusted-devices
|
|
3
|
+
Version: 1.1
|
|
4
|
+
Summary: Secure and manage trusted login devices for Django users
|
|
5
|
+
Author-email: Jakhongir Ganiev <contact@jakhongir.dev>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: django,trusted devices,user security,sessions,login,djangorestframework
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Framework :: Django
|
|
10
|
+
Classifier: Framework :: Django :: 4.2
|
|
11
|
+
Classifier: Framework :: Django :: 5.0
|
|
12
|
+
Classifier: Framework :: Django :: 5.1
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.13
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: django<5.3,>=4.2
|
|
22
|
+
Requires-Dist: djangorestframework>=3.14.0
|
|
23
|
+
Requires-Dist: djangorestframework-simplejwt>=5.5.0
|
|
24
|
+
Requires-Dist: drf-yasg>=1.21.10
|
|
25
|
+
Requires-Dist: httpx>=0.28.1
|
|
26
|
+
Provides-Extra: docs
|
|
27
|
+
Requires-Dist: mkdocs; extra == "docs"
|
|
28
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: black>=25.1.0; extra == "dev"
|
|
31
|
+
Requires-Dist: build>=1.2.2.post1; extra == "dev"
|
|
32
|
+
Requires-Dist: flake8>=7.2.0; extra == "dev"
|
|
33
|
+
Requires-Dist: isort>=6.0.1; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest>=8.3.5; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-cov>=6.1.1; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-django>=4.11.1; extra == "dev"
|
|
37
|
+
Requires-Dist: twine>=6.1.0; extra == "dev"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# π Django Trusted Device
|
|
41
|
+
|
|
42
|
+
A plug-and-play Django app that adds **trusted device management** to your API authentication system using
|
|
43
|
+
`djangorestframework-simplejwt`. Automatically associates tokens with user devices, tracks login locations,
|
|
44
|
+
and enables per-device control over access and session management.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
[](https://ganiyevuz.github.io/django-trusted-devices/)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
## π Features
|
|
51
|
+
|
|
52
|
+
* π **JWT tokens** include a unique `device_uid`
|
|
53
|
+
* π **Auto-detect IP, region, and city** via [ipapi.co](https://ipapi.co)
|
|
54
|
+
* π‘οΈ **Per-device session tracking** with update/delete restrictions
|
|
55
|
+
* π **Custom** `TokenObtainPair`, `TokenRefresh`, and `TokenVerify` views
|
|
56
|
+
* πͺ **Logout unwanted sessions** from the device list
|
|
57
|
+
* π§Ό **Automatic cleanup**, optional global control rules
|
|
58
|
+
* π§© **API-ready** β supports DRF out of the box
|
|
59
|
+
* βοΈ **Fully customizable** via `TRUSTED_DEVICE` Django settings
|
|
60
|
+
* π« **Rejects refresh/verify** from unknown or expired devices
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## π¦ Installation
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install django-trusted-device
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Add to your `INSTALLED_APPS`:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
INSTALLED_APPS = [
|
|
74
|
+
...
|
|
75
|
+
'trusted_devices',
|
|
76
|
+
'rest_framework_simplejwt.token_blacklist',
|
|
77
|
+
]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Run migrations:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
python manage.py migrate
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## βοΈ Configuration
|
|
89
|
+
|
|
90
|
+
Customize behavior in `settings.py`:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
TRUSTED_DEVICE = {
|
|
94
|
+
"DELETE_DELAY_MINUTES": 60 * 24 * 7, # 7 days
|
|
95
|
+
"UPDATE_DELAY_MINUTES": 60, # 1 hour
|
|
96
|
+
"ALLOW_GLOBAL_DELETE": True,
|
|
97
|
+
"ALLOW_GLOBAL_UPDATE": True,
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## π§© Usage
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
## π SimpleJWT configuration
|
|
107
|
+
|
|
108
|
+
Replace default SimpleJWT serializers with TrustedDevice serializers.:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from datetime import timedelta
|
|
112
|
+
|
|
113
|
+
REST_FRAMEWORK = {
|
|
114
|
+
'DEFAULT_AUTHENTICATION_CLASSES': (
|
|
115
|
+
'trusted_devices.authentication.TrustedDeviceAuthentication',
|
|
116
|
+
),
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
SIMPLE_JWT = {
|
|
120
|
+
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=60),
|
|
121
|
+
"REFRESH_TOKEN_LIFETIME": timedelta(days=30),
|
|
122
|
+
"AUTH_HEADER_TYPES": ("Bearer",),
|
|
123
|
+
"TOKEN_OBTAIN_SERIALIZER": 'trusted_devices.serializers.TrustedDeviceTokenObtainPairSerializer',
|
|
124
|
+
"TOKEN_REFRESH_SERIALIZER": 'trusted_devices.serializers.TrustedDeviceTokenRefreshSerializer',
|
|
125
|
+
"TOKEN_VERIFY_SERIALIZER": 'trusted_devices.serializers.TrustedDeviceTokenVerifySerializer',
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### π Custom Token Views
|
|
131
|
+
|
|
132
|
+
Replace the default SimpleJWT views with:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from trusted_devices.views import (
|
|
136
|
+
TrustedDeviceTokenObtainPairView,
|
|
137
|
+
TrustedDeviceTokenRefreshView,
|
|
138
|
+
TrustedDeviceTokenVerifyView,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
urlpatterns = [
|
|
142
|
+
path('api/token/', TrustedDeviceTokenObtainPairView.as_view(), name='token_obtain_pair'),
|
|
143
|
+
path('api/token/refresh/', TrustedDeviceTokenRefreshView.as_view(), name='token_refresh'),
|
|
144
|
+
path('api/token/verify/', TrustedDeviceTokenVerifyView.as_view(), name='token_verify'),
|
|
145
|
+
]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
### π‘ Device Management API
|
|
151
|
+
|
|
152
|
+
Use the provided `TrustedDeviceViewSet`:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from trusted_devices.views import TrustedDeviceViewSet
|
|
156
|
+
|
|
157
|
+
router.register(r'trusted-devices', TrustedDeviceViewSet, basename='trusted-device')
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Endpoints:
|
|
161
|
+
|
|
162
|
+
* `GET /trusted-devices` β List all trusted devices
|
|
163
|
+
* `DELETE /trusted-devices/{device_uid}` β Delete a device
|
|
164
|
+
* `PATCH /trusted-devices/{device_uid}` β Update device permissions
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## π€ Device Model
|
|
169
|
+
|
|
170
|
+
Each trusted device includes:
|
|
171
|
+
|
|
172
|
+
* `device_uid`: Unique UUID
|
|
173
|
+
* `user_agent`: Browser or device string
|
|
174
|
+
* `ip_address`: IP address
|
|
175
|
+
* `country`, `region`, `city`: Geolocation (via `ipapi.co`)
|
|
176
|
+
* `last_seen`, `created_at`: Timestamps
|
|
177
|
+
* `can_delete_other_devices`, `can_update_other_devices`: Optional privileges
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## π§ How It Works
|
|
182
|
+
|
|
183
|
+
1. During login, a `device_uid` is generated and embedded in the token.
|
|
184
|
+
2. Clients use that token (with `device_uid`) for refresh/verify.
|
|
185
|
+
3. Each request is linked to a known device.
|
|
186
|
+
4. Users can manage or restrict their devices via API or Admin.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## π§ͺ Testing Locally
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
# π§© Create and activate a uv-managed virtual environment
|
|
194
|
+
uv venv
|
|
195
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
196
|
+
|
|
197
|
+
# π¦ Install the package in editable mode with dev extras
|
|
198
|
+
uv pip install -e ".[dev]"
|
|
199
|
+
|
|
200
|
+
# π§ͺ Run the test suite
|
|
201
|
+
pytest
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## π§± Dependencies
|
|
207
|
+
|
|
208
|
+
* Django
|
|
209
|
+
* Django REST Framework
|
|
210
|
+
* djangorestframework-simplejwt
|
|
211
|
+
* [ipapi.co](https://ipapi.co) (for IP geolocation)
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## ποΈ Model Snapshot
|
|
216
|
+
|
|
217
|
+
| Field | Purpose |
|
|
218
|
+
| -------------------------- | ------------------- |
|
|
219
|
+
| `device_uid` | UUID primary key |
|
|
220
|
+
| `user_agent`, `ip_address` | Device fingerprint |
|
|
221
|
+
| `country / region / city` | Geoβlookup |
|
|
222
|
+
| `last_seen / created_at` | Activity timestamps |
|
|
223
|
+
| `can_update_other_devices` | Granular permission |
|
|
224
|
+
| `can_delete_other_devices` | Granular permission |
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## π€ Collaboration & Contributing
|
|
229
|
+
|
|
230
|
+
We love community contributions! To collaborate:
|
|
231
|
+
|
|
232
|
+
1. **Fork** the repo and create a feature branch:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
git checkout -b feature/my-amazing-idea
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
2. **Follow code style** β run:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
make lint # runs flake8, isort, black
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
3. **Write & run tests**:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
pytest
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
4. **Commit** with clear messages and open a **Pull Request**.
|
|
251
|
+
GitHub Actions will lint + test your branch automatically.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
### π£οΈ Discussions & Issues
|
|
256
|
+
|
|
257
|
+
* π‘ Questions / ideas β [GitHub Discussions](https://github.com/ganiyevuz/django-trusted-devices/discussions)
|
|
258
|
+
* π Bugs / feature requests β [GitHub Issues](https://github.com/ganiyevuz/django-trusted-devices/issues)
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
### π Maintainer Workflow
|
|
263
|
+
|
|
264
|
+
* PRs require at least one approval and passing CI
|
|
265
|
+
* We **squashβmerge** to keep history clean
|
|
266
|
+
* Follows **Semantic Versioning** (`MAJOR.MINOR.PATCH`), tagged as `vX.Y.Z`
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## π License
|
|
271
|
+
|
|
272
|
+
[MIT](LICENSE)
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
Made with β€οΈ by [Jahongir Ganiev](https://github.com/ganiyevuz)
|
|
277
|
+
Security questions or commercial support? Open an issue or email **[contact@jakhongir.dev](mailto:contact@jakhongir.dev)**
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# π Django Trusted Device
|
|
2
|
+
|
|
3
|
+
A plug-and-play Django app that adds **trusted device management** to your API authentication system using
|
|
4
|
+
`djangorestframework-simplejwt`. Automatically associates tokens with user devices, tracks login locations,
|
|
5
|
+
and enables per-device control over access and session management.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
[](https://ganiyevuz.github.io/django-trusted-devices/)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## π Features
|
|
12
|
+
|
|
13
|
+
* π **JWT tokens** include a unique `device_uid`
|
|
14
|
+
* π **Auto-detect IP, region, and city** via [ipapi.co](https://ipapi.co)
|
|
15
|
+
* π‘οΈ **Per-device session tracking** with update/delete restrictions
|
|
16
|
+
* π **Custom** `TokenObtainPair`, `TokenRefresh`, and `TokenVerify` views
|
|
17
|
+
* πͺ **Logout unwanted sessions** from the device list
|
|
18
|
+
* π§Ό **Automatic cleanup**, optional global control rules
|
|
19
|
+
* π§© **API-ready** β supports DRF out of the box
|
|
20
|
+
* βοΈ **Fully customizable** via `TRUSTED_DEVICE` Django settings
|
|
21
|
+
* π« **Rejects refresh/verify** from unknown or expired devices
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## π¦ Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install django-trusted-device
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Add to your `INSTALLED_APPS`:
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
INSTALLED_APPS = [
|
|
35
|
+
...
|
|
36
|
+
'trusted_devices',
|
|
37
|
+
'rest_framework_simplejwt.token_blacklist',
|
|
38
|
+
]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Run migrations:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
python manage.py migrate
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## βοΈ Configuration
|
|
50
|
+
|
|
51
|
+
Customize behavior in `settings.py`:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
TRUSTED_DEVICE = {
|
|
55
|
+
"DELETE_DELAY_MINUTES": 60 * 24 * 7, # 7 days
|
|
56
|
+
"UPDATE_DELAY_MINUTES": 60, # 1 hour
|
|
57
|
+
"ALLOW_GLOBAL_DELETE": True,
|
|
58
|
+
"ALLOW_GLOBAL_UPDATE": True,
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## π§© Usage
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
## π SimpleJWT configuration
|
|
68
|
+
|
|
69
|
+
Replace default SimpleJWT serializers with TrustedDevice serializers.:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from datetime import timedelta
|
|
73
|
+
|
|
74
|
+
REST_FRAMEWORK = {
|
|
75
|
+
'DEFAULT_AUTHENTICATION_CLASSES': (
|
|
76
|
+
'trusted_devices.authentication.TrustedDeviceAuthentication',
|
|
77
|
+
),
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
SIMPLE_JWT = {
|
|
81
|
+
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=60),
|
|
82
|
+
"REFRESH_TOKEN_LIFETIME": timedelta(days=30),
|
|
83
|
+
"AUTH_HEADER_TYPES": ("Bearer",),
|
|
84
|
+
"TOKEN_OBTAIN_SERIALIZER": 'trusted_devices.serializers.TrustedDeviceTokenObtainPairSerializer',
|
|
85
|
+
"TOKEN_REFRESH_SERIALIZER": 'trusted_devices.serializers.TrustedDeviceTokenRefreshSerializer',
|
|
86
|
+
"TOKEN_VERIFY_SERIALIZER": 'trusted_devices.serializers.TrustedDeviceTokenVerifySerializer',
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### π Custom Token Views
|
|
92
|
+
|
|
93
|
+
Replace the default SimpleJWT views with:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from trusted_devices.views import (
|
|
97
|
+
TrustedDeviceTokenObtainPairView,
|
|
98
|
+
TrustedDeviceTokenRefreshView,
|
|
99
|
+
TrustedDeviceTokenVerifyView,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
urlpatterns = [
|
|
103
|
+
path('api/token/', TrustedDeviceTokenObtainPairView.as_view(), name='token_obtain_pair'),
|
|
104
|
+
path('api/token/refresh/', TrustedDeviceTokenRefreshView.as_view(), name='token_refresh'),
|
|
105
|
+
path('api/token/verify/', TrustedDeviceTokenVerifyView.as_view(), name='token_verify'),
|
|
106
|
+
]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
### π‘ Device Management API
|
|
112
|
+
|
|
113
|
+
Use the provided `TrustedDeviceViewSet`:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from trusted_devices.views import TrustedDeviceViewSet
|
|
117
|
+
|
|
118
|
+
router.register(r'trusted-devices', TrustedDeviceViewSet, basename='trusted-device')
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Endpoints:
|
|
122
|
+
|
|
123
|
+
* `GET /trusted-devices` β List all trusted devices
|
|
124
|
+
* `DELETE /trusted-devices/{device_uid}` β Delete a device
|
|
125
|
+
* `PATCH /trusted-devices/{device_uid}` β Update device permissions
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## π€ Device Model
|
|
130
|
+
|
|
131
|
+
Each trusted device includes:
|
|
132
|
+
|
|
133
|
+
* `device_uid`: Unique UUID
|
|
134
|
+
* `user_agent`: Browser or device string
|
|
135
|
+
* `ip_address`: IP address
|
|
136
|
+
* `country`, `region`, `city`: Geolocation (via `ipapi.co`)
|
|
137
|
+
* `last_seen`, `created_at`: Timestamps
|
|
138
|
+
* `can_delete_other_devices`, `can_update_other_devices`: Optional privileges
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## π§ How It Works
|
|
143
|
+
|
|
144
|
+
1. During login, a `device_uid` is generated and embedded in the token.
|
|
145
|
+
2. Clients use that token (with `device_uid`) for refresh/verify.
|
|
146
|
+
3. Each request is linked to a known device.
|
|
147
|
+
4. Users can manage or restrict their devices via API or Admin.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## π§ͺ Testing Locally
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# π§© Create and activate a uv-managed virtual environment
|
|
155
|
+
uv venv
|
|
156
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
157
|
+
|
|
158
|
+
# π¦ Install the package in editable mode with dev extras
|
|
159
|
+
uv pip install -e ".[dev]"
|
|
160
|
+
|
|
161
|
+
# π§ͺ Run the test suite
|
|
162
|
+
pytest
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## π§± Dependencies
|
|
168
|
+
|
|
169
|
+
* Django
|
|
170
|
+
* Django REST Framework
|
|
171
|
+
* djangorestframework-simplejwt
|
|
172
|
+
* [ipapi.co](https://ipapi.co) (for IP geolocation)
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## ποΈ Model Snapshot
|
|
177
|
+
|
|
178
|
+
| Field | Purpose |
|
|
179
|
+
| -------------------------- | ------------------- |
|
|
180
|
+
| `device_uid` | UUID primary key |
|
|
181
|
+
| `user_agent`, `ip_address` | Device fingerprint |
|
|
182
|
+
| `country / region / city` | Geoβlookup |
|
|
183
|
+
| `last_seen / created_at` | Activity timestamps |
|
|
184
|
+
| `can_update_other_devices` | Granular permission |
|
|
185
|
+
| `can_delete_other_devices` | Granular permission |
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## π€ Collaboration & Contributing
|
|
190
|
+
|
|
191
|
+
We love community contributions! To collaborate:
|
|
192
|
+
|
|
193
|
+
1. **Fork** the repo and create a feature branch:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
git checkout -b feature/my-amazing-idea
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
2. **Follow code style** β run:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
make lint # runs flake8, isort, black
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
3. **Write & run tests**:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
pytest
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
4. **Commit** with clear messages and open a **Pull Request**.
|
|
212
|
+
GitHub Actions will lint + test your branch automatically.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
### π£οΈ Discussions & Issues
|
|
217
|
+
|
|
218
|
+
* π‘ Questions / ideas β [GitHub Discussions](https://github.com/ganiyevuz/django-trusted-devices/discussions)
|
|
219
|
+
* π Bugs / feature requests β [GitHub Issues](https://github.com/ganiyevuz/django-trusted-devices/issues)
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
### π Maintainer Workflow
|
|
224
|
+
|
|
225
|
+
* PRs require at least one approval and passing CI
|
|
226
|
+
* We **squashβmerge** to keep history clean
|
|
227
|
+
* Follows **Semantic Versioning** (`MAJOR.MINOR.PATCH`), tagged as `vX.Y.Z`
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## π License
|
|
232
|
+
|
|
233
|
+
[MIT](LICENSE)
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
Made with β€οΈ by [Jahongir Ganiev](https://github.com/ganiyevuz)
|
|
238
|
+
Security questions or commercial support? Open an issue or email **[contact@jakhongir.dev](mailto:contact@jakhongir.dev)**
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "django-trusted-devices"
|
|
7
|
+
version = "v1.1"
|
|
8
|
+
description = "Secure and manage trusted login devices for Django users"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.13"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Jakhongir Ganiev", email = "contact@jakhongir.dev" }
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Framework :: Django",
|
|
18
|
+
"Framework :: Django :: 4.2",
|
|
19
|
+
"Framework :: Django :: 5.0",
|
|
20
|
+
"Framework :: Django :: 5.1",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
]
|
|
27
|
+
keywords = ["django", "trusted devices", "user security", "sessions", "login", "djangorestframework"]
|
|
28
|
+
|
|
29
|
+
dependencies = [
|
|
30
|
+
"django>=4.2,<5.3",
|
|
31
|
+
"djangorestframework>=3.14.0",
|
|
32
|
+
"djangorestframework-simplejwt>=5.5.0",
|
|
33
|
+
"drf-yasg>=1.21.10",
|
|
34
|
+
"httpx>=0.28.1",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
docs = ["mkdocs", "mkdocs-material"]
|
|
40
|
+
dev = [
|
|
41
|
+
"black>=25.1.0",
|
|
42
|
+
"build>=1.2.2.post1",
|
|
43
|
+
"flake8>=7.2.0",
|
|
44
|
+
"isort>=6.0.1",
|
|
45
|
+
"pytest>=8.3.5",
|
|
46
|
+
"pytest-cov>=6.1.1",
|
|
47
|
+
"pytest-django>=4.11.1",
|
|
48
|
+
"twine>=6.1.0",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[tool.mkdcos-gh-deploy]
|
|
52
|
+
remote = "origin"
|
|
53
|
+
branch = "gh-pages"
|
|
54
|
+
|
|
55
|
+
[tool.setuptools]
|
|
56
|
+
packages = { find = { where = ["src"] } }
|
|
57
|
+
|
|
58
|
+
[tool.black]
|
|
59
|
+
line-length = 88
|
|
60
|
+
target-version = ["py310", "py311", "py312"]
|
|
61
|
+
|
|
62
|
+
[tool.isort]
|
|
63
|
+
profile = "black"
|
|
64
|
+
multi_line_output = 3
|
|
65
|
+
|
|
66
|
+
[tool.pytest.ini_options]
|
|
67
|
+
testpaths = ["tests"]
|
|
68
|
+
python_files = ["test_*.py"]
|