mirror of
https://github.com/langgenius/dify.git
synced 2026-05-20 16:57:01 +08:00
The four EE-only SSO handlers (sso_initiate, sso_complete, approval_context, approve_external) move from controllers/oauth_device_sso.py to controllers/openapi/oauth_device/. Each is registered on openapi_bp via @bp.route at the canonical path: /openapi/v1/oauth/device/sso-initiate /openapi/v1/oauth/device/sso-complete /openapi/v1/oauth/device/approval-context /openapi/v1/oauth/device/approve-external sso-complete moves under /oauth/device/ from its previous orphan path /v1/device/sso-complete; the IdP-side ACS callback URL hardcoded in sso_initiate now points to the canonical path. Operators must re-register the ACS callback with each IdP before Phase F deletes the legacy alias. oauth_device_sso.py shrinks to a thin re-mount file: same legacy bp with attach_anti_framing applied, four bp.add_url_rule() calls binding the legacy paths to the imported view functions. Same handler runs for both mounts — no duplicated logic. attach_anti_framing(openapi_bp) added in controllers/openapi/__init__.py so X-Frame-Options + frame-ancestors CSP cover the canonical paths too. Plan: docs/superpowers/plans/2026-04-26-openapi-migration.md (in difyctl repo).
47 lines
1.4 KiB
Python
47 lines
1.4 KiB
Python
"""GET /openapi/v1/oauth/device/approval-context — EE-only. SPA reads
|
|
the device_approval_grant cookie claims (subject email/issuer, csrf
|
|
token, user_code, expiry). Idempotent — does not consume the nonce.
|
|
|
|
Also registered on the legacy /v1/oauth/device/approval-context path
|
|
from controllers/oauth_device_sso.py until Phase F retires that mount.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from flask import jsonify, request
|
|
from werkzeug.exceptions import Unauthorized
|
|
|
|
from controllers.openapi import bp
|
|
from libs import jws
|
|
from libs.device_flow_security import (
|
|
APPROVAL_GRANT_COOKIE_NAME,
|
|
enterprise_only,
|
|
verify_approval_grant,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@bp.route("/oauth/device/approval-context", methods=["GET"])
|
|
@enterprise_only
|
|
def approval_context():
|
|
token = request.cookies.get(APPROVAL_GRANT_COOKIE_NAME)
|
|
if not token:
|
|
raise Unauthorized("no_session")
|
|
|
|
keyset = jws.KeySet.from_shared_secret()
|
|
try:
|
|
claims = verify_approval_grant(keyset, token)
|
|
except jws.VerifyError as e:
|
|
logger.warning("approval-context: bad cookie: %s", e)
|
|
raise Unauthorized("no_session") from e
|
|
|
|
return jsonify({
|
|
"subject_email": claims.subject_email,
|
|
"subject_issuer": claims.subject_issuer,
|
|
"user_code": claims.user_code,
|
|
"csrf_token": claims.csrf_token,
|
|
"expires_at": claims.expires_at.isoformat(),
|
|
}), 200
|