github packages -> in the meantime, need to keep all files in same package.
This commit is contained in:
@@ -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))
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
module example.com/web-service-gin
|
module one-ai/backend
|
||||||
|
|
||||||
go 1.22.5
|
go 1.22.5
|
||||||
|
|
||||||
require github.com/gin-gonic/gin v1.10.0
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/sonic v1.11.6 // indirect
|
github.com/bytedance/sonic v1.11.6 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
@@ -38,4 +36,5 @@ require (
|
|||||||
golang.org/x/text v0.15.0 // indirect
|
golang.org/x/text v0.15.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.1 // indirect
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
github.com/quaxlyqueen/services v1.0.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,64 +14,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
|
"github.com/quaxlyqueen/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Communication struct {
|
var chats []services.Chat
|
||||||
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
|
|
||||||
|
|
||||||
// User HTTP GET request has the prompt decrypted and verified with a SHA-1 checksum.
|
// 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
|
// 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()
|
defer r.Body.Close()
|
||||||
prompt := Communication{}
|
prompt := services.Communication{}
|
||||||
json.Unmarshal(input, &prompt)
|
json.Unmarshal(input, &prompt)
|
||||||
|
|
||||||
newChat := Chat{}
|
newChat := services.Chat{}
|
||||||
newChat.Role = true
|
newChat.Role = true
|
||||||
newChat.Content = prompt.Communication
|
newChat.Content = prompt.Communication
|
||||||
|
|
||||||
if decrypt(&prompt, &newChat) {
|
if services.decrypt(&prompt, &newChat) {
|
||||||
chats = append(chats, newChat)
|
chats = append(chats, newChat)
|
||||||
|
|
||||||
apiCall := PromptWHistory{}
|
apiCall := services.PromptWHistory{}
|
||||||
apiCall.Model = model
|
apiCall.Model = model
|
||||||
apiCall.Messages = chats
|
apiCall.Messages = chats
|
||||||
apiCall.Stream = false // TODO: Allow for this as a user setting...
|
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()
|
defer r.Body.Close()
|
||||||
response := ResponseWHistory{}
|
response := services.ResponseWHistory{}
|
||||||
json.Unmarshal(output, &response)
|
json.Unmarshal(output, &response)
|
||||||
log.Println(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)
|
input, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
prompt := Communication{}
|
prompt := services.Communication{}
|
||||||
json.Unmarshal(input, &prompt)
|
json.Unmarshal(input, &prompt)
|
||||||
|
|
||||||
// TODO: Add support for dynamically changing models.
|
// TODO: Add support for dynamically changing models.
|
||||||
@@ -162,7 +109,7 @@ func request(w http.ResponseWriter, r *http.Request) {
|
|||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
response := Response{}
|
response := services.Response{}
|
||||||
json.Unmarshal(output, &response)
|
json.Unmarshal(output, &response)
|
||||||
|
|
||||||
fmt.Println(response.Response) // Print the body as a string
|
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 serve() {
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
portfolioDir := "/home/violet/documents/development/portfolio/public_html/"
|
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{
|
srv := &http.Server{
|
||||||
Addr: "0.0.0.0:1111",
|
Addr: "0.0.0.0:1111",
|
||||||
|
|||||||
@@ -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))
|
|
||||||
}
|
|
||||||
@@ -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))
|
|
||||||
}
|
|
||||||
+1
Submodule backend/src/security-layer/container/services added at 1cdc735154
Reference in New Issue
Block a user