authly-sso 0.4.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Authly
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,400 @@
1
+ Metadata-Version: 2.4
2
+ Name: authly-sso
3
+ Version: 0.4.0
4
+ Summary: Authly SSO OAuth2 integratsiyasi uchun yagona Python / Django SDK
5
+ Author: Authly
6
+ License: MIT
7
+ Keywords: sso,authly,oauth2,django,drf,authentication
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Framework :: Django
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Requires-Python: >=3.9
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: requests>=2.28.0
21
+ Provides-Extra: django
22
+ Requires-Dist: django>=4.2; extra == "django"
23
+ Requires-Dist: djangorestframework>=3.14.0; extra == "django"
24
+ Dynamic: author
25
+ Dynamic: classifier
26
+ Dynamic: description
27
+ Dynamic: description-content-type
28
+ Dynamic: keywords
29
+ Dynamic: license
30
+ Dynamic: license-file
31
+ Dynamic: provides-extra
32
+ Dynamic: requires-dist
33
+ Dynamic: requires-python
34
+ Dynamic: summary
35
+
36
+ # 🛡️ Authly SSO
37
+
38
+ Loyiha va xizmatlar uchun yagona **Authly SSO (Single Sign-On / OAuth 2.0)** integratsiya kutubxonasi.
39
+
40
+ [![PyPI version](https://badge.fury.io/py/authly-sso.svg)](https://badge.fury.io/py/authly-sso)
41
+ [![Python](https://img.shields.io/pypi/pyversions/authly-sso)](https://pypi.org/project/authly-sso/)
42
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
43
+
44
+ ---
45
+
46
+ ## ✨ Imkoniyatlar
47
+
48
+ - **Backend va Frontend domenlari ajratilgan**:
49
+ - `AUTHLY_BACKEND_URL` — Token olish, profil yuklash va barcha server-to-server API so'rovlari.
50
+ - `AUTHLY_FRONTEND_URL` — Foydalanuvchini brauzerda SSO login sahifasiga yo'naltirish havolasini yasash.
51
+ - **Django & DRF tayyor integratsiyasi** — 4 ta tayyor endpoint:
52
+ - `login-url/` — Authly SSO ga yo'naltiruvchi login havolasini olish.
53
+ - `callback/` — OAuth2 code orqali tizimga kirish va tokenlar qaytarish.
54
+ - `webhook/` — Authly SSO dan avtomatik foydalanuvchi ma'lumotlarini yangilash.
55
+ - `sync/` — Foydalanuvchi profilini qo'lda Authly bilan sinxronlashtirish.
56
+ - **Dinamik `redirect_uri`** — Har bir so'rovda `redirect_uri` ni so'rov orqali uzatish mumkin (settings.py da ham belgilash mumkin).
57
+ - **To'liq ma'lumotlar sinxronizatsiyasi** — `authly_id`, `first_name`, `last_name`, `email`, `phone`/`phone_number`, `avatar`, `birth_date` avtomatik to'ldiriladi.
58
+ - **HMAC SHA-256 Webhook xavfsizligi** — `X-SSO-Signature` imzosini avtomatik tekshirish.
59
+ - **Avtomatik JWT** — `djangorestframework-simplejwt` mavjud bo'lsa `access` va `refresh` tokenlar generatsiya qilinadi.
60
+ - **500 xatoliksiz** — Barcha xatoliklar (`AuthlySDKError`, `IntegrityError`, `DatabaseError`, `PermissionDenied` va h.k.) DRF `Response` sifatida qaytariladi.
61
+
62
+ ---
63
+
64
+ ## 📦 O'rnatish
65
+
66
+ ```bash
67
+ # Faqat Python (FastAPI, Flask, skriptlar uchun):
68
+ pip install authly-sso
69
+
70
+ # Django / DRF bilan:
71
+ pip install "authly-sso[django]"
72
+ ```
73
+
74
+ ---
75
+
76
+ ## ⚙️ Sozlash
77
+
78
+ `settings.py` ga quyidagilarni qo'shing:
79
+
80
+ ```python
81
+ AUTHLY_BACKEND_URL = "https://api.authly.uz"
82
+ AUTHLY_FRONTEND_URL = "https://authly.uz"
83
+ AUTHLY_CLIENT_ID = "sizning-client-id"
84
+ AUTHLY_CLIENT_SECRET = "sizning-client-secret"
85
+
86
+ # Ixtiyoriy — frontend har so'rovda dinamik yuborsa kerak emas:
87
+ AUTHLY_REDIRECT_URI = "https://saytingiz.uz/api/v1/auth/authly/callback/"
88
+
89
+ # Webhook xavfsizligi uchun:
90
+ AUTHLY_WEBHOOK_SECRET = "sizning-webhook-secret"
91
+
92
+ # Ixtiyoriy (standart: 10 soniya):
93
+ AUTHLY_TIMEOUT = 10
94
+ ```
95
+
96
+ ---
97
+
98
+ ## 🛠 Django / DRF da ishlatish
99
+
100
+ ### 1. `urls.py` ga uling
101
+
102
+ ```python
103
+ from django.urls import path, include
104
+
105
+ urlpatterns = [
106
+ path("api/v1/auth/authly/", include("authly.urls")),
107
+ ]
108
+ ```
109
+
110
+ 4 ta endpoint faollashadi:
111
+
112
+ | Endpoint | Method | Tavsif |
113
+ |----------|--------|--------|
114
+ | `login-url/` | `GET` | Authly SSO login havolasini qaytaradi |
115
+ | `callback/` | `GET`, `POST` | OAuth2 code → foydalanuvchi & tokenlar |
116
+ | `webhook/` | `POST` | Authly dan avtomatik yangilash |
117
+ | `sync/` | `GET`, `POST` | Foydalanuvchi profilini qo'lda yangilash |
118
+
119
+ ---
120
+
121
+ ### 2. Login havolasini olish
122
+
123
+ ```http
124
+ GET /api/v1/auth/authly/login-url/?redirect_uri=https://saytingiz.uz/callback
125
+ ```
126
+
127
+ **Parametrlar (query string):**
128
+
129
+ | Parametr | Majburiy | Tavsif |
130
+ |----------|----------|--------|
131
+ | `redirect_uri` | Ha\* | Callback URL. `AUTHLY_REDIRECT_URI` settings da belgilangan bo'lsa ixtiyoriy. |
132
+ | `scope` | Yo'q | Ruxsatlar doirasi (standart: `read`) |
133
+ | `state` | Yo'q | CSRF himoyasi yoki holat parametri |
134
+
135
+ **Javob:**
136
+ ```json
137
+ {
138
+ "data": {
139
+ "url": "https://authly.uz/login?client_id=...&redirect_uri=...&scope=read"
140
+ },
141
+ "error": null,
142
+ "success": true
143
+ }
144
+ ```
145
+
146
+ ---
147
+
148
+ ### 3. Callback (tizimga kirish)
149
+
150
+ ```http
151
+ GET /api/v1/auth/authly/callback/?code=<CODE>&redirect_uri=<URI>
152
+ POST /api/v1/auth/authly/callback/
153
+ Content-Type: application/json
154
+
155
+ {"code": "...", "redirect_uri": "..."}
156
+ ```
157
+
158
+ **Parametrlar:**
159
+
160
+ | Parametr | Tavsif |
161
+ |----------|--------|
162
+ | `code` | Authly SSO dan kelgan bir martalik kod |
163
+ | `redirect_uri` | Login havolasi yasashda ishlatilgan xuddi shu URL |
164
+ | `code_verifier` | PKCE ishlatilsa (ixtiyoriy) |
165
+
166
+ **Muvaffaqiyatli javob:**
167
+ ```json
168
+ {
169
+ "data": {
170
+ "is_new_user": false,
171
+ "user": {
172
+ "id": 1,
173
+ "authly_id": "c1f7a0b3-1234-4567-89ab-cdef01234567",
174
+ "username": "authly_c1f7a0b3-1234-4567-89ab-cdef01234567",
175
+ "full_name": "Ism Familiya",
176
+ "first_name": "Ism",
177
+ "last_name": "Familiya",
178
+ "email": "user@example.com",
179
+ "phone": "+998901234567",
180
+ "avatar": "https://api.authly.uz/media/users/avatar/2026/10/avatar.jpg",
181
+ "birth_date": "1995-05-15"
182
+ },
183
+ "access": "<LOCAL_JWT_ACCESS_TOKEN>",
184
+ "refresh": "<LOCAL_JWT_REFRESH_TOKEN>",
185
+ "authly_access_token": "<AUTHLY_OAUTH_ACCESS_TOKEN>"
186
+ },
187
+ "error": null,
188
+ "success": true
189
+ }
190
+ ```
191
+
192
+ > **Muhim:** `authly_access_token` — bu Authly serveriga murojaat qilish uchun OAuth tokeni.
193
+ > `access` va `refresh` esa loyihangiz (lokal) JWT tokenlari.
194
+ > Frontend `authly_access_token` ni saqlashi va `/sync/` endpointiga shu tokenni yuborishi mumkin.
195
+
196
+ ---
197
+
198
+ ### 4. Profil sinxronizatsiyasi (Manual Sync)
199
+
200
+ Foydalanuvchi Authly portalida o'z ma'lumotlarini o'zgartirib qaytganda profilini yangilash:
201
+
202
+ ```http
203
+ POST /api/v1/auth/authly/sync/
204
+ Authorization: Bearer <LOCAL_JWT>
205
+ Content-Type: application/json
206
+
207
+ {"access_token": "<AUTHLY_OAUTH_ACCESS_TOKEN>"}
208
+ ```
209
+
210
+ > ⚠️ `access_token` sifatida lokal JWT emas, callback da kelgan **`authly_access_token`** yuboriladi.
211
+
212
+ **Muvaffaqiyatli javob:**
213
+ ```json
214
+ {
215
+ "data": {
216
+ "status": "success",
217
+ "updated_fields": ["first_name", "avatar"],
218
+ "user": {
219
+ "id": 1,
220
+ "authly_id": "c1f7a0b3-...",
221
+ "first_name": "Yangi Ism",
222
+ "last_name": "Familiya",
223
+ "email": "user@example.com",
224
+ "phone": "+998901234567",
225
+ "avatar": "https://api.authly.uz/media/users/avatar/2026/10/new_avatar.jpg",
226
+ "birth_date": "1995-05-15",
227
+ "region_info": { "id": "...", "name": "Toshkent shahri" },
228
+ "district_info": { "id": "...", "name": "Yunusobod tumani" }
229
+ }
230
+ },
231
+ "error": null,
232
+ "success": true
233
+ }
234
+ ```
235
+
236
+ ---
237
+
238
+ ### 5. Webhook (Avtomatik sinxronizatsiya)
239
+
240
+ Authly SSO foydalanuvchi ma'lumotlari o'zgarganda webhook yuboradi. SDK:
241
+ 1. `X-SSO-Signature` (HMAC SHA-256) imzosini tekshiradi.
242
+ 2. Foydalanuvchini `authly_id` → `phone_number` → `email` ketma-ketligida topadi.
243
+ 3. Barcha o'zgargan maydonlarni avtomatik yangilaydi.
244
+
245
+ ```http
246
+ POST /api/v1/auth/authly/webhook/
247
+ X-SSO-Signature: sha256=<HMAC>
248
+ Content-Type: application/json
249
+
250
+ {
251
+ "event": "user.updated",
252
+ "timestamp": "2026-10-31T12:00:00.000Z",
253
+ "user": {
254
+ "id": "c1f7a0b3-...",
255
+ "authly_id": "c1f7a0b3-...",
256
+ "first_name": "Alisher",
257
+ "last_name": "Valiyev",
258
+ "phone": "+998901234567",
259
+ "email": "alisher@example.com",
260
+ "avatar": "https://api.authly.uz/media/users/avatar/2026/10/new_avatar.jpg",
261
+ "birth_date": "1998-05-15",
262
+ "region": { "id": "...", "name": "Toshkent shahri" },
263
+ "district": { "id": "...", "name": "Yunusobod tumani" }
264
+ }
265
+ }
266
+ ```
267
+
268
+ ---
269
+
270
+ ## 🧩 Maxsus moslashtirish (Override)
271
+
272
+ ### Viloyat/Tuman bog'lash va bloklash tekshiruvi
273
+
274
+ ```python
275
+ from authly.views import AuthlyCallbackAPIView, AuthlyWebhookAPIView
276
+ from rest_framework.exceptions import PermissionDenied
277
+ from apps.users.models import Region, District
278
+
279
+ class CustomAuthlyCallbackView(AuthlyCallbackAPIView):
280
+ def get_or_create_user(self, user_data: dict):
281
+ user, created = super().get_or_create_user(user_data)
282
+
283
+ region_data = user_data.get("region_info") or user_data.get("region")
284
+ district_data = user_data.get("district_info") or user_data.get("district")
285
+
286
+ if region_data and hasattr(user, "region"):
287
+ user.region = Region.objects.filter(
288
+ name__iexact=region_data.get("name")
289
+ ).first()
290
+ if district_data and hasattr(user, "district"):
291
+ user.district = District.objects.filter(
292
+ name__iexact=district_data.get("name")
293
+ ).first()
294
+
295
+ user.save()
296
+
297
+ if getattr(user, "is_blocked", False):
298
+ raise PermissionDenied("Hisobingiz administrator tomonidan bloklangan.")
299
+
300
+ return user, created
301
+
302
+
303
+ class CustomAuthlyWebhookView(AuthlyWebhookAPIView):
304
+ def sync_user(self, user_data: dict):
305
+ user = super().sync_user(user_data)
306
+ # Qo'shimcha logika...
307
+ return user
308
+ ```
309
+
310
+ ### Javob formatini o'zgartirish
311
+
312
+ ```python
313
+ class CustomAuthlyCallbackView(AuthlyCallbackAPIView):
314
+ def get_response_data(self, user, user_data, created, tokens, authly_access_token=None):
315
+ data = super().get_response_data(user, user_data, created, tokens, authly_access_token)
316
+ data["my_custom_field"] = "qiymat"
317
+ return data
318
+ ```
319
+
320
+ ---
321
+
322
+ ## 🐍 Sof Python / FastAPI / Flask da ishlatish
323
+
324
+ ```python
325
+ from authly import AuthlyClient, AuthlySDKError
326
+
327
+ client = AuthlyClient(
328
+ backend_url="https://api.authly.uz",
329
+ frontend_url="https://authly.uz",
330
+ client_id="sizning-client-id",
331
+ client_secret="sizning-client-secret",
332
+ redirect_uri="https://saytingiz.uz/callback",
333
+ webhook_secret="sizning-webhook-secret",
334
+ )
335
+
336
+ # 1. Login havolasi:
337
+ url = client.get_authorization_url(scope="read", state="csrf_token")
338
+
339
+ # 2. Code → profil + authly access_token:
340
+ try:
341
+ profile, authly_access_token = client.authenticate_code(
342
+ code="authorization_code_here",
343
+ redirect_uri="https://saytingiz.uz/callback",
344
+ )
345
+ print(profile) # {"authly_id": "...", "first_name": "...", ...}
346
+ print(authly_access_token) # "eyJ..." — Authly OAuth tokeni
347
+ except AuthlySDKError as e:
348
+ print(f"Xatolik: {e}")
349
+
350
+ # 3. access_token bilan profil olish:
351
+ profile = client.get_user_profile(access_token=authly_access_token)
352
+
353
+ # 4. Webhook imzosini tekshirish:
354
+ is_valid = client.verify_webhook_signature(
355
+ payload_bytes=raw_body,
356
+ signature=request.headers.get("X-SSO-Signature"),
357
+ )
358
+ ```
359
+
360
+ ---
361
+
362
+ ## ⚠️ Xatoliklar (Exceptions)
363
+
364
+ | Sinf | Sababi |
365
+ |------|--------|
366
+ | `AuthlySDKError` | Barcha SDK xatoliklari uchun asosiy sinf |
367
+ | `AuthlyTokenExchangeError` | Kodni tokenga almashtirishdagi xatolik |
368
+ | `AuthlyProfileFetchError` | Profil olishdagi xatolik |
369
+ | `AuthlyWebhookSignatureError` | Webhook imzosi xatoligi |
370
+
371
+ ---
372
+
373
+ ## 📋 Jarayon diagrammasi
374
+
375
+ ```
376
+ Frontend Backend (SDK) Authly Server
377
+ | | |
378
+ |-- GET /login-url/ ---> | |
379
+ | |-- get_authorization_url()
380
+ |<-- {"url": "https://authly.uz/login?..."} ----|
381
+ | | |
382
+ |-- redirect to Authly login page -------------> |
383
+ |<-- redirect back with ?code=<CODE> ----------- |
384
+ | | |
385
+ |-- POST /callback/ ---> | |
386
+ | {code, redirect_uri} |-- exchange_code_for_token() --> Authly
387
+ | |<-- {access_token, ...} <------- Authly
388
+ | |-- get_user_profile(access_token) -> Authly
389
+ | |<-- {authly_id, phone, ...} <--- Authly
390
+ | |-- find_or_create_user()
391
+ | |-- generate_jwt_tokens()
392
+ |<-- {user, access, refresh, authly_access_token}
393
+ ```
394
+
395
+ ---
396
+
397
+ ## 📄 Litsenziya
398
+
399
+ MIT litsenziyasi ostida tarqatiladi.
400
+ Savollar va takliflar uchun **Authly** jamoasiga murojaat qiling.