the37lab-authlib 0.1.1750836881__py3-none-any.whl → 0.1.1750840354__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.

@@ -1,5 +1,5 @@
1
1
  from .auth import AuthManager
2
- from .decorators import require_auth
2
+ from .decorators import require_auth, public_endpoint
3
3
 
4
4
  __version__ = "0.1.0"
5
- __all__ = ["AuthManager", "require_auth"]
5
+ __all__ = ["AuthManager", "require_auth", "public_endpoint"]
the37lab_authlib/auth.py CHANGED
@@ -5,6 +5,7 @@ from datetime import datetime, timedelta
5
5
  from .db import Database
6
6
  from .models import User, Role, ApiToken
7
7
  from .exceptions import AuthError
8
+ from .decorators import public_endpoint
8
9
  import uuid
9
10
  import requests
10
11
  import bcrypt
@@ -15,6 +16,17 @@ from functools import wraps
15
16
  logging.basicConfig(level=logging.DEBUG)
16
17
  logger = logging.getLogger(__name__)
17
18
 
19
+ def handle_auth_errors(f):
20
+ @wraps(f)
21
+ def decorated(*args, **kwargs):
22
+ try:
23
+ return f(*args, **kwargs)
24
+ except AuthError as e:
25
+ response = jsonify(e.to_dict())
26
+ response.status_code = e.status_code
27
+ return response
28
+ return decorated
29
+
18
30
  class AuthManager:
19
31
  def __init__(self, app=None, db_dsn=None, jwt_secret=None, oauth_config=None, id_type='integer'):
20
32
  logger.info("INITIALIZING AUTHMANAGER {} - {} - {}".format(db_dsn, jwt_secret, not app))
@@ -121,27 +133,21 @@ class AuthManager:
121
133
  def create_blueprint(self):
122
134
  bp = Blueprint('auth', __name__, url_prefix='/api/v1/users')
123
135
 
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
136
  @bp.before_request
140
137
  def load_user():
141
- if request.endpoint not in public_endpoints:
142
- g.requesting_user = self._authenticate_request()
138
+ view = current_app.view_functions.get(request.endpoint)
139
+ if getattr(view, '_auth_public', False):
140
+ return
141
+ try:
142
+ g.current_user = self._authenticate_request()
143
+ except AuthError as e:
144
+ response = jsonify(e.to_dict())
145
+ response.status_code = e.status_code
146
+ return response
143
147
 
144
148
  @bp.route('/login', methods=['POST'])
149
+ @public_endpoint
150
+ @handle_auth_errors
145
151
  def login():
146
152
  data = request.get_json()
147
153
  username = data.get('username')
@@ -176,6 +182,8 @@ class AuthManager:
176
182
  })
177
183
 
178
184
  @bp.route('/login/oauth', methods=['POST'])
185
+ @public_endpoint
186
+ @handle_auth_errors
179
187
  def oauth_login():
180
188
  provider = request.json.get('provider')
181
189
  if provider not in self.oauth_config:
@@ -187,6 +195,8 @@ class AuthManager:
187
195
  })
188
196
 
189
197
  @bp.route('/login/oauth2callback')
198
+ @public_endpoint
199
+ @handle_auth_errors
190
200
  def oauth_callback():
191
201
  code = request.args.get('code')
192
202
  provider = request.args.get('state')
@@ -203,22 +213,28 @@ class AuthManager:
203
213
  return redirect(f"{frontend_url}/oauth-callback?token={token}&refresh_token={refresh_token}")
204
214
 
205
215
  @bp.route('/login/profile')
216
+ @handle_auth_errors
206
217
  def profile():
207
- user = g.requesting_user
218
+ token = request.headers.get('Authorization', '').split(' ')[-1]
219
+ user = self.validate_token(token)
208
220
  return jsonify(user)
209
221
 
210
222
  @bp.route('/api-tokens', methods=['GET'])
211
- def get_tokens():
212
- tokens = self.get_user_api_tokens(g.requesting_user['id'])
223
+ @handle_auth_errors
224
+ @self.require_auth
225
+ def get_tokens(requesting_user):
226
+ tokens = self.get_user_api_tokens(requesting_user['id'])
213
227
  return jsonify(tokens)
214
228
 
215
229
  @bp.route('/api-tokens', methods=['POST'])
216
- def create_token():
230
+ @handle_auth_errors
231
+ @self.require_auth
232
+ def create_token(requesting_user):
217
233
  name = request.json.get('name')
