the37lab-authlib 0.1.1750837371__tar.gz → 0.1.1750840380__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.1750837371 → the37lab_authlib-0.1.1750840380}/PKG-INFO +4 -19
- {the37lab_authlib-0.1.1750837371 → the37lab_authlib-0.1.1750840380}/README.md +3 -18
- {the37lab_authlib-0.1.1750837371 → the37lab_authlib-0.1.1750840380}/pyproject.toml +1 -1
- {the37lab_authlib-0.1.1750837371 → the37lab_authlib-0.1.1750840380}/src/the37lab_authlib/auth.py +37 -52
- {the37lab_authlib-0.1.1750837371 → the37lab_authlib-0.1.1750840380}/src/the37lab_authlib.egg-info/PKG-INFO +4 -19
- {the37lab_authlib-0.1.1750837371 → the37lab_authlib-0.1.1750840380}/setup.cfg +0 -0
- {the37lab_authlib-0.1.1750837371 → the37lab_authlib-0.1.1750840380}/src/the37lab_authlib/__init__.py +0 -0
- {the37lab_authlib-0.1.1750837371 → the37lab_authlib-0.1.1750840380}/src/the37lab_authlib/db.py +0 -0
- {the37lab_authlib-0.1.1750837371 → the37lab_authlib-0.1.1750840380}/src/the37lab_authlib/decorators.py +0 -0
- {the37lab_authlib-0.1.1750837371 → the37lab_authlib-0.1.1750840380}/src/the37lab_authlib/exceptions.py +0 -0
- {the37lab_authlib-0.1.1750837371 → the37lab_authlib-0.1.1750840380}/src/the37lab_authlib/models.py +0 -0
- {the37lab_authlib-0.1.1750837371 → the37lab_authlib-0.1.1750840380}/src/the37lab_authlib.egg-info/SOURCES.txt +0 -0
- {the37lab_authlib-0.1.1750837371 → the37lab_authlib-0.1.1750840380}/src/the37lab_authlib.egg-info/dependency_links.txt +0 -0
- {the37lab_authlib-0.1.1750837371 → the37lab_authlib-0.1.1750840380}/src/the37lab_authlib.egg-info/requires.txt +0 -0
- {the37lab_authlib-0.1.1750837371 → the37lab_authlib-0.1.1750840380}/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.1750840380
|
|
4
4
|
Summary: Python SDK for the Authlib
|
|
5
5
|
Author-email: the37lab <info@the37lab.com>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -70,21 +70,8 @@ auth = AuthManager(
|
|
|
70
70
|
@auth.require_auth(roles=["admin"])
|
|
71
71
|
def protected_route():
|
|
72
72
|
return "Protected content"
|
|
73
|
-
|
|
74
|
-
@app.route("/public")
|
|
75
|
-
@auth.public_endpoint
|
|
76
|
-
def custom_public_route():
|
|
77
|
-
return "Public content"
|
|
78
73
|
```
|
|
79
74
|
|
|
80
|
-
`AuthManager`'s blueprint now registers a global error handler for
|
|
81
|
-
`AuthError` and authenticates requests for all of its routes by default.
|
|
82
|
-
Authenticated users are made available as `flask.g.requesting_user`.
|
|
83
|
-
Only the login, OAuth, token refresh, registration and role listing
|
|
84
|
-
endpoints are exempt from this check. Additional routes can be marked as
|
|
85
|
-
public using the `@auth.public_endpoint` decorator or
|
|
86
|
-
`auth.add_public_endpoint("auth.some_endpoint")`.
|
|
87
|
-
|
|
88
75
|
## Configuration
|
|
89
76
|
|
|
90
77
|
### Required Parameters
|
|
@@ -139,8 +126,8 @@ public using the `@auth.public_endpoint` decorator or
|
|
|
139
126
|
|
|
140
127
|
### API Tokens
|
|
141
128
|
- `POST /api/v1/users/{user}/api-tokens` - Create API token
|
|
142
|
-
- **Request:** `{ "name": "string", "
|
|
143
|
-
- **Response:** `{ "token": "string", "id": "uuid",
|
|
129
|
+
- **Request:** `{ "name": "string", "expires_in_days": number | null }`
|
|
130
|
+
- **Response:** `{ "token": "string", "id": "uuid", "name": "string", "created_at": "timestamp", "expires_at": "timestamp | null" }`
|
|
144
131
|
- `GET /api/v1/users/{user}/api-tokens` - List API tokens
|
|
145
132
|
- **Response:** `[ { "id": "uuid", "name": "string", ... } ]`
|
|
146
133
|
- `DELETE /api/v1/users/{user}/api-tokens/{token_id}` - Delete API token
|
|
@@ -157,9 +144,7 @@ public using the `@auth.public_endpoint` decorator or
|
|
|
157
144
|
- Get redirect URL from `/api/v1/users/login/oauth`.
|
|
158
145
|
- Complete OAuth flow via `/api/v1/users/login/oauth2callback`.
|
|
159
146
|
4. **Protected Routes:**
|
|
160
|
-
-
|
|
161
|
-
The authenticated user can be accessed via `g.requesting_user`.
|
|
162
|
-
Use `@auth.require_auth()` to protect custom routes in your application.
|
|
147
|
+
- Use `@auth.require_auth()` decorator to protect Flask routes.
|
|
163
148
|
|
|
164
149
|
## User Object
|
|
165
150
|
|
|
@@ -53,21 +53,8 @@ auth = AuthManager(
|
|
|
53
53
|
@auth.require_auth(roles=["admin"])
|
|
54
54
|
def protected_route():
|
|
55
55
|
return "Protected content"
|
|
56
|
-
|
|
57
|
-
@app.route("/public")
|
|
58
|
-
@auth.public_endpoint
|
|
59
|
-
def custom_public_route():
|
|
60
|
-
return "Public content"
|
|
61
56
|
```
|
|
62
57
|
|
|
63
|
-
`AuthManager`'s blueprint now registers a global error handler for
|
|
64
|
-
`AuthError` and authenticates requests for all of its routes by default.
|
|
65
|
-
Authenticated users are made available as `flask.g.requesting_user`.
|
|
66
|
-
Only the login, OAuth, token refresh, registration and role listing
|
|
67
|
-
endpoints are exempt from this check. Additional routes can be marked as
|
|
68
|
-
public using the `@auth.public_endpoint` decorator or
|
|
69
|
-
`auth.add_public_endpoint("auth.some_endpoint")`.
|
|
70
|
-
|
|
71
58
|
## Configuration
|
|
72
59
|
|
|
73
60
|
### Required Parameters
|
|
@@ -122,8 +109,8 @@ public using the `@auth.public_endpoint` decorator or
|
|
|
122
109
|
|
|
123
110
|
### API Tokens
|
|
124
111
|
- `POST /api/v1/users/{user}/api-tokens` - Create API token
|
|
125
|
-
- **Request:** `{ "name": "string", "
|
|
126
|
-
- **Response:** `{ "token": "string", "id": "uuid",
|
|
112
|
+
- **Request:** `{ "name": "string", "expires_in_days": number | null }`
|
|
113
|
+
- **Response:** `{ "token": "string", "id": "uuid", "name": "string", "created_at": "timestamp", "expires_at": "timestamp | null" }`
|
|
127
114
|
- `GET /api/v1/users/{user}/api-tokens` - List API tokens
|
|
128
115
|
- **Response:** `[ { "id": "uuid", "name": "string", ... } ]`
|
|
129
116
|
- `DELETE /api/v1/users/{user}/api-tokens/{token_id}` - Delete API token
|
|
@@ -140,9 +127,7 @@ public using the `@auth.public_endpoint` decorator or
|
|
|
140
127
|
- Get redirect URL from `/api/v1/users/login/oauth`.
|
|
141
128
|
- Complete OAuth flow via `/api/v1/users/login/oauth2callback`.
|
|
142
129
|
4. **Protected Routes:**
|
|
143
|
-
-
|
|
144
|
-
The authenticated user can be accessed via `g.requesting_user`.
|
|
145
|
-
Use `@auth.require_auth()` to protect custom routes in your application.
|
|
130
|
+
- Use `@auth.require_auth()` decorator to protect Flask routes.
|
|
146
131
|
|
|
147
132
|
## User Object
|
|
148
133
|
|
|
@@ -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.1750840380"
|
|
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.1750837371 → the37lab_authlib-0.1.1750840380}/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
|
|
3
3
|
import jwt
|
|
4
4
|
from datetime import datetime, timedelta
|
|
5
5
|
from .db import Database
|
|
@@ -15,6 +15,17 @@ 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
|
+
|
|
18
29
|
class AuthManager:
|
|
19
30
|
def __init__(self, app=None, db_dsn=None, jwt_secret=None, oauth_config=None, id_type='integer'):
|
|
20
31
|
logger.info("INITIALIZING AUTHMANAGER {} - {} - {}".format(db_dsn, jwt_secret, not app))
|
|
@@ -22,15 +33,6 @@ class AuthManager:
|
|
|
22
33
|
self.jwt_secret = jwt_secret
|
|
23
34
|
logger.debug(f"Initializing AuthManager with JWT secret: {jwt_secret[:5]}..." if jwt_secret else "No JWT secret provided")
|
|
24
35
|
self.oauth_config = oauth_config or {}
|
|
25
|
-
self.public_endpoints = {
|
|
26
|
-
'auth.login',
|
|
27
|
-
'auth.oauth_login',
|
|
28
|
-
'auth.oauth_callback',
|
|
29
|
-
'auth.refresh_token',
|
|
30
|
-
'auth.register',
|
|
31
|
-
'auth.get_roles'
|
|
32
|
-
}
|
|
33
|
-
self.bp = None
|
|
34
36
|
|
|
35
37
|
if app:
|
|
36
38
|
self.init_app(app)
|
|
@@ -122,17 +124,6 @@ class AuthManager:
|
|
|
122
124
|
|
|
123
125
|
return f(*args, **kwargs)
|
|
124
126
|
return decorated
|
|
125
|
-
|
|
126
|
-
def add_public_endpoint(self, endpoint):
|
|
127
|
-
"""Mark an endpoint as public so it bypasses authentication."""
|
|
128
|
-
self.public_endpoints.add(endpoint)
|
|
129
|
-
|
|
130
|
-
def public_endpoint(self, f):
|
|
131
|
-
"""Decorator to mark a view function as public."""
|
|
132
|
-
if self.bp:
|
|
133
|
-
endpoint = f"{self.bp.name}.{f.__name__}"
|
|
134
|
-
self.add_public_endpoint(endpoint)
|
|
135
|
-
return f
|
|
136
127
|
|
|
137
128
|
def init_app(self, app):
|
|
138
129
|
app.auth_manager = self
|
|
@@ -140,21 +131,9 @@ class AuthManager:
|
|
|
140
131
|
|
|
141
132
|
def create_blueprint(self):
|
|
142
133
|
bp = Blueprint('auth', __name__, url_prefix='/api/v1/users')
|
|
143
|
-
self.bp = bp
|
|
144
|
-
bp.public_endpoint = self.public_endpoint
|
|
145
|
-
|
|
146
|
-
@bp.errorhandler(AuthError)
|
|
147
|
-
def handle_auth_error(err):
|
|
148
|
-
response = jsonify(err.to_dict())
|
|
149
|
-
response.status_code = err.status_code
|
|
150
|
-
return response
|
|
151
|
-
|
|
152
|
-
@bp.before_request
|
|
153
|
-
def load_user():
|
|
154
|
-
if request.endpoint not in self.public_endpoints:
|
|
155
|
-
g.requesting_user = self._authenticate_request()
|
|
156
134
|
|
|
157
135
|
@bp.route('/login', methods=['POST'])
|
|
136
|
+
@handle_auth_errors
|
|
158
137
|
def login():
|
|
159
138
|
data = request.get_json()
|
|
160
139
|
username = data.get('username')
|
|
@@ -189,6 +168,7 @@ class AuthManager:
|
|
|
189
168
|
})
|
|
190
169
|
|
|
191
170
|
@bp.route('/login/oauth', methods=['POST'])
|
|
171
|
+
@handle_auth_errors
|
|
192
172
|
def oauth_login():
|
|
193
173
|
provider = request.json.get('provider')
|
|
194
174
|
if provider not in self.oauth_config:
|
|
@@ -200,6 +180,7 @@ class AuthManager:
|
|
|
200
180
|
})
|
|
201
181
|
|
|
202
182
|
@bp.route('/login/oauth2callback')
|
|
183
|
+
@handle_auth_errors
|
|
203
184
|
def oauth_callback():
|
|
204
185
|
code = request.args.get('code')
|
|
205
186
|
provider = request.args.get('state')
|
|
@@ -216,22 +197,28 @@ class AuthManager:
|
|
|
216
197
|
return redirect(f"{frontend_url}/oauth-callback?token={token}&refresh_token={refresh_token}")
|
|
217
198
|
|
|
218
199
|
@bp.route('/login/profile')
|
|
200
|
+
@handle_auth_errors
|
|
219
201
|
def profile():
|
|
220
|
-
|
|
202
|
+
token = request.headers.get('Authorization', '').split(' ')[-1]
|
|
203
|
+
user = self.validate_token(token)
|
|
221
204
|
return jsonify(user)
|
|
222
205
|
|
|
223
206
|
@bp.route('/api-tokens', methods=['GET'])
|
|
224
|
-
|
|
225
|
-
|
|
207
|
+
@handle_auth_errors
|
|
208
|
+
@self.require_auth
|
|
209
|
+
def get_tokens(requesting_user):
|
|
210
|
+
tokens = self.get_user_api_tokens(requesting_user['id'])
|
|
226
211
|
return jsonify(tokens)
|
|
227
212
|
|
|
228
213
|
@bp.route('/api-tokens', methods=['POST'])
|
|
229
|
-
|
|
214
|
+
@handle_auth_errors
|
|
215
|
+
@self.require_auth
|
|
216
|
+
def create_token(requesting_user):
|
|
230
217
|
name = request.json.get('name')
|
|
231
218
|
expires_in_days = request.json.get('expires_in_days')
|
|
232
219
|
if not name:
|
|
233
220
|
raise AuthError('Token name is required', 400)
|
|
234
|
-
api_token = self.create_api_token(
|
|
221
|
+
api_token = self.create_api_token(requesting_user['id'], name, expires_in_days)
|
|
235
222
|
return jsonify({
|
|
236
223
|
'id': api_token.id,
|
|
237
224
|
'name': api_token.name,
|
|
@@ -241,6 +228,7 @@ class AuthManager:
|
|
|
241
228
|
})
|
|
242
229
|
|
|
243
230
|
@bp.route('/token-refresh', methods=['POST'])
|
|
231
|
+
@handle_auth_errors
|
|
244
232
|
def refresh_token():
|
|
245
233
|
refresh_token = request.json.get('refresh_token')
|
|
246
234
|
if not refresh_token:
|
|
@@ -264,17 +252,10 @@ class AuthManager:
|
|
|
264
252
|
except jwt.InvalidTokenError:
|
|
265
253
|
raise AuthError('Invalid refresh token', 401)
|
|
266
254
|
|
|
267
|
-
@bp.route('/api-tokens', methods=['POST'])
|
|
268
|
-
def create_api_token():
|
|
269
|
-
name = request.json.get('name')
|
|
270
|
-
if not name:
|
|
271
|
-
raise AuthError('Token name required', 400)
|
|
272
|
-
|
|
273
|
-
token = self.create_api_token(g.requesting_user['id'], name)
|
|
274
|
-
return jsonify({'token': token.token})
|
|
275
|
-
|
|
276
255
|
@bp.route('/api-tokens/validate', methods=['GET'])
|
|
277
|
-
|
|
256
|
+
@handle_auth_errors
|
|
257
|
+
@self.require_auth
|
|
258
|
+
def validate_api_token(requesting_user):
|
|
278
259
|
token = request.json.get('token')
|
|
279
260
|
if not token:
|
|
280
261
|
raise AuthError('No API token provided', 401)
|
|
@@ -284,7 +265,7 @@ class AuthManager:
|
|
|
284
265
|
cur.execute("""
|
|
285
266
|
SELECT * FROM api_tokens
|
|
286
267
|
WHERE user_id = %s AND id = %s
|
|
287
|
-
""", (
|
|
268
|
+
""", (requesting_user['id'], token))
|
|
288
269
|
api_token = cur.fetchone()
|
|
289
270
|
|
|
290
271
|
if not api_token:
|
|
@@ -305,7 +286,9 @@ class AuthManager:
|
|
|
305
286
|
return jsonify({'valid': True})
|
|
306
287
|
|
|
307
288
|
@bp.route('/api-tokens', methods=['DELETE'])
|
|
308
|
-
|
|
289
|
+
@handle_auth_errors
|
|
290
|
+
@self.require_auth
|
|
291
|
+
def delete_api_token(requesting_user):
|
|
309
292
|
token = request.json.get('token')
|
|
310
293
|
if not token:
|
|
311
294
|
raise AuthError('Token required', 400)
|
|
@@ -316,7 +299,7 @@ class AuthManager:
|
|
|
316
299
|
DELETE FROM api_tokens
|
|
317
300
|
WHERE user_id = %s AND id = %s
|
|
318
301
|
RETURNING id
|
|
319
|
-
""", (
|
|
302
|
+
""", (requesting_user['id'], token))
|
|
320
303
|
deleted_id = cur.fetchone()
|
|
321
304
|
if not deleted_id:
|
|
322
305
|
raise ValueError('Token not found or already deleted')
|
|
@@ -324,6 +307,7 @@ class AuthManager:
|
|
|
324
307
|
return jsonify({'deleted': True})
|
|
325
308
|
|
|
326
309
|
@bp.route('/register', methods=['POST'])
|
|
310
|
+
@handle_auth_errors
|
|
327
311
|
def register():
|
|
328
312
|
data = request.get_json()
|
|
329
313
|
|
|
@@ -362,6 +346,7 @@ class AuthManager:
|
|
|
362
346
|
return jsonify({'id': user.id}), 201
|
|
363
347
|
|
|
364
348
|
@bp.route('/roles', methods=['GET'])
|
|
349
|
+
@handle_auth_errors
|
|
365
350
|
def get_roles():
|
|
366
351
|
with self.db.get_cursor() as cur:
|
|
367
352
|
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.1750840380
|
|
4
4
|
Summary: Python SDK for the Authlib
|
|
5
5
|
Author-email: the37lab <info@the37lab.com>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -70,21 +70,8 @@ auth = AuthManager(
|
|
|
70
70
|
@auth.require_auth(roles=["admin"])
|
|
71
71
|
def protected_route():
|
|
72
72
|
return "Protected content"
|
|
73
|
-
|
|
74
|
-
@app.route("/public")
|
|
75
|
-
@auth.public_endpoint
|
|
76
|
-
def custom_public_route():
|
|
77
|
-
return "Public content"
|
|
78
73
|
```
|
|
79
74
|
|
|
80
|
-
`AuthManager`'s blueprint now registers a global error handler for
|
|
81
|
-
`AuthError` and authenticates requests for all of its routes by default.
|
|
82
|
-
Authenticated users are made available as `flask.g.requesting_user`.
|
|
83
|
-
Only the login, OAuth, token refresh, registration and role listing
|
|
84
|
-
endpoints are exempt from this check. Additional routes can be marked as
|
|
85
|
-
public using the `@auth.public_endpoint` decorator or
|
|
86
|
-
`auth.add_public_endpoint("auth.some_endpoint")`.
|
|
87
|
-
|
|
88
75
|
## Configuration
|
|
89
76
|
|
|
90
77
|
### Required Parameters
|
|
@@ -139,8 +126,8 @@ public using the `@auth.public_endpoint` decorator or
|
|
|
139
126
|
|
|
140
127
|
### API Tokens
|
|
141
128
|
- `POST /api/v1/users/{user}/api-tokens` - Create API token
|
|
142
|
-
- **Request:** `{ "name": "string", "
|
|
143
|
-
- **Response:** `{ "token": "string", "id": "uuid",
|
|
129
|
+
- **Request:** `{ "name": "string", "expires_in_days": number | null }`
|
|
130
|
+
- **Response:** `{ "token": "string", "id": "uuid", "name": "string", "created_at": "timestamp", "expires_at": "timestamp | null" }`
|
|
144
131
|
- `GET /api/v1/users/{user}/api-tokens` - List API tokens
|
|
145
132
|
- **Response:** `[ { "id": "uuid", "name": "string", ... } ]`
|
|
146
133
|
- `DELETE /api/v1/users/{user}/api-tokens/{token_id}` - Delete API token
|
|
@@ -157,9 +144,7 @@ public using the `@auth.public_endpoint` decorator or
|
|
|
157
144
|
- Get redirect URL from `/api/v1/users/login/oauth`.
|
|
158
145
|
- Complete OAuth flow via `/api/v1/users/login/oauth2callback`.
|
|
159
146
|
4. **Protected Routes:**
|
|
160
|
-
-
|
|
161
|
-
The authenticated user can be accessed via `g.requesting_user`.
|
|
162
|
-
Use `@auth.require_auth()` to protect custom routes in your application.
|
|
147
|
+
- Use `@auth.require_auth()` decorator to protect Flask routes.
|
|
163
148
|
|
|
164
149
|
## User Object
|
|
165
150
|
|
|
File without changes
|
{the37lab_authlib-0.1.1750837371 → the37lab_authlib-0.1.1750840380}/src/the37lab_authlib/__init__.py
RENAMED
|
File without changes
|
{the37lab_authlib-0.1.1750837371 → the37lab_authlib-0.1.1750840380}/src/the37lab_authlib/db.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{the37lab_authlib-0.1.1750837371 → the37lab_authlib-0.1.1750840380}/src/the37lab_authlib/models.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|