diff --git a/Makefile b/Makefile index 84493c9..ec65943 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,7 @@ test: HISHTORY_TEST=1 go test -p 1 ./... build-static: - go build -o web/landing/www/hishtory-offline clients/local/client.go - go build -o web/landing/www/hishtory-online clients/remote/client.go + go build -o web/landing/www/binaries/hishtory-linux client/client.go docker build -t gcr.io/dworken-k8s/hishtory-static -f web/caddy/Dockerfile . build-api: diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000..27bd459 --- /dev/null +++ b/client/client.go @@ -0,0 +1,88 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "os" + "strings" + + "github.com/ddworken/hishtory/shared" + "github.com/ddworken/hishtory/client/lib" +) + +func main() { + if len(os.Args) == 1 { + fmt.Println("Must specify a command! Do you mean `hishtory query`?") + return + } + switch os.Args[1] { + case "saveHistoryEntry": + saveHistoryEntry() + case "query": + query(strings.Join(os.Args[2:], " ")) + case "export": + export() + case "init": + lib.CheckFatalError(lib.Setup( os.Args)) + case "install": + lib.CheckFatalError(lib.Install()) + case "enable": + lib.CheckFatalError(lib.Enable()) + case "disable": + lib.CheckFatalError(lib.Disable()) + case "status": + config, err := lib.GetConfig() + lib.CheckFatalError(err) + fmt.Print("Hishtory: Offline Mode\nEnabled: ") + fmt.Print(config.IsEnabled) + fmt.Print("\n") + case "update": + lib.CheckFatalError(lib.Update("https://hishtory.dev/binaries/hishtory-linux")) + default: + lib.CheckFatalError(fmt.Errorf("unknown command: %s", os.Args[1])) + } +} + +func query(query string) { + db, err := shared.OpenLocalSqliteDb() + lib.CheckFatalError(err) + data, err := shared.Search(db, query, 25) + lib.CheckFatalError(err) + lib.DisplayResults(data, false) +} + +func saveHistoryEntry() { + config, err := lib.GetConfig() + lib.CheckFatalError(err) + if !config.IsEnabled { + return + } + entry, err := lib.BuildHistoryEntry(os.Args) + lib.CheckFatalError(err) + + // Persist it locally + db, err := shared.OpenLocalSqliteDb() + lib.CheckFatalError(err) + result := db.Create(entry) + lib.CheckFatalError(result.Error) + + // Persist it remotely + encEntry, err := shared.EncryptHistoryEntry(config.UserSecret, *entry) + lib.CheckFatalError(err) + jsonValue, err := json.Marshal(encEntry) + lib.CheckFatalError(err) + _, err = http.Post(lib.GetServerHostname()+"/api/v1/esubmit", "application/json", bytes.NewBuffer(jsonValue)) + lib.CheckFatalError(err) +} + +func export() { + db, err := shared.OpenLocalSqliteDb() + lib.CheckFatalError(err) + data, err := shared.Search(db, "", 0) + lib.CheckFatalError(err) + for _, entry := range data { + fmt.Println(entry) + } +} diff --git a/clients/local/client_test.go b/client/client_test.go similarity index 100% rename from clients/local/client_test.go rename to client/client_test.go diff --git a/shared/config.sh b/client/lib/config.sh similarity index 100% rename from shared/config.sh rename to client/lib/config.sh diff --git a/shared/client.go b/client/lib/lib.go similarity index 90% rename from shared/client.go rename to client/lib/lib.go index 5587bcf..8aa21ab 100644 --- a/shared/client.go +++ b/client/lib/lib.go @@ -1,4 +1,4 @@ -package shared +package lib import ( _ "embed" @@ -19,14 +19,11 @@ import ( "time" "net/http" - "github.com/fatih/color" "github.com/google/uuid" "github.com/rodaine/table" -) -const ( - CONFIG_PATH = ".hishtory.config" + "github.com/ddworken/hishtory/shared" ) var ( @@ -52,8 +49,8 @@ func getCwd() (string, error) { return cwd, nil } -func BuildHistoryEntry(args []string) (*HistoryEntry, error) { - var entry HistoryEntry +func BuildHistoryEntry(args []string) (*shared.HistoryEntry, error) { + var entry shared.HistoryEntry // exitCode exitCode, err := strconv.Atoi(args[2]) @@ -99,14 +96,7 @@ func BuildHistoryEntry(args []string) (*HistoryEntry, error) { return nil, fmt.Errorf("failed to build history entry: %v", err) } entry.Hostname = hostname - - // user secret - userSecret, err := GetUserSecret() - if err != nil { - return nil, fmt.Errorf("failed to build history entry: %v", err) - } - entry.UserSecret = userSecret - + return &entry, nil } @@ -139,14 +129,14 @@ func Setup(args []string) error { return fmt.Errorf("failed to persist config to disk: %v", err) } - - _, err = http.Get(getServerHostname()+"/api/v1/eregister?user_id=" + shared.UserId(userSecret) + "&device_id=" + config.DeviceId) + _, err = http.Get(GetServerHostname()+"/api/v1/eregister?user_id=" + shared.UserId(userSecret) + "&device_id=" + config.DeviceId) if err != nil { return fmt.Errorf("failed to register device with backend: %v", err) } + return nil } -func DisplayResults(results []*HistoryEntry, displayHostname bool) { +func DisplayResults(results []*shared.HistoryEntry, displayHostname bool) { headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc() tbl := table.New("CWD", "Timestamp", "Runtime", "Exit Code", "Command") if displayHostname { @@ -178,7 +168,7 @@ func GetConfig() (ClientConfig, error) { if err != nil { return ClientConfig{}, fmt.Errorf("failed to retrieve homedir: %v", err) } - data, err := os.ReadFile(path.Join(homedir, HISHTORY_PATH, CONFIG_PATH)) + data, err := os.ReadFile(path.Join(homedir, shared.HISHTORY_PATH, shared.CONFIG_PATH)) if err != nil { return ClientConfig{}, fmt.Errorf("failed to read config file: %v", err) } @@ -199,12 +189,12 @@ func SetConfig(config ClientConfig) error { if err != nil { return fmt.Errorf("failed to retrieve homedir: %v", err) } - clientDir := path.Join(homedir, HISHTORY_PATH) + clientDir := path.Join(homedir, shared.HISHTORY_PATH) err = os.MkdirAll(clientDir, 0744) if err != nil { return fmt.Errorf("failed to create ~/.hishtory/ folder: %v", err) } - err = os.WriteFile(path.Join(homedir, HISHTORY_PATH, CONFIG_PATH), serializedConfig, 0600) + err = os.WriteFile(path.Join(homedir, shared.HISHTORY_PATH, shared.CONFIG_PATH), serializedConfig, 0600) if err != nil { return fmt.Errorf("failed to write config: %v", err) } @@ -248,7 +238,7 @@ func Install() error { if err != nil { return fmt.Errorf("failed to get user's home directory: %v", err) } - clientDir := path.Join(homedir, HISHTORY_PATH) + clientDir := path.Join(homedir, shared.HISHTORY_PATH) err = os.MkdirAll(clientDir, 0744) if err != nil { return fmt.Errorf("failed to create folder for hishtory binary: %v", err) @@ -300,7 +290,7 @@ func configureBashrc(homedir, binaryPath string) error { func installBinary(homedir string) (string, error) { clientPath, err := exec.LookPath("hishtory") if err != nil { - clientPath = path.Join(homedir, HISHTORY_PATH, "hishtory") + clientPath = path.Join(homedir, shared.HISHTORY_PATH, "hishtory") } err = copyFile(os.Args[0], clientPath) if err != nil { @@ -350,3 +340,11 @@ func Update(url string) error { } return nil } + + +func GetServerHostname() string { + if server := os.Getenv("HISHTORY_SERVER"); server != "" { + return server + } + return "http://localhost:8080" +} \ No newline at end of file diff --git a/shared/client_test.go b/client/lib/lib_test.go similarity index 68% rename from shared/client_test.go rename to client/lib/lib_test.go index ce41f53..1704086 100644 --- a/shared/client_test.go +++ b/client/lib/lib_test.go @@ -1,4 +1,4 @@ -package shared +package lib import ( "os" @@ -6,34 +6,33 @@ import ( "strings" "testing" "time" + + "github.com/ddworken/hishtory/shared" ) func TestSetup(t *testing.T) { - defer BackupAndRestore(t)() + defer shared.BackupAndRestore(t)() homedir, err := os.UserHomeDir() - Check(t, err) - if _, err := os.Stat(path.Join(homedir, HISHTORY_PATH, CONFIG_PATH)); err == nil { + shared.Check(t, err) + if _, err := os.Stat(path.Join(homedir, shared.HISHTORY_PATH, shared.CONFIG_PATH)); err == nil { t.Fatalf("hishtory secret file already exists!") } - Check(t, Setup([]string{})) - if _, err := os.Stat(path.Join(homedir, HISHTORY_PATH, CONFIG_PATH)); err != nil { + shared.Check(t, Setup([]string{})) + if _, err := os.Stat(path.Join(homedir, shared.HISHTORY_PATH, shared.CONFIG_PATH)); err != nil { t.Fatalf("hishtory secret file does not exist after Setup()!") } - data, err := os.ReadFile(path.Join(homedir, HISHTORY_PATH, CONFIG_PATH)) - Check(t, err) + data, err := os.ReadFile(path.Join(homedir, shared.HISHTORY_PATH, shared.CONFIG_PATH)) + shared.Check(t, err) if len(data) < 10 { t.Fatalf("hishtory secret has unexpected length: %d", len(data)) } } func TestBuildHistoryEntry(t *testing.T) { - defer BackupAndRestore(t)() - Check(t, Setup([]string{})) + defer shared.BackupAndRestore(t)() + shared.Check(t, Setup([]string{})) entry, err := BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls / ", "1641774958326745663"}) - Check(t, err) - if entry.UserSecret == "" || len(entry.UserSecret) < 10 || strings.TrimSpace(entry.UserSecret) != entry.UserSecret { - t.Fatalf("history entry has unexpected user secret: %v", entry.UserSecret) - } + shared.Check(t, err) if entry.ExitCode != 120 { t.Fatalf("history entry has unexpected exit code: %v", entry.ExitCode) } @@ -52,17 +51,17 @@ func TestBuildHistoryEntry(t *testing.T) { } func TestGetUserSecret(t *testing.T) { - defer BackupAndRestore(t)() - Check(t, Setup([]string{})) + defer shared.BackupAndRestore(t)() + shared.Check(t, Setup([]string{})) secret1, err := GetUserSecret() - Check(t, err) + shared.Check(t, err) if len(secret1) < 10 || strings.Contains(secret1, " ") || strings.Contains(secret1, "\n") { t.Fatalf("unexpected secret: %v", secret1) } - Check(t, Setup([]string{})) + shared.Check(t, Setup([]string{})) secret2, err := GetUserSecret() - Check(t, err) + shared.Check(t, err) if secret1 == secret2 { t.Fatalf("GetUserSecret() returned the same values for different setups! val=%v", secret1) diff --git a/clients/local/client.go b/clients/local/client.go deleted file mode 100644 index ef4ef44..0000000 --- a/clients/local/client.go +++ /dev/null @@ -1,98 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "os" - "strings" - - "github.com/ddworken/hishtory/shared" -) - -func main() { - if len(os.Args) == 1 { - fmt.Println("Must specify a command! Do you mean `hishtory query`?") - return - } - switch os.Args[1] { - case "saveHistoryEntry": - saveHistoryEntry() - case "query": - query(strings.Join(os.Args[2:], " ")) - case "export": - export() - case "init": - shared.CheckFatalError(shared.Setup( os.Args)) - case "install": - shared.CheckFatalError(shared.Install()) - case "enable": - shared.CheckFatalError(shared.Enable()) - case "disable": - shared.CheckFatalError(shared.Disable()) - case "status": - config, err := shared.GetConfig() - shared.CheckFatalError(err) - fmt.Print("Hishtory: Offline Mode\nEnabled: ") - fmt.Print(config.IsEnabled) - fmt.Print("\n") - case "update": - shared.CheckFatalError(shared.Update("https://hishtory.dev/hishtory-offline")) - default: - shared.CheckFatalError(fmt.Errorf("unknown command: %s", os.Args[1])) - } -} - -func getServerHostname() string { - if server := os.Getenv("HISHTORY_SERVER"); server != "" { - return server - } - return "http://localhost:8080" -} - -func query(query string) { - userSecret, err := shared.GetUserSecret() - shared.CheckFatalError(err) - db, err := shared.OpenLocalSqliteDb() - shared.CheckFatalError(err) - data, err := shared.Search(db, userSecret, query, 25) - shared.CheckFatalError(err) - shared.DisplayResults(data, false) -} - -func saveHistoryEntry() { - config, err := shared.GetConfig() - shared.CheckFatalError(err) - if !config.IsEnabled { - return - } - entry, err := shared.BuildHistoryEntry(os.Args) - shared.CheckFatalError(err) - - // Persist it locally - db, err := shared.OpenLocalSqliteDb() - shared.CheckFatalError(err) - result := db.Create(entry) - shared.CheckFatalError(result.Error) - - // Persist it remotely - encEntry, err := shared.EncryptHistoryEntry(config.UserSecret, *entry) - shared.CheckFatalError(err) - jsonValue, err := json.Marshal(encEntry) - shared.CheckFatalError(err) - _, err = http.Post(getServerHostname()+"/api/v1/esubmit", "application/json", bytes.NewBuffer(jsonValue)) - shared.CheckFatalError(err) -} - -func export() { - userSecret, err := shared.GetUserSecret() - shared.CheckFatalError(err) - db, err := shared.OpenLocalSqliteDb() - shared.CheckFatalError(err) - data, err := shared.Search(db, userSecret, "", 0) - shared.CheckFatalError(err) - for _, entry := range data { - fmt.Println(entry) - } -} diff --git a/clients/remote/client.go b/clients/remote/client.go deleted file mode 100644 index 0b31145..0000000 --- a/clients/remote/client.go +++ /dev/null @@ -1,125 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "os" - "strings" - - "github.com/ddworken/hishtory/shared" -) - -func main() { - if len(os.Args) == 1 { - fmt.Println("Must specify a command! Do you mean `hishtory query`?") - return - } - switch os.Args[1] { - case "saveHistoryEntry": - saveHistoryEntry() - case "query": - query(strings.Join(os.Args[2:], " ")) - case "export": - export() - case "init": - shared.CheckFatalError(shared.Setup(os.Args)) - case "install": - shared.CheckFatalError(shared.Install()) - // TODO: should be able to do hishtory install $secret - case "enable": - shared.CheckFatalError(shared.Enable()) - case "disable": - shared.CheckFatalError(shared.Disable()) - case "status": - config, err := shared.GetConfig() - shared.CheckFatalError(err) - fmt.Print("Hishtory: Online Mode\nEnabled: ") - fmt.Print(config.IsEnabled) - fmt.Print("\nSecret Key: " + config.UserSecret + "\n") - fmt.Print("Remote Server: " + getServerHostname() + "\n") - case "update": - shared.CheckFatalError(shared.Update("https://hishtory.dev/hishtory-online")) - default: - shared.CheckFatalError(fmt.Errorf("unknown command: %s", os.Args[1])) - } -} - -func getServerHostname() string { - if server := os.Getenv("HISHTORY_SERVER"); server != "" { - return server - } - return "https://api.hishtory.dev" -} - -func query(query string) { - data, err := doQuery(query) - shared.CheckFatalError(err) - shared.DisplayResults(data, true) -} - -func doQuery(query string) ([]*shared.HistoryEntry, error) { - userSecret, err := shared.GetUserSecret() - if err != nil { - return nil, err - } - req, err := http.NewRequest("GET", getServerHostname()+"/api/v1/search", nil) - if err != nil { - return nil, err - } - - q := req.URL.Query() - q.Add("query", query) - q.Add("user_secret", userSecret) - q.Add("limit", "25") - req.URL.RawQuery = q.Encode() - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - resp_body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - if resp.Status != "200 OK" { - return nil, fmt.Errorf("search API returned invalid result. status=" + resp.Status) - } - - var data []*shared.HistoryEntry - err = json.Unmarshal(resp_body, &data) - return data, err -} - -func saveHistoryEntry() { - isEnabled, err := shared.IsEnabled() - shared.CheckFatalError(err) - if !isEnabled { - return - } - entry, err := shared.BuildHistoryEntry(os.Args) - shared.CheckFatalError(err) - err = send(*entry) - shared.CheckFatalError(err) -} - -func send(entry shared.HistoryEntry) error { - jsonValue, err := json.Marshal(entry) - if err != nil { - return fmt.Errorf("failed to marshal HistoryEntry as json: %v", err) - } - - _, err = http.Post(getServerHostname()+"/api/v1/submit", "application/json", bytes.NewBuffer(jsonValue)) - if err != nil { - return fmt.Errorf("failed to send HistoryEntry to api: %v", err) - } - return nil -} - -func export() { - // TODO(ddworken) -} diff --git a/clients/remote/client_test.go b/clients/remote/client_test.go deleted file mode 100644 index ca0834f..0000000 --- a/clients/remote/client_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package main - -import ( - "bytes" - "os/exec" - "regexp" - "strings" - "testing" - - "github.com/ddworken/hishtory/shared" -) - -func RunInteractiveBashCommands(t *testing.T, script string) string { - cmd := exec.Command("bash", "-i") - cmd.Stdin = strings.NewReader("export HISHTORY_SERVER=http://localhost:8080; /tmp/server &; sleep 2; " + script + "\nsleep 2; killall server || true;") - var out bytes.Buffer - cmd.Stdout = &out - var err bytes.Buffer - cmd.Stderr = &err - shared.CheckWithInfo(t, cmd.Run(), out.String()+err.String()) - return out.String() -} - -func TestIntegration(t *testing.T) { - // Set up - defer shared.BackupAndRestore(t)() - - // TODO(ddworken): Test status - - // Test install - out := RunInteractiveBashCommands(t, ` - gvm use go1.17 - cd ../../ - go build -o /tmp/client clients/remote/client.go - go build -o /tmp/server server/server.go - /tmp/client install`) - match, err := regexp.MatchString(`Setting secret hishtory key to .*`, out) - shared.Check(t, err) - if !match { - t.Fatalf("unexpected output from install: %v", out) - } - - // Test recording commands - out = RunInteractiveBashCommands(t, ` - ls /a - ls /bar - ls /foo - echo foo - echo bar - hishtory disable - echo thisisnotrecorded - hishtory enable - echo thisisrecorded - `) - if out != "foo\nbar\nthisisnotrecorded\nthisisrecorded\n" { - t.Fatalf("unexpected output from running commands: %#v", out) - } - - // Test querying for all commands - out = RunInteractiveBashCommands(t, `hishtory query`) - expected := []string{"echo thisisrecorded", "hishtory enable", "echo bar", "echo foo", "ls /foo", "ls /bar", "ls /a", "ms"} - for _, item := range expected { - if !strings.Contains(out, item) { - t.Fatalf("output is missing expected item %#v: %#v", item, out) - } - } - // match, err = regexp.MatchString(`.*[a-zA-Z-_0-9]+\s+~/.*\s+[a-zA-Z]{3} \d+ 2022 \d\d:\d\d:\d\d PST\s+\d{1,2}ms\s+0\s+echo thisisrecorded.*`, out) - // shared.Check(t, err) - // if !match { - // t.Fatalf("output is missing the row for `echo thisisrecorded`: %v", out) - // } - - // Test querying for a specific command - out = RunInteractiveBashCommands(t, "hishtory query foo") - expected = []string{"echo foo", "ls /foo", "ms"} - unexpected := []string{"echo thisisrecorded", "hishtory enable", "echo bar", "ls /bar", "ls /a"} - for _, item := range expected { - if !strings.Contains(out, item) { - t.Fatalf("output is missing expected item %#v: %#v", item, out) - } - } - for _, item := range unexpected { - if strings.Contains(out, item) { - t.Fatalf("output is containing unexpected item %#v: %#v", item, out) - } - } -} - -// TODO(ddworken): Test export diff --git a/server/server.go b/server/server.go index c81bdad..62dd7d7 100644 --- a/server/server.go +++ b/server/server.go @@ -5,7 +5,6 @@ import ( "fmt" "log" "net/http" - "strconv" "github.com/ddworken/hishtory/shared" _ "github.com/lib/pq" @@ -19,16 +18,6 @@ const ( var GLOBAL_DB *gorm.DB -func apiSubmitHandler(w http.ResponseWriter, r *http.Request) { - decoder := json.NewDecoder(r.Body) - var entry shared.HistoryEntry - err := decoder.Decode(&entry) - if err != nil { - panic(err) - } - GLOBAL_DB.Create(&entry) -} - func apiESubmitHandler(w http.ResponseWriter, r *http.Request) { decoder := json.NewDecoder(r.Body) var entries []shared.EncHistoryEntry @@ -109,29 +98,6 @@ func OpenDB() (*gorm.DB, error) { return db, nil } -func apiSearchHandler(w http.ResponseWriter, r *http.Request) { - userSecret := r.URL.Query().Get("user_secret") - query := r.URL.Query().Get("query") - fmt.Println("Received search query: " + query) - limitStr := r.URL.Query().Get("limit") - limit, err := strconv.Atoi(limitStr) - if err != nil { - limit = 0 - } - entries, err := shared.Search(GLOBAL_DB, userSecret, query, limit) - if err != nil { - panic(err) - } - for _, entry := range entries { - entry.UserSecret = "" - } - resp, err := json.Marshal(entries) - if err != nil { - panic(err) - } - w.Write(resp) -} - func init() { InitDB() } @@ -154,8 +120,6 @@ func InitDB() { func main() { fmt.Println("Listening on localhost:8080") - http.HandleFunc("/api/v1/submit", apiSubmitHandler) - http.HandleFunc("/api/v1/search", apiSearchHandler) http.HandleFunc("/api/v1/esubmit", apiESubmitHandler) http.HandleFunc("/api/v1/equery", apiEQueryHandler) http.HandleFunc("/api/v1/ebootstrap", apiEBootstrapHandler) diff --git a/server/server_test.go b/server/server_test.go index d754d69..f615d69 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -12,129 +12,11 @@ import ( "github.com/ddworken/hishtory/shared" ) -func TestSubmitThenQuery(t *testing.T) { - // Set up - defer shared.BackupAndRestore(t)() - InitDB() - shared.Check(t, shared.Setup([]string{})) - - // Submit an entry - entry, err := shared.BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls / ", "1641774958326745663"}) - shared.Check(t, err) - reqBody, err := json.Marshal(entry) - shared.Check(t, err) - submitReq := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(reqBody)) - apiSubmitHandler(nil, submitReq) - // Also submit one for another user - otherEntry := *entry - otherEntry.UserSecret = "aaaaaaaaa" - otherEntry.Command = "other" - reqBody, err = json.Marshal(otherEntry) - shared.Check(t, err) - submitReq = httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(reqBody)) - apiSubmitHandler(nil, submitReq) - - // Retrieve the entry - secret, err := shared.GetUserSecret() - shared.Check(t, err) - w := httptest.NewRecorder() - searchReq := httptest.NewRequest(http.MethodGet, "/?user_secret="+secret, nil) - apiSearchHandler(w, searchReq) - res := w.Result() - defer res.Body.Close() - data, err := ioutil.ReadAll(res.Body) - shared.Check(t, err) - var retrievedEntries []*shared.HistoryEntry - shared.Check(t, json.Unmarshal(data, &retrievedEntries)) - if len(retrievedEntries) != 1 { - t.Fatalf("Expected to retrieve 1 entry, found %d", len(retrievedEntries)) - } - dbEntry := retrievedEntries[0] - if dbEntry.UserSecret != "" { - t.Fatalf("Response contains a user secret: %#v", *dbEntry) - } - entry.UserSecret = "" - if !shared.EntryEquals(*dbEntry, *entry) { - t.Fatalf("DB data is different than input! \ndb =%#v\ninput=%#v", *dbEntry, *entry) - } -} - -func TestNoUserSecretGivesNoResults(t *testing.T) { - // Set up - defer shared.BackupAndRestore(t)() - InitDB() - shared.Check(t, shared.Setup([]string{})) - - // Submit an entry - entry, err := shared.BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls / ", "1641774958326745663"}) - shared.Check(t, err) - reqBody, err := json.Marshal(entry) - shared.Check(t, err) - submitReq := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(reqBody)) - apiSubmitHandler(nil, submitReq) - - // Retrieve entries with no user secret - w := httptest.NewRecorder() - searchReq := httptest.NewRequest(http.MethodGet, "/", nil) - apiSearchHandler(w, searchReq) - res := w.Result() - defer res.Body.Close() - data, err := ioutil.ReadAll(res.Body) - shared.Check(t, err) - var retrievedEntries []*shared.HistoryEntry - shared.Check(t, json.Unmarshal(data, &retrievedEntries)) - if len(retrievedEntries) != 0 { - t.Fatalf("Expected to retrieve 0 entries, found %d, results[0]=%#v", len(retrievedEntries), retrievedEntries[0]) - } -} - -func TestSearchQuery(t *testing.T) { - // Set up - defer shared.BackupAndRestore(t)() - InitDB() - shared.Check(t, shared.Setup([]string{})) - - // Submit an entry that we'll match - entry, err := shared.BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls /bar ", "1641774958326745663"}) - shared.Check(t, err) - reqBody, err := json.Marshal(entry) - shared.Check(t, err) - submitReq := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(reqBody)) - apiSubmitHandler(nil, submitReq) - // Submit an entry that we won't match - entry, err = shared.BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls /foo ", "1641774958326745663"}) - shared.Check(t, err) - reqBody, err = json.Marshal(entry) - shared.Check(t, err) - submitReq = httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(reqBody)) - apiSubmitHandler(nil, submitReq) - - // Retrieve the entry - secret, err := shared.GetUserSecret() - shared.Check(t, err) - w := httptest.NewRecorder() - searchReq := httptest.NewRequest(http.MethodGet, "/?user_secret="+secret+"&query=foo", nil) - apiSearchHandler(w, searchReq) - res := w.Result() - defer res.Body.Close() - data, err := ioutil.ReadAll(res.Body) - shared.Check(t, err) - var retrievedEntries []*shared.HistoryEntry - shared.Check(t, json.Unmarshal(data, &retrievedEntries)) - if len(retrievedEntries) != 1 { - t.Fatalf("Expected to retrieve 1 entry, found %d", len(retrievedEntries)) - } - dbEntry := retrievedEntries[0] - if dbEntry.Command != "ls /foo" { - t.Fatalf("Response contains an unexpected command: %#v", *dbEntry) - } -} - func TestESubmitThenQuery(t *testing.T) { // Set up defer shared.BackupAndRestore(t)() InitDB() - shared.Check(t, shared.Setup([]string{})) + // shared.Check(t, shared.Setup([]string{})) // Register a few devices userId := shared.UserId("key") @@ -150,9 +32,8 @@ func TestESubmitThenQuery(t *testing.T) { apiERegisterHandler(nil, deviceReq) // Submit a few entries for different devices - entry, err := shared.BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls / ", "1641774958326745663"}) - shared.Check(t, err) - encEntry, err := shared.EncryptHistoryEntry("key", *entry) + entry := shared.MakeFakeHistoryEntry("ls ~/") + encEntry, err := shared.EncryptHistoryEntry("key", entry) shared.Check(t, err) reqBody, err := json.Marshal([]shared.EncHistoryEntry{encEntry}) shared.Check(t, err) @@ -184,8 +65,8 @@ func TestESubmitThenQuery(t *testing.T) { } decEntry, err := shared.DecryptHistoryEntry("key", *dbEntry) shared.Check(t, err) - if !shared.EntryEquals(decEntry, *entry) { - t.Fatalf("DB data is different than input! \ndb =%#v\ninput=%#v", *dbEntry, *entry) + if !shared.EntryEquals(decEntry, entry) { + t.Fatalf("DB data is different than input! \ndb =%#v\ninput=%#v", *dbEntry, entry) } // Same for device id 2 @@ -212,8 +93,8 @@ func TestESubmitThenQuery(t *testing.T) { } decEntry, err = shared.DecryptHistoryEntry("key", *dbEntry) shared.Check(t, err) - if !shared.EntryEquals(decEntry, *entry) { - t.Fatalf("DB data is different than input! \ndb =%#v\ninput=%#v", *dbEntry, *entry) + if !shared.EntryEquals(decEntry, entry) { + t.Fatalf("DB data is different than input! \ndb =%#v\ninput=%#v", *dbEntry, entry) } // Bootstrap handler should return 2 entries, one for each device diff --git a/shared/data.go b/shared/data.go index a1ce586..cadf45e 100644 --- a/shared/data.go +++ b/shared/data.go @@ -21,8 +21,6 @@ import ( ) type HistoryEntry struct { - // TODO: UserSecret needs to be removed from here once I drop all the old code - UserSecret string `json:"user_secret" gorm:"index"` LocalUsername string `json:"local_username"` Hostname string `json:"hostname"` Command string `json:"command"` @@ -56,6 +54,7 @@ type Device struct { // } const ( + CONFIG_PATH = ".hishtory.config" HISHTORY_PATH = ".hishtory" DB_PATH = ".hishtory.db" KDF_USER_ID = "user_id" @@ -192,17 +191,18 @@ func OpenLocalSqliteDb() (*gorm.DB, error) { return db, nil } +// TODO: DELETE THIS METHOD func Persist(db *gorm.DB, entry HistoryEntry) error { db.Create(&entry) return nil } -func Search(db *gorm.DB, userSecret, query string, limit int) ([]*HistoryEntry, error) { +func Search(db *gorm.DB, query string, limit int) ([]*HistoryEntry, error) { tokens, err := tokenize(query) if err != nil { return nil, fmt.Errorf("failed to tokenize query: %v", err) } - tx := db.Where("user_secret = ?", userSecret) + tx := db.Where("true") for _, token := range tokens { if strings.Contains(token, ":") { splitToken := strings.SplitN(token, ":", 2) diff --git a/shared/data_test.go b/shared/data_test.go index b11f262..6de96ce 100644 --- a/shared/data_test.go +++ b/shared/data_test.go @@ -6,13 +6,12 @@ import ( func TestPersist(t *testing.T) { defer BackupAndRestore(t)() - Check(t, Setup([]string{})) + // Check(t, Setup([]string{})) db, err := OpenLocalSqliteDb() Check(t, err) - entry, err := BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls / ", "1641774958326745663"}) - Check(t, err) - Check(t, Persist(db, *entry)) + entry := MakeFakeHistoryEntry("ls ~/") + Check(t, Persist(db, entry)) var historyEntries []*HistoryEntry result := db.Find(&historyEntries) Check(t, result.Error) @@ -20,40 +19,33 @@ func TestPersist(t *testing.T) { t.Fatalf("DB has %d entries, expected 1!", len(historyEntries)) } dbEntry := historyEntries[0] - if !EntryEquals(*entry, *dbEntry) { - t.Fatalf("DB data is different than input! \ndb =%#v \ninput=%#v", *dbEntry, *entry) + if !EntryEquals(entry, *dbEntry) { + t.Fatalf("DB data is different than input! \ndb =%#v \ninput=%#v", *dbEntry, entry) } } func TestSearch(t *testing.T) { defer BackupAndRestore(t)() - Check(t, Setup([]string{})) + // Check(t, Setup([]string{})) db, err := OpenLocalSqliteDb() Check(t, err) // Insert data - entry1, err := BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls / ", "1641774958326745663"}) - Check(t, err) - Check(t, Persist(db, *entry1)) - entry2, err := BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 ls /foo ", "1641774958326745663"}) - Check(t, err) - Check(t, Persist(db, *entry2)) - entry3, err := BuildHistoryEntry([]string{"unused", "saveHistoryEntry", "120", " 123 echo hi ", "1641774958326745663"}) - Check(t, err) - Check(t, Persist(db, *entry3)) + entry1 := MakeFakeHistoryEntry("ls /foo") + Check(t, Persist(db, entry1)) + entry2 := MakeFakeHistoryEntry("ls /bar") + Check(t, Persist(db, entry2)) // Search for data - secret, err := GetUserSecret() - Check(t, err) - results, err := Search(db, secret, "ls", 5) + results, err := Search(db, "ls", 5) Check(t, err) if len(results) != 2 { t.Fatalf("Search() returned %d results, expected 2!", len(results)) } - if !EntryEquals(*results[0], *entry2) { + if !EntryEquals(*results[0], entry2) { t.Fatalf("Search()[0]=%#v, expected: %#v", results[0], entry2) } - if !EntryEquals(*results[1], *entry1) { + if !EntryEquals(*results[1], entry1) { t.Fatalf("Search()[0]=%#v, expected: %#v", results[1], entry1) } } diff --git a/shared/testutils.go b/shared/testutils.go index f087a65..9337c91 100644 --- a/shared/testutils.go +++ b/shared/testutils.go @@ -1,24 +1,12 @@ package shared import ( - "os" - "path" "testing" "time" + "os" + "path" ) -func Check(t *testing.T, err error) { - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } -} - -func CheckWithInfo(t *testing.T, err error, additionalInfo string) { - if err != nil { - t.Fatalf("Unexpected error: %v! Additional info: %v", err, additionalInfo) - } -} - func BackupAndRestore(t *testing.T) func() { homedir, err := os.UserHomeDir() if err != nil { @@ -33,14 +21,36 @@ func BackupAndRestore(t *testing.T) func() { } } +func Check(t *testing.T, err error) { + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } +} + +func CheckWithInfo(t *testing.T, err error, additionalInfo string) { + if err != nil { + t.Fatalf("Unexpected error: %v! Additional info: %v", err, additionalInfo) + } +} + func EntryEquals(entry1, entry2 HistoryEntry) bool { - return entry1.UserSecret == entry2.UserSecret && - entry1.LocalUsername == entry2.LocalUsername && + return entry1.LocalUsername == entry2.LocalUsername && entry1.Hostname == entry2.Hostname && entry1.Command == entry2.Command && entry1.CurrentWorkingDirectory == entry2.CurrentWorkingDirectory && entry1.ExitCode == entry2.ExitCode && entry1.StartTime.Format(time.RFC3339) == entry2.StartTime.Format(time.RFC3339) && entry1.EndTime.Format(time.RFC3339) == entry2.EndTime.Format(time.RFC3339) - } + +func MakeFakeHistoryEntry(command string) HistoryEntry { + return HistoryEntry{ + LocalUsername: "david", + Hostname: "localhost", + Command: command, + CurrentWorkingDirectory: "/tmp/", + ExitCode: 2, + StartTime: time.Now(), + EndTime: time.Now(), + } +} \ No newline at end of file diff --git a/web/landing/www/hishtory-offline b/web/landing/www/hishtory-offline deleted file mode 100755 index fca9994..0000000 Binary files a/web/landing/www/hishtory-offline and /dev/null differ diff --git a/web/landing/www/hishtory-online b/web/landing/www/hishtory-online deleted file mode 100755 index 2a16334..0000000 Binary files a/web/landing/www/hishtory-online and /dev/null differ