the37lab-authlib 0.1.1750837371__py3-none-any.whl → 0.1.1750840380__py3-none-any.whl

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/auth.py CHANGED
@@ -1,5 +1,5 @@
1
1
  import inspect
2
- from flask import Blueprint, request, jsonify, current_app, url_for, redirect, g
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
- user = g.requesting_user
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
- def get_tokens():
225
- tokens = self.get_user_api_tokens(g.requesting_user['id'])
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
- def create_token():
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(g.requesting_user['id'], name, expires_in_days)
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
- def validate_api_token():
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
- """, (g.requesting_user['id'], token))
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
- def delete_api_token():
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
- """, (g.requesting_user['id'], token))
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.1750837371
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", "scopes": [ ... ] }`
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
- - All routes inside the provided blueprint are authenticated by default.
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
 
@@ -1,10 +1,10 @@
1
1
  the37lab_authlib/__init__.py,sha256=cFVTWL-0YIMqwOMVy1P8mOt_bQODJp-L9bfp2QQ8CTo,132
2
- the37lab_authlib/auth.py,sha256=qj3b5PBT2egtygQAAPBiHFcWEgfD-9cIOlMwP5_HJ3M,20835
2
+ the37lab_authlib/auth.py,sha256=tGN3zIBjDd_ZZPNO1VC-olMzXpucAnsmUy624HmJnJg,20254
3
3
  the37lab_authlib/db.py,sha256=fTXxnfju0lmbFGPVbXpTMeDmJMeBgURVZTndyxyRyCc,2734
4
4
  the37lab_authlib/decorators.py,sha256=AEQfix31fHUZvhEZd4Ud8Zh2KBGjV6O_braiPL-BU7w,1219
5
5
  the37lab_authlib/exceptions.py,sha256=mdplK5sKNtagPAzSGq5NGsrQ4r-k03DKJBKx6myWwZc,317
6
6
  the37lab_authlib/models.py,sha256=-PlvQlHGIsSdrH0H9Cdh_vTPlltGV8G1Z1mmGQvAg9Y,3422
7
- the37lab_authlib-0.1.1750837371.dist-info/METADATA,sha256=nAsElJNxxbQgDfwvVe0Ia4_22MJ3i0VOyW8ZJk3vmO0,6352
8
- the37lab_authlib-0.1.1750837371.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
- the37lab_authlib-0.1.1750837371.dist-info/top_level.txt,sha256=6Jmxw4UeLrhfJXgRKbXWY4OhxRSaMs0dKKhNCGWWSwc,17
10
- the37lab_authlib-0.1.1750837371.dist-info/RECORD,,
7
+ the37lab_authlib-0.1.1750840380.dist-info/METADATA,sha256=MbmW2QTnvriUKBoG8wVrVC6zDGPkj50qkSUBWCJDO74,5730
8
+ the37lab_authlib-0.1.1750840380.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ the37lab_authlib-0.1.1750840380.dist-info/top_level.txt,sha256=6Jmxw4UeLrhfJXgRKbXWY4OhxRSaMs0dKKhNCGWWSwc,17
10
+ the37lab_authlib-0.1.1750840380.dist-info/RECORD,,