From 43d45081eb33526cea2e2dac4192062b8a545eef Mon Sep 17 00:00:00 2001 From: broccoli Date: Sun, 13 Sep 2026 13:52:18 +0200 Subject: [PATCH 1/2] fix: allow POST for connector redirect route Connector plugins that collect credentials in a login form (e.g. an LDAP connector) need to submit them via POST rather than GET, since GET params get logged in server/proxy access logs and browser history. ConnectorRedirectDispatcher itself is already method-agnostic (it just reads ctx.Request), so this only needed a route registration change. --- internal/router/plugin_api_router.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/router/plugin_api_router.go b/internal/router/plugin_api_router.go index 3b74b3ddd..0a72876d3 100644 --- a/internal/router/plugin_api_router.go +++ b/internal/router/plugin_api_router.go @@ -56,6 +56,7 @@ func (pr *PluginAPIRouter) RegisterUnAuthConnectorRouter(r *gin.RouterGroup) { connectorController := pr.connectorController r.GET(controller.ConnectorLoginRouterPrefix+":name", connectorController.ConnectorLoginDispatcher) r.GET(controller.ConnectorRedirectRouterPrefix+":name", connectorController.ConnectorRedirectDispatcher) + r.POST(controller.ConnectorRedirectRouterPrefix+":name", connectorController.ConnectorRedirectDispatcher) r.GET("/connector/info", connectorController.ConnectorsInfo) r.POST("/connector/binding/email", connectorController.ExternalLoginBindingUserSendEmail) From 86f4bb554d070af7acbf2c1218fbe6e6fd105de5 Mon Sep 17 00:00:00 2001 From: broccoli Date: Thu, 17 Sep 2026 15:25:38 +0200 Subject: [PATCH 2/2] fix: reject missing/invalid state for connectors that require it + added an optional ConnectorStateRequired interface. Connectors that implement it now get their consumed state checked in ConnectorRedirect. A missing or invalid state is rejected. Its optional, so no existing connectors behavior changes unless it opts in. + required by the LDAP connector, whose login form is a same-site POST rather than an external OAuth redirect, so state doesn't naturally round-trip without it --- internal/controller/connector_controller.go | 5 +++++ plugin/connector.go | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/internal/controller/connector_controller.go b/internal/controller/connector_controller.go index 28aa5fc6a..c81e644f4 100644 --- a/internal/controller/connector_controller.go +++ b/internal/controller/connector_controller.go @@ -175,6 +175,11 @@ func (cc *ConnectorController) ConnectorRedirect(connector plugin.Connector) (fn ctx.Redirect(http.StatusFound, "/50x") return } + if requirer, ok := connector.(plugin.ConnectorStateRequired); ok && requirer.ConnectorRequireState() && stateInfo == nil { + log.Errorf("missing or invalid connector oauth state for provider %s", connector.ConnectorSlugName()) + ctx.Redirect(http.StatusFound, "/50x") + return + } if stateInfo != nil && stateInfo.Intent == schema.ExternalLoginOAuthStateBindIntent { if err = cc.userExternalService.BindExternalLoginToUser(ctx, stateInfo.UserID, u); err != nil { log.Errorf("bind external login failed: %v", err) diff --git a/plugin/connector.go b/plugin/connector.go index 267cd3916..cd8468fcb 100644 --- a/plugin/connector.go +++ b/plugin/connector.go @@ -44,6 +44,10 @@ type Connector interface { ConnectorReceiver(ctx *GinContext, receiverURL string) (userInfo ExternalLoginUserInfo, err error) } +type ConnectorStateRequired interface { + ConnectorRequireState() bool +} + // ExternalLoginUserInfo external login user info type ExternalLoginUserInfo struct { // required. The unique user ID provided by the third-party login