From 73a8461a2d000488ab75aa3d611d64dda22319d1 Mon Sep 17 00:00:00 2001 From: k3-cat Date: Fri, 4 Jul 2025 16:30:07 +1000 Subject: [PATCH] feat: Improve oauth redirect (#303) * fix: redirects after oauth can potentially misalign with server's actually hostname * feat: remove `RedirectURL` from oauth config, as it should checked by provider rather than client * feat: align oauth endpoint with the hostname in requests --- cmd/apimain.go | 9 +++++---- config/oauth.go | 4 ---- docs/admin/admin_docs.go | 9 +-------- docs/admin/admin_swagger.json | 11 ++--------- docs/admin/admin_swagger.yaml | 5 ----- http/controller/admin/login.go | 3 ++- http/controller/admin/oauth.go | 5 +++-- http/controller/api/ouath.go | 13 ++++++------- http/request/admin/oauth.go | 2 -- model/oauth.go | 9 ++++----- service/oauth.go | 27 +++++++++++++++++---------- 11 files changed, 40 insertions(+), 57 deletions(-) diff --git a/cmd/apimain.go b/cmd/apimain.go index 3d402d4..4407633 100644 --- a/cmd/apimain.go +++ b/cmd/apimain.go @@ -2,6 +2,10 @@ package main import ( "fmt" + "os" + "strconv" + "time" + "github.com/go-redis/redis/v8" "github.com/lejianwen/rustdesk-api/v2/config" "github.com/lejianwen/rustdesk-api/v2/global" @@ -17,9 +21,6 @@ import ( "github.com/lejianwen/rustdesk-api/v2/utils" "github.com/nicksnyder/go-i18n/v2/i18n" "github.com/spf13/cobra" - "os" - "strconv" - "time" ) // @title 管理系统API @@ -210,7 +211,7 @@ func InitGlobal() { } func DatabaseAutoUpdate() { - version := 262 + version := 263 db := global.DB diff --git a/config/oauth.go b/config/oauth.go index 3d863ec..e8b4793 100644 --- a/config/oauth.go +++ b/config/oauth.go @@ -3,24 +3,20 @@ package config type GithubOauth struct { ClientId string `mapstructure:"client-id"` ClientSecret string `mapstructure:"client-secret"` - RedirectUrl string `mapstructure:"redirect-url"` } type GoogleOauth struct { ClientId string `mapstructure:"client-id"` ClientSecret string `mapstructure:"client-secret"` - RedirectUrl string `mapstructure:"redirect-url"` } type OidcOauth struct { Issuer string `mapstructure:"issuer"` ClientId string `mapstructure:"client-id"` ClientSecret string `mapstructure:"client-secret"` - RedirectUrl string `mapstructure:"redirect-url"` } type LinuxdoOauth struct { ClientId string `mapstructure:"client-id"` ClientSecret string `mapstructure:"client-secret"` - RedirectUrl string `mapstructure:"redirect-url"` } diff --git a/docs/admin/admin_docs.go b/docs/admin/admin_docs.go index ca779b2..7fbbe8d 100644 --- a/docs/admin/admin_docs.go +++ b/docs/admin/admin_docs.go @@ -5569,8 +5569,7 @@ const docTemplateadmin = `{ "required": [ "client_id", "client_secret", - "oauth_type", - "redirect_url" + "oauth_type" ], "properties": { "auto_register": { @@ -5600,9 +5599,6 @@ const docTemplateadmin = `{ "pkce_method": { "type": "string" }, - "redirect_url": { - "type": "string" - }, "scopes": { "type": "string" } @@ -6296,9 +6292,6 @@ const docTemplateadmin = `{ "pkce_method": { "type": "string" }, - "redirect_url": { - "type": "string" - }, "scopes": { "type": "string" }, diff --git a/docs/admin/admin_swagger.json b/docs/admin/admin_swagger.json index 653bfc8..6e4df7e 100644 --- a/docs/admin/admin_swagger.json +++ b/docs/admin/admin_swagger.json @@ -5562,8 +5562,7 @@ "required": [ "client_id", "client_secret", - "oauth_type", - "redirect_url" + "oauth_type" ], "properties": { "auto_register": { @@ -5593,9 +5592,6 @@ "pkce_method": { "type": "string" }, - "redirect_url": { - "type": "string" - }, "scopes": { "type": "string" } @@ -6289,9 +6285,6 @@ "pkce_method": { "type": "string" }, - "redirect_url": { - "type": "string" - }, "scopes": { "type": "string" }, @@ -6595,4 +6588,4 @@ "in": "header" } } -} \ No newline at end of file +} diff --git a/docs/admin/admin_swagger.yaml b/docs/admin/admin_swagger.yaml index 827b185..978b9c8 100644 --- a/docs/admin/admin_swagger.yaml +++ b/docs/admin/admin_swagger.yaml @@ -143,15 +143,12 @@ definitions: type: boolean pkce_method: type: string - redirect_url: - type: string scopes: type: string required: - client_id - client_secret - oauth_type - - redirect_url type: object admin.PeerBatchDeleteForm: properties: @@ -611,8 +608,6 @@ definitions: type: boolean pkce_method: type: string - redirect_url: - type: string scopes: type: string updated_at: diff --git a/http/controller/admin/login.go b/http/controller/admin/login.go index ab9e491..7153e3a 100644 --- a/http/controller/admin/login.go +++ b/http/controller/admin/login.go @@ -2,6 +2,7 @@ package admin import ( "fmt" + "github.com/gin-gonic/gin" "github.com/lejianwen/rustdesk-api/v2/global" "github.com/lejianwen/rustdesk-api/v2/http/controller/api" @@ -188,7 +189,7 @@ func (ct *Login) OidcAuth(c *gin.Context) { return } - err, state, verifier, nonce, url := service.AllService.OauthService.BeginAuth(f.Op) + err, state, verifier, nonce, url := service.AllService.OauthService.BeginAuth(c, f.Op) if err != nil { response.Error(c, response.TranslateMsg(c, err.Error())) return diff --git a/http/controller/admin/oauth.go b/http/controller/admin/oauth.go index 5155219..6ae1c3a 100644 --- a/http/controller/admin/oauth.go +++ b/http/controller/admin/oauth.go @@ -1,13 +1,14 @@ package admin import ( + "strconv" + "github.com/gin-gonic/gin" "github.com/lejianwen/rustdesk-api/v2/global" "github.com/lejianwen/rustdesk-api/v2/http/request/admin" adminReq "github.com/lejianwen/rustdesk-api/v2/http/request/admin" "github.com/lejianwen/rustdesk-api/v2/http/response" "github.com/lejianwen/rustdesk-api/v2/service" - "strconv" ) type Oauth struct { @@ -43,7 +44,7 @@ func (o *Oauth) ToBind(c *gin.Context) { return } - err, state, verifier, nonce, url := service.AllService.OauthService.BeginAuth(f.Op) + err, state, verifier, nonce, url := service.AllService.OauthService.BeginAuth(c, f.Op) if err != nil { response.Error(c, response.TranslateMsg(c, err.Error())) return diff --git a/http/controller/api/ouath.go b/http/controller/api/ouath.go index 5489538..3ffb924 100644 --- a/http/controller/api/ouath.go +++ b/http/controller/api/ouath.go @@ -1,6 +1,8 @@ package api import ( + "net/http" + "github.com/gin-gonic/gin" "github.com/lejianwen/rustdesk-api/v2/global" "github.com/lejianwen/rustdesk-api/v2/http/request/api" @@ -10,7 +12,6 @@ import ( "github.com/lejianwen/rustdesk-api/v2/service" "github.com/lejianwen/rustdesk-api/v2/utils" "github.com/nicksnyder/go-i18n/v2/i18n" - "net/http" ) type Oauth struct { @@ -35,7 +36,7 @@ func (o *Oauth) OidcAuth(c *gin.Context) { oauthService := service.AllService.OauthService - err, state, verifier, nonce, url := oauthService.BeginAuth(f.Op) + err, state, verifier, nonce, url := oauthService.BeginAuth(c, f.Op) if err != nil { response.Error(c, response.TranslateMsg(c, err.Error())) return @@ -169,7 +170,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) { var user *model.User // 获取用户信息 code := c.Query("code") - err, oauthUser := oauthService.Callback(code, verifier, op, nonce) + err, oauthUser := oauthService.Callback(c, code, verifier, op, nonce) if err != nil { c.HTML(http.StatusOK, "oauth_fail.html", gin.H{ "message": "OauthFailed", @@ -225,8 +226,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) { if !*oauthConfig.AutoRegister { //c.String(http.StatusInternalServerError, "还未绑定用户,请先绑定") oauthCache.UpdateFromOauthUser(oauthUser) - url := global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/bind/" + cacheKey - c.Redirect(http.StatusFound, url) + c.Redirect(http.StatusFound, "/_admin/#/oauth/bind/"+cacheKey) return } @@ -251,8 +251,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) { Type: model.LoginLogTypeOauth, Platform: oauthService.DeviceOs, })*/ - url := global.Config.Rustdesk.ApiServer + "/_admin/#/" - c.Redirect(http.StatusFound, url) + c.Redirect(http.StatusFound, "/_admin/#/") return } c.HTML(http.StatusOK, "oauth_success.html", gin.H{ diff --git a/http/request/admin/oauth.go b/http/request/admin/oauth.go index 00da0a3..7ad74cc 100644 --- a/http/request/admin/oauth.go +++ b/http/request/admin/oauth.go @@ -22,7 +22,6 @@ type OauthForm struct { Scopes string `json:"scopes" validate:"omitempty"` ClientId string `json:"client_id" validate:"required"` ClientSecret string `json:"client_secret" validate:"required"` - RedirectUrl string `json:"redirect_url" validate:"required"` AutoRegister *bool `json:"auto_register"` PkceEnable *bool `json:"pkce_enable"` PkceMethod string `json:"pkce_method"` @@ -34,7 +33,6 @@ func (of *OauthForm) ToOauth() *model.Oauth { OauthType: of.OauthType, ClientId: of.ClientId, ClientSecret: of.ClientSecret, - RedirectUrl: of.RedirectUrl, AutoRegister: of.AutoRegister, Issuer: of.Issuer, Scopes: of.Scopes, diff --git a/model/oauth.go b/model/oauth.go index 218e89e..98a4be1 100644 --- a/model/oauth.go +++ b/model/oauth.go @@ -30,9 +30,9 @@ func ValidateOauthType(oauthType string) error { } const ( - UserEndpointGithub string = "https://api.github.com/user" + UserEndpointGithub string = "https://api.github.com/user" UserEndpointLinuxdo string = "https://connect.linux.do/api/user" - IssuerGoogle string = "https://accounts.google.com" + IssuerGoogle string = "https://accounts.google.com" ) type Oauth struct { @@ -41,12 +41,11 @@ type Oauth struct { OauthType string `json:"oauth_type"` ClientId string `json:"client_id"` ClientSecret string `json:"client_secret"` - RedirectUrl string `json:"redirect_url"` AutoRegister *bool `json:"auto_register"` Scopes string `json:"scopes"` Issuer string `json:"issuer"` - PkceEnable *bool `json:"pkce_enable"` - PkceMethod string `json:"pkce_method"` + PkceEnable *bool `json:"pkce_enable"` + PkceMethod string `json:"pkce_method"` TimeModel } diff --git a/service/oauth.go b/service/oauth.go index 399f042..d03375a 100644 --- a/service/oauth.go +++ b/service/oauth.go @@ -4,11 +4,14 @@ import ( "context" "encoding/json" "errors" + "github.com/coreos/go-oidc/v3/oidc" + "github.com/gin-gonic/gin" "github.com/lejianwen/rustdesk-api/v2/model" "github.com/lejianwen/rustdesk-api/v2/utils" "golang.org/x/oauth2" "golang.org/x/oauth2/github" + // "golang.org/x/oauth2/google" "gorm.io/gorm" // "io" @@ -93,16 +96,20 @@ func (os *OauthService) DeleteOauthCache(key string) { OauthCache.Delete(key) } -func (os *OauthService) BeginAuth(op string) (error error, state, verifier, nonce, url string) { +func (os *OauthService) BeginAuth(c *gin.Context, op string) (error error, state, verifier, nonce, url string) { state = utils.RandomString(10) + strconv.FormatInt(time.Now().Unix(), 10) verifier = "" nonce = "" if op == model.OauthTypeWebauth { - url = Config.Rustdesk.ApiServer + "/_admin/#/oauth/" + state + host := c.GetHeader("Origin") + if host == "" { + host = Config.Rustdesk.ApiServer + } + url = host + "/_admin/#/oauth/" + state //url = "http://localhost:8888/_admin/#/oauth/" + code return nil, state, verifier, nonce, url } - err, oauthInfo, oauthConfig, _ := os.GetOauthConfig(op) + err, oauthInfo, oauthConfig, _ := os.GetOauthConfig(c, op) if err == nil { extras := make([]oauth2.AuthCodeOption, 0, 3) @@ -167,20 +174,20 @@ func (os *OauthService) LinuxdoProvider() *oidc.Provider { } // GetOauthConfig retrieves the OAuth2 configuration based on the provider name -func (os *OauthService) GetOauthConfig(op string) (err error, oauthInfo *model.Oauth, oauthConfig *oauth2.Config, provider *oidc.Provider) { +func (os *OauthService) GetOauthConfig(c *gin.Context, op string) (err error, oauthInfo *model.Oauth, oauthConfig *oauth2.Config, provider *oidc.Provider) { //err, oauthInfo, oauthConfig = os.getOauthConfigGeneral(op) oauthInfo = os.InfoByOp(op) if oauthInfo.Id == 0 || oauthInfo.ClientId == "" || oauthInfo.ClientSecret == "" { return errors.New("ConfigNotFound"), nil, nil, nil } - // If the redirect URL is empty, use the default redirect URL - if oauthInfo.RedirectUrl == "" { - oauthInfo.RedirectUrl = Config.Rustdesk.ApiServer + "/api/oidc/callback" + host := c.GetHeader("Origin") + if host == "" { + host = Config.Rustdesk.ApiServer } oauthConfig = &oauth2.Config{ ClientID: oauthInfo.ClientId, ClientSecret: oauthInfo.ClientSecret, - RedirectURL: oauthInfo.RedirectUrl, + RedirectURL: host + "/api/oidc/callback", } // Maybe should validate the oauthConfig here @@ -335,8 +342,8 @@ func (os *OauthService) oidcCallback(oauthConfig *oauth2.Config, provider *oidc. } // Callback: Get user information by code and op(Oauth provider) -func (os *OauthService) Callback(code, verifier, op, nonce string) (err error, oauthUser *model.OauthUser) { - err, oauthInfo, oauthConfig, provider := os.GetOauthConfig(op) +func (os *OauthService) Callback(c *gin.Context, code, verifier, op, nonce string) (err error, oauthUser *model.OauthUser) { + err, oauthInfo, oauthConfig, provider := os.GetOauthConfig(c, op) // oauthType is already validated in GetOauthConfig if err != nil { return err, nil