diff --git a/.gitignore b/.gitignore index 4c80600..819a868 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,5 @@ dist/casaos-user-service-amd64_linux_amd64_v1/build/sysroot/usr/bin/casaos-user- dist/casaos-user-service-amd64_linux_amd64_v1/build/sysroot/usr/bin/casaos-user-service linux-amd64-nextzenos-user-service-v1.2.3.tar.gz dist/casaos-user-service-amd64_linux_amd64_v1/build/sysroot/usr/bin/casaos-user-service +linux-amd64-nextzenos-user-service-v1.2.4.tar.gz +dist/casaos-user-service-amd64_linux_amd64_v1/build/sysroot/usr/bin/casaos-user-service diff --git a/build/sysroot/etc/casaos/user-service.conf.sample b/build/sysroot/etc/casaos/user-service.conf.sample index fe1828b..0b788a3 100644 --- a/build/sysroot/etc/casaos/user-service.conf.sample +++ b/build/sysroot/etc/casaos/user-service.conf.sample @@ -9,4 +9,4 @@ DBPath = /var/lib/casaos/db UserDataPath = /var/lib/casaos OMVServer = http://10.0.0.4:1081/rpc.php SecretKey = N1PCdw3M2B1TfJhoaY2mL736p2vCUc47 -AuthentikServer = https://auth.c14soft.com \ No newline at end of file +AuthentikServer = http://10.0.0.26:9000 \ No newline at end of file diff --git a/dist/casaos-user-service-amd64_linux_amd64_v1/build/sysroot/usr/bin/casaos-user-service b/dist/casaos-user-service-amd64_linux_amd64_v1/build/sysroot/usr/bin/casaos-user-service index 9ef3797..420de30 100755 Binary files a/dist/casaos-user-service-amd64_linux_amd64_v1/build/sysroot/usr/bin/casaos-user-service and b/dist/casaos-user-service-amd64_linux_amd64_v1/build/sysroot/usr/bin/casaos-user-service differ diff --git a/dist/metadata.json b/dist/metadata.json index 15a156f..e39ceed 100644 --- a/dist/metadata.json +++ b/dist/metadata.json @@ -1 +1 @@ -{"project_name":"casaos-user-service","tag":"v1.0.0","previous_tag":"","version":"1.0.1","commit":"74786ca6c859a55918f01be0bbb824dcf14b3018","date":"2024-08-14T11:40:02.309227418+07:00","runtime":{"goos":"linux","goarch":"amd64"}} \ No newline at end of file +{"project_name":"casaos-user-service","tag":"v1.0.0","previous_tag":"","version":"1.0.1","commit":"bcaa226c29d41cc7812f45c932f0521d069d5d63","date":"2024-08-14T18:19:00.096002906+07:00","runtime":{"goos":"linux","goarch":"amd64"}} \ No newline at end of file diff --git a/route/v1.go b/route/v1.go index 59b80a1..c6143d5 100644 --- a/route/v1.go +++ b/route/v1.go @@ -37,6 +37,8 @@ func InitRouter() *gin.Engine { r.POST("/v1/users/oidc/login", v1.OIDCLogin) r.GET("/v1/users/oidc/callback", v1.OIDCCallback) r.GET("/v1/users/oidc/profile", v1.OIDCProfile) + r.GET("/v1/users/oidc/userinfo", v1.OIDCUserInfo) + r.POST("/v1/users/oidc/validateToken", v1.OIDCValidateToken) v1Group := r.Group("/v1") v1Group.Use(jwt.JWT( diff --git a/route/v1/user.go b/route/v1/user.go index b39db1c..dbf1c16 100644 --- a/route/v1/user.go +++ b/route/v1/user.go @@ -43,11 +43,12 @@ import ( ) var ( - baseURL = "https://auth.c14soft.com" + baseURL = "http://10.0.0.26:9000" clientID = "6KwKSxLCtaQ4r6HoAn3gdNMbNOAf75j3SejLIAx7" clientSecret = "PE05fcDP4qESUmyZ1TNYpZNBxRPq70VpFI81vehsoJ6WhGz5yPXMljrFrOdMRdRhrYmF03fHWTZHgO9ZdNENrLN13BzL8CAgtEkTsyjXfgx9GvISheIjYfpSfvo219fL" - authURL = "https://auth.c14soft.com/application/o/nextzenos-oidc/" - callbackURL = "http://172.26.157.79:8080/v1/users/oidc/callback" + authURL = "http://10.0.0.26:9000/application/o/nextzenos-oidc/" + // callbackURL = "http://nextzenos.local/v1/users/oidc/callback" + callbackURL = "http://172.26.157.79:8080/v1/users/oidc/callback" ) // @Summary register user @@ -184,7 +185,6 @@ func randString(nByte int) (string, error) { } var oauth2Config oauth2.Config -var providerOIDC *oidc.Provider // Use an init function to initialize the oauth2Config variable. func OIDC() { @@ -193,13 +193,13 @@ func OIDC() { if err != nil { log.Fatalf("Error creating OIDC provider: %v", err) // This will print the error and stop execution } - providerOIDC = provider oauth2Config = oauth2.Config{ ClientID: clientID, ClientSecret: clientSecret, RedirectURL: callbackURL, Endpoint: provider.Endpoint(), - Scopes: []string{oidc.ScopeOpenID, "profile", "email", "goauthentik.io/api", "offline_access"}, + Scopes: []string{oidc.ScopeOpenID, "profile", "email", "goauthentik.io/api"}, + //add offline access for refresh token } } func OIDCLogin(c *gin.Context) { @@ -240,13 +240,47 @@ func OIDCCallback(c *gin.Context) { } expiryDuration := time.Until(oauth2Token.Expiry) c.SetCookie("accessToken", oauth2Token.AccessToken, int(expiryDuration.Seconds()), "/", "", false, true) - c.SetCookie("refreshToken", oauth2Token.RefreshToken, int(expiryDuration.Seconds()), "/", "", false, true) + // c.SetCookie("refreshToken", oauth2Token.RefreshToken, int(expiryDuration.Seconds()), "/", "", false, true) c.Redirect(http.StatusFound, state.Value) } +func OIDCUserInfo(c *gin.Context) { + json := make(map[string]string) + c.ShouldBind(&json) + accessToken, err := c.Cookie("accessToken") + if err != nil { + c.Redirect(http.StatusFound, "/#/oidc") + } + authentikUser, err := service.MyService.Authentik().GetUserInfo(accessToken, baseURL) + if err != nil { + c.JSON(http.StatusInternalServerError, model.Result{Success: common_err.ERROR_AUTH_TOKEN, Message: common_err.GetMsg(common_err.ERROR_AUTH_TOKEN)}) + return + } + c.JSON(common_err.SUCCESS, + model.Result{ + Success: common_err.SUCCESS, + Message: common_err.GetMsg(common_err.SUCCESS), + Data: authentikUser, + }) +} +func OIDCValidateToken(c *gin.Context) { + json := make(map[string]string) + c.ShouldBind(&json) + accessToken := json["authentikToken"] + var validateToken model2.AuthentikToken + validateToken, err := service.MyService.Authentik().ValidateToken(clientID, clientSecret, accessToken, baseURL) + if err != nil { + c.JSON(http.StatusUnauthorized, model.Result{Success: common_err.ERROR_AUTH_TOKEN, Message: common_err.GetMsg(common_err.ERROR_AUTH_TOKEN)}) + return + } + if !validateToken.Active { + c.JSON(http.StatusUnauthorized, model.Result{Success: common_err.ERROR_AUTH_TOKEN, Message: common_err.GetMsg(common_err.ERROR_AUTH_TOKEN)}) + return + } + c.JSON(http.StatusOK, model.Result{Success: common_err.ERROR_AUTH_TOKEN, Message: common_err.GetMsg(common_err.ERROR_AUTH_TOKEN)}) +} func OIDCProfile(c *gin.Context) { json := make(map[string]string) c.ShouldBind(&json) - w := c.Writer accessToken, err := c.Cookie("accessToken") if err != nil { c.Redirect(http.StatusFound, "/#/oidc") @@ -255,7 +289,7 @@ func OIDCProfile(c *gin.Context) { // Get Authentik user info authentikUser, err := service.MyService.Authentik().GetUserInfo(accessToken, baseURL) if err != nil { - http.Error(w, "Failed to get Authentik user info: "+err.Error(), http.StatusInternalServerError) + c.JSON(http.StatusInternalServerError, model.Result{Success: common_err.ERROR_AUTH_TOKEN, Message: common_err.GetMsg(common_err.ERROR_AUTH_TOKEN)}) return } @@ -292,6 +326,7 @@ func OIDCProfile(c *gin.Context) { data := make(map[string]interface{}, 2) data["token"] = token data["user"] = user + data["authToken"] = accessToken c.JSON(common_err.SUCCESS, model.Result{ Success: common_err.SUCCESS, diff --git a/service/authentik.go b/service/authentik.go index d20d7b3..fc7db88 100644 --- a/service/authentik.go +++ b/service/authentik.go @@ -1,10 +1,13 @@ package service import ( + "encoding/base64" "encoding/json" "fmt" "log" "net/http" + "net/url" + "strings" model2 "github.com/IceWhaleTech/CasaOS-UserService/service/model" "gorm.io/gorm" @@ -16,6 +19,7 @@ type AuthentikService interface { CreateCredential(m model2.AuthentikCredentialsDBModel) model2.AuthentikCredentialsDBModel UpdateCredential(m model2.AuthentikCredentialsDBModel) model2.AuthentikCredentialsDBModel GetCredential(id int) model2.AuthentikCredentialsDBModel + ValidateToken(clientId string, clientSecret string, accessToken string, baseURL string) (model2.AuthentikToken, error) } type authentikService struct { @@ -39,6 +43,37 @@ func (a *authentikService) GetCredential(id int) model2.AuthentikCredentialsDBMo a.db.Limit(1).Where("id = ?", id).First(&m) return m } +func (a *authentikService) ValidateToken(clientId string, clientSecret string, accessToken string, baseURL string) (model2.AuthentikToken, error) { + auth := clientId + ":" + clientSecret + basicAuth := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) + path := baseURL + "/application/o/introspect/" + formData := url.Values{} + formData.Set("token", accessToken) + responseBody := strings.NewReader(formData.Encode()) + req, err := http.NewRequest("POST", path, responseBody) + if err != nil { + return model2.AuthentikToken{}, fmt.Errorf("error creating request: %v", err) + } + req.Header.Set("Authorization", basicAuth) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Accept", "application/json") + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return model2.AuthentikToken{}, fmt.Errorf("error making request: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + log.Println("HTTP error:", resp.Status) + return model2.AuthentikToken{}, fmt.Errorf("HTTP error: %s", resp.Status) + } + var token model2.AuthentikToken + if err := json.NewDecoder(resp.Body).Decode(&token); err != nil { + log.Println("Error decoding response:", err) + return model2.AuthentikToken{}, err + } + return token, nil +} func (a *authentikService) GetUserApp(accessToken string, baseURL string) (model2.AuthentikApplication, error) { bearer := "Bearer " + accessToken path := baseURL + APICorePrefix + "/applications/" diff --git a/service/model/o_authentik_token.go b/service/model/o_authentik_token.go new file mode 100644 index 0000000..b3b5d08 --- /dev/null +++ b/service/model/o_authentik_token.go @@ -0,0 +1,21 @@ +package model + +type AuthentikToken struct { + Acr string `json:"acr"` + Active bool `json:"active"` + Aud string `json:"aud"` + AuthTime int64 `json:"auth_time"` + ClientID string `json:"client_id"` + Email string `json:"email"` + EmailVerified bool `json:"email_verified"` + Exp int64 `json:"exp"` + GivenName string `json:"given_name"` + Groups []string `json:"groups"` + Iat int64 `json:"iat"` + Iss string `json:"iss"` + Name string `json:"name"` + Nickname string `json:"nickname"` + PreferredUsername string `json:"preferred_username"` + Scope string `json:"scope"` + Sub string `json:"sub"` +}