From 3e7442395c56d7086aafb8762bdcef5c853337d1 Mon Sep 17 00:00:00 2001 From: Josh Ashton Date: Mon, 5 Aug 2024 17:34:57 -0600 Subject: [PATCH] github packages -> in the meantime, need to keep all files in same package. --- .../container/authentication.go | 230 ------------------ backend/src/security-layer/container/go.mod | 5 +- .../src/security-layer/container/router.go | 93 ++----- .../src/security-layer/container/serveAPI.go | 42 ---- .../src/security-layer/container/servePage.go | 31 --- backend/src/security-layer/container/services | 1 + 6 files changed, 26 insertions(+), 376 deletions(-) delete mode 100644 backend/src/security-layer/container/authentication.go delete mode 100644 backend/src/security-layer/container/serveAPI.go delete mode 100644 backend/src/security-layer/container/servePage.go create mode 160000 backend/src/security-layer/container/services diff --git a/backend/src/security-layer/container/authentication.go b/backend/src/security-layer/container/authentication.go deleted file mode 100644 index 05da4e7..0000000 --- a/backend/src/security-layer/container/authentication.go +++ /dev/null @@ -1,230 +0,0 @@ -package main - -import ( - //"context" - "crypto/aes" - "crypto/cipher" - //"sync" - - "crypto/rand" - "crypto/sha1" - "encoding/base64" - "encoding/hex" - //"encoding/json" - "errors" - "fmt" - - "io" - "log" - "net/http" - - //"os" - //"os/signal" - "strconv" - "strings" - "time" - - "github.com/golang-jwt/jwt/v5" - "github.com/gorilla/mux" -) - -// TODO: Dynamically get key? Need to research best way to store private keys between two devices. -var key = []byte("passphrasewhichneedstobe32bytes!") - -func createToken(username string) (string, error) { - token := jwt.NewWithClaims(jwt.SigningMethodHS256, - jwt.MapClaims{ - "username": username, - "exp": time.Now().Add(time.Hour * 24).Unix(), - }) - - jwtToken, err := token.SignedString(key) - if err != nil { - return "Error creating JWT.", err - } - return jwtToken, nil -} - -func verifyToken(tokenString string) error { - token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { - return key, nil - }) - - if err != nil { - return err - } - - if !token.Valid { - return fmt.Errorf("invalid token") - } - - return nil -} - -func hash(text string) string { - hasher := sha1.New() - hasher.Write([]byte(text)) - return base64.URLEncoding.EncodeToString(hasher.Sum(nil)) -} - -func encrypt() { - var newCommunication Communication - - text := []byte(newCommunication.Communication) - - c, err := aes.NewCipher(key) - if err != nil { - return - //gc.IndentedJSON(http.StatusBadRequest, gin.H{"message": err}) - } - - gcm, err := cipher.NewGCM(c) - if err != nil { - return - //gc.IndentedJSON(http.StatusBadRequest, gin.H{"message": err}) - } - - nonce := make([]byte, gcm.NonceSize()) - if _, err = io.ReadFull(rand.Reader, nonce); err != nil { - return - //gc.IndentedJSON(http.StatusBadRequest, gin.H{"message": err}) - } - - var b []byte = gcm.Seal(nonce, nonce, text, nil) - hex, err := convertBytesToHex(b) - if err != nil { - fmt.Println(err) - } - var test string = "{data: " + hex + ", hash: " + hash(string(text)) + "}" - log.Println("encrypted string:" + test) - //gc.IndentedJSON(http.StatusCreated, gin.H{"message": test}) -} - -func convertBytesToHex(b []byte) (string, error) { - // Handle nil pointer case - if b == nil { - return "", errors.New("nil pointer provided for hex string") - } - - // Split the hex string by spaces - var h string = hex.EncodeToString(b) - var builder strings.Builder - for i := 0; i < len(h); i += 2 { - end := i + 2 - if end > len(h) { - end = len(h) - } - chunk := h[i:end] - builder.WriteString(chunk) - if end < len(h) { - builder.WriteString(" ") - } - } - - // Return the slice of uint8 and any errors encountered - return builder.String(), nil -} - -func convertHexToBytes(hexString *string) ([]uint8, error) { - // Handle nil pointer case - if hexString == nil { - return nil, errors.New("nil pointer provided for hex string") - } - - // Split the hex string by spaces - hexBytes := strings.Fields(*hexString) - - // Initialize an empty slice for uint8 - data := make([]uint8, len(hexBytes)) - - // Iterate and convert each hex byte - for i, hexByte := range hexBytes { - // Convert each hex string to a uint8 value (handling errors) - value, err := strconv.ParseUint(hexByte, 16, 8) - if err != nil { - return nil, fmt.Errorf("error parsing hex byte '%s': %w", hexByte, err) - } - - // Assign the converted value to the slice - data[i] = uint8(value) - } - - // Return the slice of uint8 and any errors encountered - return data, nil -} - -func decrypt(input *Communication, output *Chat) bool { - // TODO: Add testing flag for easier manipulation. - //ciphertext, err := ioutil.ReadFile("myfile") - ciphertext, err := convertHexToBytes(&input.Communication) - - // if our program was unable to read the file - // print out the reason why it can't - if err != nil { - fmt.Println(err) - } - - c, err := aes.NewCipher(key) - if err != nil { - fmt.Println(err) - } - - gcm, err := cipher.NewGCM(c) - if err != nil { - fmt.Println(err) - } - - nonceSize := gcm.NonceSize() - if len(ciphertext) < nonceSize { - fmt.Println(err) - } - - nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] - plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) - if err != nil { - fmt.Println(err) - } - s := string(plaintext) - if validateHash(s, input.Hash) { - output.Content = s - return true - } - input.Communication = "DATA CORRUPTED OR TAMPERED" - return false -} - -// Validate there's no tampering with SHA-1 sum. The decrypted hash and the transmitted hash should be identical. -func validateHash(decrypted string, hash string) bool { - // Calculate the SHA256 sum of the decrypted request - hasher := sha1.New() - hasher.Write([]byte(decrypted)) - decryptedHashString := base64.URLEncoding.EncodeToString(hasher.Sum(nil)) - - // TODO: Add error handling if hash doesn't match. - return decryptedHashString == hash -} - -// Serve the authentication and encryption layer to a provided local port. -// Authentication takes place solely on the backend. -func serveAuthentication( - router *mux.Router, - port int, -) { - // TODO: Add error handling - authR := router.Host("http://localhost").Subrouter() - authSrv := &http.Server{ - Addr: "0.0.0.0:" + string(port), - WriteTimeout: time.Second * 15, - ReadTimeout: time.Second * 15, - IdleTimeout: time.Second * 60, - Handler: authR, - } - - go func() { - if err := authSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Println(err) - } - }() - - log.Println("Auth server is running on port " + string(port)) -} diff --git a/backend/src/security-layer/container/go.mod b/backend/src/security-layer/container/go.mod index 19b6913..56cbe7e 100644 --- a/backend/src/security-layer/container/go.mod +++ b/backend/src/security-layer/container/go.mod @@ -1,9 +1,7 @@ -module example.com/web-service-gin +module one-ai/backend go 1.22.5 -require github.com/gin-gonic/gin v1.10.0 - require ( github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect @@ -38,4 +36,5 @@ require ( golang.org/x/text v0.15.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + github.com/quaxlyqueen/services v1.0.0 ) diff --git a/backend/src/security-layer/container/router.go b/backend/src/security-layer/container/router.go index 717d25c..40effc5 100644 --- a/backend/src/security-layer/container/router.go +++ b/backend/src/security-layer/container/router.go @@ -14,64 +14,11 @@ import ( "time" "github.com/gorilla/mux" + + "github.com/quaxlyqueen/services" ) -type Communication struct { - Communication string `json:"communication"` - Hash string `json:"hash"` -} - -type Response struct { - Model string `json:"model"` - CreatedAt string `json:"created_at"` - Response string `json:"response"` - Done bool `json:"done"` - DoneReason string `json:"done_reason"` - Context []int `json:"context"` - TotalDuration int `json:"total_duration"` - LoadDuration int `json:"load_duration"` - PromptEC int `json:"prompt_eval_count"` - PromptED int `json:"prompt_eval_duration"` - EvalCount int `json:"eval_count"` - EvalDuration int `json:"eval_duration"` -} - -// Chats are updated once the content is decrypted. Since this is never leaving -// the server once decrypted, and possibly will not be saved (depending on user -// settings), it will remain decrypted until it's time to transform into a -// Communication JSON object, which is the HTTP response that is re-encrypted. - -// TODO: If the user has enabled conversation history, then save the encrypted -// chats to the server's drive. Additionally, only accept incoming additional -// messages from the user, rather than having the client re-send Chats already -// stored on the server. -type Chat struct { - Role bool `json:"role"` - Content string `json:"content"` -} - -type PromptWHistory struct { - Model string `json:"model"` - Messages []Chat `json:"messages"` - Stream bool `json:"stream"` -} - -type ResponseWHistory struct { - Model string `json:"model"` - CreatedAt string `json:"created_at"` - Message Chat `json:"message"` - Done bool `json:"done"` - DoneReason string `json:"done_reason"` - Context []int `json:"context"` - TotalDuration int `json:"total_duration"` - LoadDuration int `json:"load_duration"` - PromptEC int `json:"prompt_eval_count"` - PromptED int `json:"prompt_eval_duration"` - EvalCount int `json:"eval_count"` - EvalDuration int `json:"eval_duration"` -} - -var chats []Chat +var chats []services.Chat // User HTTP GET request has the prompt decrypted and verified with a SHA-1 checksum. // If there's been no data corruption, re-construct user prompt for ollama @@ -85,17 +32,17 @@ func chat(w http.ResponseWriter, r *http.Request) { } defer r.Body.Close() - prompt := Communication{} + prompt := services.Communication{} json.Unmarshal(input, &prompt) - newChat := Chat{} + newChat := services.Chat{} newChat.Role = true newChat.Content = prompt.Communication - if decrypt(&prompt, &newChat) { + if services.decrypt(&prompt, &newChat) { chats = append(chats, newChat) - apiCall := PromptWHistory{} + apiCall := services.PromptWHistory{} apiCall.Model = model apiCall.Messages = chats apiCall.Stream = false // TODO: Allow for this as a user setting... @@ -119,7 +66,7 @@ func chat(w http.ResponseWriter, r *http.Request) { } defer r.Body.Close() - response := ResponseWHistory{} + response := services.ResponseWHistory{} json.Unmarshal(output, &response) log.Println(response) @@ -128,14 +75,14 @@ func chat(w http.ResponseWriter, r *http.Request) { } } -func request(w http.ResponseWriter, r *http.Request) { +func generate(w http.ResponseWriter, r *http.Request) { input, err := io.ReadAll(r.Body) if err != nil { return } defer r.Body.Close() - prompt := Communication{} + prompt := services.Communication{} json.Unmarshal(input, &prompt) // TODO: Add support for dynamically changing models. @@ -162,7 +109,7 @@ func request(w http.ResponseWriter, r *http.Request) { defer resp.Body.Close() defer r.Body.Close() - response := Response{} + response := services.Response{} json.Unmarshal(output, &response) fmt.Println(response.Response) // Print the body as a string @@ -171,15 +118,21 @@ func request(w http.ResponseWriter, r *http.Request) { } func serve() { - -} - -func init() { var wg sync.WaitGroup - r := mux.NewRouter() portfolioDir := "/home/violet/documents/development/portfolio/public_html/" - servePage(r, portfolioDir, "/", 1112) + endpoint := []string{ + "/generate", + "/chat", + } + function := []func(http.ResponseWriter, *http.Request){ + generate, + chat, + } + + r := mux.NewRouter() + services.servePage(r, portfolioDir, "/", 1112) + services.serveApi(r, "ai.joshashton.dev", endpoint, function, 1113) srv := &http.Server{ Addr: "0.0.0.0:1111", diff --git a/backend/src/security-layer/container/serveAPI.go b/backend/src/security-layer/container/serveAPI.go deleted file mode 100644 index b9d478a..0000000 --- a/backend/src/security-layer/container/serveAPI.go +++ /dev/null @@ -1,42 +0,0 @@ -package main - -import ( - "github.com/gorilla/mux" - "log" - "net/http" - "time" -) - -// Serve an API using mux.Router().Host({domain}).Subrouter(). -// Provide the router, domain, an array of endpoints and functions, and -// the port you would like the API accessible to. -func serveApi( - router *mux.Router, - domain string, - endpoint []string, - function []func(http.ResponseWriter, *http.Request), - port int, -) { - // TODO: Add error handling - apiR := router.Host(domain).Subrouter() - - for i := 0; i < len(endpoint); i++ { - apiR.HandleFunc(endpoint[i], function[i]) - } - - apiSrv := &http.Server{ - Addr: "0.0.0.0:" + string(port), - WriteTimeout: time.Second * 15, - ReadTimeout: time.Second * 15, - IdleTimeout: time.Second * 60, - Handler: apiR, - } - - go func() { - if err := apiSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Println(err) - } - }() - - log.Println("API server is running on port " + string(port)) -} diff --git a/backend/src/security-layer/container/servePage.go b/backend/src/security-layer/container/servePage.go deleted file mode 100644 index 84669f8..0000000 --- a/backend/src/security-layer/container/servePage.go +++ /dev/null @@ -1,31 +0,0 @@ -package main - -import ( - "github.com/gorilla/mux" - "log" - "net/http" - "time" -) - -// Serve a directory to a port. -func servePage(router *mux.Router, dest string, path string, port int) { - router.PathPrefix(path).Handler(http.FileServer(http.Dir(dest))) - - srv := &http.Server{ - Addr: "0.0.0.0: " + string(port), - // Good practice to set timeouts to avoid Slowloris attacks. - WriteTimeout: time.Second * 15, - ReadTimeout: time.Second * 15, - IdleTimeout: time.Second * 60, - Handler: router, // Pass our instance of gorilla/mux in. - } - - // Run our server in a goroutine so that it doesn't block. - go func() { - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Println(err) - } - }() - - log.Println("Web server is running on port " + string(port)) -} diff --git a/backend/src/security-layer/container/services b/backend/src/security-layer/container/services new file mode 160000 index 0000000..1cdc735 --- /dev/null +++ b/backend/src/security-layer/container/services @@ -0,0 +1 @@ +Subproject commit 1cdc735154a5363fa33f01d1f2f1319e26c433c8