Skip to content

Commit 60b5da6

Browse files
committed
Unify the web UI login and api key authentication
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
1 parent fa98cc2 commit 60b5da6

File tree

1 file changed

+89
-28
lines changed

1 file changed

+89
-28
lines changed

api/apps/__init__.py

Lines changed: 89 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,13 @@
3232

3333
from flask_mail import Mail
3434
from flask_session import Session
35-
from flask_login import LoginManager
35+
from flask_login import LoginManager, UserMixin
3636
from common import settings
37-
from api.utils.api_utils import server_error_response
37+
from api.utils.api_utils import server_error_response, get_json_result
3838
from api.constants import API_VERSION
39+
from flask import request as flask_request
40+
from api.db.db_models import APIToken
41+
from common.constants import RetCode
3942

4043
__all__ = ["app"]
4144

@@ -142,40 +145,98 @@ def register_page(page_path):
142145
]
143146

144147

145-
@login_manager.request_loader
146-
def load_user(web_request):
147-
jwt = Serializer(secret_key=settings.SECRET_KEY)
148-
authorization = web_request.headers.get("Authorization")
149-
if authorization:
150-
try:
151-
access_token = str(jwt.loads(authorization))
148+
class DefaultUser(UserMixin):
149+
def __init__(self, tenant_id: str):
150+
self.tenant_id = tenant_id
152151

153-
if not access_token or not access_token.strip():
154-
logging.warning("Authentication attempt with empty access token")
155-
return None
156152

157-
# Access tokens should be UUIDs (32 hex characters)
158-
if len(access_token.strip()) < 32:
159-
logging.warning(f"Authentication attempt with invalid token format: {len(access_token)} chars")
160-
return None
153+
def load_user_from_jwt(authorization):
154+
"""
155+
Load user from JWT token for web UI authentication.
156+
157+
Args:
158+
authorization: The authorization info
159+
160+
Returns:
161+
User object if JWT is valid, None otherwise
162+
"""
163+
164+
try:
165+
jwt = Serializer(secret_key=settings.SECRET_KEY)
166+
access_token = str(jwt.loads(authorization))
161167

162-
user = UserService.query(
163-
access_token=access_token, status=StatusEnum.VALID.value
164-
)
165-
if user:
166-
if not user[0].access_token or not user[0].access_token.strip():
167-
logging.warning(f"User {user[0].email} has empty access_token in database")
168-
return None
169-
return user[0]
170-
else:
168+
if not access_token or not access_token.strip():
169+
logging.warning("Authentication attempt with empty access token")
170+
return None
171+
172+
# Access tokens should be UUIDs (32 hex characters)
173+
if len(access_token.strip()) < 32:
174+
logging.warning(f"Authentication attempt with invalid token format: {len(access_token)} chars")
175+
return None
176+
177+
user = UserService.query(
178+
access_token=access_token, status=StatusEnum.VALID.value
179+
)
180+
if user:
181+
if not user[0].access_token or not user[0].access_token.strip():
182+
logging.warning(f"User {user[0].email} has empty access_token in database")
171183
return None
172-
except Exception as e:
173-
logging.warning(f"load_user got exception {e}")
184+
return user[0]
185+
else:
174186
return None
175-
else:
187+
except Exception as e:
188+
logging.warning(f"JWT authentication failed: {e}")
176189
return None
177190

178191

192+
def load_user_from_api_key(authorization):
193+
"""
194+
Load user from API Key for external API authentication.
195+
196+
Args:
197+
authorization: The authorization info
198+
199+
Returns:
200+
User object if API Key is valid, None otherwise
201+
"""
202+
try:
203+
if os.environ.get("DISABLE_SDK"):
204+
return None
205+
authorization_str = flask_request.headers.get("Authorization")
206+
if not authorization_str:
207+
return None
208+
authorization_list = authorization_str.split()
209+
if len(authorization_list) < 2:
210+
return None
211+
token = authorization_list[1]
212+
objs = APIToken.query(token=token)
213+
if not objs:
214+
return None
215+
216+
default_user = DefaultUser(objs[0].tenant_id)
217+
return default_user
218+
except Exception as e:
219+
logging.warning(f"API Key authentication failed: {e}")
220+
return None
221+
222+
223+
@login_manager.request_loader
224+
def load_user(web_request):
225+
authorization = web_request.headers.get("Authorization")
226+
if authorization is None:
227+
return None
228+
229+
user = load_user_from_jwt(authorization)
230+
if user:
231+
return user
232+
233+
user = load_user_from_api_key(authorization)
234+
if user:
235+
return user
236+
237+
return None
238+
239+
179240
@app.teardown_request
180241
def _db_close(exc):
181242
close_connection()

0 commit comments

Comments
 (0)