diff --git a/build/sysroot/etc/casaos/user-service.conf.sample b/build/sysroot/etc/casaos/user-service.conf.sample index 1d2d1ba..165f725 100644 --- a/build/sysroot/etc/casaos/user-service.conf.sample +++ b/build/sysroot/etc/casaos/user-service.conf.sample @@ -7,3 +7,5 @@ LogSaveName = user-service LogFileExt = log DBPath = /var/lib/casaos/db UserDataPath = /var/lib/casaos +OMVServer = http://10.0.0.4:1081/rpc.php +SecretKey = N1PCdw3M2B1TfJhoaY2mL736p2vCUc47 \ 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 43c4c9c..1867dce 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 159999f..4a0837b 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":"f0d6780c126d2fd12d8c20ca59acd6759f29ce7a","date":"2024-07-03T17:38:16.266556363+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":"dd3a9ebea4fd121d6a2383881265ed85147dbeca","date":"2024-07-04T10:29:51.218513943+07:00","runtime":{"goos":"linux","goarch":"amd64"}} \ No newline at end of file diff --git a/model/sys_common.go b/model/sys_common.go index 9d56b36..7a961cb 100644 --- a/model/sys_common.go +++ b/model/sys_common.go @@ -10,6 +10,8 @@ type APPModel struct { LogFileExt string UserDataPath string DBPath string + OMVServer string + SecretKey string } type Result struct { diff --git a/pkg/config/init.go b/pkg/config/init.go index ac9b341..96c0e34 100644 --- a/pkg/config/init.go +++ b/pkg/config/init.go @@ -23,6 +23,8 @@ var ( LogPath: constants.DefaultLogPath, LogSaveName: "user", LogFileExt: "log", + OMVServer: constants.DefaultOMVServer, + SecretKey: constants.DefaultSecretKey, } Cfg *ini.File diff --git a/pkg/utils/encryption/md5_helper.go b/pkg/utils/encryption/md5_helper.go index 1a142e6..d72aa86 100644 --- a/pkg/utils/encryption/md5_helper.go +++ b/pkg/utils/encryption/md5_helper.go @@ -10,8 +10,20 @@ package encryption import ( + "crypto/aes" + "crypto/cipher" "crypto/md5" + "crypto/rand" "encoding/hex" + + "github.com/IceWhaleTech/CasaOS-UserService/pkg/config" +) + +var ( + // We're using a 32 byte long secret key. + // This is probably something you generate first + // then put into and environment variable. + secretKey string = config.AppInfo.SecretKey ) func GetMD5ByStr(str string) string { @@ -19,3 +31,54 @@ func GetMD5ByStr(str string) string { h.Write([]byte(str)) return hex.EncodeToString(h.Sum(nil)) } + +func Encrypt(plaintext string) string { + aes, err := aes.NewCipher([]byte(secretKey)) + if err != nil { + panic(err) + } + + gcm, err := cipher.NewGCM(aes) + if err != nil { + panic(err) + } + + // We need a 12-byte nonce for GCM (modifiable if you use cipher.NewGCMWithNonceSize()) + // A nonce should always be randomly generated for every encryption. + nonce := make([]byte, gcm.NonceSize()) + _, err = rand.Read(nonce) + if err != nil { + panic(err) + } + + // ciphertext here is actually nonce+ciphertext + // So that when we decrypt, just knowing the nonce size + // is enough to separate it from the ciphertext. + ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil) + + return string(ciphertext) +} + +func Decrypt(ciphertext string) string { + aes, err := aes.NewCipher([]byte(secretKey)) + if err != nil { + panic(err) + } + + gcm, err := cipher.NewGCM(aes) + if err != nil { + panic(err) + } + + // Since we know the ciphertext is actually nonce+ciphertext + // And len(nonce) == NonceSize(). We can separate the two. + nonceSize := gcm.NonceSize() + nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] + + plaintext, err := gcm.Open(nil, []byte(nonce), []byte(ciphertext), nil) + if err != nil { + panic(err) + } + + return string(plaintext) +} diff --git a/route/v1/user.go b/route/v1/user.go index 8fbbe8d..04bf5db 100644 --- a/route/v1/user.go +++ b/route/v1/user.go @@ -1,17 +1,14 @@ package v1 import ( - "bytes" "context" "crypto/ecdsa" "encoding/base64" "encoding/json" json2 "encoding/json" - "fmt" "image" "image/png" "io" - "io/ioutil" "log" "net/http" url2 "net/url" @@ -148,9 +145,7 @@ func PostUserLogin(c *gin.Context) { json := make(map[string]string) c.ShouldBind(&json) - username := json["username"] - password := json["password"] // check params is empty if len(username) == 0 || len(password) == 0 { @@ -206,6 +201,14 @@ func PostUserLogin(c *gin.Context) { Data: data, }) } + +// @Summary login user to openmediavault +// @Produce application/json +// @Tags user +// @Param username password +// @Security SessionID +// @Success 200 {string} string "ok" +// @Router /users/omvLogin [post] func PostOMVLogin(c *gin.Context) { if !limiter.Allow() { c.JSON(common_err.TOO_MANY_REQUEST, @@ -222,14 +225,13 @@ func PostOMVLogin(c *gin.Context) { username := json["username"] password := json["password"] - res := LoginSession(username, password) + res := service.MyService.OMV().LoginSession(username, password) var resData OMVLogin err := json2.Unmarshal([]byte(res), &resData) if err != nil { - // Handle the error, for example, log it or return it log.Printf("Error getting user: %v", err) - return // or handle it in a way that fits your application's error handling strategy + return } if !resData.Response.Authenticated { @@ -238,8 +240,7 @@ func PostOMVLogin(c *gin.Context) { return } - getUser, err := GetOMVUser(username, resData.Response.SessionID) - + getUser, err := service.MyService.OMV().GetUser(username, resData.Response.SessionID) if err != nil { // Handle the error, for example, log it or return it log.Printf("Error getting user: %v", err) @@ -262,14 +263,11 @@ func PostOMVLogin(c *gin.Context) { Message: common_err.GetMsg(common_err.USER_NOT_EXIST_OR_PWD_INVALID)}) return } - c.SetCookie( - "sessionID", - resData.Response.SessionID, - 3600, - "/", - "", - false, - true) + // cookie_value, err := c.Cookie("sessionID") + // decrypt := encryption.Decrypt(cookie_value) + // fmt.Printf(decrypt) + sessionId := encryption.Encrypt(resData.Response.SessionID) + c.SetCookie("sessionID", sessionId, 3600, "/", "", false, true) c.JSON(common_err.SUCCESS, model.Result{ Success: common_err.SUCCESS, @@ -287,67 +285,6 @@ func isEmpty(obj interface{}) bool { return false } -func GetOMVUser(username string, sessionID string) (string, error) { - // Prepare the RPC request - postBody, _ := json.Marshal(map[string]interface{}{ - "service": "UserMgmt", - "method": "getUser", - "params": map[string]string{ - "name": username, - }, - }) - responseBody := bytes.NewBuffer(postBody) - - // Create HTTP request and set session ID header - req, err := http.NewRequest("POST", "http://10.0.0.4:1081/rpc.php", responseBody) - if err != nil { - return "", fmt.Errorf("error creating request: %v", err) - } - req.Header.Set("X-OPENMEDIAVAULT-SESSIONID", sessionID) // Set session ID header - - // Send the request - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return "", fmt.Errorf("error making request: %v", err) - } - defer resp.Body.Close() // Ensure the response body is closed - - // Check for HTTP errors - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("HTTP error: %s", resp.Status) - } - - // Read the response body - responseData, err := ioutil.ReadAll(resp.Body) - if err != nil { - return "", fmt.Errorf("error reading response body: %v", err) - } - - return string(responseData), nil -} -func LoginSession(username string, password string) string { - postBody, _ := json.Marshal(map[string]interface{}{ - "service": "session", - "method": "login", - "params": map[string]string{ - "username": username, - "password": password, - }, - }) - responseBody := bytes.NewBuffer(postBody) - response, err := http.Post("http://10.0.0.4:1081/rpc.php", "application/json", responseBody) - if err != nil { - fmt.Print(err.Error()) - os.Exit(1) - } - responseData, err := ioutil.ReadAll(response.Body) - if err != nil { - log.Fatal(err) - } - return string(responseData) -} - // @Summary edit user head // @Produce application/json // @Accept multipart/form-data diff --git a/service/omv.go b/service/omv.go index 0546a23..fcff054 100644 --- a/service/omv.go +++ b/service/omv.go @@ -4,30 +4,26 @@ import ( "bytes" "encoding/json" "fmt" - "io/ioutil" + "io" "log" "net/http" "os" + "github.com/IceWhaleTech/CasaOS-UserService/pkg/config" "github.com/IceWhaleTech/CasaOS-UserService/service/model" ) type OMVService interface { LoginSession(userName string, password string) string - Logout() - GetUser(username string) string + Logout(sessionID string) (string, error) + GetUser(username string, sessionID string) (string, error) SetUser(m model.UserDBModel) model.UserDBModel ApplyChange() } type omvService struct { } -// LoginSession implements OMVService. -func (o *omvService) LoginSession(userName string, password string) string { - panic("unimplemented") -} - -func LoginSession(username string, password string) string { +func (o *omvService) LoginSession(username string, password string) string { postBody, _ := json.Marshal(map[string]interface{}{ "service": "session", "method": "login", @@ -37,22 +33,51 @@ func LoginSession(username string, password string) string { }, }) responseBody := bytes.NewBuffer(postBody) - response, err := http.Post("http://10.0.0.4:1081/rpc.php", "application/json", responseBody) + response, err := http.Post(config.AppInfo.OMVServer, "application/json", responseBody) if err != nil { fmt.Print(err.Error()) os.Exit(1) } - responseData, err := ioutil.ReadAll(response.Body) + responseData, err := io.ReadAll(response.Body) if err != nil { log.Fatal(err) } return string(responseData) } -func (o *omvService) Logout() { - // Implement logout logic here +func (o *omvService) Logout(sessionID string) (string, error) { + postBody, _ := json.Marshal(map[string]interface{}{ + "service": "UserMgmt", + "method": "logout", + "params": nil, + }) + responseBody := bytes.NewBuffer(postBody) + req, err := http.NewRequest("POST", config.AppInfo.OMVServer, responseBody) + if err != nil { + return "", fmt.Errorf("error creating request: %v", err) + } + req.Header.Set("X-OPENMEDIAVAULT-SESSIONID", sessionID) // Set session ID header + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return "", fmt.Errorf("error making request: %v", err) + } + defer resp.Body.Close() // Ensure the response body is closed + // Check for HTTP errors + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("HTTP error: %s", resp.Status) + } + + // Read the response body + responseData, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("error reading response body: %v", err) + } + + return string(responseData), nil } -func (o *omvService) GetUser(username string) string { +func (o *omvService) GetUser(username string, sessionID string) (string, error) { + // Prepare the RPC request postBody, _ := json.Marshal(map[string]interface{}{ "service": "UserMgmt", "method": "getUser", @@ -61,16 +86,34 @@ func (o *omvService) GetUser(username string) string { }, }) responseBody := bytes.NewBuffer(postBody) - response, err := http.Post("http://10.0.0.4:1081/rpc.php", "application/json", responseBody) + + // Create HTTP request and set session ID header + req, err := http.NewRequest("POST", config.AppInfo.OMVServer, responseBody) if err != nil { - fmt.Print(err.Error()) - os.Exit(1) + return "", fmt.Errorf("error creating request: %v", err) } - responseData, err := ioutil.ReadAll(response.Body) + req.Header.Set("X-OPENMEDIAVAULT-SESSIONID", sessionID) // Set session ID header + + // Send the request + client := &http.Client{} + resp, err := client.Do(req) if err != nil { - log.Fatal(err) + return "", fmt.Errorf("error making request: %v", err) } - return string(responseData) + defer resp.Body.Close() // Ensure the response body is closed + + // Check for HTTP errors + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("HTTP error: %s", resp.Status) + } + + // Read the response body + responseData, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("error reading response body: %v", err) + } + + return string(responseData), nil } func (o *omvService) SetUser(m model.UserDBModel) model.UserDBModel { diff --git a/service/service.go b/service/service.go index 1ad43f6..302b589 100644 --- a/service/service.go +++ b/service/service.go @@ -39,9 +39,6 @@ type store struct { omv OMVService } -func (c *store) OMV() OMVService { - return c.omv -} func (c *store) Event() EventService { return c.event } @@ -52,6 +49,9 @@ func (c *store) Gateway() external.ManagementService { func (c *store) User() UserService { return c.user } +func (c *store) OMV() OMVService { + return c.omv +} func (c *store) MessageBus() *message_bus.ClientWithResponses { client, _ := message_bus.NewClientWithResponses("", func(c *message_bus.Client) error { // error will never be returned, as we always want to return a client, even with wrong address,