the37lab-authlib 0.1.1750156111__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 +33 -42
- {the37lab_authlib-0.1.1750156111.dist-info → the37lab_authlib-0.1.1750836881.dist-info}/METADATA +24 -16
- {the37lab_authlib-0.1.1750156111.dist-info → the37lab_authlib-0.1.1750836881.dist-info}/RECORD +5 -5
- {the37lab_authlib-0.1.1750156111.dist-info → the37lab_authlib-0.1.1750836881.dist-info}/WHEEL +0 -0
- {the37lab_authlib-0.1.1750156111.dist-info → the37lab_authlib-0.1.1750836881.dist-info}/top_level.txt +0 -0
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))
|
|
@@ -130,10 +119,29 @@ class AuthManager:
|
|
|
130
119
|
app.register_blueprint(self.create_blueprint())
|
|
131
120
|
|
|
132
121
|
def create_blueprint(self):
|
|
133
|
-
bp = Blueprint('auth', __name__, url_prefix='/v1/users')
|
|
122
|
+
bp = Blueprint('auth', __name__, url_prefix='/api/v1/users')
|
|
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()
|
|
134
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")
|
{the37lab_authlib-0.1.1750156111.dist-info → the37lab_authlib-0.1.1750836881.dist-info}/METADATA
RENAMED
|
@@ -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
|
|
@@ -101,50 +107,52 @@ def protected_route():
|
|
|
101
107
|
## API Endpoints
|
|
102
108
|
|
|
103
109
|
### Authentication
|
|
104
|
-
- `POST /v1/users/login` - Login with username/password
|
|
110
|
+
- `POST /api/v1/users/login` - Login with username/password
|
|
105
111
|
- **Request:** `{ "username": "string", "password": "string" }`
|
|
106
112
|
- **Response:** `{ "token": "jwt", "refresh_token": "jwt", "user": { ... } }`
|
|
107
|
-
- `POST /v1/users/login/oauth` - Get OAuth redirect URL
|
|
113
|
+
- `POST /api/v1/users/login/oauth` - Get OAuth redirect URL
|
|
108
114
|
- **Request:** `{ "provider": "google|github|..." }`
|
|
109
115
|
- **Response:** `{ "redirect_url": "string" }`
|
|
110
|
-
- `GET /v1/users/login/oauth2callback` - OAuth callback
|
|
116
|
+
- `GET /api/v1/users/login/oauth2callback` - OAuth callback
|
|
111
117
|
- **Query Params:** `code`, `state`, `provider`
|
|
112
118
|
- **Response:** `{ "token": "jwt", "refresh_token": "jwt", "user": { ... } }`
|
|
113
|
-
- `POST /v1/users/token-refresh` - Refresh JWT token
|
|
119
|
+
- `POST /api/v1/users/token-refresh` - Refresh JWT token
|
|
114
120
|
- **Request:** `{ "refresh_token": "jwt" }`
|
|
115
121
|
- **Response:** `{ "token": "jwt", "refresh_token": "jwt" }`
|
|
116
122
|
|
|
117
123
|
### User Management
|
|
118
|
-
- `POST /v1/users/register` - Register new user
|
|
124
|
+
- `POST /api/v1/users/register` - Register new user
|
|
119
125
|
- **Request:** `{ "username": "string", "password": "string", "email": "string", ... }`
|
|
120
126
|
- **Response:** `{ "user": { ... }, "token": "jwt", "refresh_token": "jwt" }`
|
|
121
|
-
- `GET /v1/users/login/profile` - Get user profile
|
|
127
|
+
- `GET /api/v1/users/login/profile` - Get user profile
|
|
122
128
|
- **Auth:** Bearer JWT
|
|
123
129
|
- **Response:** `{ "user": { ... } }`
|
|
124
|
-
- `GET /v1/users/roles` - Get available roles
|
|
130
|
+
- `GET /api/v1/users/roles` - Get available roles
|
|
125
131
|
- **Response:** `[ "admin", "user", ... ]`
|
|
126
132
|
|
|
127
133
|
### API Tokens
|
|
128
|
-
- `POST /v1/users/{user}/api-tokens` - Create API token
|
|
134
|
+
- `POST /api/v1/users/{user}/api-tokens` - Create API token
|
|
129
135
|
- **Request:** `{ "name": "string", "scopes": [ ... ] }`
|
|
130
136
|
- **Response:** `{ "token": "string", "id": "uuid", ... }`
|
|
131
|
-
- `GET /v1/users/{user}/api-tokens` - List API tokens
|
|
137
|
+
- `GET /api/v1/users/{user}/api-tokens` - List API tokens
|
|
132
138
|
- **Response:** `[ { "id": "uuid", "name": "string", ... } ]`
|
|
133
|
-
- `DELETE /v1/users/{user}/api-tokens/{token_id}` - Delete API token
|
|
139
|
+
- `DELETE /api/v1/users/{user}/api-tokens/{token_id}` - Delete API token
|
|
134
140
|
- **Response:** `{ "success": true }`
|
|
135
141
|
|
|
136
142
|
## Authentication Flow
|
|
137
143
|
|
|
138
144
|
1. **Login:**
|
|
139
|
-
- User submits credentials to `/v1/users/login`.
|
|
145
|
+
- User submits credentials to `/api/v1/users/login`.
|
|
140
146
|
- Receives JWT and refresh token.
|
|
141
147
|
2. **Token Refresh:**
|
|
142
|
-
- Use `/v1/users/token-refresh` with refresh token to get new JWT.
|
|
148
|
+
- Use `/api/v1/users/token-refresh` with refresh token to get new JWT.
|
|
143
149
|
3. **OAuth:**
|
|
144
|
-
- Get redirect URL from `/v1/users/login/oauth`.
|
|
145
|
-
- Complete OAuth flow via `/v1/users/login/oauth2callback`.
|
|
150
|
+
- Get redirect URL from `/api/v1/users/login/oauth`.
|
|
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
|
|
{the37lab_authlib-0.1.1750156111.dist-info → the37lab_authlib-0.1.1750836881.dist-info}/RECORD
RENAMED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
the37lab_authlib/__init__.py,sha256=cFVTWL-0YIMqwOMVy1P8mOt_bQODJp-L9bfp2QQ8CTo,132
|
|
2
|
-
the37lab_authlib/auth.py,sha256=
|
|
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.
|
|
8
|
-
the37lab_authlib-0.1.
|
|
9
|
-
the37lab_authlib-0.1.
|
|
10
|
-
the37lab_authlib-0.1.
|
|
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,,
|
{the37lab_authlib-0.1.1750156111.dist-info → the37lab_authlib-0.1.1750836881.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|