mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-08-10 21:39:02 +08:00
Feat: Add /login/channels
route and improve auth logic for frontend third-party login integration (#7521)
### What problem does this PR solve? Add `/login/channels` route and improve auth logic to support frontend integration with third-party login providers: - Add `/login/channels` route to provide authentication channel list with `display_name` and `icon` - Optimize user info parsing logic by prioritizing `avatar_url` and falling back to `picture` - Simplify OIDC token validation by removing unnecessary `kid` checks - Ensure `client_id` is safely cast to string during `audience` validation - Fix typo --- - Related pull request: #7379 ### Type of change - [x] New Feature (non-breaking change which adds functionality) - [x] Documentation Update
This commit is contained in:
parent
014a1535f2
commit
e349635a3d
@ -20,7 +20,7 @@ oauth_config = {
|
|||||||
"authorization_url": "https://provider.com/oauth/authorize",
|
"authorization_url": "https://provider.com/oauth/authorize",
|
||||||
"token_url": "https://provider.com/oauth/token",
|
"token_url": "https://provider.com/oauth/token",
|
||||||
"userinfo_url": "https://provider.com/oauth/userinfo",
|
"userinfo_url": "https://provider.com/oauth/userinfo",
|
||||||
"redirect_uri": "https://your-app.com/oauth/callback/<channel>"
|
"redirect_uri": "https://your-app.com/v1/user/oauth/callback/<channel>"
|
||||||
}
|
}
|
||||||
|
|
||||||
# OIDC configuration
|
# OIDC configuration
|
||||||
@ -29,7 +29,7 @@ oidc_config = {
|
|||||||
"issuer": "https://provider.com/v1/oidc",
|
"issuer": "https://provider.com/v1/oidc",
|
||||||
"client_id": "your_client_id",
|
"client_id": "your_client_id",
|
||||||
"client_secret": "your_client_secret",
|
"client_secret": "your_client_secret",
|
||||||
"redirect_uri": "https://your-app.com/oauth/callback/<channel>"
|
"redirect_uri": "https://your-app.com/v1/user/oauth/callback/<channel>"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get client instance
|
# Get client instance
|
||||||
|
@ -102,5 +102,7 @@ class OAuthClient:
|
|||||||
email = user_info.get("email")
|
email = user_info.get("email")
|
||||||
username = user_info.get("username", str(email).split("@")[0])
|
username = user_info.get("username", str(email).split("@")[0])
|
||||||
nickname = user_info.get("nickname", username)
|
nickname = user_info.get("nickname", username)
|
||||||
avatar_url = user_info.get("picture", "")
|
avatar_url = user_info.get("avatar_url", None)
|
||||||
|
if avatar_url is None:
|
||||||
|
avatar_url = user_info.get("picture", "")
|
||||||
return UserInfo(email=email, username=username, nickname=nickname, avatar_url=avatar_url)
|
return UserInfo(email=email, username=username, nickname=nickname, avatar_url=avatar_url)
|
||||||
|
@ -39,6 +39,7 @@ class OIDCClient(OAuthClient):
|
|||||||
})
|
})
|
||||||
|
|
||||||
super().__init__(config)
|
super().__init__(config)
|
||||||
|
self.issuer = config['issuer']
|
||||||
self.jwks_uri = config['jwks_uri']
|
self.jwks_uri = config['jwks_uri']
|
||||||
|
|
||||||
|
|
||||||
@ -60,11 +61,8 @@ class OIDCClient(OAuthClient):
|
|||||||
Parse and validate OIDC ID Token (JWT format) with signature verification.
|
Parse and validate OIDC ID Token (JWT format) with signature verification.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Decode JWT header to extract key ID (kid) without verifying signature
|
# Decode JWT header without verifying signature
|
||||||
headers = jwt.get_unverified_header(id_token)
|
headers = jwt.get_unverified_header(id_token)
|
||||||
kid = headers.get("kid")
|
|
||||||
if not kid:
|
|
||||||
raise ValueError("ID Token missing 'kid' in header")
|
|
||||||
|
|
||||||
# OIDC usually uses `RS256` for signing
|
# OIDC usually uses `RS256` for signing
|
||||||
alg = headers.get("alg", "RS256")
|
alg = headers.get("alg", "RS256")
|
||||||
@ -79,7 +77,7 @@ class OIDCClient(OAuthClient):
|
|||||||
id_token,
|
id_token,
|
||||||
key=signing_key,
|
key=signing_key,
|
||||||
algorithms=[alg],
|
algorithms=[alg],
|
||||||
audience=self.client_id,
|
audience=str(self.client_id),
|
||||||
issuer=self.issuer,
|
issuer=self.issuer,
|
||||||
)
|
)
|
||||||
return decoded_token
|
return decoded_token
|
||||||
|
@ -116,7 +116,30 @@ def login():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@manager.route("/login/<channel>") # noqa: F821
|
@manager.route("/login/channels", methods=["GET"]) # noqa: F821
|
||||||
|
def get_login_channels():
|
||||||
|
"""
|
||||||
|
Get all supported authentication channels.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
channels = []
|
||||||
|
for channel, config in settings.OAUTH_CONFIG.items():
|
||||||
|
channels.append({
|
||||||
|
"channel": channel,
|
||||||
|
"display_name": config.get("display_name", channel.title()),
|
||||||
|
"icon": config.get("icon", "sso"),
|
||||||
|
})
|
||||||
|
return get_json_result(data=channels)
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception(e)
|
||||||
|
return get_json_result(
|
||||||
|
data=[],
|
||||||
|
message=f"Load channels failure, error: {str(e)}",
|
||||||
|
code=settings.RetCode.EXCEPTION_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@manager.route("/login/<channel>", methods=["GET"]) # noqa: F821
|
||||||
def oauth_login(channel):
|
def oauth_login(channel):
|
||||||
channel_config = settings.OAUTH_CONFIG.get(channel)
|
channel_config = settings.OAUTH_CONFIG.get(channel)
|
||||||
if not channel_config:
|
if not channel_config:
|
||||||
@ -171,7 +194,7 @@ def oauth_callback(channel):
|
|||||||
users = user_register(
|
users = user_register(
|
||||||
user_id,
|
user_id,
|
||||||
{
|
{
|
||||||
"access_token": access_token,
|
"access_token": get_uuid(),
|
||||||
"email": user_info.email,
|
"email": user_info.email,
|
||||||
"avatar": avatar,
|
"avatar": avatar,
|
||||||
"nickname": user_info.nickname,
|
"nickname": user_info.nickname,
|
||||||
@ -189,7 +212,7 @@ def oauth_callback(channel):
|
|||||||
# Try to log in
|
# Try to log in
|
||||||
user = users[0]
|
user = users[0]
|
||||||
login_user(user)
|
login_user(user)
|
||||||
return redirect(f"/?auth_success=true&user_id={user.get_id()}")
|
return redirect(f"/?auth={user.get_id()}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
rollback_user_registration(user_id)
|
rollback_user_registration(user_id)
|
||||||
@ -201,8 +224,9 @@ def oauth_callback(channel):
|
|||||||
user.access_token = get_uuid()
|
user.access_token = get_uuid()
|
||||||
login_user(user)
|
login_user(user)
|
||||||
user.save()
|
user.save()
|
||||||
return redirect(f"/?auth_success=true&user_id={user.get_id()}")
|
return redirect(f"/?auth={user.get_id()}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logging.exception(e)
|
||||||
return redirect(f"/?error={str(e)}")
|
return redirect(f"/?error={str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,11 +75,13 @@ redis:
|
|||||||
# grant_type: 'authorization_code'
|
# grant_type: 'authorization_code'
|
||||||
# custom_channel:
|
# custom_channel:
|
||||||
# type: oidc
|
# type: oidc
|
||||||
|
# icon: sso
|
||||||
|
# display_name: "Custom Channel"
|
||||||
# issuer: https://provider.com/v1/oidc
|
# issuer: https://provider.com/v1/oidc
|
||||||
# client_id: xxxxxxxxxxxxxxxxxxxxxxxxx
|
# client_id: xxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
# client_secret: xxxxxxxxxxxxxxxxxxxxxxxx
|
# client_secret: xxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
# scope: "openid email profile"
|
# scope: "openid email profile"
|
||||||
# redirect_uri: https://your-app.com/oauth/callback/custom_channel
|
# redirect_uri: https://your-app.com/v1/user/oauth/callback/custom_channel
|
||||||
# authentication:
|
# authentication:
|
||||||
# client:
|
# client:
|
||||||
# switch: false
|
# switch: false
|
||||||
|
@ -87,11 +87,13 @@ redis:
|
|||||||
# grant_type: 'authorization_code'
|
# grant_type: 'authorization_code'
|
||||||
# custom_channel:
|
# custom_channel:
|
||||||
# type: oidc
|
# type: oidc
|
||||||
|
# icon: sso
|
||||||
|
# display_name: "Custom Channel"
|
||||||
# issuer: https://provider.com/v1/oidc
|
# issuer: https://provider.com/v1/oidc
|
||||||
# client_id: xxxxxxxxxxxxxxxxxxxxxxxxx
|
# client_id: xxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
# client_secret: xxxxxxxxxxxxxxxxxxxxxxxx
|
# client_secret: xxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
# scope: "openid email profile"
|
# scope: "openid email profile"
|
||||||
# redirect_uri: https://your-app.com/oauth/callback/custom_channel
|
# redirect_uri: https://your-app.com/v1/user/oauth/callback/custom_channel
|
||||||
# authentication:
|
# authentication:
|
||||||
# client:
|
# client:
|
||||||
# switch: false
|
# switch: false
|
||||||
|
Loading…
x
Reference in New Issue
Block a user