218
234
  expires_in_days = request.json.get('expires_in_days')
219
235
  if not name:
220
236
  raise AuthError('Token name is required', 400)
221
- api_token = self.create_api_token(g.requesting_user['id'], name, expires_in_days)
237
+ api_token = self.create_api_token(requesting_user['id'], name, expires_in_days)
222
238
  return jsonify({
223
239
  'id': api_token.id,
224
240
  'name': api_token.name,
@@ -228,6 +244,8 @@ class AuthManager:
228
244
  })
229
245
 
230
246
  @bp.route('/token-refresh', methods=['POST'])
247
+ @public_endpoint
248
+ @handle_auth_errors
231
249
  def refresh_token():
232
250
  refresh_token = request.json.get('refresh_token')
233
251
  if not refresh_token:
@@ -252,16 +270,20 @@ class AuthManager:
252
270
  raise AuthError('Invalid refresh token', 401)
253
271
 
254
272
  @bp.route('/api-tokens', methods=['POST'])
255
- def create_api_token():
273
+ @handle_auth_errors
274
+ @self.require_auth
275
+ def create_api_token(requesting_user):
256
276
  name = request.json.get('name')
257
277
  if not name:
258
278
  raise AuthError('Token name required', 400)
259
279
 
260
- token = self.create_api_token(g.requesting_user['id'], name)
280
+ token = self.create_api_token(requesting_user['id'], name)
261
281
  return jsonify({'token': token.token})
262
282
 
263
283
  @bp.route('/api-tokens/validate', methods=['GET'])
264
- def validate_api_token():
284
+ @handle_auth_errors
285
+ @self.require_auth
286
+ def validate_api_token(requesting_user):
265
287
  token = request.json.get('token')
266
288
  if not token:
267
289
  raise AuthError('No API token provided', 401)
