the37lab-authlib 0.1.1750187527__py3-none-any.whl → 0.1.1750836881__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
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
- token = request.headers.get('Authorization', '').split(' ')[-1]
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
- @handle_auth_errors
208
- @self.require_auth
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
- @handle_auth_errors
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
- @handle_auth_errors
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
- @handle_auth_errors
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
- @handle_auth_errors
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.1750187527
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
- - Use `@auth.require_auth()` decorator to protect Flask routes.
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
 
@@ -1,10 +1,10 @@
1
1
  the37lab_authlib/__init__.py,sha256=cFVTWL-0YIMqwOMVy1P8mOt_bQODJp-L9bfp2QQ8CTo,132
2
- the37lab_authlib/auth.py,sha256=dQkE6z9GZZpnl0nfqulcveho8W5lM95XUBLmtE-5JIc,20660
2
+ the37lab_authlib/auth.py,sha256=tBs-THT_sJeolT9hxwOmtCcsHCMfVEQXtYdVJOCRdAs,20338
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.1750187527.dist-info/METADATA,sha256=I1q0GUs96_gBGEDq4no_p0t_UXXyw5IlWsPVbaJJTnM,5641
8
- the37lab_authlib-0.1.1750187527.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
- the37lab_authlib-0.1.1750187527.dist-info/top_level.txt,sha256=6Jmxw4UeLrhfJXgRKbXWY4OhxRSaMs0dKKhNCGWWSwc,17
10
- the37lab_authlib-0.1.1750187527.dist-info/RECORD,,
7
+ the37lab_authlib-0.1.1750836881.dist-info/METADATA,sha256=Jdhr9dU1rR4pDX9m5VJooujmEOpAB0zAxCkmXKf0N_M,6113
8
+ the37lab_authlib-0.1.1750836881.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ the37lab_authlib-0.1.1750836881.dist-info/top_level.txt,sha256=6Jmxw4UeLrhfJXgRKbXWY4OhxRSaMs0dKKhNCGWWSwc,17
10
+ the37lab_authlib-0.1.1750836881.dist-info/RECORD,,