fix(api): add session_id validation for webapp JWT authentication (#28297)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
yangzheli 2025-11-21 11:23:52 +08:00 committed by GitHub
parent 3cf19dc07f
commit a4c4d18f42
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 13 additions and 5 deletions

View File

@ -81,6 +81,7 @@ class LoginStatusApi(Resource):
) )
def get(self): def get(self):
app_code = request.args.get("app_code") app_code = request.args.get("app_code")
user_id = request.args.get("user_id")
token = extract_webapp_access_token(request) token = extract_webapp_access_token(request)
if not app_code: if not app_code:
return { return {
@ -103,7 +104,7 @@ class LoginStatusApi(Resource):
user_logged_in = False user_logged_in = False
try: try:
_ = decode_jwt_token(app_code=app_code) _ = decode_jwt_token(app_code=app_code, user_id=user_id)
app_logged_in = True app_logged_in = True
except Exception: except Exception:
app_logged_in = False app_logged_in = False

View File

@ -38,7 +38,7 @@ def validate_jwt_token(view: Callable[Concatenate[App, EndUser, P], R] | None =
return decorator return decorator
def decode_jwt_token(app_code: str | None = None): def decode_jwt_token(app_code: str | None = None, user_id: str | None = None):
system_features = FeatureService.get_system_features() system_features = FeatureService.get_system_features()
if not app_code: if not app_code:
app_code = str(request.headers.get(HEADER_NAME_APP_CODE)) app_code = str(request.headers.get(HEADER_NAME_APP_CODE))
@ -63,6 +63,10 @@ def decode_jwt_token(app_code: str | None = None):
if not end_user: if not end_user:
raise NotFound() raise NotFound()
# Validate user_id against end_user's session_id if provided
if user_id is not None and end_user.session_id != user_id:
raise Unauthorized("Authentication has expired.")
# for enterprise webapp auth # for enterprise webapp auth
app_web_auth_enabled = False app_web_auth_enabled = False
webapp_settings = None webapp_settings = None

View File

@ -58,7 +58,7 @@ const Splash: FC<PropsWithChildren> = ({ children }) => {
(async () => { (async () => {
// if access mode is public, user login is always true, but the app login(passport) may be expired // if access mode is public, user login is always true, but the app login(passport) may be expired
const { userLoggedIn, appLoggedIn } = await webAppLoginStatus(shareCode!) const { userLoggedIn, appLoggedIn } = await webAppLoginStatus(shareCode!, embeddedUserId || undefined)
if (userLoggedIn && appLoggedIn) { if (userLoggedIn && appLoggedIn) {
redirectOrFinish() redirectOrFinish()
} }

View File

@ -30,10 +30,13 @@ type isWebAppLogin = {
app_logged_in: boolean app_logged_in: boolean
} }
export async function webAppLoginStatus(shareCode: string) { export async function webAppLoginStatus(shareCode: string, userId?: string) {
// always need to check login to prevent passport from being outdated // always need to check login to prevent passport from being outdated
// check remotely, the access token could be in cookie (enterprise SSO redirected with https) // check remotely, the access token could be in cookie (enterprise SSO redirected with https)
const { logged_in, app_logged_in } = await getPublic<isWebAppLogin>(`/login/status?app_code=${shareCode}`) const params = new URLSearchParams({ app_code: shareCode })
if (userId)
params.append('user_id', userId)
const { logged_in, app_logged_in } = await getPublic<isWebAppLogin>(`/login/status?${params.toString()}`)
return { return {
userLoggedIn: logged_in, userLoggedIn: logged_in,
appLoggedIn: app_logged_in, appLoggedIn: app_logged_in,