@@ -271,7 +293,7 @@ class AuthManager:
271
293
  cur.execute("""
272
294
  SELECT * FROM api_tokens
273
295
  WHERE user_id = %s AND id = %s
274
- """, (g.requesting_user['id'], token))
296
+ """, (requesting_user['id'], token))
275
297
  api_token = cur.fetchone()
276
298
 
277
299
  if not api_token:
@@ -292,7 +314,9 @@ class AuthManager:
292
314
  return jsonify({'valid': True})
293
315
 
294
316
  @bp.route('/api-tokens', methods=['DELETE'])
295
- def delete_api_token():
317
+ @handle_auth_errors
318
+ @self.require_auth
319
+ def delete_api_token(requesting_user):
296
320
  token = request.json.get('token')
297
321
  if not token:
298
322
  raise AuthError('Token required', 400)
@@ -303,7 +327,7 @@ class AuthManager:
303
327
  DELETE FROM api_tokens
304
328
  WHERE user_id = %s AND id = %s
305
329
  RETURNING id
306
- """, (g.requesting_user['id'], token))
330
+ """, (requesting_user['id'], token))
307
331
  deleted_id = cur.fetchone()
308
332
  if not deleted_id:
309
333
  raise ValueError('Token not found or already deleted')
@@ -311,6 +335,8 @@ class AuthManager:
311
335
  return jsonify({'deleted': True})
312
336
 
313
337
  @bp.route('/register', methods=['POST'])
338
+ @public_endpoint
339
+ @handle_auth_errors
314
340
  def register():
315
341
  data = request.get_json()
316
342
 
@@ -349,6 +375,8 @@ class AuthManager:
349
375
  return jsonify({'id': user.id}), 201
350
376
 
351
377
  @bp.route('/roles', methods=['GET'])
378
+ @public_endpoint
379
+ @handle_auth_errors
352
380
  def get_roles():
353
381
  with self.db.get_cursor() as cur:
354
382
  cur.execute("SELECT * FROM roles")
@@ -388,6 +416,8 @@ class AuthManager:
388
416
  raise AuthError(str(e), 500)
389
417
 
390
418
  def get_current_user(self):
419
+ if hasattr(g, 'current_user'):
420
+ return g.current_user
391
421
  return self._authenticate_request()
392
422
 
393
423
  def get_user_api_tokens(self, user_id):
@@ -442,6 +472,7 @@ class AuthManager:
442
472
  return f'https://accounts.google.com/o/oauth2/v2/auth?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&scope={scope}&state={state}'
443
473
  raise AuthError('Invalid OAuth provider')
444
474
 
475
+
445
476
  def _get_oauth_user_info(self, provider, code):
446
477
  if provider == 'google':
447
478
  client_id = self.oauth_config['google']['client_id']
@@ -503,4 +534,4 @@ class AuthManager:
503
534
  user['real_name'] = userinfo.get('name', userinfo['email'])
504
535
 
505
536
  return user
506
- raise AuthError('Invalid OAuth provider')
537
+ raise AuthError('Invalid OAuth provider')
@@ -2,6 +2,12 @@ from functools import wraps
2
2
  from flask import request, current_app, jsonify
3
3
  from .exceptions import AuthError
4
4
 
5
+
6
+ def public_endpoint(f):
7
+ """Mark an endpoint as public (no authentication required)."""
8
+ f._auth_public = True
9
+ return f
10
+
5
11
  def require_auth(roles=None):
6
12
  def decorator(f):
7
13
  @wraps(f)
@@ -28,4 +34,4 @@ def require_auth(roles=None):
28
34
  response.status_code = e.status_code
29
35
  return response
30
36
  return decorated
31
- return decorator
37
+ return decorator
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: the37lab_authlib
3
- Version: 0.1.1750836881
3
+ Version: 0.1.1750840354
4
4
  Summary: Python SDK for the Authlib
5
5
  Author-email: the37lab <info@the37lab.com>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -72,12 +72,6 @@ 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
-
81
75
  ## Configuration
82
76
 
83
77
  ### Required Parameters
@@ -150,9 +144,7 @@ endpoints are exempt from this check.
150
144
  - Get redirect URL from `/api/v1/users/login/oauth`.
151
145
  - Complete OAuth flow via `/api/v1/users/login/oauth2callback`.
152
146
  4. **Protected 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.
147
+ - Use `@auth.require_auth()` decorator to protect Flask routes.
156
148
 
157
149
  ## User Object
158
150
 
@@ -0,0 +1,10 @@
1
+ the37lab_authlib/__init__.py,sha256=YV1C1iaIs-8cD5dFe-VEC6dRhrT6mglgTdcveT6AMCQ,168
2
+ the37lab_authlib/auth.py,sha256=dkmRfkJ03W8FsLO1OcT_erG0DzP7kMlpPdrw_jts0IE,21372
3
+ the37lab_authlib/db.py,sha256=fTXxnfju0lmbFGPVbXpTMeDmJMeBgURVZTndyxyRyCc,2734
4
+ the37lab_authlib/decorators.py,sha256=oBO3fbRo7H0rcXeUq6M8yK-5mgHKfaJEDG6XdNsxQPI,1351
5
+ the37lab_authlib/exceptions.py,sha256=mdplK5sKNtagPAzSGq5NGsrQ4r-k03DKJBKx6myWwZc,317
6
+ the37lab_authlib/models.py,sha256=-PlvQlHGIsSdrH0H9Cdh_vTPlltGV8G1Z1mmGQvAg9Y,3422
7
+ the37lab_authlib-0.1.1750840354.dist-info/METADATA,sha256=01Z5Jknra_QYDGgu-_qNC9Rqz0sCafF2TdzI7LaVmgU,5641
8
+ the37lab_authlib-0.1.1750840354.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ the37lab_authlib-0.1.1750840354.dist-info/top_level.txt,sha256=6Jmxw4UeLrhfJXgRKbXWY4OhxRSaMs0dKKhNCGWWSwc,17
10
+ the37lab_authlib-0.1.1750840354.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- the37lab_authlib/__init__.py,sha256=cFVTWL-0YIMqwOMVy1P8mOt_bQODJp-L9bfp2QQ8CTo,132
2
- the37lab_authlib/auth.py,sha256=tBs-THT_sJeolT9hxwOmtCcsHCMfVEQXtYdVJOCRdAs,20338
3
- the37lab_authlib/db.py,sha256=fTXxnfju0lmbFGPVbXpTMeDmJMeBgURVZTndyxyRyCc,2734
4
- the37lab_authlib/decorators.py,sha256=AEQfix31fHUZvhEZd4Ud8Zh2KBGjV6O_braiPL-BU7w,1219
5
- the37lab_authlib/exceptions.py,sha256=mdplK5sKNtagPAzSGq5NGsrQ4r-k03DKJBKx6myWwZc,317
6
- the37lab_authlib/models.py,sha256=-PlvQlHGIsSdrH0H9Cdh_vTPlltGV8G1Z1mmGQvAg9Y,3422
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,,