the37lab-authlib 0.1.1750187527__tar.gz → 0.1.1750836881__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.
Potentially problematic release.
This version of the37lab-authlib might be problematic. Click here for more details.
- {the37lab_authlib-0.1.1750187527 → the37lab_authlib-0.1.1750836881}/PKG-INFO +10 -2
- {the37lab_authlib-0.1.1750187527 → the37lab_authlib-0.1.1750836881}/README.md +9 -1
- {the37lab_authlib-0.1.1750187527 → the37lab_authlib-0.1.1750836881}/pyproject.toml +1 -1
- {the37lab_authlib-0.1.1750187527 → the37lab_authlib-0.1.1750836881}/src/the37lab_authlib/auth.py +32 -41
- {the37lab_authlib-0.1.1750187527 → the37lab_authlib-0.1.1750836881}/src/the37lab_authlib.egg-info/PKG-INFO +10 -2
- {the37lab_authlib-0.1.1750187527 → the37lab_authlib-0.1.1750836881}/setup.cfg +0 -0
- {the37lab_authlib-0.1.1750187527 → the37lab_authlib-0.1.1750836881}/src/the37lab_authlib/__init__.py +0 -0
- {the37lab_authlib-0.1.1750187527 → the37lab_authlib-0.1.1750836881}/src/the37lab_authlib/db.py +0 -0
- {the37lab_authlib-0.1.1750187527 → the37lab_authlib-0.1.1750836881}/src/the37lab_authlib/decorators.py +0 -0
- {the37lab_authlib-0.1.1750187527 → the37lab_authlib-0.1.1750836881}/src/the37lab_authlib/exceptions.py +0 -0
- {the37lab_authlib-0.1.1750187527 → the37lab_authlib-0.1.1750836881}/src/the37lab_authlib/models.py +0 -0
- {the37lab_authlib-0.1.1750187527 → the37lab_authlib-0.1.1750836881}/src/the37lab_authlib.egg-info/SOURCES.txt +0 -0
- {the37lab_authlib-0.1.1750187527 → the37lab_authlib-0.1.1750836881}/src/the37lab_authlib.egg-info/dependency_links.txt +0 -0
- {the37lab_authlib-0.1.1750187527 → the37lab_authlib-0.1.1750836881}/src/the37lab_authlib.egg-info/requires.txt +0 -0
- {the37lab_authlib-0.1.1750187527 → the37lab_authlib-0.1.1750836881}/src/the37lab_authlib.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: the37lab_authlib
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1750836881
|
|
4
4
|
Summary: Python SDK for the Authlib
|
|
5
5
|
Author-email: the37lab <info@the37lab.com>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -72,6 +72,12 @@ def protected_route():
|
|
|
72
72
|
return "Protected content"
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
+
`AuthManager`'s blueprint now registers a global error handler for
|
|
76
|
+
`AuthError` and authenticates requests for all of its routes by default.
|
|
77
|
+
Authenticated users are made available as `flask.g.requesting_user`.
|
|
78
|
+
Only the login, OAuth, token refresh, registration and role listing
|
|
79
|
+
endpoints are exempt from this check.
|
|
80
|
+
|
|
75
81
|
## Configuration
|
|
76
82
|
|
|
77
83
|
### Required Parameters
|
|
@@ -144,7 +150,9 @@ def protected_route():
|
|
|
144
150
|
- Get redirect URL from `/api/v1/users/login/oauth`.
|
|
145
151
|
- Complete OAuth flow via `/api/v1/users/login/oauth2callback`.
|
|
146
152
|
4. **Protected Routes:**
|
|
147
|
-
-
|
|
153
|
+
- All routes inside the provided blueprint are authenticated by default.
|
|
154
|
+
The authenticated user can be accessed via `g.requesting_user`.
|
|
155
|
+
Use `@auth.require_auth()` to protect custom routes in your application.
|
|
148
156
|
|
|
149
157
|
## User Object
|
|
150
158
|
|
|
@@ -55,6 +55,12 @@ def protected_route():
|
|
|
55
55
|
return "Protected content"
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
`AuthManager`'s blueprint now registers a global error handler for
|
|
59
|
+
`AuthError` and authenticates requests for all of its routes by default.
|
|
60
|
+
Authenticated users are made available as `flask.g.requesting_user`.
|
|
61
|
+
Only the login, OAuth, token refresh, registration and role listing
|
|
62
|
+
endpoints are exempt from this check.
|
|
63
|
+
|
|
58
64
|
## Configuration
|
|
59
65
|
|
|
60
66
|
### Required Parameters
|
|
@@ -127,7 +133,9 @@ def protected_route():
|
|
|
127
133
|
- Get redirect URL from `/api/v1/users/login/oauth`.
|
|
128
134
|
- Complete OAuth flow via `/api/v1/users/login/oauth2callback`.
|
|
129
135
|
4. **Protected Routes:**
|
|
130
|
-
-
|
|
136
|
+
- All routes inside the provided blueprint are authenticated by default.
|
|
137
|
+
The authenticated user can be accessed via `g.requesting_user`.
|
|
138
|
+
Use `@auth.require_auth()` to protect custom routes in your application.
|
|
131
139
|
|
|
132
140
|
## User Object
|
|
133
141
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "the37lab_authlib"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.1750836881"
|
|
8
8
|
description = "Python SDK for the Authlib"
|
|
9
9
|
authors = [{name = "the37lab", email = "info@the37lab.com"}]
|
|
10
10
|
dependencies = ["flask", "psycopg2-binary", "pyjwt", "python-dotenv", "requests", "authlib", "bcrypt"]
|
{the37lab_authlib-0.1.1750187527 → the37lab_authlib-0.1.1750836881}/src/the37lab_authlib/auth.py
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import inspect
|
|
2
|
-
from flask import Blueprint, request, jsonify, current_app, url_for, redirect
|
|
2
|
+
from flask import Blueprint, request, jsonify, current_app, url_for, redirect, g
|
|
3
3
|
import jwt
|
|
4
4
|
from datetime import datetime, timedelta
|
|
5
5
|
from .db import Database
|
|
@@ -15,17 +15,6 @@ from functools import wraps
|
|
|
15
15
|
logging.basicConfig(level=logging.DEBUG)
|
|
16
16
|
logger = logging.getLogger(__name__)
|
|
17
17
|
|
|
18
|
-
def handle_auth_errors(f):
|
|
19
|
-
@wraps(f)
|
|
20
|
-
def decorated(*args, **kwargs):
|
|
21
|
-
try:
|
|
22
|
-
return f(*args, **kwargs)
|
|
23
|
-
except AuthError as e:
|
|
24
|
-
response = jsonify(e.to_dict())
|
|
25
|
-
response.status_code = e.status_code
|
|
26
|
-
return response
|
|
27
|
-
return decorated
|
|
28
|
-
|
|
29
18
|
class AuthManager:
|
|
30
19
|
def __init__(self, app=None, db_dsn=None, jwt_secret=None, oauth_config=None, id_type='integer'):
|
|
31
20
|
logger.info("INITIALIZING AUTHMANAGER {} - {} - {}".format(db_dsn, jwt_secret, not app))
|
|
@@ -132,8 +121,27 @@ class AuthManager:
|
|
|
132
121
|
def create_blueprint(self):
|
|
133
122
|
bp = Blueprint('auth', __name__, url_prefix='/api/v1/users')
|
|
134
123
|
|
|
124
|
+
public_endpoints = {
|
|
125
|
+
'auth.login',
|
|
126
|
+
'auth.oauth_login',
|
|
127
|
+
'auth.oauth_callback',
|
|
128
|
+
'auth.refresh_token',
|
|
129
|
+
'auth.register',
|
|
130
|
+
'auth.get_roles'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@bp.errorhandler(AuthError)
|
|
134
|
+
def handle_auth_error(err):
|
|
135
|
+
response = jsonify(err.to_dict())
|
|
136
|
+
response.status_code = err.status_code
|
|
137
|
+
return response
|
|
138
|
+
|
|
139
|
+
@bp.before_request
|
|
140
|
+
def load_user():
|
|
141
|
+
if request.endpoint not in public_endpoints:
|
|
142
|
+
g.requesting_user = self._authenticate_request()
|
|
143
|
+
|
|
135
144
|
@bp.route('/login', methods=['POST'])
|
|
136
|
-
@handle_auth_errors
|
|
137
145
|
def login():
|
|
138
146
|
data = request.get_json()
|
|
139
147
|
username = data.get('username')
|
|
@@ -168,7 +176,6 @@ class AuthManager:
|
|
|
168
176
|
})
|
|
169
177
|
|
|
170
178
|
@bp.route('/login/oauth', methods=['POST'])
|
|
171
|
-
@handle_auth_errors
|
|
172
179
|
def oauth_login():
|
|
173
180
|
provider = request.json.get('provider')
|
|
174
181
|
if provider not in self.oauth_config:
|
|
@@ -180,7 +187,6 @@ class AuthManager:
|
|
|
180
187
|
})
|
|
181
188
|
|
|
182
189
|
@bp.route('/login/oauth2callback')
|
|
183
|
-
@handle_auth_errors
|
|
184
190
|
def oauth_callback():
|
|
185
191
|
code = request.args.get('code')
|
|
186
192
|
provider = request.args.get('state')
|
|
@@ -197,28 +203,22 @@ class AuthManager:
|
|
|
197
203
|
return redirect(f"{frontend_url}/oauth-callback?token={token}&refresh_token={refresh_token}")
|
|
198
204
|
|
|
199
205
|
@bp.route('/login/profile')
|
|
200
|
-
@handle_auth_errors
|
|
201
206
|
def profile():
|
|
202
|
-
|
|
203
|
-
user = self.validate_token(token)
|
|
207
|
+
user = g.requesting_user
|
|
204
208
|
return jsonify(user)
|
|
205
209
|
|
|
206
210
|
@bp.route('/api-tokens', methods=['GET'])
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def get_tokens(requesting_user):
|
|
210
|
-
tokens = self.get_user_api_tokens(requesting_user['id'])
|
|
211
|
+
def get_tokens():
|
|
212
|
+
tokens = self.get_user_api_tokens(g.requesting_user['id'])
|
|
211
213
|
return jsonify(tokens)
|
|
212
214
|
|
|
213
215
|
@bp.route('/api-tokens', methods=['POST'])
|
|
214
|
-
|
|
215
|
-
@self.require_auth
|
|
216
|
-
def create_token(requesting_user):
|
|
216
|
+
def create_token():
|
|
217
217
|
name = request.json.get('name')
|
|
218
218
|
expires_in_days = request.json.get('expires_in_days')
|
|
219
219
|
if not name:
|
|
220
220
|
raise AuthError('Token name is required', 400)
|
|
221
|
-
api_token = self.create_api_token(requesting_user['id'], name, expires_in_days)
|
|
221
|
+
api_token = self.create_api_token(g.requesting_user['id'], name, expires_in_days)
|
|
222
222
|
return jsonify({
|
|
223
223
|
'id': api_token.id,
|
|
224
224
|
'name': api_token.name,
|
|
@@ -228,7 +228,6 @@ class AuthManager:
|
|
|
228
228
|
})
|
|
229
229
|
|
|
230
230
|
@bp.route('/token-refresh', methods=['POST'])
|
|
231
|
-
@handle_auth_errors
|
|
232
231
|
def refresh_token():
|
|
233
232
|
refresh_token = request.json.get('refresh_token')
|
|
234
233
|
if not refresh_token:
|
|
@@ -253,20 +252,16 @@ class AuthManager:
|
|
|
253
252
|
raise AuthError('Invalid refresh token', 401)
|
|
254
253
|
|
|
255
254
|
@bp.route('/api-tokens', methods=['POST'])
|
|
256
|
-
|
|
257
|
-
@self.require_auth
|
|
258
|
-
def create_api_token(requesting_user):
|
|
255
|
+
def create_api_token():
|
|
259
256
|
name = request.json.get('name')
|
|
260
257
|
if not name:
|
|
261
258
|
raise AuthError('Token name required', 400)
|
|
262
259
|
|
|
263
|
-
token = self.create_api_token(requesting_user['id'], name)
|
|
260
|
+
token = self.create_api_token(g.requesting_user['id'], name)
|
|
264
261
|
return jsonify({'token': token.token})
|
|
265
262
|
|
|
266
263
|
@bp.route('/api-tokens/validate', methods=['GET'])
|
|
267
|
-
|
|
268
|
-
@self.require_auth
|
|
269
|
-
def validate_api_token(requesting_user):
|
|
264
|
+
def validate_api_token():
|
|
270
265
|
token = request.json.get('token')
|
|
271
266
|
if not token:
|
|
272
267
|
raise AuthError('No API token provided', 401)
|
|
@@ -276,7 +271,7 @@ class AuthManager:
|
|
|
276
271
|
cur.execute("""
|
|
277
272
|
SELECT * FROM api_tokens
|
|
278
273
|
WHERE user_id = %s AND id = %s
|
|
279
|
-
""", (requesting_user['id'], token))
|
|
274
|
+
""", (g.requesting_user['id'], token))
|
|
280
275
|
api_token = cur.fetchone()
|
|
281
276
|
|
|
282
277
|
if not api_token:
|
|
@@ -297,9 +292,7 @@ class AuthManager:
|
|
|
297
292
|
return jsonify({'valid': True})
|
|
298
293
|
|
|
299
294
|
@bp.route('/api-tokens', methods=['DELETE'])
|
|
300
|
-
|
|
301
|
-
@self.require_auth
|
|
302
|
-
def delete_api_token(requesting_user):
|
|
295
|
+
def delete_api_token():
|
|
303
296
|
token = request.json.get('token')
|
|
304
297
|
if not token:
|
|
305
298
|
raise AuthError('Token required', 400)
|
|
@@ -310,7 +303,7 @@ class AuthManager:
|
|
|
310
303
|
DELETE FROM api_tokens
|
|
311
304
|
WHERE user_id = %s AND id = %s
|
|
312
305
|
RETURNING id
|
|
313
|
-
""", (requesting_user['id'], token))
|
|
306
|
+
""", (g.requesting_user['id'], token))
|
|
314
307
|
deleted_id = cur.fetchone()
|
|
315
308
|
if not deleted_id:
|
|
316
309
|
raise ValueError('Token not found or already deleted')
|
|
@@ -318,7 +311,6 @@ class AuthManager:
|
|
|
318
311
|
return jsonify({'deleted': True})
|
|
319
312
|
|
|
320
313
|
@bp.route('/register', methods=['POST'])
|
|
321
|
-
@handle_auth_errors
|
|
322
314
|
def register():
|
|
323
315
|
data = request.get_json()
|
|
324
316
|
|
|
@@ -357,7 +349,6 @@ class AuthManager:
|
|
|
357
349
|
return jsonify({'id': user.id}), 201
|
|
358
350
|
|
|
359
351
|
@bp.route('/roles', methods=['GET'])
|
|
360
|
-
@handle_auth_errors
|
|
361
352
|
def get_roles():
|
|
362
353
|
with self.db.get_cursor() as cur:
|
|
363
354
|
cur.execute("SELECT * FROM roles")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: the37lab_authlib
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1750836881
|
|
4
4
|
Summary: Python SDK for the Authlib
|
|
5
5
|
Author-email: the37lab <info@the37lab.com>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -72,6 +72,12 @@ def protected_route():
|
|
|
72
72
|
return "Protected content"
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
+
`AuthManager`'s blueprint now registers a global error handler for
|
|
76
|
+
`AuthError` and authenticates requests for all of its routes by default.
|
|
77
|
+
Authenticated users are made available as `flask.g.requesting_user`.
|
|
78
|
+
Only the login, OAuth, token refresh, registration and role listing
|
|
79
|
+
endpoints are exempt from this check.
|
|
80
|
+
|
|
75
81
|
## Configuration
|
|
76
82
|
|
|
77
83
|
### Required Parameters
|
|
@@ -144,7 +150,9 @@ def protected_route():
|
|
|
144
150
|
- Get redirect URL from `/api/v1/users/login/oauth`.
|
|
145
151
|
- Complete OAuth flow via `/api/v1/users/login/oauth2callback`.
|
|
146
152
|
4. **Protected Routes:**
|
|
147
|
-
-
|
|
153
|
+
- All routes inside the provided blueprint are authenticated by default.
|
|
154
|
+
The authenticated user can be accessed via `g.requesting_user`.
|
|
155
|
+
Use `@auth.require_auth()` to protect custom routes in your application.
|
|
148
156
|
|
|
149
157
|
## User Object
|
|
150
158
|
|
|
File without changes
|
{the37lab_authlib-0.1.1750187527 → the37lab_authlib-0.1.1750836881}/src/the37lab_authlib/__init__.py
RENAMED
|
File without changes
|
{the37lab_authlib-0.1.1750187527 → the37lab_authlib-0.1.1750836881}/src/the37lab_authlib/db.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{the37lab_authlib-0.1.1750187527 → the37lab_authlib-0.1.1750836881}/src/the37lab_authlib/models.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|