feat: add Provider.CustomLogoutUrl field

This commit is contained in:
Yang Luo
2026-04-11 01:01:58 +08:00
parent 5ed9158368
commit 49d35ac161
3 changed files with 70 additions and 1 deletions

View File

@@ -374,6 +374,10 @@ func (c *ApiController) Logout() {
return
}
// Retrieve application and token before clearing the session
application := c.GetSessionApplication()
sessionToken := c.GetSessionToken()
c.ClearUserSession()
c.ClearTokenSession()
@@ -382,7 +386,9 @@ func (c *ApiController) Logout() {
return
}
application := c.GetSessionApplication()
// Propagate logout to external Custom OAuth2 providers
object.InvokeCustomProviderLogout(application, sessionToken)
if application == nil || application.Name == "app-built-in" || application.HomepageUrl == "" {
c.ResponseOk(user)
return
@@ -427,6 +433,9 @@ func (c *ApiController) Logout() {
return
}
// Propagate logout to external Custom OAuth2 providers
object.InvokeCustomProviderLogout(application, accessToken)
if redirectUri == "" {
c.ResponseOk()
return
@@ -469,6 +478,10 @@ func (c *ApiController) SsoLogout() {
logoutAll := c.Ctx.Input.Query("logoutAll")
logoutAllSessions := logoutAll == "" || logoutAll == "true" || logoutAll == "1"
// Retrieve application and token before clearing the session
ssoApplication := c.GetSessionApplication()
ssoSessionToken := c.GetSessionToken()
c.ClearUserSession()
c.ClearTokenSession()
owner, username, err := util.GetOwnerAndNameFromIdWithError(user)
@@ -548,6 +561,9 @@ func (c *ApiController) SsoLogout() {
}
}
// Propagate logout to external Custom OAuth2 providers
object.InvokeCustomProviderLogout(ssoApplication, ssoSessionToken)
c.ResponseOk()
}

View File

@@ -17,6 +17,8 @@ package object
import (
"errors"
"fmt"
"net/http"
"net/url"
"regexp"
"strings"
@@ -48,6 +50,7 @@ type Provider struct {
CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"`
CustomTokenUrl string `xorm:"varchar(200)" json:"customTokenUrl"`
CustomUserInfoUrl string `xorm:"varchar(200)" json:"customUserInfoUrl"`
CustomLogoutUrl string `xorm:"varchar(200)" json:"customLogoutUrl"`
CustomLogo string `xorm:"varchar(200)" json:"customLogo"`
Scopes string `xorm:"varchar(100)" json:"scopes"`
UserMapping map[string]string `xorm:"varchar(500)" json:"userMapping"`
@@ -686,3 +689,43 @@ func GetLogProviderFromProvider(provider *Provider) (log.LogProvider, error) {
return log.GetLogProvider(provider.Type, provider.Host, provider.Port, provider.Title)
}
// InvokeCustomProviderLogout iterates through the application's Custom OAuth2 providers
// and calls their logout endpoint (if configured) to terminate the upstream session.
func InvokeCustomProviderLogout(application *Application, accessToken string) {
if application == nil {
return
}
for _, providerItem := range application.Providers {
provider := providerItem.Provider
if provider == nil || provider.Category != "OAuth" || !strings.HasPrefix(provider.Type, "Custom") {
continue
}
if provider.CustomLogoutUrl == "" {
continue
}
go callProviderLogoutUrl(provider, accessToken)
}
}
// callProviderLogoutUrl sends a logout/token-revocation request to the provider's logout URL.
// Supports RFC 7009 token revocation and Keycloak-style end_session endpoints.
func callProviderLogoutUrl(provider *Provider, accessToken string) {
params := url.Values{}
params.Set("token", accessToken)
params.Set("client_id", provider.ClientId)
params.Set("client_secret", provider.ClientSecret)
resp, err := http.PostForm(provider.CustomLogoutUrl, params)
if err != nil {
util.LogWarning(nil, "InvokeCustomProviderLogout: failed to call logout URL %s for provider %s: %v", provider.CustomLogoutUrl, provider.Name, err)
return
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
util.LogWarning(nil, "InvokeCustomProviderLogout: logout URL %s returned status %d for provider %s", provider.CustomLogoutUrl, resp.StatusCode, provider.Name)
}
}

View File

@@ -164,6 +164,16 @@ export function renderOAuthProviderFields(provider, updateProviderField, renderU
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Logout URL"), i18next.t("provider:Logout URL - Tooltip"))}
</Col>
<Col span={22} >
<Input value={provider.customLogoutUrl} onChange={e => {
updateProviderField("customLogoutUrl", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Enable PKCE"), i18next.t("provider:Enable PKCE - Tooltip"))} :