diff --git a/api/constants/__init__.py b/api/constants/__init__.py index 248cdfc09f..e441395afc 100644 --- a/api/constants/__init__.py +++ b/api/constants/__init__.py @@ -56,11 +56,15 @@ else: } DOCUMENT_EXTENSIONS: set[str] = convert_to_lower_and_upper_set(_doc_extensions) +# console COOKIE_NAME_ACCESS_TOKEN = "access_token" COOKIE_NAME_REFRESH_TOKEN = "refresh_token" -COOKIE_NAME_PASSPORT = "passport" COOKIE_NAME_CSRF_TOKEN = "csrf_token" +# webapp +COOKIE_NAME_WEBAPP_ACCESS_TOKEN = "webapp_access_token" +COOKIE_NAME_PASSPORT = "passport" + HEADER_NAME_CSRF_TOKEN = "X-CSRF-Token" HEADER_NAME_APP_CODE = "X-App-Code" HEADER_NAME_PASSPORT = "X-App-Passport" diff --git a/api/controllers/web/login.py b/api/controllers/web/login.py index f213fd8c90..244ef47982 100644 --- a/api/controllers/web/login.py +++ b/api/controllers/web/login.py @@ -17,8 +17,8 @@ from libs.helper import email from libs.passport import PassportService from libs.password import valid_password from libs.token import ( - clear_access_token_from_cookie, - extract_access_token, + clear_webapp_access_token_from_cookie, + extract_webapp_access_token, ) from services.account_service import AccountService from services.app_service import AppService @@ -81,7 +81,7 @@ class LoginStatusApi(Resource): ) def get(self): app_code = request.args.get("app_code") - token = extract_access_token(request) + token = extract_webapp_access_token(request) if not app_code: return { "logged_in": bool(token), @@ -128,7 +128,7 @@ class LogoutApi(Resource): response = make_response({"result": "success"}) # enterprise SSO sets same site to None in https deployment # so we need to logout by calling api - clear_access_token_from_cookie(response, samesite="None") + clear_webapp_access_token_from_cookie(response, samesite="None") return response diff --git a/api/controllers/web/passport.py b/api/controllers/web/passport.py index a344777783..6a2e0b65fb 100644 --- a/api/controllers/web/passport.py +++ b/api/controllers/web/passport.py @@ -12,7 +12,7 @@ from controllers.web import web_ns from controllers.web.error import WebAppAuthRequiredError from extensions.ext_database import db from libs.passport import PassportService -from libs.token import extract_access_token +from libs.token import extract_webapp_access_token from models.model import App, EndUser, Site from services.feature_service import FeatureService from services.webapp_auth_service import WebAppAuthService, WebAppAuthType @@ -35,7 +35,7 @@ class PassportResource(Resource): system_features = FeatureService.get_system_features() app_code = request.headers.get(HEADER_NAME_APP_CODE) user_id = request.args.get("user_id") - access_token = extract_access_token(request) + access_token = extract_webapp_access_token(request) if app_code is None: raise Unauthorized("X-App-Code header is missing.") if system_features.webapp_auth.enabled: diff --git a/api/libs/token.py b/api/libs/token.py index 4be25696e7..0b40f18143 100644 --- a/api/libs/token.py +++ b/api/libs/token.py @@ -12,6 +12,7 @@ from constants import ( COOKIE_NAME_CSRF_TOKEN, COOKIE_NAME_PASSPORT, COOKIE_NAME_REFRESH_TOKEN, + COOKIE_NAME_WEBAPP_ACCESS_TOKEN, HEADER_NAME_CSRF_TOKEN, HEADER_NAME_PASSPORT, ) @@ -81,6 +82,14 @@ def extract_access_token(request: Request) -> str | None: return _try_extract_from_cookie(request) or _try_extract_from_header(request) +def extract_webapp_access_token(request: Request) -> str | None: + """ + Try to extract webapp access token from cookie, then header. + """ + + return request.cookies.get(_real_cookie_name(COOKIE_NAME_WEBAPP_ACCESS_TOKEN)) or _try_extract_from_header(request) + + def extract_webapp_passport(app_code: str, request: Request) -> str | None: """ Try to extract app token from header or params. @@ -155,6 +164,10 @@ def clear_access_token_from_cookie(response: Response, samesite: str = "Lax"): _clear_cookie(response, COOKIE_NAME_ACCESS_TOKEN, samesite) +def clear_webapp_access_token_from_cookie(response: Response, samesite: str = "Lax"): + _clear_cookie(response, COOKIE_NAME_WEBAPP_ACCESS_TOKEN, samesite) + + def clear_refresh_token_from_cookie(response: Response): _clear_cookie(response, COOKIE_NAME_REFRESH_TOKEN) diff --git a/api/tests/unit_tests/libs/test_token.py b/api/tests/unit_tests/libs/test_token.py index 22790fa4a6..a611d3eb0e 100644 --- a/api/tests/unit_tests/libs/test_token.py +++ b/api/tests/unit_tests/libs/test_token.py @@ -1,5 +1,5 @@ -from constants import COOKIE_NAME_ACCESS_TOKEN -from libs.token import extract_access_token +from constants import COOKIE_NAME_ACCESS_TOKEN, COOKIE_NAME_WEBAPP_ACCESS_TOKEN +from libs.token import extract_access_token, extract_webapp_access_token class MockRequest: @@ -14,10 +14,12 @@ def test_extract_access_token(): return MockRequest(headers, cookies, args) test_cases = [ - (_mock_request({"Authorization": "Bearer 123"}, {}, {}), "123"), - (_mock_request({}, {COOKIE_NAME_ACCESS_TOKEN: "123"}, {}), "123"), - (_mock_request({}, {}, {}), None), - (_mock_request({"Authorization": "Bearer_aaa 123"}, {}, {}), None), + (_mock_request({"Authorization": "Bearer 123"}, {}, {}), "123", "123"), + (_mock_request({}, {COOKIE_NAME_ACCESS_TOKEN: "123"}, {}), "123", None), + (_mock_request({}, {}, {}), None, None), + (_mock_request({"Authorization": "Bearer_aaa 123"}, {}, {}), None, None), + (_mock_request({}, {COOKIE_NAME_WEBAPP_ACCESS_TOKEN: "123"}, {}), None, "123"), ] - for request, expected in test_cases: - assert extract_access_token(request) == expected # pyright: ignore[reportArgumentType] + for request, expected_console, expected_webapp in test_cases: + assert extract_access_token(request) == expected_console # pyright: ignore[reportArgumentType] + assert extract_webapp_access_token(request) == expected_webapp # pyright: ignore[reportArgumentType]