fix: preserve share code headers after login redirect (#27225)

Co-authored-by: yunlu.wen <yunlu.wen@dify.ai>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
-LAN- 2025-10-22 14:59:08 +08:00 committed by GitHub
parent 8e45753c68
commit 40d3332690
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 44 additions and 27 deletions

View File

@ -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

View File

@ -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))

View File

@ -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()}`