diff --git a/api/controllers/web/passport.py b/api/controllers/web/passport.py index 776b743e92..a344777783 100644 --- a/api/controllers/web/passport.py +++ b/api/controllers/web/passport.py @@ -14,8 +14,6 @@ from extensions.ext_database import db from libs.passport import PassportService from libs.token import extract_access_token from models.model import App, EndUser, Site -from services.app_service import AppService -from services.enterprise.enterprise_service import EnterpriseService from services.feature_service import FeatureService from services.webapp_auth_service import WebAppAuthService, WebAppAuthType @@ -38,22 +36,17 @@ class PassportResource(Resource): app_code = request.headers.get(HEADER_NAME_APP_CODE) user_id = request.args.get("user_id") access_token = extract_access_token(request) - if app_code is None: raise Unauthorized("X-App-Code header is missing.") - app_id = AppService.get_app_id_by_code(app_code) - # exchange token for enterprise logined web user - enterprise_user_decoded = decode_enterprise_webapp_user_id(access_token) - if enterprise_user_decoded: - # a web user has already logged in, exchange a token for this app without redirecting to the login page - return exchange_token_for_existing_web_user( - app_code=app_code, enterprise_user_decoded=enterprise_user_decoded - ) - if system_features.webapp_auth.enabled: - app_settings = EnterpriseService.WebAppAuth.get_app_access_mode_by_id(app_id=app_id) - if not app_settings or not app_settings.access_mode == "public": - raise WebAppAuthRequiredError() + enterprise_user_decoded = decode_enterprise_webapp_user_id(access_token) + app_auth_type = WebAppAuthService.get_app_auth_type(app_code=app_code) + if app_auth_type != WebAppAuthType.PUBLIC: + if not enterprise_user_decoded: + raise WebAppAuthRequiredError() + return exchange_token_for_existing_web_user( + app_code=app_code, enterprise_user_decoded=enterprise_user_decoded, auth_type=app_auth_type + ) # get site from db and check if it is normal site = db.session.scalar(select(Site).where(Site.code == app_code, Site.status == "normal")) @@ -124,7 +117,7 @@ def decode_enterprise_webapp_user_id(jwt_token: str | None): return decoded -def exchange_token_for_existing_web_user(app_code: str, enterprise_user_decoded: dict): +def exchange_token_for_existing_web_user(app_code: str, enterprise_user_decoded: dict, auth_type: WebAppAuthType): """ Exchange a token for an existing web user session. """ @@ -145,13 +138,11 @@ def exchange_token_for_existing_web_user(app_code: str, enterprise_user_decoded: if not app_model or app_model.status != "normal" or not app_model.enable_site: raise NotFound() - app_auth_type = WebAppAuthService.get_app_auth_type(app_code=app_code) - - if app_auth_type == WebAppAuthType.PUBLIC: + if auth_type == WebAppAuthType.PUBLIC: return _exchange_for_public_app_token(app_model, site, enterprise_user_decoded) - elif app_auth_type == WebAppAuthType.EXTERNAL and user_auth_type != "external": + elif auth_type == WebAppAuthType.EXTERNAL and user_auth_type != "external": raise WebAppAuthRequiredError("Please login as external user.") - elif app_auth_type == WebAppAuthType.INTERNAL and user_auth_type != "internal": + elif auth_type == WebAppAuthType.INTERNAL and user_auth_type != "internal": raise WebAppAuthRequiredError("Please login as internal user.") end_user = None diff --git a/web/service/fetch.ts b/web/service/fetch.ts index 541b1246d4..8d663c902b 100644 --- a/web/service/fetch.ts +++ b/web/service/fetch.ts @@ -69,12 +69,36 @@ const beforeErrorToast = (otherOptions: IOtherOptions): BeforeErrorHook => { } } +const SHARE_ROUTE_DENY_LIST = new Set(['webapp-signin', 'check-code', 'login']) + +const resolveShareCode = () => { + const pathnameSegments = globalThis.location.pathname.split('/').filter(Boolean) + const lastSegment = pathnameSegments.at(-1) || '' + if (lastSegment && !SHARE_ROUTE_DENY_LIST.has(lastSegment)) + return lastSegment + + const redirectParam = new URLSearchParams(globalThis.location.search).get('redirect_url') + if (!redirectParam) + return '' + try { + const redirectUrl = new URL(decodeURIComponent(redirectParam), globalThis.location.origin) + const redirectSegments = redirectUrl.pathname.split('/').filter(Boolean) + const redirectSegment = redirectSegments.at(-1) || '' + return SHARE_ROUTE_DENY_LIST.has(redirectSegment) ? '' : redirectSegment + } + catch { + return '' + } +} + const beforeRequestPublicWithCode = (request: Request) => { - request.headers.set('Authorization', `Bearer ${getWebAppAccessToken()}`) - const shareCode = globalThis.location.pathname.split('/').filter(Boolean).pop() || '' - // some pages does not end with share code, so we need to check it - // TODO: maybe find a better way to access app code? - if (shareCode === 'webapp-signin' || shareCode === 'check-code') + const accessToken = getWebAppAccessToken() + if (accessToken) + request.headers.set('Authorization', `Bearer ${accessToken}`) + else + request.headers.delete('Authorization') + const shareCode = resolveShareCode() + if (!shareCode) return request.headers.set(WEB_APP_SHARE_CODE_HEADER_NAME, shareCode) request.headers.set(PASSPORT_HEADER_NAME, getWebAppPassport(shareCode)) diff --git a/web/service/share.ts b/web/service/share.ts index ce03f508d1..b19dbc896d 100644 --- a/web/service/share.ts +++ b/web/service/share.ts @@ -291,7 +291,9 @@ export const textToAudioStream = (url: string, isPublicAPI: boolean, header: { c export const fetchAccessToken = async ({ userId, appCode }: { userId?: string, appCode: string }) => { const headers = new Headers() headers.append(WEB_APP_SHARE_CODE_HEADER_NAME, appCode) - headers.append('Authorization', `Bearer ${getWebAppAccessToken()}`) + const accessToken = getWebAppAccessToken() + if (accessToken) + headers.append('Authorization', `Bearer ${accessToken}`) const params = new URLSearchParams() userId && params.append('user_id', userId) const url = `/passport?${params.toString()}`