From dee286546607e13124be643c0811000a8dbb66bf Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 29 Oct 2024 10:58:17 +0800 Subject: [PATCH 1/8] optimize build.sh --- build.sh | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) mode change 100644 => 100755 build.sh diff --git a/build.sh b/build.sh old mode 100644 new mode 100755 index 895c3b1..a4474c7 --- a/build.sh +++ b/build.sh @@ -1,16 +1,46 @@ #!/bin/sh -rm release -rf +set -e +# Automatically get the current environment's GOARCH; if not defined, use the detected system architecture +GOARCH=${GOARCH:-$(go env GOARCH)} +DOCS="true" +# Safely remove the old release directory +rm -rf release + +# Set Go environment variables go env -w GO111MODULE=on go env -w GOPROXY=https://goproxy.cn,direct go env -w CGO_ENABLED=1 go env -w GOOS=linux -go env -w GOARCH=amd64 -swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin -swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api +go env -w GOARCH=${GOARCH} + + +# Generate Swagger documentation if DOCS is not empty +if [ -n "${DOCS}" ]; then + # Check if swag is installed + if ! command -v swag &> /dev/null; then + echo "swag command not found. Please install it using:" + echo "go install github.com/swaggo/swag/cmd/swag@latest" + echo "Skipping Swagger documentation generation due to missing swag tool." + else + echo "Generating Swagger documentation..." + swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin + swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api + fi +else + echo "Skipping Swagger documentation generation due to DOCS is empty." +fi + +# Compile the Go code and output it to the release directory go build -o release/apimain cmd/apimain.go + +# Copy resource files to the release directory cp -ar resources release/ cp -ar docs release/ cp -ar conf release/ + +# Create necessary directory structures mkdir -p release/data mkdir -p release/runtime + +echo "Build and setup completed successfully." \ No newline at end of file From 49e5eb186ac90a674c82c7864a2cf865bb9975fd Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 29 Oct 2024 11:50:55 +0800 Subject: [PATCH 2/8] optimize docker --- .dockerignore | 4 ++++ Dockerfile.dev | 42 ++++++++++++++++++++++++++++++++++++++++++ docker-compose.yaml | 8 ++++++-- 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile.dev diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d3c29bd --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +docker-compose.yaml +Dcokerfile +Dcokerfile.dev +data \ No newline at end of file diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000..667aa55 --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,42 @@ +# Use build arguments for Go version and architecture +ARG GO_VERSION=1.23.2 +ARG BUILDARCH + +# Stage 1: Build the Go application with swag +FROM golang:${GO_VERSION} AS builder + +# Set up working directory +WORKDIR /app + +# Install dependencies and copy the source code +COPY go.mod ./ +RUN go install github.com/swaggo/swag/cmd/swag@latest +RUN go mod download +COPY . . + +#run the build script +RUN chmod +x build.sh && ./build.sh + +# Stage 2: Prepare the final image +FROM alpine:latest + +# Set up working directory +WORKDIR /app + +# Install necessary dependencies +RUN apk add --no-cache tzdata file + +# Copy the built application from the builder stage +COPY --from=builder /app/release /app/ + +# Ensure the binary is correctly built +RUN file /app/apimain + +# Set up a volume for persistent data +VOLUME /app/data + +# Expose the necessary port +EXPOSE 21114 + +# Define the command to run the application +CMD ["app/apimain"] \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index bd95588..9328fcd 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,5 +1,8 @@ services: rustdesk-api: + build: + context: . + dockerfile: Dockerfile.dev image: lejianwen/rustdesk-api container_name: rustdesk-api environment: @@ -11,5 +14,6 @@ services: ports: - 21114:21114 volumes: - - /data/rustdesk/api:/app/data #将数据库挂载出来方便备份 - restart: unless-stopped \ No newline at end of file + - ./data/rustdesk/api:/app/data #将数据库挂载出来方便备份 + restart: unless-stopped + command: sleep infnite \ No newline at end of file From fee2808bca4c71b9737e22bb13d407d8000f1708 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 29 Oct 2024 11:51:01 +0800 Subject: [PATCH 3/8] try add oidc --- config/oauth.go | 7 ++ http/controller/admin/oauth.go | 7 ++ http/controller/api/login.go | 4 + http/controller/api/ouath.go | 65 ++++++++++- http/request/admin/oauth.go | 4 + model/oauth.go | 3 + service/oauth.go | 190 ++++++++++++++++++++++++++++----- service/user.go | 10 ++ 8 files changed, 263 insertions(+), 27 deletions(-) diff --git a/config/oauth.go b/config/oauth.go index 81108aa..ff9272f 100644 --- a/config/oauth.go +++ b/config/oauth.go @@ -11,3 +11,10 @@ type GoogleOauth struct { 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"` +} \ No newline at end of file diff --git a/http/controller/admin/oauth.go b/http/controller/admin/oauth.go index a3571f2..fec011d 100644 --- a/http/controller/admin/oauth.go +++ b/http/controller/admin/oauth.go @@ -140,6 +140,13 @@ func (o *Oauth) Unbind(c *gin.Context) { return } } + if f.Op == model.OauthTypeOidc { + err = service.AllService.OauthService.UnBindOidcUser(u.Id) + if err != nil { + response.Fail(c, 101, response.TranslateMsg(c, "OperationFailed")+err.Error()) + return + } + } response.Success(c, nil) } diff --git a/http/controller/api/login.go b/http/controller/api/login.go index 57eeb48..bae13e1 100644 --- a/http/controller/api/login.go +++ b/http/controller/api/login.go @@ -92,6 +92,10 @@ func (l *Login) LoginOptions(c *gin.Context) { if err == nil { oauthOks = append(oauthOks, model.OauthTypeGoogle) } + err, _ = service.AllService.OauthService.GetOauthConfig(model.OauthTypeOidc) + if err == nil { + oauthOks = append(oauthOks, model.OauthTypeOidc) + } oauthOks = append(oauthOks, model.OauthTypeWebauth) var oidcItems []map[string]string for _, v := range oauthOks { diff --git a/http/controller/api/ouath.go b/http/controller/api/ouath.go index fc37322..7c95e36 100644 --- a/http/controller/api/ouath.go +++ b/http/controller/api/ouath.go @@ -32,7 +32,7 @@ func (o *Oauth) OidcAuth(c *gin.Context) { response.Error(c, response.TranslateMsg(c, "ParamsError")+err.Error()) return } - if f.Op != model.OauthTypeWebauth && f.Op != model.OauthTypeGoogle && f.Op != model.OauthTypeGithub { + if f.Op != model.OauthTypeWebauth && f.Op != model.OauthTypeGoogle && f.Op != model.OauthTypeGithub && f.Op != model.OauthTypeOidc { response.Error(c, response.TranslateMsg(c, "ParamsError")) return } @@ -254,6 +254,69 @@ func (o *Oauth) OauthCallback(c *gin.Context) { return } } + if ty == model.OauthTypeOidc { + code := c.Query("code") + err, userData := service.AllService.OauthService.OidcCallback(code) + if err != nil { + c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthFailed")+response.TranslateMsg(c, err.Error())) + return + } + //将空格替换成_ + // OidcName := strings.Replace(userData.Name, " ", "_", -1) + if ac == service.OauthActionTypeBind { + //fmt.Println("bind", ty, userData) + utr := service.AllService.OauthService.UserThirdInfo(ty, userData.Sub) + if utr.UserId > 0 { + c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBindOtherUser")) + return + } + //绑定 + u := service.AllService.UserService.InfoById(v.UserId) + if u == nil { + c.String(http.StatusInternalServerError, response.TranslateMsg(c, "ItemNotFound")) + return + } + //绑定, user preffered_username as username + err = service.AllService.OauthService.BindOidcUser(userData.Sub, userData.PrefferedUsername, v.UserId) + if err != nil { + c.String(http.StatusInternalServerError, response.TranslateMsg(c, "BindFail")) + return + } + c.String(http.StatusOK, response.TranslateMsg(c, "BindSuccess")) + return + } else if ac == service.OauthActionTypeLogin { + if v.UserId != 0 { + c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthHasBeenSuccess")) + return + } + u := service.AllService.UserService.InfoByOidcSub(userData.Sub) + if u == nil { + oa := service.AllService.OauthService.InfoByOp(ty) + if !*oa.AutoRegister { + //c.String(http.StatusInternalServerError, "还未绑定用户,请先绑定") + + v.ThirdName = userData.PrefferedUsername + v.ThirdOpenId = userData.Sub + url := global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/bind/" + cacheKey + c.Redirect(http.StatusFound, url) + return + } + + //自动注册 + u = service.AllService.UserService.RegisterByOidc(userData.PrefferedUsername, userData.Sub) + if u.Id == 0 { + c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthRegisterFailed")) + return + } + } + + v.UserId = u.Id + service.AllService.OauthService.SetOauthCache(cacheKey, v, 0) + c.String(http.StatusOK, response.TranslateMsg(c, "OauthSuccess")) + return + } + } + c.String(http.StatusInternalServerError, response.TranslateMsg(c, "SystemError")) } diff --git a/http/request/admin/oauth.go b/http/request/admin/oauth.go index 11698ee..db519f8 100644 --- a/http/request/admin/oauth.go +++ b/http/request/admin/oauth.go @@ -15,6 +15,8 @@ type UnBindOauthForm struct { type OauthForm struct { Id uint `json:"id"` Op string `json:"op" validate:"required"` + Issuer string `json:"issuer" validate:"omitempty,url"` + 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"` @@ -28,6 +30,8 @@ func (of *OauthForm) ToOauth() *model.Oauth { ClientSecret: of.ClientSecret, RedirectUrl: of.RedirectUrl, AutoRegister: of.AutoRegister, + Issuer: of.Issuer, + Scopes: of.Scopes, } oa.Id = of.Id return oa diff --git a/model/oauth.go b/model/oauth.go index 64de80c..35e7b96 100644 --- a/model/oauth.go +++ b/model/oauth.go @@ -7,12 +7,15 @@ type Oauth struct { ClientSecret string `json:"client_secret"` RedirectUrl string `json:"redirect_url"` AutoRegister *bool `json:"auto_register"` + Scopes string `json:"scopes"` + Issuer string `json:"issuer"` TimeModel } const ( OauthTypeGithub = "github" OauthTypeGoogle = "google" + OauthTypeOidc = "oidc" OauthTypeWebauth = "webauth" ) diff --git a/service/oauth.go b/service/oauth.go index 0ee6add..eb9609c 100644 --- a/service/oauth.go +++ b/service/oauth.go @@ -17,9 +17,19 @@ import ( "strconv" "sync" "time" + "strings" ) +// Define a struct to parse the .well-known/openid-configuration response +type OidcEndpoint struct { + Issuer string `json:"issuer"` + AuthURL string `json:"authorization_endpoint"` + TokenURL string `json:"token_endpoint"` + UserInfo string `json:"userinfo_endpoint"` +} + type OauthService struct { + OidcEndpoint *OidcEndpoint } type GithubUserdata struct { @@ -78,6 +88,15 @@ type GoogleUserdata struct { Picture string `json:"picture"` VerifiedEmail bool `json:"verified_email"` } +type OidcUserdata struct { + Sub string `json:"sub"` + Email string `json:"email"` + VerifiedEmail bool `json:"email_verified"` + Name string `json:"name"` + Picture string `json:"picture"` + PrefferedUsername string `json:"preffered_username"` +} + type OauthCacheItem struct { UserId uint `json:"user_id"` Id string `json:"id"` //rustdesk的设备ID @@ -137,35 +156,105 @@ func (os *OauthService) BeginAuth(op string) (error error, code, url string) { return err, code, "" } -// GetOauthConfig 获取配置 +// Method to fetch OIDC configuration dynamically +func (os *OauthService) FetchOIDCConfig(issuer string) error { + configURL := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration" + + // Get the HTTP client (with or without proxy based on configuration) + client := getHTTPClientWithProxy() + + resp, err := client.Get(configURL) + if err != nil { + return errors.New("failed to fetch OIDC configuration") + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return errors.New("OIDC configuration not found") + } + + var endpoint OidcEndpoint + if err := json.NewDecoder(resp.Body).Decode(&endpoint); err != nil { + return errors.New("failed to parse OIDC configuration") + } + + os.OidcEndpoint = &endpoint + return nil +} + +// GetOauthConfig retrieves the OAuth2 configuration based on the provider type func (os *OauthService) GetOauthConfig(op string) (error, *oauth2.Config) { - if op == model.OauthTypeGithub { - g := os.InfoByOp(model.OauthTypeGithub) - if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" { - return errors.New("ConfigNotFound"), nil - } - return nil, &oauth2.Config{ - ClientID: g.ClientId, - ClientSecret: g.ClientSecret, - RedirectURL: g.RedirectUrl, - Endpoint: github.Endpoint, - Scopes: []string{"read:user", "user:email"}, - } + switch op { + case model.OauthTypeGithub: + return os.getGithubConfig() + case model.OauthTypeGoogle: + return os.getGoogleConfig() + case model.OauthTypeOidc: + return os.getOidcConfig() + default: + return errors.New("unsupported OAuth type"), nil } - if op == model.OauthTypeGoogle { - g := os.InfoByOp(model.OauthTypeGoogle) - if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" { - return errors.New("ConfigNotFound"), nil - } - return nil, &oauth2.Config{ - ClientID: g.ClientId, - ClientSecret: g.ClientSecret, - RedirectURL: g.RedirectUrl, - Endpoint: google.Endpoint, - Scopes: []string{"https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/userinfo.email"}, - } +} + +// Helper function to get GitHub OAuth2 configuration +func (os *OauthService) getGithubConfig() (error, *oauth2.Config) { + g := os.InfoByOp(model.OauthTypeGithub) + if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" { + return errors.New("ConfigNotFound"), nil + } + return nil, &oauth2.Config{ + ClientID: g.ClientId, + ClientSecret: g.ClientSecret, + RedirectURL: g.RedirectUrl, + Endpoint: github.Endpoint, + Scopes: []string{"read:user", "user:email"}, + } +} + +// Helper function to get Google OAuth2 configuration +func (os *OauthService) getGoogleConfig() (error, *oauth2.Config) { + g := os.InfoByOp(model.OauthTypeGoogle) + if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" { + return errors.New("ConfigNotFound"), nil + } + return nil, &oauth2.Config{ + ClientID: g.ClientId, + ClientSecret: g.ClientSecret, + RedirectURL: g.RedirectUrl, + Endpoint: google.Endpoint, + Scopes: []string{"https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/userinfo.email"}, + } +} + +// Helper function to get OIDC OAuth2 configuration +func (os *OauthService) getOidcConfig() (error, *oauth2.Config) { + g := os.InfoByOp(model.OauthTypeOidc) + if g.Id == 0 || g.ClientId == "" || g.ClientSecret == "" || g.RedirectUrl == "" || g.Issuer == "" { + return errors.New("ConfigNotFound"), nil + } + + // Set scopes + scopes := g.Scopes + if scopes == "" { + scopes = "openid,profile,email" + } + scopeList := strings.Split(scopes, ",") + + // Fetch OIDC configuration + if err := os.FetchOIDCConfig(g.Issuer); err != nil { + return err, nil + } + + return nil, &oauth2.Config{ + ClientID: g.ClientId, + ClientSecret: g.ClientSecret, + RedirectURL: g.RedirectUrl, + Endpoint: oauth2.Endpoint{ + AuthURL: os.OidcEndpoint.AuthURL, + TokenURL: os.OidcEndpoint.TokenURL, + }, + Scopes: scopeList, } - return errors.New("ConfigNotFound"), nil } func getHTTPClientWithProxy() *http.Client { @@ -269,6 +358,47 @@ func (os *OauthService) GoogleCallback(code string) (error error, userData *Goog return } +func (os *OauthService) OidcCallback(code string) (error error, userData *OidcUserdata) { + err, oauthConfig := os.GetOauthConfig(model.OauthTypeOidc) + if err != nil { + return err, nil + } + + // 使用代理配置创建 HTTP 客户端 + httpClient := getHTTPClientWithProxy() + ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient) + + token, err := oauthConfig.Exchange(ctx, code) + if err != nil { + global.Logger.Warn("oauthConfig.Exchange() failed: ", err) + error = errors.New("GetOauthTokenError") + return + } + + // 使用带有代理的 HTTP 客户端获取用户信息 + client := oauthConfig.Client(ctx, token) + resp, err := client.Get(os.OidcEndpoint.UserInfo) + if err != nil { + global.Logger.Warn("failed getting user info: ", err) + error = errors.New("GetOauthUserInfoError") + return + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + global.Logger.Warn("failed closing response body: ", err) + } + }(resp.Body) + + // 解析用户信息 + if err = json.NewDecoder(resp.Body).Decode(&userData); err != nil { + global.Logger.Warn("failed decoding user info: ", err) + error = errors.New("DecodeOauthUserInfoError") + return + } + return +} + func (os *OauthService) UserThirdInfo(op, openid string) *model.UserThird { ut := &model.UserThird{} global.DB.Where("open_id = ? and third_type = ?", openid, op).First(ut) @@ -282,6 +412,11 @@ func (os *OauthService) BindGithubUser(openid, username string, userId uint) err func (os *OauthService) BindGoogleUser(email, username string, userId uint) error { return os.BindOauthUser(model.OauthTypeGoogle, email, username, userId) } + +func (os *OauthService) BindOidcUser(openid, username string, userId uint) error { + return os.BindOauthUser(model.OauthTypeOidc, openid, username, userId) +} + func (os *OauthService) BindOauthUser(thirdType, openid, username string, userId uint) error { utr := &model.UserThird{ OpenId: openid, @@ -298,6 +433,9 @@ func (os *OauthService) UnBindGithubUser(userid uint) error { func (os *OauthService) UnBindGoogleUser(userid uint) error { return os.UnBindThird(model.OauthTypeGoogle, userid) } +func (os *OauthService) UnBindOidcUser(userid uint) error { + return os.UnBindThird(model.OauthTypeOidc, userid) +} func (os *OauthService) UnBindThird(thirdType string, userid uint) error { return global.DB.Where("user_id = ? and third_type = ?", userid, thirdType).Delete(&model.UserThird{}).Error } diff --git a/service/user.go b/service/user.go index d62a4eb..2c938af 100644 --- a/service/user.go +++ b/service/user.go @@ -196,6 +196,11 @@ func (us *UserService) InfoByGoogleEmail(email string) *model.User { return us.InfoByOauthId(model.OauthTypeGithub, email) } +// InfoByOidcSub 根据oidc取用户信息 +func (us *UserService) InfoByOidcSub(sub string) *model.User { + return us.InfoByOauthId(model.OauthTypeOidc, sub) +} + // InfoByOauthId 根据oauth取用户信息 func (us *UserService) InfoByOauthId(thirdType, uid string) *model.User { ut := AllService.OauthService.UserThirdInfo(thirdType, uid) @@ -219,6 +224,11 @@ func (us *UserService) RegisterByGoogle(name string, email string) *model.User { return us.RegisterByOauth(model.OauthTypeGoogle, name, email) } +// RegisterByOidc 注册, use prefferedUsername as username, sub as openid +func (us *UserService) RegisterByOidc(prefferedUsername string, sub string) *model.User { + return us.RegisterByOauth(model.OauthTypeOidc, prefferedUsername, sub) +} + // RegisterByOauth 注册 func (us *UserService) RegisterByOauth(thirdType, thirdName, uid string) *model.User { tx := global.DB.Begin() From 2cd7dfb2b3906be14a3e08ede99a3f9a4a9beed1 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 29 Oct 2024 14:27:15 +0800 Subject: [PATCH 4/8] fix bug --- .dockerignore | 28 ++++++++++++++++++++++++--- Dockerfile.dev | 46 +++++++++++++++++++++++++++++---------------- docker-compose.yaml | 7 +++++-- 3 files changed, 60 insertions(+), 21 deletions(-) diff --git a/.dockerignore b/.dockerignore index d3c29bd..5bd7cfb 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,26 @@ +# Ignore Docker Compose configuration files docker-compose.yaml -Dcokerfile -Dcokerfile.dev -data \ No newline at end of file + +# Ignore development Dockerfile +Dockerfile.dev + +# Ignore the data directory +data/ + +# Ignore version control system directories +.git/ + +# Ignore log and temporary files +*.log +*.tmp +*.swp + +# Ignore editor/IDE configuration files +.vscode/ +.idea/ + +# Ignore binaries and build cache +release/ +bin/ +*.exe +*.out \ No newline at end of file diff --git a/Dockerfile.dev b/Dockerfile.dev index 667aa55..04df912 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,36 +1,50 @@ # Use build arguments for Go version and architecture -ARG GO_VERSION=1.23.2 -ARG BUILDARCH +ARG GO_VERSION=1.22 +ARG BUILDARCH=amd64 -# Stage 1: Build the Go application with swag -FROM golang:${GO_VERSION} AS builder +# Stage 1: Builder Stage +# FROM golang:${GO_VERSION}-alpine AS builder +FROM crazymax/xgo:${GO_VERSION} AS builder # Set up working directory WORKDIR /app -# Install dependencies and copy the source code -COPY go.mod ./ -RUN go install github.com/swaggo/swag/cmd/swag@latest -RUN go mod download +# Step 1: Copy the source code COPY . . -#run the build script -RUN chmod +x build.sh && ./build.sh +# Step 2: Download dependencies +RUN go mod tidy && go mod download -# Stage 2: Prepare the final image + +# Step 3: Install swag and Run the build script +RUN go install github.com/swaggo/swag/cmd/swag@latest && \ + swag init -g cmd/apimain.go --output docs/api --instanceName api --exclude http/controller/admin && \ + swag init -g cmd/apimain.go --output docs/admin --instanceName admin --exclude http/controller/api + +# Build the Go application with CGO enabled and specified ldflags +RUN CGO_ENABLED=1 GOOS=linux go build -a \ + -ldflags "-s -w --extldflags '-static -fpic'" \ + -installsuffix cgo -o release/apimain cmd/apimain.go + + +# Stage 2: Final Image FROM alpine:latest # Set up working directory WORKDIR /app -# Install necessary dependencies +# Install necessary runtime dependencies RUN apk add --no-cache tzdata file -# Copy the built application from the builder stage +# Copy the built application and resources from the builder stage COPY --from=builder /app/release /app/ +COPY --from=builder /app/conf /app/conf/ +COPY --from=builder /app/resources /app/resources/ -# Ensure the binary is correctly built -RUN file /app/apimain +# Ensure the binary is correctly built and linked +RUN file /app/apimain && \ + mkdir -p /app/data && \ + mkdir -p /app/runtime # Set up a volume for persistent data VOLUME /app/data @@ -39,4 +53,4 @@ VOLUME /app/data EXPOSE 21114 # Define the command to run the application -CMD ["app/apimain"] \ No newline at end of file +CMD ["./apimain"] \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 9328fcd..4ecf1e0 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -3,17 +3,20 @@ services: build: context: . dockerfile: Dockerfile.dev + args: + - DOCS="" image: lejianwen/rustdesk-api container_name: rustdesk-api environment: - TZ=Asia/Shanghai - RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116 - RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117 - - RUSTDESK_API_RUSTDESK_API_SERVER=http://192.168.1.66:21114 + - RUSTDESK_API_RUSTDESK_API_SERVER=http://localhost:21114 - RUSTDESK_API_RUSTDESK_KEY=123456789 ports: - 21114:21114 volumes: - ./data/rustdesk/api:/app/data #将数据库挂载出来方便备份 + # - ./conf:/app/conf # config restart: unless-stopped - command: sleep infnite \ No newline at end of file + # command: sleep infnite \ No newline at end of file From ffa47177aa7178cf54ae915d28dcbb2f87e02c3a Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 29 Oct 2024 18:46:45 +0800 Subject: [PATCH 5/8] fix bug - oidc scopes --- service/oauth.go | 63 ++++++++++++++++++++++++------------------------ 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/service/oauth.go b/service/oauth.go index eb9609c..798cda7 100644 --- a/service/oauth.go +++ b/service/oauth.go @@ -29,7 +29,6 @@ type OidcEndpoint struct { } type OauthService struct { - OidcEndpoint *OidcEndpoint } type GithubUserdata struct { @@ -93,7 +92,6 @@ type OidcUserdata struct { Email string `json:"email"` VerifiedEmail bool `json:"email_verified"` Name string `json:"name"` - Picture string `json:"picture"` PrefferedUsername string `json:"preffered_username"` } @@ -157,29 +155,28 @@ func (os *OauthService) BeginAuth(op string) (error error, code, url string) { } // Method to fetch OIDC configuration dynamically -func (os *OauthService) FetchOIDCConfig(issuer string) error { - configURL := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration" +func FetchOidcConfig(issuer string) (error, OidcEndpoint) { + configURL := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration" - // Get the HTTP client (with or without proxy based on configuration) - client := getHTTPClientWithProxy() + // Get the HTTP client (with or without proxy based on configuration) + client := getHTTPClientWithProxy() - resp, err := client.Get(configURL) - if err != nil { - return errors.New("failed to fetch OIDC configuration") - } - defer resp.Body.Close() + resp, err := client.Get(configURL) + if err != nil { + return errors.New("failed to fetch OIDC configuration"), OidcEndpoint{} + } + defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return errors.New("OIDC configuration not found") - } + if resp.StatusCode != http.StatusOK { + return errors.New("OIDC configuration not found, status code: %d"), OidcEndpoint{} + } - var endpoint OidcEndpoint - if err := json.NewDecoder(resp.Body).Decode(&endpoint); err != nil { - return errors.New("failed to parse OIDC configuration") - } + var endpoint OidcEndpoint + if err := json.NewDecoder(resp.Body).Decode(&endpoint); err != nil { + return errors.New("failed to parse OIDC configuration"), OidcEndpoint{} + } - os.OidcEndpoint = &endpoint - return nil + return nil, endpoint } // GetOauthConfig retrieves the OAuth2 configuration based on the provider type @@ -234,24 +231,22 @@ func (os *OauthService) getOidcConfig() (error, *oauth2.Config) { } // Set scopes - scopes := g.Scopes + scopes := strings.TrimSpace(g.Scopes) if scopes == "" { scopes = "openid,profile,email" } scopeList := strings.Split(scopes, ",") - - // Fetch OIDC configuration - if err := os.FetchOIDCConfig(g.Issuer); err != nil { + err, endpoint := FetchOidcConfig(g.Issuer) + if err != nil { return err, nil } - return nil, &oauth2.Config{ ClientID: g.ClientId, ClientSecret: g.ClientSecret, RedirectURL: g.RedirectUrl, Endpoint: oauth2.Endpoint{ - AuthURL: os.OidcEndpoint.AuthURL, - TokenURL: os.OidcEndpoint.TokenURL, + AuthURL: endpoint.AuthURL, + TokenURL: endpoint.TokenURL, }, Scopes: scopeList, } @@ -363,7 +358,6 @@ func (os *OauthService) OidcCallback(code string) (error error, userData *OidcUs if err != nil { return err, nil } - // 使用代理配置创建 HTTP 客户端 httpClient := getHTTPClientWithProxy() ctx := context.WithValue(context.Background(), oauth2.HTTPClient, httpClient) @@ -377,7 +371,14 @@ func (os *OauthService) OidcCallback(code string) (error error, userData *OidcUs // 使用带有代理的 HTTP 客户端获取用户信息 client := oauthConfig.Client(ctx, token) - resp, err := client.Get(os.OidcEndpoint.UserInfo) + g := os.InfoByOp(model.OauthTypeOidc) + err, endpoint := FetchOidcConfig(g.Issuer) + if err != nil { + global.Logger.Warn("failed fetching OIDC configuration: ", err) + error = errors.New("FetchOidcConfigError") + return + } + resp, err := client.Get(endpoint.UserInfo) if err != nil { global.Logger.Warn("failed getting user info: ", err) error = errors.New("GetOauthUserInfoError") @@ -413,8 +414,8 @@ func (os *OauthService) BindGoogleUser(email, username string, userId uint) erro return os.BindOauthUser(model.OauthTypeGoogle, email, username, userId) } -func (os *OauthService) BindOidcUser(openid, username string, userId uint) error { - return os.BindOauthUser(model.OauthTypeOidc, openid, username, userId) +func (os *OauthService) BindOidcUser(sub, username string, userId uint) error { + return os.BindOauthUser(model.OauthTypeOidc, sub, username, userId) } func (os *OauthService) BindOauthUser(thirdType, openid, username string, userId uint) error { From 337ef330ebd265b722b021f79052cf59c83aa479 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 29 Oct 2024 18:48:37 +0800 Subject: [PATCH 6/8] fix bug --- Dockerfile.dev | 16 ++++++++++++++++ docker-compose.yaml | 12 +++++------- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index 04df912..d0e517b 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -26,6 +26,19 @@ RUN CGO_ENABLED=1 GOOS=linux go build -a \ -ldflags "-s -w --extldflags '-static -fpic'" \ -installsuffix cgo -o release/apimain cmd/apimain.go +# Stage 2: Frontend Build Stage (builder2) +FROM node:18-alpine AS builder2 + +# Set working directory +WORKDIR /frontend + +RUN apk update && apk add git --no-cache + +# Clone the frontend repository +RUN git clone https://github.com/lejianwen/rustdesk-api-web . + +# Install npm dependencies and build the frontend +RUN npm install && npm run build # Stage 2: Final Image FROM alpine:latest @@ -40,6 +53,9 @@ RUN apk add --no-cache tzdata file COPY --from=builder /app/release /app/ COPY --from=builder /app/conf /app/conf/ COPY --from=builder /app/resources /app/resources/ +COPY --from=builder /app/docs /app/docs/ +# Copy frontend build from builder2 stage +COPY --from=builder2 /frontend/dist/ /app/resources/admin/ # Ensure the binary is correctly built and linked RUN file /app/apimain && \ diff --git a/docker-compose.yaml b/docker-compose.yaml index 4ecf1e0..d015bc1 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -3,20 +3,18 @@ services: build: context: . dockerfile: Dockerfile.dev - args: - - DOCS="" - image: lejianwen/rustdesk-api + # image: lejianwen/rustdesk-api container_name: rustdesk-api environment: - TZ=Asia/Shanghai - RUSTDESK_API_RUSTDESK_ID_SERVER=192.168.1.66:21116 - RUSTDESK_API_RUSTDESK_RELAY_SERVER=192.168.1.66:21117 - - RUSTDESK_API_RUSTDESK_API_SERVER=http://localhost:21114 + - RUSTDESK_API_RUSTDESK_API_SERVER=http://127.0.0.1:21114 - RUSTDESK_API_RUSTDESK_KEY=123456789 ports: - 21114:21114 volumes: - ./data/rustdesk/api:/app/data #将数据库挂载出来方便备份 - # - ./conf:/app/conf # config - restart: unless-stopped - # command: sleep infnite \ No newline at end of file + - ./conf:/app/conf # config + # - ./resources:/app/resources # 静态资源 + restart: unless-stopped \ No newline at end of file From fe910c37cf00bc6f549f7c87460e626e4f85a4c8 Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 29 Oct 2024 23:00:17 +0800 Subject: [PATCH 7/8] fix: spelling --- http/controller/api/ouath.go | 6 +++--- service/oauth.go | 2 +- service/user.go | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/http/controller/api/ouath.go b/http/controller/api/ouath.go index 7c95e36..674d218 100644 --- a/http/controller/api/ouath.go +++ b/http/controller/api/ouath.go @@ -277,7 +277,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) { return } //绑定, user preffered_username as username - err = service.AllService.OauthService.BindOidcUser(userData.Sub, userData.PrefferedUsername, v.UserId) + err = service.AllService.OauthService.BindOidcUser(userData.Sub, userData.PreferredUsername, v.UserId) if err != nil { c.String(http.StatusInternalServerError, response.TranslateMsg(c, "BindFail")) return @@ -295,7 +295,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) { if !*oa.AutoRegister { //c.String(http.StatusInternalServerError, "还未绑定用户,请先绑定") - v.ThirdName = userData.PrefferedUsername + v.ThirdName = userData.PreferredUsername v.ThirdOpenId = userData.Sub url := global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/bind/" + cacheKey c.Redirect(http.StatusFound, url) @@ -303,7 +303,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) { } //自动注册 - u = service.AllService.UserService.RegisterByOidc(userData.PrefferedUsername, userData.Sub) + u = service.AllService.UserService.RegisterByOidc(userData.PreferredUsername, userData.Sub) if u.Id == 0 { c.String(http.StatusInternalServerError, response.TranslateMsg(c, "OauthRegisterFailed")) return diff --git a/service/oauth.go b/service/oauth.go index 798cda7..ea97608 100644 --- a/service/oauth.go +++ b/service/oauth.go @@ -92,7 +92,7 @@ type OidcUserdata struct { Email string `json:"email"` VerifiedEmail bool `json:"email_verified"` Name string `json:"name"` - PrefferedUsername string `json:"preffered_username"` + PreferredUsername string `json:"preferred_username"` } type OauthCacheItem struct { diff --git a/service/user.go b/service/user.go index 2c938af..0eb8b56 100644 --- a/service/user.go +++ b/service/user.go @@ -224,9 +224,9 @@ func (us *UserService) RegisterByGoogle(name string, email string) *model.User { return us.RegisterByOauth(model.OauthTypeGoogle, name, email) } -// RegisterByOidc 注册, use prefferedUsername as username, sub as openid -func (us *UserService) RegisterByOidc(prefferedUsername string, sub string) *model.User { - return us.RegisterByOauth(model.OauthTypeOidc, prefferedUsername, sub) +// RegisterByOidc 注册, use PreferredUsername as username, sub as openid +func (us *UserService) RegisterByOidc(PreferredUsername string, sub string) *model.User { + return us.RegisterByOauth(model.OauthTypeOidc, PreferredUsername, sub) } // RegisterByOauth 注册 From b52c5cfca1663f96111fa8485255c42a637c029f Mon Sep 17 00:00:00 2001 From: Tao Chen Date: Tue, 29 Oct 2024 23:09:54 +0800 Subject: [PATCH 8/8] bind oidc ThirdEmail --- http/controller/api/ouath.go | 1 + 1 file changed, 1 insertion(+) diff --git a/http/controller/api/ouath.go b/http/controller/api/ouath.go index 674d218..47fa129 100644 --- a/http/controller/api/ouath.go +++ b/http/controller/api/ouath.go @@ -297,6 +297,7 @@ func (o *Oauth) OauthCallback(c *gin.Context) { v.ThirdName = userData.PreferredUsername v.ThirdOpenId = userData.Sub + v.ThirdEmail = userData.Email url := global.Config.Rustdesk.ApiServer + "/_admin/#/oauth/bind/" + cacheKey c.Redirect(http.StatusFound, url) return