subdomains enabled, implemented go-routines, connected to portfolio, etc. heading into some major refactoring of the codebase, documentation, and installation/update scripts to enable rapid development.
This commit is contained in:
@@ -9,6 +9,7 @@ require (
|
|||||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
@@ -16,6 +17,11 @@ require (
|
|||||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||||
|
github.com/gorilla/handlers v1.5.2 // indirect
|
||||||
|
github.com/gorilla/mux v1.8.1 // indirect
|
||||||
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
|
github.com/gorilla/sessions v1.3.0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
|||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
@@ -25,6 +27,16 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
|
|||||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
|
||||||
|
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
|
||||||
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
|
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
|
github.com/gorilla/sessions v1.3.0 h1:XYlkq7KcpOB2ZhHBPv5WpjMIxrQosiZanfoy1HLZFzg=
|
||||||
|
github.com/gorilla/sessions v1.3.0/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||||
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
|||||||
@@ -1,205 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha1"
|
|
||||||
|
|
||||||
"errors"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Communication struct {
|
|
||||||
Communication string `json:"communication"`
|
|
||||||
Hash string `json:"hash"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var requests []Communication
|
|
||||||
|
|
||||||
// TODO: Test if 256 bit key works.
|
|
||||||
// TODO: Dynamically get key? Need to research best way to store private keys between two devices.
|
|
||||||
var key = []byte("passphrasewhichneedstobe32bytes!")
|
|
||||||
|
|
||||||
func hash(text string) string {
|
|
||||||
hasher := sha1.New()
|
|
||||||
hasher.Write([]byte(text))
|
|
||||||
return base64.URLEncoding.EncodeToString(hasher.Sum(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func encrypt(gc *gin.Context) {
|
|
||||||
var newCommunication Communication
|
|
||||||
if err := gc.BindJSON(&newCommunication); err != nil {
|
|
||||||
gc.IndentedJSON(http.StatusBadRequest, gin.H{"message": err})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
text := []byte(newCommunication.Communication)
|
|
||||||
|
|
||||||
c, err := aes.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
gc.IndentedJSON(http.StatusBadRequest, gin.H{"message": err})
|
|
||||||
}
|
|
||||||
|
|
||||||
gcm, err := cipher.NewGCM(c)
|
|
||||||
if err != nil {
|
|
||||||
gc.IndentedJSON(http.StatusBadRequest, gin.H{"message": err})
|
|
||||||
}
|
|
||||||
|
|
||||||
nonce := make([]byte, gcm.NonceSize())
|
|
||||||
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
|
||||||
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)) + "}"
|
|
||||||
|
|
||||||
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) bool {
|
|
||||||
//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) {
|
|
||||||
input.Communication = 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// TODO: Eventually, add additional logic that will allow for re-direction and contextual awareness (pre-processing)
|
|
||||||
func request(c *gin.Context) {
|
|
||||||
var newInput Communication
|
|
||||||
|
|
||||||
//Call BindJSON to bind the received JSON to
|
|
||||||
if err := c.BindJSON(&newInput); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if decrypt(&newInput) {
|
|
||||||
requests = append(requests, newInput)
|
|
||||||
c.IndentedJSON(http.StatusCreated, newInput) // TODO: Formulate prompt and transfer to AI layer to formulate new JSON response back
|
|
||||||
} else {
|
|
||||||
c.IndentedJSON(http.StatusBadRequest, gin.H{"message": "Failed checksum. Presumably data corrupted."})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
router := gin.Default()
|
|
||||||
router.POST("/request", request)
|
|
||||||
router.POST("/encrypt", encrypt)
|
|
||||||
|
|
||||||
err := router.Run("0.0.0.0:8000")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,407 @@
|
|||||||
|
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/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
// TODO: Test if 256 bit key works.
|
||||||
|
// TODO: Dynamically get key? Need to research best way to store private keys between two devices.
|
||||||
|
var key = []byte("passphrasewhichneedstobe32bytes!")
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return true // 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// TODO: Eventually, add additional logic that will allow for re-direction and contextual awareness (pre-processing)
|
||||||
|
func request(w http.ResponseWriter, r *http.Request) {
|
||||||
|
model := "qwen:0.5b" // qwen:0.5b used for testing while hosting from my laptop. llama3 seems to be the best to use normally.
|
||||||
|
|
||||||
|
input, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer r.Body.Close()
|
||||||
|
prompt := Communication{}
|
||||||
|
json.Unmarshal(input, &prompt)
|
||||||
|
|
||||||
|
newChat := Chat{}
|
||||||
|
newChat.Role = true
|
||||||
|
newChat.Content = prompt.Communication
|
||||||
|
|
||||||
|
if decrypt(&prompt, &newChat) {
|
||||||
|
chats = append(chats, newChat)
|
||||||
|
|
||||||
|
apiCall := PromptWHistory{}
|
||||||
|
apiCall.Model = model
|
||||||
|
apiCall.Messages = chats
|
||||||
|
apiCall.Stream = false // TODO: Allow for this as a user setting...
|
||||||
|
log.Print("Pre-JSON ification: ")
|
||||||
|
log.Println(apiCall)
|
||||||
|
|
||||||
|
j, err := json.Marshal(apiCall)
|
||||||
|
buf := strings.NewReader(string(j))
|
||||||
|
log.Print("Post-JSON ification: ")
|
||||||
|
log.Println(string(j))
|
||||||
|
|
||||||
|
// Port 11434 corresponds to Ollama. Direct access to Ollama is restricted.
|
||||||
|
resp, err := http.Post("http://localhost:11434/api/chat", "application/json", buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer r.Body.Close()
|
||||||
|
response := ResponseWHistory{}
|
||||||
|
json.Unmarshal(output, &response)
|
||||||
|
|
||||||
|
log.Println(response)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
http.Error(w, "Decryption/Hash failed.", 401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func chat(w http.ResponseWriter, r *http.Request) {
|
||||||
|
input, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer r.Body.Close()
|
||||||
|
prompt := Communication{}
|
||||||
|
json.Unmarshal(input, &prompt)
|
||||||
|
|
||||||
|
// TODO: Add support for dynamically changing models.
|
||||||
|
model := "qwen:0.5b" // qwen:0.5b used for testing while hosting from my laptop. llama3 seems to be the best to use normally.
|
||||||
|
|
||||||
|
apicall := "{\"model\": \""
|
||||||
|
apicall = apicall + model
|
||||||
|
apicall = apicall + "\", \"prompt\": \""
|
||||||
|
apicall = apicall + prompt.Communication
|
||||||
|
apicall = apicall + "\", \"stream\": false }"
|
||||||
|
|
||||||
|
buf := strings.NewReader(apicall)
|
||||||
|
|
||||||
|
// Port 11434 corresponds to Ollama. Direct access to Ollama is restricted.
|
||||||
|
resp, err := http.Post("http://localhost:11434/api/generate", "application/json", buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
defer r.Body.Close()
|
||||||
|
response := Response{}
|
||||||
|
json.Unmarshal(output, &response)
|
||||||
|
|
||||||
|
fmt.Println(response.Response) // Print the body as a string
|
||||||
|
// TODO: Encrypt response.Response and send back as a Communication JSON object.
|
||||||
|
//c.IndentedJSON(http.StatusCreated, resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
portfolioDir := "/home/violet/documents/development/portfolio/public_html/"
|
||||||
|
r.PathPrefix("/").Handler(http.FileServer(http.Dir(portfolioDir)))
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Addr: "0.0.0.0:8080",
|
||||||
|
// Good practice to set timeouts to avoid Slowloris attacks.
|
||||||
|
WriteTimeout: time.Second * 15,
|
||||||
|
ReadTimeout: time.Second * 15,
|
||||||
|
IdleTimeout: time.Second * 60,
|
||||||
|
Handler: r, // Pass our instance of gorilla/mux in.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run our server in a goroutine so that it doesn't block.
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Println("Portfolio server is running on port 8080")
|
||||||
|
|
||||||
|
// Routing for AI web app and API
|
||||||
|
apiR := r.Host("ai.joshashton.dev").Subrouter()
|
||||||
|
apiR.HandleFunc("/request", chat)
|
||||||
|
apiR.PathPrefix("/").Handler(http.FileServer(http.Dir("/home/violet/templates/construction/")))
|
||||||
|
|
||||||
|
apiSrv := &http.Server{
|
||||||
|
Addr: "0.0.0.0:8081",
|
||||||
|
// Good practice to set timeouts to avoid Slowloris attacks.
|
||||||
|
WriteTimeout: time.Second * 15,
|
||||||
|
ReadTimeout: time.Second * 15,
|
||||||
|
IdleTimeout: time.Second * 60,
|
||||||
|
Handler: apiR, // Pass our instance of gorilla/mux in.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run our server in a goroutine so that it doesn't block.
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if err := apiSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Println("API server is running on port 8081")
|
||||||
|
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
// We'll accept graceful shutdowns when quit via SIGINT (Ctrl+Shift+C)
|
||||||
|
signal.Notify(c, os.Interrupt)
|
||||||
|
|
||||||
|
// Block until we receive our signal.
|
||||||
|
<-c
|
||||||
|
|
||||||
|
// Create a deadline to wait for.
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(30))
|
||||||
|
defer cancel()
|
||||||
|
// Doesn't block if no connections, but will otherwise wait
|
||||||
|
// until the timeout deadline.
|
||||||
|
srv.Shutdown(ctx)
|
||||||
|
apiSrv.Shutdown(ctx)
|
||||||
|
// Optionally, you could run srv.Shutdown in a goroutine and block on
|
||||||
|
// <-ctx.Done() if your application should wait for other services
|
||||||
|
// to finalize based on context cancellation.
|
||||||
|
log.Println("shutting down")
|
||||||
|
wg.Wait()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
#!/bin/zsh
|
#!/bin/zsh
|
||||||
# Force delete the old container and re-initialize the container.
|
# Force delete the old container and re-initialize the container.
|
||||||
docker rm -f security-layer
|
sudo docker rm -f security-layer
|
||||||
|
|
||||||
# Build the Docker image.
|
# Build the Docker image.
|
||||||
docker build --tag security-layer:latest .
|
sudo docker build --tag security-layer:latest .
|
||||||
|
|
||||||
# Create a Docker container from the image and connect host port 8000 to container port 8000.
|
# Create a Docker container from the image and connect host port 8000 to container port 8000.
|
||||||
docker run --name security-layer -d -p 8000:8000 security-layer:latest
|
sudo docker run --name security-layer -d -p 8000:8000 security-layer:latest
|
||||||
|
|||||||
Reference in New Issue
Block a user