diff --git a/Backend/Api/Auth/AddProfileImage.go b/Backend/Api/Auth/AddProfileImage.go index 31c7f64..af88ced 100644 --- a/Backend/Api/Auth/AddProfileImage.go +++ b/Backend/Api/Auth/AddProfileImage.go @@ -5,9 +5,9 @@ import ( "encoding/json" "net/http" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Util" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Util" ) // AddProfileImage adds a profile image @@ -35,7 +35,17 @@ func AddProfileImage(w http.ResponseWriter, r *http.Request) { } decodedFile, err = base64.StdEncoding.DecodeString(attachment.Data) + if err != nil { + http.Error(w, "Error", http.StatusInternalServerError) + return + } + fileName, err = Util.WriteFile(decodedFile) + if err != nil { + http.Error(w, "Error", http.StatusInternalServerError) + return + } + attachment.FilePath = fileName user.Attachment = attachment diff --git a/Backend/Api/Auth/AddProfileImage_test.go b/Backend/Api/Auth/AddProfileImage_test.go new file mode 100644 index 0000000..c0d0dc3 --- /dev/null +++ b/Backend/Api/Auth/AddProfileImage_test.go @@ -0,0 +1,79 @@ +package Auth_test + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "net/http" + "os" + "testing" + + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database/Seeder" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Tests" +) + +func Test_AddProfileImage(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + defer ts.Close() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + key, err := Seeder.GenerateAesKey() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + dat, err := os.ReadFile("./profile_picture_test.png") + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + encDat, err := key.AesEncrypt(dat) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + a := Models.Attachment{ + Mimetype: "image/png", + Extension: "png", + Data: base64.StdEncoding.EncodeToString(encDat), + } + + jsonStr, _ := json.Marshal(a) + req, _ := http.NewRequest("POST", ts.URL+"/api/v1/auth/image", bytes.NewBuffer(jsonStr)) + req.Header.Set("Content-Type", "application/json") + + resp, err := client.Do(req) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusNoContent { + t.Errorf("Expected %d, recieved %d", http.StatusNoContent, resp.StatusCode) + return + } + + u, err := Database.GetUserByUsername("test") + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if u.AttachmentID.IsNil() { + t.Errorf("Attachment not assigned to user") + } + + err = os.Remove("/app/attachments/" + u.Attachment.FilePath) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } +} diff --git a/Backend/Api/Auth/ChangeMessageExpiry.go b/Backend/Api/Auth/ChangeMessageExpiry.go index acad218..aa2fd5e 100644 --- a/Backend/Api/Auth/ChangeMessageExpiry.go +++ b/Backend/Api/Auth/ChangeMessageExpiry.go @@ -5,8 +5,8 @@ import ( "io/ioutil" "net/http" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" ) type rawChangeMessageExpiry struct { @@ -37,7 +37,11 @@ func ChangeMessageExpiry(w http.ResponseWriter, r *http.Request) { return } - user.MessageExpiryDefault.Scan(changeMessageExpiry.MessageExpiry) + err = user.MessageExpiryDefault.Scan(changeMessageExpiry.MessageExpiry) + if err != nil { + http.Error(w, "Error", http.StatusUnprocessableEntity) + return + } err = Database.UpdateUser( user.ID.String(), @@ -48,5 +52,5 @@ func ChangeMessageExpiry(w http.ResponseWriter, r *http.Request) { return } - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusNoContent) } diff --git a/Backend/Api/Auth/ChangeMessageExpiry_test.go b/Backend/Api/Auth/ChangeMessageExpiry_test.go new file mode 100644 index 0000000..2c48c75 --- /dev/null +++ b/Backend/Api/Auth/ChangeMessageExpiry_test.go @@ -0,0 +1,89 @@ +package Auth_test + +import ( + "bytes" + "encoding/json" + "net/http" + "testing" + + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Tests" +) + +func Test_ChangeMessageExpiry(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + defer ts.Close() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + d := struct { + MessageExpiry string `json:"message_expiry"` + }{ + MessageExpiry: "fifteen_min", + } + + jsonStr, _ := json.Marshal(d) + req, _ := http.NewRequest("POST", ts.URL+"/api/v1/auth/message_expiry", bytes.NewBuffer(jsonStr)) + req.Header.Set("Content-Type", "application/json") + + resp, err := client.Do(req) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusNoContent { + t.Errorf("Expected %d, recieved %d", http.StatusNoContent, resp.StatusCode) + } + + u, err := Database.GetUserByUsername("test") + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if u.MessageExpiryDefault.String() != "fifteen_min" { + t.Errorf("Failed to verify the MessageExpiryDefault has been changed") + } +} + +func Test_ChangeMessageExpiryInvalidData(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + defer ts.Close() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + d := struct { + MessageExpiry string `json:"message_expiry"` + }{ + MessageExpiry: "invalid_message_expiry", + } + + jsonStr, _ := json.Marshal(d) + req, _ := http.NewRequest("POST", ts.URL+"/api/v1/auth/message_expiry", bytes.NewBuffer(jsonStr)) + req.Header.Set("Content-Type", "application/json") + + resp, err := client.Do(req) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusUnprocessableEntity { + t.Errorf("Expected %d, recieved %d", http.StatusUnprocessableEntity, resp.StatusCode) + } + + u, err := Database.GetUserByUsername("test") + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if u.MessageExpiryDefault.String() != "no_expiry" { + t.Errorf("Failed to verify the MessageExpiryDefault has not been changed") + } +} diff --git a/Backend/Api/Auth/ChangePassword.go b/Backend/Api/Auth/ChangePassword.go index f4335cc..6e04bb5 100644 --- a/Backend/Api/Auth/ChangePassword.go +++ b/Backend/Api/Auth/ChangePassword.go @@ -5,8 +5,8 @@ import ( "io/ioutil" "net/http" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" ) type rawChangePassword struct { @@ -72,5 +72,5 @@ func ChangePassword(w http.ResponseWriter, r *http.Request) { return } - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusNoContent) } diff --git a/Backend/Api/Auth/ChangePassword_test.go b/Backend/Api/Auth/ChangePassword_test.go new file mode 100644 index 0000000..29c1e42 --- /dev/null +++ b/Backend/Api/Auth/ChangePassword_test.go @@ -0,0 +1,128 @@ +package Auth_test + +import ( + "bytes" + "encoding/json" + "net/http" + "testing" + + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Api/Auth" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Tests" +) + +func Test_ChangePassword(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + defer ts.Close() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + d := struct { + OldPassword string `json:"old_password"` + NewPassword string `json:"new_password"` + NewPasswordConfirm string `json:"new_password_confirm"` + PrivateKey string `json:"private_key"` + }{ + OldPassword: "password", + NewPassword: "password1", + NewPasswordConfirm: "password1", + PrivateKey: "", + } + + jsonStr, _ := json.Marshal(d) + req, _ := http.NewRequest("POST", ts.URL+"/api/v1/auth/change_password", bytes.NewBuffer(jsonStr)) + req.Header.Set("Content-Type", "application/json") + + resp, err := client.Do(req) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusNoContent { + t.Errorf("Expected %d, recieved %d", http.StatusNoContent, resp.StatusCode) + return + } + + u, err := Database.GetUserByUsername("test") + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if !Auth.CheckPasswordHash("password1", u.Password) { + t.Errorf("Failed to verify the password has been changed") + } +} + +func Test_ChangePasswordMismatchConfirmFails(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + defer ts.Close() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + d := struct { + OldPassword string `json:"old_password"` + NewPassword string `json:"new_password"` + NewPasswordConfirm string `json:"new_password_confirm"` + PrivateKey string `json:"private_key"` + }{ + OldPassword: "password", + NewPassword: "password1", + NewPasswordConfirm: "password2", + PrivateKey: "", + } + + jsonStr, _ := json.Marshal(d) + req, _ := http.NewRequest("POST", ts.URL+"/api/v1/auth/change_password", bytes.NewBuffer(jsonStr)) + req.Header.Set("Content-Type", "application/json") + + resp, err := client.Do(req) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusUnprocessableEntity { + t.Errorf("Expected %d, recieved %d", http.StatusUnprocessableEntity, resp.StatusCode) + } +} + +func Test_ChangePasswordInvalidCurrentPasswordFails(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + defer ts.Close() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + d := struct { + OldPassword string `json:"old_password"` + NewPassword string `json:"new_password"` + NewPasswordConfirm string `json:"new_password_confirm"` + PrivateKey string `json:"private_key"` + }{ + OldPassword: "password2", + NewPassword: "password1", + NewPasswordConfirm: "password1", + PrivateKey: "", + } + + jsonStr, _ := json.Marshal(d) + req, _ := http.NewRequest("POST", ts.URL+"/api/v1/auth/change_password", bytes.NewBuffer(jsonStr)) + req.Header.Set("Content-Type", "application/json") + + resp, err := client.Do(req) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusForbidden { + t.Errorf("Expected %d, recieved %d", http.StatusForbidden, resp.StatusCode) + } +} diff --git a/Backend/Api/Auth/Login.go b/Backend/Api/Auth/Login.go index d217493..7c72e07 100644 --- a/Backend/Api/Auth/Login.go +++ b/Backend/Api/Auth/Login.go @@ -6,8 +6,8 @@ import ( "net/http" "time" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" ) type credentials struct { diff --git a/Backend/Api/Auth/Login_test.go b/Backend/Api/Auth/Login_test.go new file mode 100644 index 0000000..7eef436 --- /dev/null +++ b/Backend/Api/Auth/Login_test.go @@ -0,0 +1,95 @@ +package Auth_test + +import ( + "bytes" + "encoding/json" + "net/http" + "testing" + + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Tests" +) + +func Test_Login(t *testing.T) { + _, ts, err := Tests.InitTestEnv() + defer ts.Close() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + d := struct { + Username string `json:"username"` + Password string `json:"password"` + }{ + Username: "test", + Password: "password", + } + + jsonStr, _ := json.Marshal(d) + req, _ := http.NewRequest("POST", ts.URL+"/api/v1/login", bytes.NewBuffer(jsonStr)) + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + u, err := Database.GetUserByUsername("test") + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + var session Models.Session + + err = Database.DB.First(&session, "user_id = ?", u.ID.String()).Error + + if err != nil { + t.Errorf("Expected user record, recieved %s", err.Error()) + return + } +} + +func Test_Login_PasswordFails(t *testing.T) { + _, ts, err := Tests.InitTestEnv() + defer ts.Close() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + d := struct { + Username string `json:"username"` + Password string `json:"password"` + }{ + Username: "test", + Password: "password1", + } + + jsonStr, _ := json.Marshal(d) + req, _ := http.NewRequest("POST", ts.URL+"/api/v1/login", bytes.NewBuffer(jsonStr)) + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusUnauthorized { + t.Errorf("Expected %d, recieved %d", http.StatusUnauthorized, resp.StatusCode) + return + } +} diff --git a/Backend/Api/Auth/Logout.go b/Backend/Api/Auth/Logout.go index 486b575..484fcc7 100644 --- a/Backend/Api/Auth/Logout.go +++ b/Backend/Api/Auth/Logout.go @@ -5,9 +5,10 @@ import ( "net/http" "time" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" ) +// Logout logs out from system func Logout(w http.ResponseWriter, r *http.Request) { var ( c *http.Cookie @@ -27,7 +28,7 @@ func Logout(w http.ResponseWriter, r *http.Request) { sessionToken = c.Value - err = Database.DeleteSessionById(sessionToken) + err = Database.DeleteSessionByID(sessionToken) if err != nil { log.Println("Could not delete session cookie") } @@ -37,4 +38,6 @@ func Logout(w http.ResponseWriter, r *http.Request) { Value: "", Expires: time.Now(), }) + + w.WriteHeader(http.StatusOK) } diff --git a/Backend/Api/Auth/Logout_test.go b/Backend/Api/Auth/Logout_test.go new file mode 100644 index 0000000..ca43f0c --- /dev/null +++ b/Backend/Api/Auth/Logout_test.go @@ -0,0 +1,44 @@ +package Auth_test + +import ( + "net/http" + "testing" + + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Tests" +) + +func Test_Logout(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + defer ts.Close() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + resp, err := client.Get(ts.URL + "/api/v1/logout") + if err != nil { + t.Errorf("Expected user record, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + var session Models.Session + + u, err := Database.GetUserByUsername("test") + if err != nil { + t.Errorf("Expected user record, recieved %s", err.Error()) + return + } + + err = Database.DB.First(&session, "user_id = ?", u.ID.String()).Error + if err == nil { + t.Errorf("Expected no session record, recieved %s", session.UserID) + return + } +} diff --git a/Backend/Api/Auth/Session.go b/Backend/Api/Auth/Session.go index ffcfae2..4c4c5a1 100644 --- a/Backend/Api/Auth/Session.go +++ b/Backend/Api/Auth/Session.go @@ -4,8 +4,8 @@ import ( "errors" "net/http" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" ) func CheckCookie(r *http.Request) (Models.Session, error) { @@ -23,7 +23,7 @@ func CheckCookie(r *http.Request) (Models.Session, error) { sessionToken = c.Value // We then get the session from our session map - userSession, err = Database.GetSessionById(sessionToken) + userSession, err = Database.GetSessionByID(sessionToken) if err != nil { return userSession, errors.New("Cookie not found") } diff --git a/Backend/Api/Auth/Signup.go b/Backend/Api/Auth/Signup.go index b60f880..90252a9 100644 --- a/Backend/Api/Auth/Signup.go +++ b/Backend/Api/Auth/Signup.go @@ -2,95 +2,67 @@ package Auth import ( "encoding/json" - "io/ioutil" - "log" "net/http" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/JsonSerialization" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" ) -type signupResponse struct { - Status string `json:"status"` - Message string `json:"message"` -} - -func makeSignupResponse(w http.ResponseWriter, code int, message string) { - var ( - status = "error" - returnJSON []byte - err error - ) - if code > 200 && code < 300 { - status = "success" - } - - returnJSON, err = json.MarshalIndent(signupResponse{ - Status: status, - Message: message, - }, "", " ") - if err != nil { - http.Error(w, "Error", http.StatusInternalServerError) - return - } - - // Return updated json - w.WriteHeader(code) - w.Write(returnJSON) - +type signup struct { + Username string `json:"username"` + Password string `json:"password"` + ConfirmPassword string `json:"confirm_password"` + PublicKey string `json:"asymmetric_public_key"` + PrivateKey string `json:"asymmetric_private_key"` + SymmetricKey string `json:"symmetric_key"` } // Signup to the platform func Signup(w http.ResponseWriter, r *http.Request) { var ( - userData Models.User - requestBody []byte - err error + user Models.User + err error ) - requestBody, err = ioutil.ReadAll(r.Body) + err = json.NewDecoder(r.Body).Decode(&user) if err != nil { - log.Printf("Error encountered reading POST body: %s\n", err.Error()) - makeSignupResponse(w, http.StatusInternalServerError, "An error occurred") + http.Error(w, "Invalid Data", http.StatusUnprocessableEntity) return } - userData, err = JsonSerialization.DeserializeUser(requestBody, []string{ - "id", - }, false) - if err != nil { - log.Printf("Invalid data provided to Signup: %s\n", err.Error()) - makeSignupResponse(w, http.StatusUnprocessableEntity, "Invalid data provided") + if user.Username == "" || + user.Password == "" || + user.ConfirmPassword == "" || + len(user.AsymmetricPrivateKey) == 0 || + len(user.AsymmetricPublicKey) == 0 || + len(user.SymmetricKey) == 0 { + + http.Error(w, "Invalid Data", http.StatusUnprocessableEntity) return } - if userData.Username == "" || - userData.Password == "" || - userData.ConfirmPassword == "" || - len(userData.AsymmetricPrivateKey) == 0 || - len(userData.AsymmetricPublicKey) == 0 { - makeSignupResponse(w, http.StatusUnprocessableEntity, "Invalid data provided") + if user.Password != user.ConfirmPassword { + http.Error(w, "Invalid Data", http.StatusUnprocessableEntity) return } - err = Database.CheckUniqueUsername(userData.Username) + err = Database.CheckUniqueUsername(user.Username) if err != nil { - makeSignupResponse(w, http.StatusUnprocessableEntity, "Invalid data provided") + http.Error(w, "Invalid Data", http.StatusUnprocessableEntity) return } - userData.Password, err = HashPassword(userData.Password) + user.Password, err = HashPassword(user.Password) if err != nil { - makeSignupResponse(w, http.StatusInternalServerError, "An error occurred") + http.Error(w, "Error", http.StatusInternalServerError) return } - err = Database.CreateUser(&userData) + err = Database.CreateUser(&user) if err != nil { - makeSignupResponse(w, http.StatusInternalServerError, "An error occurred") + http.Error(w, "Error", http.StatusInternalServerError) return } - makeSignupResponse(w, http.StatusCreated, "Successfully signed up") + w.WriteHeader(http.StatusNoContent) } diff --git a/Backend/Api/Auth/Signup_test.go b/Backend/Api/Auth/Signup_test.go new file mode 100644 index 0000000..c57b125 --- /dev/null +++ b/Backend/Api/Auth/Signup_test.go @@ -0,0 +1,168 @@ +package Auth_test + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "io/ioutil" + "log" + "net/http" + "net/http/httptest" + "testing" + + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Api" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database/Seeder" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" + "github.com/gorilla/mux" +) + +func Test_Signup(t *testing.T) { + log.SetOutput(ioutil.Discard) + Database.InitTest() + + r := mux.NewRouter() + Api.InitAPIEndpoints(r) + ts := httptest.NewServer(r) + defer ts.Close() + + userKey, _ := Seeder.GenerateAesKey() + + pubKey := Seeder.GetPubKey() + + d := struct { + Username string `json:"username"` + Password string `json:"password"` + ConfirmPassword string `json:"confirm_password"` + PubKey string `json:"asymmetric_public_key"` + PrivKey string `json:"asymmetric_private_key"` + SymKey string `json:"symmetric_key"` + }{ + Username: "test", + Password: "password", + ConfirmPassword: "password", + PubKey: Seeder.PublicKey, + PrivKey: Seeder.EncryptedPrivateKey, + SymKey: base64.StdEncoding.EncodeToString( + Seeder.EncryptWithPublicKey(userKey.Key, pubKey), + ), + } + + jsonStr, _ := json.Marshal(d) + req, _ := http.NewRequest("POST", ts.URL+"/api/v1/signup", bytes.NewBuffer(jsonStr)) + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusNoContent { + t.Errorf("Expected %d, recieved %d", http.StatusNoContent, resp.StatusCode) + return + } + + var user Models.User + + err = Database.DB.First(&user, "username = ?", "test").Error + + if err != nil { + t.Errorf("Expected user record, recieved %s", err.Error()) + return + } +} + +func Test_Signup_PasswordMismatchFails(t *testing.T) { + log.SetOutput(ioutil.Discard) + Database.InitTest() + + r := mux.NewRouter() + Api.InitAPIEndpoints(r) + ts := httptest.NewServer(r) + defer ts.Close() + + userKey, _ := Seeder.GenerateAesKey() + + pubKey := Seeder.GetPubKey() + + d := struct { + Username string `json:"username"` + Password string `json:"password"` + ConfirmPassword string `json:"confirm_password"` + PubKey string `json:"asymmetric_public_key"` + PrivKey string `json:"asymmetric_private_key"` + SymKey string `json:"symmetric_key"` + }{ + Username: "test", + Password: "password", + ConfirmPassword: "password1", + PubKey: Seeder.PublicKey, + PrivKey: Seeder.EncryptedPrivateKey, + SymKey: base64.StdEncoding.EncodeToString( + Seeder.EncryptWithPublicKey(userKey.Key, pubKey), + ), + } + + jsonStr, _ := json.Marshal(d) + req, _ := http.NewRequest("POST", ts.URL+"/api/v1/signup", bytes.NewBuffer(jsonStr)) + req.Header.Set("X-Custom-Header", "myvalue") + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusUnprocessableEntity { + t.Errorf("Expected %d, recieved %d", http.StatusUnprocessableEntity, resp.StatusCode) + return + } +} + +func Test_Signup_MissingDataFails(t *testing.T) { + log.SetOutput(ioutil.Discard) + Database.InitTest() + + r := mux.NewRouter() + Api.InitAPIEndpoints(r) + ts := httptest.NewServer(r) + defer ts.Close() + + d := struct { + Username string `json:"username"` + Password string `json:"password"` + ConfirmPassword string `json:"confirm_password"` + PubKey string `json:"asymmetric_public_key"` + PrivKey string `json:"asymmetric_private_key"` + SymKey string `json:"symmetric_key"` + }{ + Username: "test", + Password: "password", + ConfirmPassword: "password", + PubKey: "", + PrivKey: "", + SymKey: "", + } + + jsonStr, _ := json.Marshal(d) + req, _ := http.NewRequest("POST", ts.URL+"/api/v1/signup", bytes.NewBuffer(jsonStr)) + req.Header.Set("X-Custom-Header", "myvalue") + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + } + + if resp.StatusCode != http.StatusUnprocessableEntity { + t.Errorf("Expected %d, recieved %d", http.StatusUnprocessableEntity, resp.StatusCode) + } +} diff --git a/Backend/Api/Auth/profile_picture_test.png b/Backend/Api/Auth/profile_picture_test.png new file mode 100644 index 0000000..bec7dbc Binary files /dev/null and b/Backend/Api/Auth/profile_picture_test.png differ diff --git a/Backend/Api/Friends/AcceptFriendRequest.go b/Backend/Api/Friends/AcceptFriendRequest.go index aa9e233..9b68bdd 100644 --- a/Backend/Api/Friends/AcceptFriendRequest.go +++ b/Backend/Api/Friends/AcceptFriendRequest.go @@ -6,8 +6,8 @@ import ( "net/http" "time" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" "github.com/gorilla/mux" ) diff --git a/Backend/Api/Friends/CreateFriendRequest.go b/Backend/Api/Friends/CreateFriendRequest.go new file mode 100644 index 0000000..d7f0b53 --- /dev/null +++ b/Backend/Api/Friends/CreateFriendRequest.go @@ -0,0 +1,87 @@ +package Friends + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "time" + + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" +) + +// CreateFriendRequest creates a FriendRequest from post data +func CreateFriendRequest(w http.ResponseWriter, r *http.Request) { + var ( + friendRequest Models.FriendRequest + requestBody []byte + returnJSON []byte + err error + ) + + requestBody, err = ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, "Error", http.StatusInternalServerError) + return + } + + err = json.Unmarshal(requestBody, &friendRequest) + if err != nil { + http.Error(w, "Error", http.StatusInternalServerError) + return + } + + friendRequest.AcceptedAt.Scan(nil) + + err = Database.CreateFriendRequest(&friendRequest) + if err != nil { + http.Error(w, "Error", http.StatusInternalServerError) + return + } + + returnJSON, err = json.MarshalIndent(friendRequest, "", " ") + if err != nil { + http.Error(w, "Error", http.StatusInternalServerError) + return + } + + // Return updated json + w.WriteHeader(http.StatusOK) + w.Write(returnJSON) +} + +// CreateFriendRequestQrCode creates a FriendRequest from post data from qr code scan +func CreateFriendRequestQrCode(w http.ResponseWriter, r *http.Request) { + var ( + friendRequests []Models.FriendRequest + requestBody []byte + i int + err error + ) + + requestBody, err = ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, "Error", http.StatusInternalServerError) + return + } + + err = json.Unmarshal(requestBody, &friendRequests) + if err != nil { + http.Error(w, "Error", http.StatusInternalServerError) + return + } + + for i = range friendRequests { + friendRequests[i].AcceptedAt.Time = time.Now() + friendRequests[i].AcceptedAt.Valid = true + } + + err = Database.CreateFriendRequests(&friendRequests) + if err != nil { + http.Error(w, "Error", http.StatusInternalServerError) + return + } + + // Return updated json + w.WriteHeader(http.StatusOK) +} diff --git a/Backend/Api/Friends/EncryptedFriendsList.go b/Backend/Api/Friends/EncryptedFriendsList.go deleted file mode 100644 index 410c75c..0000000 --- a/Backend/Api/Friends/EncryptedFriendsList.go +++ /dev/null @@ -1,41 +0,0 @@ -package Friends - -import ( - "encoding/json" - "net/http" - - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Auth" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" -) - -// EncryptedFriendRequestList gets friend request list -func EncryptedFriendRequestList(w http.ResponseWriter, r *http.Request) { - var ( - userSession Models.Session - friends []Models.FriendRequest - returnJSON []byte - err error - ) - - userSession, err = Auth.CheckCookie(r) - if err != nil { - http.Error(w, "Forbidden", http.StatusUnauthorized) - return - } - - friends, err = Database.GetFriendRequestsByUserID(userSession.UserID.String()) - if err != nil { - http.Error(w, "Error", http.StatusInternalServerError) - return - } - - returnJSON, err = json.MarshalIndent(friends, "", " ") - if err != nil { - http.Error(w, "Error", http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) - w.Write(returnJSON) -} diff --git a/Backend/Api/Friends/FriendRequest.go b/Backend/Api/Friends/FriendRequest.go index 126605d..c704800 100644 --- a/Backend/Api/Friends/FriendRequest.go +++ b/Backend/Api/Friends/FriendRequest.go @@ -5,9 +5,9 @@ import ( "io/ioutil" "net/http" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Util" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Util" ) func FriendRequest(w http.ResponseWriter, r *http.Request) { diff --git a/Backend/Api/Friends/Friends.go b/Backend/Api/Friends/Friends.go index a1db196..3bd58ba 100644 --- a/Backend/Api/Friends/Friends.go +++ b/Backend/Api/Friends/Friends.go @@ -2,86 +2,47 @@ package Friends import ( "encoding/json" - "io/ioutil" "net/http" - "time" + "net/url" + "strconv" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Api/Auth" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" ) -// CreateFriendRequest creates a FriendRequest from post data -func CreateFriendRequest(w http.ResponseWriter, r *http.Request) { +// FriendRequestList gets friend request list +func FriendRequestList(w http.ResponseWriter, r *http.Request) { var ( - friendRequest Models.FriendRequest - requestBody []byte - returnJSON []byte - err error + userSession Models.Session + friends []Models.FriendRequest + values url.Values + returnJSON []byte + page int + err error ) - requestBody, err = ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, "Error", http.StatusInternalServerError) - return - } + values = r.URL.Query() - err = json.Unmarshal(requestBody, &friendRequest) + page, err = strconv.Atoi(values.Get("page")) if err != nil { - http.Error(w, "Error", http.StatusInternalServerError) - return + page = 0 } - friendRequest.AcceptedAt.Scan(nil) + userSession, _ = Auth.CheckCookie(r) - err = Database.CreateFriendRequest(&friendRequest) + friends, err = Database.GetFriendRequestsByUserID(userSession.UserID.String(), page) if err != nil { http.Error(w, "Error", http.StatusInternalServerError) return } - returnJSON, err = json.MarshalIndent(friendRequest, "", " ") + returnJSON, err = json.MarshalIndent(friends, "", " ") if err != nil { http.Error(w, "Error", http.StatusInternalServerError) return } - // Return updated json w.WriteHeader(http.StatusOK) w.Write(returnJSON) } - -// CreateFriendRequestQrCode creates a FriendRequest from post data from qr code scan -func CreateFriendRequestQrCode(w http.ResponseWriter, r *http.Request) { - var ( - friendRequests []Models.FriendRequest - requestBody []byte - i int - err error - ) - - requestBody, err = ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, "Error", http.StatusInternalServerError) - return - } - - err = json.Unmarshal(requestBody, &friendRequests) - if err != nil { - http.Error(w, "Error", http.StatusInternalServerError) - return - } - - for i = range friendRequests { - friendRequests[i].AcceptedAt.Time = time.Now() - friendRequests[i].AcceptedAt.Valid = true - } - - err = Database.CreateFriendRequests(&friendRequests) - if err != nil { - http.Error(w, "Error", http.StatusInternalServerError) - return - } - - // Return updated json - w.WriteHeader(http.StatusOK) -} diff --git a/Backend/Api/Friends/Friends_test.go b/Backend/Api/Friends/Friends_test.go new file mode 100644 index 0000000..d18de12 --- /dev/null +++ b/Backend/Api/Friends/Friends_test.go @@ -0,0 +1,124 @@ +package Friends_test + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "testing" + "time" + + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database/Seeder" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Tests" +) + +func Test_FriendRequestList(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + u, err := Database.GetUserByUsername("test") + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + for i := 0; i < 30; i++ { + u2, err := Tests.InitTestCreateUser(fmt.Sprintf("test%d", i)) + + decodedPublicKey := Seeder.GetPubKey() + + key, err := Seeder.GenerateAesKey() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + encPublicKey, err := key.AesEncrypt([]byte(Seeder.PublicKey)) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + friendReq := Models.FriendRequest{ + UserID: u.ID, + FriendID: base64.StdEncoding.EncodeToString( + Seeder.EncryptWithPublicKey( + []byte(u2.ID.String()), + decodedPublicKey, + ), + ), + FriendUsername: base64.StdEncoding.EncodeToString( + Seeder.EncryptWithPublicKey( + []byte(u2.Username), + decodedPublicKey, + ), + ), + FriendPublicAsymmetricKey: base64.StdEncoding.EncodeToString( + encPublicKey, + ), + SymmetricKey: base64.StdEncoding.EncodeToString( + Seeder.EncryptWithPublicKey(key.Key, decodedPublicKey), + ), + } + + if i > 20 { + friendReq.AcceptedAt.Time = time.Now() + friendReq.AcceptedAt.Valid = true + } + + err = Database.CreateFriendRequest(&friendReq) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + } + + req, _ := http.NewRequest("GET", ts.URL+"/api/v1/auth/friend_requests", nil) + + resp, err := client.Do(req) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + requestBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + var users []Models.FriendRequest + + json.Unmarshal(requestBody, &users) + + if len(users) != 20 { + t.Errorf("Expected %d, recieved %d", 1, len(users)) + return + } + + for i := 0; i < 20; i++ { + eq := true + if i > 8 { + eq = false + } + if users[i].AcceptedAt.Valid != eq { + t.Errorf( + "Expected %v, recieved %v, on user %d", + eq, users[i].AcceptedAt.Valid, + i, + ) + return + } + } +} diff --git a/Backend/Api/Friends/RejectFriendRequest.go b/Backend/Api/Friends/RejectFriendRequest.go index e341858..8ba5829 100644 --- a/Backend/Api/Friends/RejectFriendRequest.go +++ b/Backend/Api/Friends/RejectFriendRequest.go @@ -3,8 +3,8 @@ package Friends import ( "net/http" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" "github.com/gorilla/mux" ) diff --git a/Backend/Api/JsonSerialization/DeserializeUserJson.go b/Backend/Api/JsonSerialization/DeserializeUserJson.go deleted file mode 100644 index 9220be8..0000000 --- a/Backend/Api/JsonSerialization/DeserializeUserJson.go +++ /dev/null @@ -1,76 +0,0 @@ -package JsonSerialization - -import ( - "encoding/json" - "errors" - "fmt" - "strings" - - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" - - schema "github.com/Kangaroux/go-map-schema" -) - -func DeserializeUser(data []byte, allowMissing []string, allowAllMissing bool) (Models.User, error) { - var ( - userData Models.User = Models.User{} - jsonStructureTest map[string]interface{} = make(map[string]interface{}) - jsonStructureTestResults *schema.CompareResults - field schema.FieldMissing - allowed string - missingFields []string - i int - err error - ) - - // Verify the JSON has the correct structure - json.Unmarshal(data, &jsonStructureTest) - jsonStructureTestResults, err = schema.CompareMapToStruct( - &userData, - jsonStructureTest, - &schema.CompareOpts{ - ConvertibleFunc: CanConvert, - TypeNameFunc: schema.DetailedTypeName, - }) - if err != nil { - return userData, err - } - - if len(jsonStructureTestResults.MismatchedFields) > 0 { - return userData, errors.New(fmt.Sprintf( - "MismatchedFields found when deserializing data: %s", - jsonStructureTestResults.Errors().Error(), - )) - } - - // Remove allowed missing fields from MissingFields - for _, allowed = range allowMissing { - for i, field = range jsonStructureTestResults.MissingFields { - if allowed == field.String() { - jsonStructureTestResults.MissingFields = append( - jsonStructureTestResults.MissingFields[:i], - jsonStructureTestResults.MissingFields[i+1:]..., - ) - } - } - } - - if !allowAllMissing && len(jsonStructureTestResults.MissingFields) > 0 { - for _, field = range jsonStructureTestResults.MissingFields { - missingFields = append(missingFields, field.String()) - } - - return userData, errors.New(fmt.Sprintf( - "MissingFields found when deserializing data: %s", - strings.Join(missingFields, ", "), - )) - } - - // Deserialize the JSON into the struct - err = json.Unmarshal(data, &userData) - if err != nil { - return userData, err - } - - return userData, err -} diff --git a/Backend/Api/JsonSerialization/VerifyJson.go b/Backend/Api/JsonSerialization/VerifyJson.go deleted file mode 100644 index 3a3ae78..0000000 --- a/Backend/Api/JsonSerialization/VerifyJson.go +++ /dev/null @@ -1,109 +0,0 @@ -package JsonSerialization - -import ( - "math" - "reflect" -) - -// isIntegerType returns whether the type is an integer and if it's unsigned. -// See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L328 -func isIntegerType(t reflect.Type) (bool, bool) { - var ( - yes bool - unsigned bool - ) - switch t.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - yes = true - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - yes = true - unsigned = true - } - - return yes, unsigned -} - -// isFloatType returns true if the type is a floating point. Note that this doesn't -// care about the value -- unmarshaling the number "0" gives a float, not an int. -// See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L319 -func isFloatType(t reflect.Type) bool { - var ( - yes bool - ) - switch t.Kind() { - case reflect.Float32, reflect.Float64: - yes = true - } - - return yes -} - -// CanConvert returns whether value v is convertible to type t. -// -// If t is a pointer and v is not nil, it checks if v is convertible to the type that -// t points to. -// Modified due to not handling slices (DefaultCanConvert fails on PhotoUrls and Tags) -// See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L191 -func CanConvert(t reflect.Type, v reflect.Value) bool { - var ( - isPtr bool - isStruct bool - isArray bool - dstType reflect.Type - dstInt bool - unsigned bool - f float64 - srcInt bool - ) - - isPtr = t.Kind() == reflect.Ptr - isStruct = t.Kind() == reflect.Struct - isArray = t.Kind() == reflect.Array - dstType = t - - // Check if v is a nil value. - if !v.IsValid() || (v.CanAddr() && v.IsNil()) { - return isPtr - } - - // If the dst is a pointer, check if we can convert to the type it's pointing to. - if isPtr { - dstType = t.Elem() - isStruct = t.Elem().Kind() == reflect.Struct - } - - // If the dst is a struct, we should check its nested fields. - if isStruct { - return v.Kind() == reflect.Map - } - - if isArray { - return v.Kind() == reflect.String - } - - if t.Kind() == reflect.Slice { - return v.Kind() == reflect.Slice - } - - if !v.Type().ConvertibleTo(dstType) { - return false - } - - // Handle converting to an integer type. - dstInt, unsigned = isIntegerType(dstType) - if dstInt { - if isFloatType(v.Type()) { - f = v.Float() - - if math.Trunc(f) != f || unsigned && f < 0 { - return false - } - } - srcInt, _ = isIntegerType(v.Type()) - if srcInt && unsigned && v.Int() < 0 { - return false - } - } - - return true -} diff --git a/Backend/Api/Messages/AddConversationImage.go b/Backend/Api/Messages/AddConversationImage.go index 1da2866..31c36e9 100644 --- a/Backend/Api/Messages/AddConversationImage.go +++ b/Backend/Api/Messages/AddConversationImage.go @@ -5,9 +5,9 @@ import ( "encoding/json" "net/http" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Util" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Util" "github.com/gorilla/mux" ) diff --git a/Backend/Api/Messages/Conversations.go b/Backend/Api/Messages/Conversations.go index a1681da..1639111 100644 --- a/Backend/Api/Messages/Conversations.go +++ b/Backend/Api/Messages/Conversations.go @@ -4,30 +4,37 @@ import ( "encoding/json" "net/http" "net/url" + "strconv" "strings" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Auth" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Api/Auth" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" ) -// EncryptedConversationList returns an encrypted list of all Conversations -func EncryptedConversationList(w http.ResponseWriter, r *http.Request) { +// ConversationList returns an encrypted list of all Conversations +func ConversationList(w http.ResponseWriter, r *http.Request) { var ( conversationDetails []Models.UserConversation userSession Models.Session returnJSON []byte + values url.Values + page int err error ) - userSession, err = Auth.CheckCookie(r) + values = r.URL.Query() + + page, err = strconv.Atoi(values.Get("page")) if err != nil { - http.Error(w, "Forbidden", http.StatusUnauthorized) - return + page = 0 } + userSession, _ = Auth.CheckCookie(r) + conversationDetails, err = Database.GetUserConversationsByUserId( userSession.UserID.String(), + page, ) if err != nil { http.Error(w, "Error", http.StatusInternalServerError) @@ -44,8 +51,8 @@ func EncryptedConversationList(w http.ResponseWriter, r *http.Request) { w.Write(returnJSON) } -// EncryptedConversationDetailsList returns an encrypted list of all ConversationDetails -func EncryptedConversationDetailsList(w http.ResponseWriter, r *http.Request) { +// ConversationDetailsList returns an encrypted list of all ConversationDetails +func ConversationDetailsList(w http.ResponseWriter, r *http.Request) { var ( conversationDetails []Models.ConversationDetail detail Models.ConversationDetail diff --git a/Backend/Api/Messages/Conversations_test.go b/Backend/Api/Messages/Conversations_test.go new file mode 100644 index 0000000..49f3654 --- /dev/null +++ b/Backend/Api/Messages/Conversations_test.go @@ -0,0 +1,256 @@ +package Messages_test + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "testing" + + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database/Seeder" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Tests" +) + +func Test_ConversationsList(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + u, err := Database.GetUserByUsername("test") + + key, err := Seeder.GenerateAesKey() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + nameCiphertext, err := key.AesEncrypt([]byte("Test conversation")) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + twoUserCiphertext, err := key.AesEncrypt([]byte("false")) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + messageThread := Models.ConversationDetail{ + Name: base64.StdEncoding.EncodeToString(nameCiphertext), + TwoUser: base64.StdEncoding.EncodeToString(twoUserCiphertext), + } + + err = Database.CreateConversationDetail(&messageThread) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + conversationDetailIDCiphertext, err := key.AesEncrypt([]byte(messageThread.ID.String())) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + adminCiphertext, err := key.AesEncrypt([]byte("true")) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + pubKey := Seeder.GetPubKey() + + messageThreadUser := Models.UserConversation{ + UserID: u.ID, + ConversationDetailID: base64.StdEncoding.EncodeToString(conversationDetailIDCiphertext), + Admin: base64.StdEncoding.EncodeToString(adminCiphertext), + SymmetricKey: base64.StdEncoding.EncodeToString( + Seeder.EncryptWithPublicKey(key.Key, pubKey), + ), + } + + err = Database.CreateUserConversation(&messageThreadUser) + + req, _ := http.NewRequest("GET", ts.URL+"/api/v1/auth/conversations", nil) + + resp, err := client.Do(req) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + requestBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + var conversations []Models.UserConversation + + json.Unmarshal(requestBody, &conversations) + + if len(conversations) != 1 { + t.Errorf("Expected %d, recieved %d", 1, len(conversations)) + return + } + + conv := conversations[0] + + decodedId, err := base64.StdEncoding.DecodeString(conv.ConversationDetailID) + if err != nil { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + decrypedId, err := key.AesDecrypt(decodedId) + if err != nil { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + req, _ = http.NewRequest( + "GET", + ts.URL+"/api/v1/auth/conversation_details?conversation_detail_ids="+string(decrypedId), + nil, + ) + + resp, err = client.Do(req) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + var conversationDetails []Models.ConversationDetail + + requestBody, err = ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + json.Unmarshal(requestBody, &conversationDetails) + + if len(conversationDetails) != 1 { + t.Errorf("Expected %d, recieved %d", 1, len(conversations)) + } + + decodedName, err := base64.StdEncoding.DecodeString(conversationDetails[0].Name) + if err != nil { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + decrypedName, err := key.AesDecrypt(decodedName) + if err != nil { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + if string(decrypedName) != "Test conversation" { + t.Errorf("Expected %s, recieved %s", "Test converation", string(decrypedName)) + } +} + +func Test_ConversationsListPagination(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + u, err := Database.GetUserByUsername("test") + + key, err := Seeder.GenerateAesKey() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + for i := 0; i < 40; i++ { + nameCiphertext, err := key.AesEncrypt([]byte( + fmt.Sprintf("Test conversation %d", i), + )) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + twoUserCiphertext, err := key.AesEncrypt([]byte("false")) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + messageThread := Models.ConversationDetail{ + Name: base64.StdEncoding.EncodeToString(nameCiphertext), + TwoUser: base64.StdEncoding.EncodeToString(twoUserCiphertext), + } + + err = Database.CreateConversationDetail(&messageThread) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + conversationDetailIDCiphertext, err := key.AesEncrypt([]byte(messageThread.ID.String())) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + adminCiphertext, err := key.AesEncrypt([]byte("true")) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + pubKey := Seeder.GetPubKey() + + messageThreadUser := Models.UserConversation{ + UserID: u.ID, + ConversationDetailID: base64.StdEncoding.EncodeToString(conversationDetailIDCiphertext), + Admin: base64.StdEncoding.EncodeToString(adminCiphertext), + SymmetricKey: base64.StdEncoding.EncodeToString( + Seeder.EncryptWithPublicKey(key.Key, pubKey), + ), + } + + err = Database.CreateUserConversation(&messageThreadUser) + } + + req, _ := http.NewRequest("GET", ts.URL+"/api/v1/auth/conversations?page=0", nil) + + resp, err := client.Do(req) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + requestBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + var conversations []Models.UserConversation + + json.Unmarshal(requestBody, &conversations) + + if len(conversations) != 20 { + t.Errorf("Expected %d, recieved %d", 1, len(conversations)) + } +} diff --git a/Backend/Api/Messages/CreateConversation.go b/Backend/Api/Messages/CreateConversation.go index 41de38c..12d54e1 100644 --- a/Backend/Api/Messages/CreateConversation.go +++ b/Backend/Api/Messages/CreateConversation.go @@ -6,8 +6,8 @@ import ( "github.com/gofrs/uuid" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" ) // RawCreateConversationData for holding POST payload @@ -54,5 +54,5 @@ func CreateConversation(w http.ResponseWriter, r *http.Request) { return } - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusNoContent) } diff --git a/Backend/Api/Messages/CreateConversation_test.go b/Backend/Api/Messages/CreateConversation_test.go new file mode 100644 index 0000000..5659dff --- /dev/null +++ b/Backend/Api/Messages/CreateConversation_test.go @@ -0,0 +1,128 @@ +package Messages_test + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "net/http" + "testing" + + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database/Seeder" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Tests" + "github.com/gofrs/uuid" +) + +func Test_CreateConversation(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + u, err := Database.GetUserByUsername("test") + + key, err := Seeder.GenerateAesKey() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + nameCiphertext, err := key.AesEncrypt([]byte("Test conversation")) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + twoUserCiphertext, err := key.AesEncrypt([]byte("false")) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + id, err := uuid.NewV4() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + conversationDetailIDCiphertext, err := key.AesEncrypt([]byte(id.String())) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + adminCiphertext, err := key.AesEncrypt([]byte("true")) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + userIDCiphertext, err := key.AesEncrypt([]byte(u.ID.String())) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + usernameCiphertext, err := key.AesEncrypt([]byte(u.Username)) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + pubKey := Seeder.GetPubKey() + + d := struct { + ID string `json:"id"` + Name string `json:"name"` + TwoUser string `json:"two_user"` + Users []Models.ConversationDetailUser `json:"users"` + UserConversations []Models.UserConversation `json:"user_conversations"` + }{ + ID: id.String(), + Name: base64.StdEncoding.EncodeToString(nameCiphertext), + TwoUser: base64.StdEncoding.EncodeToString(twoUserCiphertext), + Users: []Models.ConversationDetailUser{ + { + ConversationDetailID: id, + UserID: base64.StdEncoding.EncodeToString(userIDCiphertext), + Username: base64.StdEncoding.EncodeToString(usernameCiphertext), + AssociationKey: "", + PublicKey: "", + Admin: base64.StdEncoding.EncodeToString(adminCiphertext), + }, + }, + UserConversations: []Models.UserConversation{ + { + UserID: u.ID, + ConversationDetailID: base64.StdEncoding.EncodeToString(conversationDetailIDCiphertext), + Admin: base64.StdEncoding.EncodeToString(adminCiphertext), + SymmetricKey: base64.StdEncoding.EncodeToString( + Seeder.EncryptWithPublicKey(key.Key, pubKey), + ), + }, + }, + } + + jsonStr, _ := json.Marshal(d) + req, _ := http.NewRequest("POST", ts.URL+"/api/v1/auth/conversations", bytes.NewBuffer(jsonStr)) + req.Header.Set("Content-Type", "application/json") + + resp, err := client.Do(req) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusNoContent { + t.Errorf("Expected %d, recieved %d", http.StatusNoContent, resp.StatusCode) + } + + var c Models.ConversationDetail + err = Database.DB.First(&c, "id = ?", id.String()).Error + if err != nil { + t.Errorf("Expected conversation detail record, received %s", err.Error()) + return + } +} diff --git a/Backend/Api/Messages/CreateMessage.go b/Backend/Api/Messages/CreateMessage.go index 052f128..04cb15e 100644 --- a/Backend/Api/Messages/CreateMessage.go +++ b/Backend/Api/Messages/CreateMessage.go @@ -4,10 +4,11 @@ import ( "encoding/base64" "encoding/json" "net/http" + "time" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Util" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Util" ) type rawMessageData struct { @@ -20,8 +21,11 @@ func CreateMessage(w http.ResponseWriter, r *http.Request) { var ( messagesData []rawMessageData messageData rawMessageData + message Models.Message + t time.Time decodedFile []byte fileName string + i int err error ) @@ -38,6 +42,19 @@ func CreateMessage(w http.ResponseWriter, r *http.Request) { messageData.MessageData.Attachment.FilePath = fileName } + for i, message = range messageData.Messages { + t, err = time.Parse("2006-01-02T15:04:05Z", message.ExpiryRaw) + if err != nil { + http.Error(w, "Error", http.StatusInternalServerError) + return + } + err = messageData.Messages[i].Expiry.Scan(t) + if err != nil { + http.Error(w, "Error", http.StatusInternalServerError) + return + } + } + err = Database.CreateMessageData(&messageData.MessageData) if err != nil { http.Error(w, "Error", http.StatusInternalServerError) @@ -51,5 +68,5 @@ func CreateMessage(w http.ResponseWriter, r *http.Request) { } } - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusNoContent) } diff --git a/Backend/Api/Messages/CreateMessage_test.go b/Backend/Api/Messages/CreateMessage_test.go new file mode 100644 index 0000000..81666c5 --- /dev/null +++ b/Backend/Api/Messages/CreateMessage_test.go @@ -0,0 +1,133 @@ +package Messages_test + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "net/http" + "testing" + "time" + + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database/Seeder" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Tests" + "github.com/gofrs/uuid" +) + +// TODO: Write test for message expiry + +func Test_CreateMessage(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + u, err := Database.GetUserByUsername("test") + + key, err := Seeder.GenerateAesKey() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + dataCiphertext, err := key.AesEncrypt([]byte("Test message...")) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + senderIDCiphertext, err := key.AesEncrypt([]byte(u.ID.String())) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + id, err := uuid.NewV4() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + id2, err := uuid.NewV4() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + d := []struct { + MessageData struct { + ID uuid.UUID `json:"id"` + Data string `json:"data"` + SenderID string `json:"sender_id"` + SymmetricKey string `json:"symmetric_key"` + } `json:"message_data"` + Messages []struct { + ID uuid.UUID `json:"id"` + MessageDataID uuid.UUID `json:"message_data_id"` + SymmetricKey string `json:"symmetric_key"` + AssociationKey string `json:"association_key"` + Expiry time.Time `json:"expiry"` + } `json:"message"` + }{ + { + MessageData: struct { + ID uuid.UUID `json:"id"` + Data string `json:"data"` + SenderID string `json:"sender_id"` + SymmetricKey string `json:"symmetric_key"` + }{ + ID: id, + Data: base64.StdEncoding.EncodeToString(dataCiphertext), + SenderID: base64.StdEncoding.EncodeToString(senderIDCiphertext), + SymmetricKey: "", + }, + Messages: []struct { + ID uuid.UUID `json:"id"` + MessageDataID uuid.UUID `json:"message_data_id"` + SymmetricKey string `json:"symmetric_key"` + AssociationKey string `json:"association_key"` + Expiry time.Time `json:"expiry"` + }{ + { + ID: id2, + MessageDataID: id, + SymmetricKey: "", + AssociationKey: "", + Expiry: time.Now(), + }, + }, + }, + } + + jsonStr, _ := json.Marshal(d) + req, _ := http.NewRequest("POST", ts.URL+"/api/v1/auth/message", bytes.NewBuffer(jsonStr)) + req.Header.Set("Content-Type", "application/json") + + resp, err := client.Do(req) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusNoContent { + t.Errorf("Expected %d, recieved %d", http.StatusNoContent, resp.StatusCode) + return + } + + var m Models.Message + err = Database.DB.First(&m).Error + if err != nil { + t.Errorf("Expected conversation detail record, received %s", err.Error()) + return + } + + var md Models.MessageData + err = Database.DB.First(&md).Error + if err != nil { + t.Errorf("Expected conversation detail record, received %s", err.Error()) + return + } + +} diff --git a/Backend/Api/Messages/MessageThread.go b/Backend/Api/Messages/MessageThread.go index 686f1c1..1135c20 100644 --- a/Backend/Api/Messages/MessageThread.go +++ b/Backend/Api/Messages/MessageThread.go @@ -3,9 +3,11 @@ package Messages import ( "encoding/json" "net/http" + "net/url" + "strconv" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" "github.com/gorilla/mux" ) @@ -17,7 +19,9 @@ func Messages(w http.ResponseWriter, r *http.Request) { message Models.Message urlVars map[string]string associationKey string + values url.Values returnJSON []byte + page int i int ok bool err error @@ -30,7 +34,14 @@ func Messages(w http.ResponseWriter, r *http.Request) { return } - messages, err = Database.GetMessagesByAssociationKey(associationKey) + values = r.URL.Query() + + page, err = strconv.Atoi(values.Get("page")) + if err != nil { + page = 0 + } + + messages, err = Database.GetMessagesByAssociationKey(associationKey, page) if !ok { http.Error(w, "Not Found", http.StatusNotFound) return diff --git a/Backend/Api/Messages/MessageThread_test.go b/Backend/Api/Messages/MessageThread_test.go new file mode 100644 index 0000000..bf4325e --- /dev/null +++ b/Backend/Api/Messages/MessageThread_test.go @@ -0,0 +1,205 @@ +package Messages_test + +import ( + "encoding/base64" + "encoding/json" + "io/ioutil" + "net/http" + "testing" + + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database/Seeder" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Tests" +) + +func Test_Messages(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + u, err := Database.GetUserByUsername("test") + + userKey, err := Seeder.GenerateAesKey() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + key, err := Seeder.GenerateAesKey() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + dataCiphertext, err := key.AesEncrypt([]byte("Test message")) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + senderIDCiphertext, err := key.AesEncrypt([]byte(u.ID.String())) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + keyCiphertext, err := userKey.AesEncrypt( + []byte(base64.StdEncoding.EncodeToString(key.Key)), + ) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + pubKey := Seeder.GetPubKey() + + message := Models.Message{ + MessageData: Models.MessageData{ + Data: base64.StdEncoding.EncodeToString(dataCiphertext), + SenderID: base64.StdEncoding.EncodeToString(senderIDCiphertext), + SymmetricKey: base64.StdEncoding.EncodeToString(keyCiphertext), + }, + SymmetricKey: base64.StdEncoding.EncodeToString( + Seeder.EncryptWithPublicKey(userKey.Key, pubKey), + ), + AssociationKey: "AssociationKey", + } + + err = Database.CreateMessage(&message) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + resp, err := client.Get(ts.URL + "/api/v1/auth/messages/AssociationKey") + if err != nil { + t.Errorf("Expected user record, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + requestBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + var m []Models.Message + err = json.Unmarshal(requestBody, &m) + + if len(m) != 1 { + t.Errorf("Expected %d, recieved %d", 1, len(m)) + } + + msg := m[0] + + decodedData, err := base64.StdEncoding.DecodeString(msg.MessageData.Data) + if err != nil { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + decrypedData, err := key.AesDecrypt(decodedData) + if err != nil { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + if string(decrypedData) != "Test message" { + t.Errorf("Expected %s, recieved %s", "Test converation", string(decrypedData)) + } +} + +func Test_MessagesPagination(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + u, err := Database.GetUserByUsername("test") + + userKey, err := Seeder.GenerateAesKey() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + key, err := Seeder.GenerateAesKey() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + dataCiphertext, err := key.AesEncrypt([]byte("Test message")) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + senderIDCiphertext, err := key.AesEncrypt([]byte(u.ID.String())) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + keyCiphertext, err := userKey.AesEncrypt( + []byte(base64.StdEncoding.EncodeToString(key.Key)), + ) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + pubKey := Seeder.GetPubKey() + + for i := 0; i < 50; i++ { + message := Models.Message{ + MessageData: Models.MessageData{ + Data: base64.StdEncoding.EncodeToString(dataCiphertext), + SenderID: base64.StdEncoding.EncodeToString(senderIDCiphertext), + SymmetricKey: base64.StdEncoding.EncodeToString(keyCiphertext), + }, + SymmetricKey: base64.StdEncoding.EncodeToString( + Seeder.EncryptWithPublicKey(userKey.Key, pubKey), + ), + AssociationKey: "AssociationKey", + } + + err = Database.CreateMessage(&message) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + } + + resp, err := client.Get(ts.URL + "/api/v1/auth/messages/AssociationKey") + if err != nil { + t.Errorf("Expected user record, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + requestBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + var m []Models.Message + err = json.Unmarshal(requestBody, &m) + + if len(m) != 20 { + t.Errorf("Expected %d, recieved %d", 20, len(m)) + } +} diff --git a/Backend/Api/Messages/UpdateConversation.go b/Backend/Api/Messages/UpdateConversation.go index 4900ba8..67d2a2c 100644 --- a/Backend/Api/Messages/UpdateConversation.go +++ b/Backend/Api/Messages/UpdateConversation.go @@ -6,8 +6,8 @@ import ( "github.com/gofrs/uuid" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" ) type rawUpdateConversationData struct { diff --git a/Backend/Api/Messages/UpdateConversation_test.go b/Backend/Api/Messages/UpdateConversation_test.go new file mode 100644 index 0000000..cdc3684 --- /dev/null +++ b/Backend/Api/Messages/UpdateConversation_test.go @@ -0,0 +1,183 @@ +package Messages_test + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "net/http" + "testing" + + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database/Seeder" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Tests" +) + +func createConversation(key Seeder.AesKey) (Models.ConversationDetail, Models.UserConversation, Models.ConversationDetailUser, error) { + var ( + cd Models.ConversationDetail + uc Models.UserConversation + cdu Models.ConversationDetailUser + ) + + u, err := Database.GetUserByUsername("test") + + nameCiphertext, err := key.AesEncrypt([]byte("Test conversation")) + if err != nil { + return cd, uc, cdu, err + } + + twoUserCiphertext, err := key.AesEncrypt([]byte("false")) + if err != nil { + return cd, uc, cdu, err + } + + cd = Models.ConversationDetail{ + Name: base64.StdEncoding.EncodeToString(nameCiphertext), + TwoUser: base64.StdEncoding.EncodeToString(twoUserCiphertext), + } + + err = Database.CreateConversationDetail(&cd) + if err != nil { + return cd, uc, cdu, err + } + + conversationDetailIDCiphertext, err := key.AesEncrypt([]byte(cd.ID.String())) + if err != nil { + return cd, uc, cdu, err + } + + adminCiphertext, err := key.AesEncrypt([]byte("true")) + if err != nil { + return cd, uc, cdu, err + } + + pubKey := Seeder.GetPubKey() + + uc = Models.UserConversation{ + UserID: u.ID, + ConversationDetailID: base64.StdEncoding.EncodeToString(conversationDetailIDCiphertext), + Admin: base64.StdEncoding.EncodeToString(adminCiphertext), + SymmetricKey: base64.StdEncoding.EncodeToString( + Seeder.EncryptWithPublicKey(key.Key, pubKey), + ), + } + + err = Database.CreateUserConversation(&uc) + if err != nil { + return cd, uc, cdu, err + } + + userIDCiphertext, err := key.AesEncrypt([]byte(u.ID.String())) + if err != nil { + return cd, uc, cdu, err + } + + usernameCiphertext, err := key.AesEncrypt([]byte(u.Username)) + if err != nil { + return cd, uc, cdu, err + } + + adminCiphertext, err = key.AesEncrypt([]byte("true")) + if err != nil { + return cd, uc, cdu, err + } + + associationKeyCiphertext, err := key.AesEncrypt([]byte("association")) + if err != nil { + return cd, uc, cdu, err + } + + publicKeyCiphertext, err := key.AesEncrypt([]byte(u.AsymmetricPublicKey)) + if err != nil { + return cd, uc, cdu, err + } + + cdu = Models.ConversationDetailUser{ + ConversationDetailID: cd.ID, + UserID: base64.StdEncoding.EncodeToString(userIDCiphertext), + Username: base64.StdEncoding.EncodeToString(usernameCiphertext), + Admin: base64.StdEncoding.EncodeToString(adminCiphertext), + AssociationKey: base64.StdEncoding.EncodeToString(associationKeyCiphertext), + PublicKey: base64.StdEncoding.EncodeToString(publicKeyCiphertext), + } + + err = Database.CreateConversationDetailUser(&cdu) + return cd, uc, cdu, err +} + +func Test_UpdateConversation(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + // u, err := Database.GetUserByUsername("test") + + key, err := Seeder.GenerateAesKey() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + cd, uc, cdu, err := createConversation(key) + + nameCiphertext, err := key.AesEncrypt([]byte("Not test conversation")) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + } + + d := struct { + ID string `json:"id"` + Name string `json:"name"` + Users []Models.ConversationDetailUser + UserConversations []Models.UserConversation + }{ + ID: cd.ID.String(), + Name: base64.StdEncoding.EncodeToString(nameCiphertext), + Users: []Models.ConversationDetailUser{ + cdu, + }, + UserConversations: []Models.UserConversation{ + uc, + }, + } + + jsonStr, _ := json.Marshal(d) + req, _ := http.NewRequest("PUT", ts.URL+"/api/v1/auth/conversations", bytes.NewBuffer(jsonStr)) + req.Header.Set("Content-Type", "application/json") + + resp, err := client.Do(req) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusNoContent { + t.Errorf("Expected %d, recieved %d", http.StatusNoContent, resp.StatusCode) + } + + var ncd Models.ConversationDetail + err = Database.DB.First(&ncd, "id = ?", cd.ID.String()).Error + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + decodedName, err := base64.StdEncoding.DecodeString(ncd.Name) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + decrypedName, err := key.AesDecrypt(decodedName) + if err != nil { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + if string(decrypedName) != "Not test conversation" { + t.Errorf("Expected %s, recieved %s", "Not test converation", string(decrypedName)) + } + +} diff --git a/Backend/Api/Routes.go b/Backend/Api/Routes.go index 8b0c280..73d98f4 100644 --- a/Backend/Api/Routes.go +++ b/Backend/Api/Routes.go @@ -4,10 +4,10 @@ import ( "log" "net/http" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Auth" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Friends" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Messages" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Users" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Api/Auth" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Api/Friends" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Api/Messages" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Api/Users" "github.com/gorilla/mux" ) @@ -68,14 +68,14 @@ func InitAPIEndpoints(router *mux.Router) { authAPI.HandleFunc("/users", Users.SearchUsers).Methods("GET") - authAPI.HandleFunc("/friend_requests", Friends.EncryptedFriendRequestList).Methods("GET") + authAPI.HandleFunc("/friend_requests", Friends.FriendRequestList).Methods("GET") authAPI.HandleFunc("/friend_request", Friends.CreateFriendRequest).Methods("POST") authAPI.HandleFunc("/friend_request/qr_code", Friends.CreateFriendRequestQrCode).Methods("POST") authAPI.HandleFunc("/friend_request/{requestID}", Friends.AcceptFriendRequest).Methods("POST") authAPI.HandleFunc("/friend_request/{requestID}", Friends.RejectFriendRequest).Methods("DELETE") - authAPI.HandleFunc("/conversations", Messages.EncryptedConversationList).Methods("GET") - authAPI.HandleFunc("/conversation_details", Messages.EncryptedConversationDetailsList).Methods("GET") + authAPI.HandleFunc("/conversations", Messages.ConversationList).Methods("GET") + authAPI.HandleFunc("/conversation_details", Messages.ConversationDetailsList).Methods("GET") authAPI.HandleFunc("/conversations", Messages.CreateConversation).Methods("POST") authAPI.HandleFunc("/conversations", Messages.UpdateConversation).Methods("PUT") authAPI.HandleFunc("/conversations/{detailID}/image", Messages.AddConversationImage).Methods("POST") diff --git a/Backend/Api/Users/SearchUsers.go b/Backend/Api/Users/SearchUsers.go index 56ecd89..a073749 100644 --- a/Backend/Api/Users/SearchUsers.go +++ b/Backend/Api/Users/SearchUsers.go @@ -5,8 +5,8 @@ import ( "net/http" "net/url" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" ) // SearchUsers searches a for a user by username @@ -46,7 +46,6 @@ func SearchUsers(w http.ResponseWriter, r *http.Request) { returnJSON, err = json.MarshalIndent(user, "", " ") if err != nil { - panic(err) http.Error(w, "Not Found", http.StatusNotFound) return } diff --git a/Backend/Api/Users/SearchUsers_test.go b/Backend/Api/Users/SearchUsers_test.go new file mode 100644 index 0000000..1b018f3 --- /dev/null +++ b/Backend/Api/Users/SearchUsers_test.go @@ -0,0 +1,106 @@ +package Users_test + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "testing" + + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Tests" +) + +func Test_SearchUsers(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + u2, err := Tests.InitTestCreateUser("abcd") + + req, _ := http.NewRequest( + "GET", + fmt.Sprintf("%s/api/v1/auth/users?username=%s", ts.URL, u2.Username), + nil, + ) + + resp, err := client.Do(req) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + requestBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + var user Models.User + + json.Unmarshal(requestBody, &user) + + if user.Username != "abcd" { + t.Errorf("Expected abcd, recieved %s", user.Username) + return + } + + if user.Password != "" { + t.Errorf("Expected \"\", recieved %s", user.Password) + return + } + + if user.AsymmetricPrivateKey != "" { + t.Errorf("Expected \"\", recieved %s", user.AsymmetricPrivateKey) + return + } +} + +func Test_SearchUsersPartialMatchFails(t *testing.T) { + client, ts, err := Tests.InitTestEnv() + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + _, err = Tests.InitTestCreateUser("abcd") + + req, _ := http.NewRequest( + "GET", + fmt.Sprintf("%s/api/v1/auth/users?username=%s", ts.URL, "abc"), + nil, + ) + + resp, err := client.Do(req) + if err != nil { + t.Errorf("Expected nil, recieved %s", err.Error()) + return + } + + if resp.StatusCode != http.StatusNotFound { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + requestBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("Expected %d, recieved %d", http.StatusOK, resp.StatusCode) + return + } + + var user interface{} + + json.Unmarshal(requestBody, &user) + + if user != nil { + t.Errorf("Expected nil, recieved %+v", user) + return + } +} diff --git a/Backend/Database/Attachments.go b/Backend/Database/Attachments.go index 3097a04..bda1415 100644 --- a/Backend/Database/Attachments.go +++ b/Backend/Database/Attachments.go @@ -1,7 +1,7 @@ package Database import ( - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" "gorm.io/gorm" "gorm.io/gorm/clause" diff --git a/Backend/Database/ConversationDetailUsers.go b/Backend/Database/ConversationDetailUsers.go index 6396acb..9215c0f 100644 --- a/Backend/Database/ConversationDetailUsers.go +++ b/Backend/Database/ConversationDetailUsers.go @@ -1,7 +1,7 @@ package Database import ( - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" "gorm.io/gorm" "gorm.io/gorm/clause" diff --git a/Backend/Database/ConversationDetails.go b/Backend/Database/ConversationDetails.go index af04edb..811fa62 100644 --- a/Backend/Database/ConversationDetails.go +++ b/Backend/Database/ConversationDetails.go @@ -1,7 +1,7 @@ package Database import ( - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" "gorm.io/gorm" "gorm.io/gorm/clause" diff --git a/Backend/Database/FriendRequests.go b/Backend/Database/FriendRequests.go index 0f6e58a..951a7a1 100644 --- a/Backend/Database/FriendRequests.go +++ b/Backend/Database/FriendRequests.go @@ -1,7 +1,7 @@ package Database import ( - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" "gorm.io/gorm" "gorm.io/gorm/clause" @@ -22,14 +22,20 @@ func GetFriendRequestByID(id string) (Models.FriendRequest, error) { } // GetFriendRequestsByUserID gets friend request by user id -func GetFriendRequestsByUserID(userID string) ([]Models.FriendRequest, error) { +func GetFriendRequestsByUserID(userID string, page int) ([]Models.FriendRequest, error) { var ( friends []Models.FriendRequest + offset int err error ) + offset = page * PageSize + err = DB.Model(Models.FriendRequest{}). Where("user_id = ?", userID). + Offset(offset). + Limit(PageSize). + Order("created_at DESC"). Find(&friends). Error diff --git a/Backend/Database/Init.go b/Backend/Database/Init.go index f4b6fb9..982a7d4 100644 --- a/Backend/Database/Init.go +++ b/Backend/Database/Init.go @@ -3,39 +3,38 @@ package Database import ( "log" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" "gorm.io/driver/postgres" "gorm.io/gorm" ) const ( - dbURL = "postgres://postgres:@localhost:5432/envelope" - dbTestURL = "postgres://postgres:@localhost:5432/envelope_test" + dbURL = "postgres://postgres:password@postgres:5432/capsule" + dbTestURL = "postgres://postgres:password@postgres-testing:5432/capsule-testing" + + PageSize = 20 ) // DB db var DB *gorm.DB -func getModels() []interface{} { - return []interface{}{ - &Models.Session{}, - &Models.Attachment{}, - &Models.User{}, - &Models.FriendRequest{}, - &Models.MessageData{}, - &Models.Message{}, - &Models.ConversationDetail{}, - &Models.ConversationDetailUser{}, - &Models.UserConversation{}, - } +var models = []interface{}{ + &Models.Session{}, + &Models.Attachment{}, + &Models.User{}, + &Models.FriendRequest{}, + &Models.MessageData{}, + &Models.Message{}, + &Models.ConversationDetail{}, + &Models.ConversationDetailUser{}, + &Models.UserConversation{}, } // Init initializes the database connection func Init() { var ( - model interface{} - err error + err error ) log.Println("Initializing database...") @@ -48,19 +47,16 @@ func Init() { log.Println("Running AutoMigrate...") - for _, model = range getModels() { - err = DB.AutoMigrate(model) - if err != nil { - log.Fatalln(err) - } + err = DB.AutoMigrate(models...) + if err != nil { + log.Fatalln(err) } } // InitTest initializes the test datbase func InitTest() { var ( - model interface{} - err error + err error ) DB, err = gorm.Open(postgres.Open(dbTestURL), &gorm.Config{}) @@ -69,8 +65,12 @@ func InitTest() { log.Fatalln(err) } - for _, model = range getModels() { - DB.Migrator().DropTable(model) - DB.AutoMigrate(model) + err = DB.Migrator().DropTable(models...) + if err != nil { + panic(err) + } + err = DB.AutoMigrate(models...) + if err != nil { + panic(err) } } diff --git a/Backend/Database/MessageData.go b/Backend/Database/MessageData.go index 80c6515..4198f2a 100644 --- a/Backend/Database/MessageData.go +++ b/Backend/Database/MessageData.go @@ -1,7 +1,7 @@ package Database import ( - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" "gorm.io/gorm" "gorm.io/gorm/clause" diff --git a/Backend/Database/Messages.go b/Backend/Database/Messages.go index f415c0e..37d0c14 100644 --- a/Backend/Database/Messages.go +++ b/Backend/Database/Messages.go @@ -1,7 +1,7 @@ package Database import ( - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" "gorm.io/gorm" "gorm.io/gorm/clause" @@ -22,15 +22,20 @@ func GetMessageByID(id string) (Models.Message, error) { } // GetMessagesByAssociationKey for getting whole thread -// TODO: Add pagination -func GetMessagesByAssociationKey(associationKey string) ([]Models.Message, error) { +func GetMessagesByAssociationKey(associationKey string, page int) ([]Models.Message, error) { var ( messages []Models.Message + offset int err error ) + offset = page * PageSize + err = DB.Preload("MessageData"). Preload("MessageData.Attachment"). + Offset(offset). + Limit(PageSize). + Order("created_at DESC"). Find(&messages, "association_key = ?", associationKey). Error diff --git a/Backend/Database/Seeder/FriendSeeder.go b/Backend/Database/Seeder/FriendSeeder.go index e317d13..43cdd0f 100644 --- a/Backend/Database/Seeder/FriendSeeder.go +++ b/Backend/Database/Seeder/FriendSeeder.go @@ -6,24 +6,24 @@ import ( "os" "time" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" ) func seedFriend(userRequestTo, userRequestFrom Models.User, accepted bool) error { var ( friendRequest Models.FriendRequest - symKey aesKey + symKey AesKey encPublicKey []byte err error ) - symKey, err = generateAesKey() + symKey, err = GenerateAesKey() if err != nil { return err } - encPublicKey, err = symKey.aesEncrypt([]byte(publicKey)) + encPublicKey, err = symKey.AesEncrypt([]byte(PublicKey)) if err != nil { return err } @@ -31,13 +31,13 @@ func seedFriend(userRequestTo, userRequestFrom Models.User, accepted bool) error friendRequest = Models.FriendRequest{ UserID: userRequestTo.ID, FriendID: base64.StdEncoding.EncodeToString( - encryptWithPublicKey( + EncryptWithPublicKey( []byte(userRequestFrom.ID.String()), decodedPublicKey, ), ), FriendUsername: base64.StdEncoding.EncodeToString( - encryptWithPublicKey( + EncryptWithPublicKey( []byte(userRequestFrom.Username), decodedPublicKey, ), @@ -46,7 +46,7 @@ func seedFriend(userRequestTo, userRequestFrom Models.User, accepted bool) error encPublicKey, ), SymmetricKey: base64.StdEncoding.EncodeToString( - encryptWithPublicKey(symKey.Key, decodedPublicKey), + EncryptWithPublicKey(symKey.Key, decodedPublicKey), ), } diff --git a/Backend/Database/Seeder/MessageSeeder.go b/Backend/Database/Seeder/MessageSeeder.go index 1bdffb9..a117742 100644 --- a/Backend/Database/Seeder/MessageSeeder.go +++ b/Backend/Database/Seeder/MessageSeeder.go @@ -3,8 +3,8 @@ package Seeder import ( "encoding/base64" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" "github.com/gofrs/uuid" ) @@ -17,7 +17,7 @@ func seedMessage( var ( message Models.Message messageData Models.MessageData - key, userKey aesKey + key, userKey AesKey keyCiphertext []byte plaintext string dataCiphertext []byte @@ -27,34 +27,34 @@ func seedMessage( plaintext = "Test Message" - userKey, err = generateAesKey() + userKey, err = GenerateAesKey() if err != nil { panic(err) } - key, err = generateAesKey() + key, err = GenerateAesKey() if err != nil { panic(err) } - dataCiphertext, err = key.aesEncrypt([]byte(plaintext)) + dataCiphertext, err = key.AesEncrypt([]byte(plaintext)) if err != nil { panic(err) } - senderIDCiphertext, err = key.aesEncrypt([]byte(primaryUser.ID.String())) + senderIDCiphertext, err = key.AesEncrypt([]byte(primaryUser.ID.String())) if err != nil { panic(err) } if i%2 == 0 { - senderIDCiphertext, err = key.aesEncrypt([]byte(secondaryUser.ID.String())) + senderIDCiphertext, err = key.AesEncrypt([]byte(secondaryUser.ID.String())) if err != nil { panic(err) } } - keyCiphertext, err = userKey.aesEncrypt( + keyCiphertext, err = userKey.AesEncrypt( []byte(base64.StdEncoding.EncodeToString(key.Key)), ) if err != nil { @@ -70,7 +70,7 @@ func seedMessage( message = Models.Message{ MessageData: messageData, SymmetricKey: base64.StdEncoding.EncodeToString( - encryptWithPublicKey(userKey.Key, decodedPublicKey), + EncryptWithPublicKey(userKey.Key, decodedPublicKey), ), AssociationKey: primaryUserAssociationKey, } @@ -83,7 +83,7 @@ func seedMessage( message = Models.Message{ MessageData: messageData, SymmetricKey: base64.StdEncoding.EncodeToString( - encryptWithPublicKey(userKey.Key, decodedPublicKey), + EncryptWithPublicKey(userKey.Key, decodedPublicKey), ), AssociationKey: secondaryUserAssociationKey, } @@ -91,7 +91,7 @@ func seedMessage( return Database.CreateMessage(&message) } -func seedConversationDetail(key aesKey) (Models.ConversationDetail, error) { +func seedConversationDetail(key AesKey) (Models.ConversationDetail, error) { var ( messageThread Models.ConversationDetail name string @@ -102,12 +102,12 @@ func seedConversationDetail(key aesKey) (Models.ConversationDetail, error) { name = "Test Conversation" - nameCiphertext, err = key.aesEncrypt([]byte(name)) + nameCiphertext, err = key.AesEncrypt([]byte(name)) if err != nil { panic(err) } - twoUserCiphertext, err = key.aesEncrypt([]byte("false")) + twoUserCiphertext, err = key.AesEncrypt([]byte("false")) if err != nil { panic(err) } @@ -124,7 +124,7 @@ func seedConversationDetail(key aesKey) (Models.ConversationDetail, error) { func seedUserConversation( user Models.User, threadID uuid.UUID, - key aesKey, + key AesKey, ) (Models.UserConversation, error) { var ( messageThreadUser Models.UserConversation @@ -133,12 +133,12 @@ func seedUserConversation( err error ) - conversationDetailIDCiphertext, err = key.aesEncrypt([]byte(threadID.String())) + conversationDetailIDCiphertext, err = key.AesEncrypt([]byte(threadID.String())) if err != nil { return messageThreadUser, err } - adminCiphertext, err = key.aesEncrypt([]byte("true")) + adminCiphertext, err = key.AesEncrypt([]byte("true")) if err != nil { return messageThreadUser, err } @@ -148,7 +148,7 @@ func seedUserConversation( ConversationDetailID: base64.StdEncoding.EncodeToString(conversationDetailIDCiphertext), Admin: base64.StdEncoding.EncodeToString(adminCiphertext), SymmetricKey: base64.StdEncoding.EncodeToString( - encryptWithPublicKey(key.Key, decodedPublicKey), + EncryptWithPublicKey(key.Key, decodedPublicKey), ), } @@ -161,7 +161,7 @@ func seedConversationDetailUser( conversationDetail Models.ConversationDetail, associationKey uuid.UUID, admin bool, - key aesKey, + key AesKey, ) (Models.ConversationDetailUser, error) { var ( conversationDetailUser Models.ConversationDetailUser @@ -181,27 +181,27 @@ func seedConversationDetailUser( adminString = "true" } - userIDCiphertext, err = key.aesEncrypt([]byte(user.ID.String())) + userIDCiphertext, err = key.AesEncrypt([]byte(user.ID.String())) if err != nil { return conversationDetailUser, err } - usernameCiphertext, err = key.aesEncrypt([]byte(user.Username)) + usernameCiphertext, err = key.AesEncrypt([]byte(user.Username)) if err != nil { return conversationDetailUser, err } - adminCiphertext, err = key.aesEncrypt([]byte(adminString)) + adminCiphertext, err = key.AesEncrypt([]byte(adminString)) if err != nil { return conversationDetailUser, err } - associationKeyCiphertext, err = key.aesEncrypt([]byte(associationKey.String())) + associationKeyCiphertext, err = key.AesEncrypt([]byte(associationKey.String())) if err != nil { return conversationDetailUser, err } - publicKeyCiphertext, err = key.aesEncrypt([]byte(user.AsymmetricPublicKey)) + publicKeyCiphertext, err = key.AesEncrypt([]byte(user.AsymmetricPublicKey)) if err != nil { return conversationDetailUser, err } @@ -224,7 +224,7 @@ func seedConversationDetailUser( func SeedMessages() { var ( conversationDetail Models.ConversationDetail - key aesKey + key AesKey primaryUser Models.User primaryUserAssociationKey uuid.UUID secondaryUser Models.User @@ -233,7 +233,7 @@ func SeedMessages() { err error ) - key, err = generateAesKey() + key, err = GenerateAesKey() if err != nil { panic(err) } diff --git a/Backend/Database/Seeder/Seed.go b/Backend/Database/Seeder/Seed.go index 7bd5c40..bdfc1d0 100644 --- a/Backend/Database/Seeder/Seed.go +++ b/Backend/Database/Seeder/Seed.go @@ -9,11 +9,11 @@ import ( ) const ( - // Encrypted with "password" - encryptedPrivateKey string = `sPhQsHpXYFqPb7qdmTY7APFwBb4m7meCITujDeKMQFnIjplOVm9ijjXU+YAmGvrX13ukBj8zo9MTVhjJUjJ917pyLhl4w8uyg1jCvplUYtJVXhGA9Wy3NqHMuq3SU3fKdlEM+oR4zYkbAYWp42XvulbcuVBEWiWkvHOrbdKPFpMmd54SL2c/vcWrmjgC7rTlJf2TYICZwRK+6Y0XZi5fSWeU0vg7+rHWKHc5MHHtAdAiL+HCa90c5gfh+hXkT5ojGHOkhT9kdLy3PTPN19EGpdXgZ3WFq1z9CZ6zX7uM091uR0IvgzfwaLx8HJCx7ViWQhioH9LJZgC73RMf/dwzejg2COy4QT/E59RPOczgd779rxiRmphMoR8xJYBFRlkTVmcUO4NcUE50Cc39hXezcekHuV1YQK4BXTrxGX1ceiCXYlKAWS9wHZpog9OldTCPBpw5XAWExh3kRzqdvsdHxHVE+TpAEIjDljAlc3r+FPHYH1zWWk41eQ/zz3Vkx5Zl4dMF9x+uUOspQXVb/4K42e9fMKychNUN5o/JzIwy7xOzgXa6iwf223On/mXKV6FK6Q8lojK7Wc8g7AwfqnN9//HjI14pVqGBJtn5ggL/g4qt0JFl3pV/6n/ZLMG6k8wpsaApLGvsTPqZHcv+C69Z33rZQ4TagXVxpmnWMpPCaR0+Dawn4iAce2UvUtIN2KbJNcTtRQo4z30+BbgmVKHgkR0EHMu4cYjJPYwJ5H8IYcQuFKb7+Cp33FD2Lv54I9uvtVHH9bWcid9K82y68PufJi/0icZ3EyEqZygez9mgJzxXO1b7xZMiosGs82QRv7IIOSzqBPRYv1Lxi3fWkgnOvw4dWFxJnKEI2+KD9K0z+XsgVlm26fdRklQAAf6xOJ1nJXBScbm12FBTWLMjLzHWz/iI9mQ+eGV9AREqrgQjUayXdnCsa0Q9bTTktxBkrJND4NUEDSGklhj9SY+VM0mhgAbkCvSE59vKtcNmCHx2Y+JnbZyKzJ71EaErX9vOpYCneKOjn8phVBJHQHM16QRLGyW4DUfn2CtAvb7Kks56kf/mn9YZDU68zSoLzm9rz7fjS2OUsxwmuv2IRCv/UTGgtfEfCs34qzagADfTNKTou7qkedhoygvuHiN4PzgGnjw1DQMks9PWr44z1gvIV4pEGiqgIuNHDjxKsfgQy0Cp2AV1+FNLWd1zd5t/K2pXR+knDoeHIZ2m6txQMl9I4GIyQ1bQFJWrYXPS8oMjvoH0YYVsHyShBsU2SKlG7nGbuUyoCR1EtRIzHMgP1Dq+Whqdbv67pRvhGVmydkCh0wbD+LJBcp2KJK+EQT9vv6GT5JW0oVHnE5TEXCnEJOW/rMhNMTMSccRmnVdguIE4HZsXx+cmV36jHgEt9bzcsvyWvFFoG4xL+t2UUnztX870vu//XaeVuOEAgehY/KLncrY7lhsQA4puCFIWpPteiCNhU1D8DTKc8V0ZtLT9a31SL1NLhZ+YHiD8Hs5SYdj6FW50E5yYUqPRPkg5mpbh88cRcPdsngCxU8iusNN3MSP07lO0h8zULDqtQsAq9p5o7IFTvWlAjekMy1sKTj3CuH7FuAkMHvwU0odMFeaS9T+8+4OGeprHwogWTzTbPnoOqOP/RC6vGfBvpju5s264hYguT24iXzhDFYk/8JQQe+USIbkQ7wXRw+/9cK8h5cs4LyaxMOx0pXHooxJ01bF8BYgYG4s0RB2gItzMk/L5/XhrOdWxEAdYR27s0dCN58gyvoU6phgQbTqvNTFYAObRcjfKfHu3PrFCYBBAKJ7Nm58C3rz832+ZTGVdQ3490TvO+sCLYKzpgtsqr8KyedG9LKa8wn/wlRD7kYn+J2SrMPY2Q0e4evyJaCAsolp/BQfy9JFtyRDPWTHn+jOHjW8ZN7vswGkRwYlSJSl0UC8mmJyS4lwnO/Vv4wBnDHQEzIycjn3JZAlV5ing0HKqUfW6G07453JXd8oZiMC/kIQjgWkdg34zxBYarVVrHFG5FIH9w7QWY8PCDU/kkcLniT0yD1/gkqAG2HpwaXEcSqX8Ofrbpd/IA7R7iCXYE5Q1mAvSvICpPg9Cf3CHjLyAEDz9cwKnZHkocXC8evdsTf2e7Wz8FFPAI3onFvym0MfZuRrIZitX1V8NOLedd3y74CwuErfzrr60DjyPRxGbJ4llMbm+ojeENe0HBedNm71jf+McSihKbSo5GDBxfVYVreYZ8A4iP0LsxtzQFxuzdeDL5KA9uNNw+LN9FN9vKhdALhQSnSfLPfMBsM/ey7dbxb4eRT0fpApX` + // EncryptedPrivateKey with "password" + EncryptedPrivateKey string = `sPhQsHpXYFqPb7qdmTY7APFwBb4m7meCITujDeKMQFnIjplOVm9ijjXU+YAmGvrX13ukBj8zo9MTVhjJUjJ917pyLhl4w8uyg1jCvplUYtJVXhGA9Wy3NqHMuq3SU3fKdlEM+oR4zYkbAYWp42XvulbcuVBEWiWkvHOrbdKPFpMmd54SL2c/vcWrmjgC7rTlJf2TYICZwRK+6Y0XZi5fSWeU0vg7+rHWKHc5MHHtAdAiL+HCa90c5gfh+hXkT5ojGHOkhT9kdLy3PTPN19EGpdXgZ3WFq1z9CZ6zX7uM091uR0IvgzfwaLx8HJCx7ViWQhioH9LJZgC73RMf/dwzejg2COy4QT/E59RPOczgd779rxiRmphMoR8xJYBFRlkTVmcUO4NcUE50Cc39hXezcekHuV1YQK4BXTrxGX1ceiCXYlKAWS9wHZpog9OldTCPBpw5XAWExh3kRzqdvsdHxHVE+TpAEIjDljAlc3r+FPHYH1zWWk41eQ/zz3Vkx5Zl4dMF9x+uUOspQXVb/4K42e9fMKychNUN5o/JzIwy7xOzgXa6iwf223On/mXKV6FK6Q8lojK7Wc8g7AwfqnN9//HjI14pVqGBJtn5ggL/g4qt0JFl3pV/6n/ZLMG6k8wpsaApLGvsTPqZHcv+C69Z33rZQ4TagXVxpmnWMpPCaR0+Dawn4iAce2UvUtIN2KbJNcTtRQo4z30+BbgmVKHgkR0EHMu4cYjJPYwJ5H8IYcQuFKb7+Cp33FD2Lv54I9uvtVHH9bWcid9K82y68PufJi/0icZ3EyEqZygez9mgJzxXO1b7xZMiosGs82QRv7IIOSzqBPRYv1Lxi3fWkgnOvw4dWFxJnKEI2+KD9K0z+XsgVlm26fdRklQAAf6xOJ1nJXBScbm12FBTWLMjLzHWz/iI9mQ+eGV9AREqrgQjUayXdnCsa0Q9bTTktxBkrJND4NUEDSGklhj9SY+VM0mhgAbkCvSE59vKtcNmCHx2Y+JnbZyKzJ71EaErX9vOpYCneKOjn8phVBJHQHM16QRLGyW4DUfn2CtAvb7Kks56kf/mn9YZDU68zSoLzm9rz7fjS2OUsxwmuv2IRCv/UTGgtfEfCs34qzagADfTNKTou7qkedhoygvuHiN4PzgGnjw1DQMks9PWr44z1gvIV4pEGiqgIuNHDjxKsfgQy0Cp2AV1+FNLWd1zd5t/K2pXR+knDoeHIZ2m6txQMl9I4GIyQ1bQFJWrYXPS8oMjvoH0YYVsHyShBsU2SKlG7nGbuUyoCR1EtRIzHMgP1Dq+Whqdbv67pRvhGVmydkCh0wbD+LJBcp2KJK+EQT9vv6GT5JW0oVHnE5TEXCnEJOW/rMhNMTMSccRmnVdguIE4HZsXx+cmV36jHgEt9bzcsvyWvFFoG4xL+t2UUnztX870vu//XaeVuOEAgehY/KLncrY7lhsQA4puCFIWpPteiCNhU1D8DTKc8V0ZtLT9a31SL1NLhZ+YHiD8Hs5SYdj6FW50E5yYUqPRPkg5mpbh88cRcPdsngCxU8iusNN3MSP07lO0h8zULDqtQsAq9p5o7IFTvWlAjekMy1sKTj3CuH7FuAkMHvwU0odMFeaS9T+8+4OGeprHwogWTzTbPnoOqOP/RC6vGfBvpju5s264hYguT24iXzhDFYk/8JQQe+USIbkQ7wXRw+/9cK8h5cs4LyaxMOx0pXHooxJ01bF8BYgYG4s0RB2gItzMk/L5/XhrOdWxEAdYR27s0dCN58gyvoU6phgQbTqvNTFYAObRcjfKfHu3PrFCYBBAKJ7Nm58C3rz832+ZTGVdQ3490TvO+sCLYKzpgtsqr8KyedG9LKa8wn/wlRD7kYn+J2SrMPY2Q0e4evyJaCAsolp/BQfy9JFtyRDPWTHn+jOHjW8ZN7vswGkRwYlSJSl0UC8mmJyS4lwnO/Vv4wBnDHQEzIycjn3JZAlV5ing0HKqUfW6G07453JXd8oZiMC/kIQjgWkdg34zxBYarVVrHFG5FIH9w7QWY8PCDU/kkcLniT0yD1/gkqAG2HpwaXEcSqX8Ofrbpd/IA7R7iCXYE5Q1mAvSvICpPg9Cf3CHjLyAEDz9cwKnZHkocXC8evdsTf2e7Wz8FFPAI3onFvym0MfZuRrIZitX1V8NOLedd3y74CwuErfzrr60DjyPRxGbJ4llMbm+ojeENe0HBedNm71jf+McSihKbSo5GDBxfVYVreYZ8A4iP0LsxtzQFxuzdeDL5KA9uNNw+LN9FN9vKhdALhQSnSfLPfMBsM/ey7dbxb4eRT0fpApX` - // Private key for testing server side - privateKey string = `-----BEGIN PRIVATE KEY----- + // PrivateKey for testing server side + PrivateKey string = `-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDJScQQJxWxKwqf FmXH64QnRBVyW7cU25F+O9Zy96dqTjbV4ruWrzb4+txmK20ZPQvMxDLefhEzTXWb HZV1P/XxgmEpaBVHwHnkhaPzzChOa/G18CDoCNrgyVzh5a31OotTCuGlS1bSkR53 @@ -42,7 +42,8 @@ b0XvaLzh1iKG7HZ9tvPt/VhHlKKosNBK/j4fvgMZg7/bhRfHmaDQKoqlGbtyWjEQ mj1b2/Gnbk3VYDR16BFfj7m2 -----END PRIVATE KEY-----` - publicKey string = `-----BEGIN PUBLIC KEY----- + // PublicKey for encryption + PublicKey string = `-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyUnEECcVsSsKnxZlx+uE J0QVclu3FNuRfjvWcvenak421eK7lq82+PrcZittGT0LzMQy3n4RM011mx2VdT/1 8YJhKWgVR8B55IWj88woTmvxtfAg6Aja4Mlc4eWt9TqLUwrhpUtW0pEedxMT10Kv @@ -58,35 +59,57 @@ var ( decodedPrivateKey *rsa.PrivateKey ) -// Seed seeds semi random data for use in testing & development -func Seed() { +// GetPubKey for seeding & tests +func GetPubKey() *rsa.PublicKey { var ( - block *pem.Block - decKey any - ok bool - err error + block *pem.Block + decKey any + decPubKey *rsa.PublicKey + ok bool + err error ) - - block, _ = pem.Decode([]byte(publicKey)) + block, _ = pem.Decode([]byte(PublicKey)) decKey, err = x509.ParsePKIXPublicKey(block.Bytes) if err != nil { panic(err) } - decodedPublicKey, ok = decKey.(*rsa.PublicKey) + + decPubKey, ok = decKey.(*rsa.PublicKey) if !ok { panic(errors.New("Invalid decodedPublicKey")) } - block, _ = pem.Decode([]byte(privateKey)) + return decPubKey +} + +// GetPrivKey for seeding & tests +func GetPrivKey() *rsa.PrivateKey { + var ( + block *pem.Block + decKey any + decPrivKey *rsa.PrivateKey + ok bool + err error + ) + block, _ = pem.Decode([]byte(PrivateKey)) decKey, err = x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { panic(err) } - decodedPrivateKey, ok = decKey.(*rsa.PrivateKey) + decPrivKey, ok = decKey.(*rsa.PrivateKey) if !ok { panic(errors.New("Invalid decodedPrivateKey")) } + return decPrivKey +} + +// Seed seeds semi random data for use in testing & development +func Seed() { + + decodedPublicKey = GetPubKey() + decodedPrivateKey = GetPrivKey() + log.Println("Seeding users...") SeedUsers() diff --git a/Backend/Database/Seeder/UserSeeder.go b/Backend/Database/Seeder/UserSeeder.go index c65a94e..eae651f 100644 --- a/Backend/Database/Seeder/UserSeeder.go +++ b/Backend/Database/Seeder/UserSeeder.go @@ -3,9 +3,9 @@ package Seeder import ( "encoding/base64" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Auth" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Api/Auth" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" ) var userNames = []string{ @@ -25,12 +25,12 @@ var userNames = []string{ func createUser(username string) (Models.User, error) { var ( userData Models.User - userKey aesKey + userKey AesKey password string err error ) - userKey, err = generateAesKey() + userKey, err = GenerateAesKey() if err != nil { panic(err) } @@ -43,10 +43,10 @@ func createUser(username string) (Models.User, error) { userData = Models.User{ Username: username, Password: password, - AsymmetricPrivateKey: encryptedPrivateKey, - AsymmetricPublicKey: publicKey, + AsymmetricPrivateKey: EncryptedPrivateKey, + AsymmetricPublicKey: PublicKey, SymmetricKey: base64.StdEncoding.EncodeToString( - encryptWithPublicKey(userKey.Key, decodedPublicKey), + EncryptWithPublicKey(userKey.Key, decodedPublicKey), ), } diff --git a/Backend/Database/Seeder/encryption.go b/Backend/Database/Seeder/encryption.go index a116134..16afc11 100644 --- a/Backend/Database/Seeder/encryption.go +++ b/Backend/Database/Seeder/encryption.go @@ -17,12 +17,12 @@ import ( "golang.org/x/crypto/pbkdf2" ) -type aesKey struct { +type AesKey struct { Key []byte Iv []byte } -func (key aesKey) encode() string { +func (key AesKey) encode() string { return base64.StdEncoding.EncodeToString(key.Key) } @@ -71,7 +71,7 @@ func pkcs7strip(data []byte, blockSize int) ([]byte, error) { return data[:length-padLen], nil } -func generateAesKey() (aesKey, error) { +func GenerateAesKey() (AesKey, error) { var ( saltBytes []byte = []byte{} password []byte @@ -83,22 +83,22 @@ func generateAesKey() (aesKey, error) { password = make([]byte, 64) _, err = rand.Read(password) if err != nil { - return aesKey{}, err + return AesKey{}, err } seed = make([]byte, 64) _, err = rand.Read(seed) if err != nil { - return aesKey{}, err + return AesKey{}, err } iv = make([]byte, 16) _, err = rand.Read(iv) if err != nil { - return aesKey{}, err + return AesKey{}, err } - return aesKey{ + return AesKey{ Key: pbkdf2.Key( password, saltBytes, @@ -110,7 +110,7 @@ func generateAesKey() (aesKey, error) { }, nil } -func (key aesKey) aesEncrypt(plaintext []byte) ([]byte, error) { +func (key AesKey) AesEncrypt(plaintext []byte) ([]byte, error) { var ( bPlaintext []byte ciphertext []byte @@ -134,7 +134,7 @@ func (key aesKey) aesEncrypt(plaintext []byte) ([]byte, error) { return ciphertext, nil } -func (key aesKey) aesDecrypt(ciphertext []byte) ([]byte, error) { +func (key AesKey) AesDecrypt(ciphertext []byte) ([]byte, error) { var ( plaintext []byte iv []byte @@ -153,11 +153,16 @@ func (key aesKey) aesDecrypt(ciphertext []byte) ([]byte, error) { decMode := cipher.NewCBCDecrypter(block, iv) decMode.CryptBlocks(plaintext, plaintext) + plaintext, err = pkcs7strip(plaintext, 16) + if err != nil { + return []byte{}, err + } + return plaintext, nil } // EncryptWithPublicKey encrypts data with public key -func encryptWithPublicKey(msg []byte, pub *rsa.PublicKey) []byte { +func EncryptWithPublicKey(msg []byte, pub *rsa.PublicKey) []byte { var ( hash hash.Hash ) diff --git a/Backend/Database/Sessions.go b/Backend/Database/Sessions.go index 1f125df..afee878 100644 --- a/Backend/Database/Sessions.go +++ b/Backend/Database/Sessions.go @@ -1,12 +1,13 @@ package Database import ( - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" "gorm.io/gorm/clause" ) -func GetSessionById(id string) (Models.Session, error) { +// GetSessionByID Gets session +func GetSessionByID(id string) (Models.Session, error) { var ( session Models.Session err error @@ -19,6 +20,7 @@ func GetSessionById(id string) (Models.Session, error) { return session, err } +// CreateSession creates session func CreateSession(session *Models.Session) error { var ( err error @@ -29,10 +31,16 @@ func CreateSession(session *Models.Session) error { return err } +// DeleteSession deletes session func DeleteSession(session *Models.Session) error { return DB.Delete(session).Error } -func DeleteSessionById(id string) error { - return DB.Delete(&Models.Session{}, id).Error +// DeleteSessionByID deletes session +func DeleteSessionByID(id string) error { + return DB.Delete( + &Models.Session{}, + "id = ?", + id, + ).Error } diff --git a/Backend/Database/UserConversations.go b/Backend/Database/UserConversations.go index 930a98f..cc876c7 100644 --- a/Backend/Database/UserConversations.go +++ b/Backend/Database/UserConversations.go @@ -1,7 +1,7 @@ package Database import ( - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" "gorm.io/gorm" "gorm.io/gorm/clause" @@ -19,13 +19,19 @@ func GetUserConversationById(id string) (Models.UserConversation, error) { return message, err } -func GetUserConversationsByUserId(id string) ([]Models.UserConversation, error) { +func GetUserConversationsByUserId(id string, page int) ([]Models.UserConversation, error) { var ( conversations []Models.UserConversation + offset int err error ) - err = DB.Find(&conversations, "user_id = ?", id). + offset = page * PageSize + + err = DB.Offset(offset). + Limit(PageSize). + Order("created_at DESC"). + Find(&conversations, "user_id = ?", id). Error return conversations, err diff --git a/Backend/Database/Users.go b/Backend/Database/Users.go index 2df6a73..c27ba2c 100644 --- a/Backend/Database/Users.go +++ b/Backend/Database/Users.go @@ -3,7 +3,7 @@ package Database import ( "errors" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" "gorm.io/gorm" "gorm.io/gorm/clause" diff --git a/Backend/Dockerfile b/Backend/Dockerfile new file mode 100644 index 0000000..9fea42e --- /dev/null +++ b/Backend/Dockerfile @@ -0,0 +1,16 @@ +FROM golang:1.19-alpine + +RUN mkdir -p /go/src/git.tovijaeschke.xyz/Capsule/Backend + +COPY ./ /go/src/git.tovijaeschke.xyz/Capsule/Backend + +WORKDIR /go/src/git.tovijaeschke.xyz/Capsule/Backend + +# For "go test" +RUN apk add gcc libc-dev + +RUN go mod download + +RUN go build -o /go/bin/capsule-server main.go + +CMD [ "/go/bin/capsule-server" ] diff --git a/Backend/Models/Conversations.go b/Backend/Models/Conversations.go index 1c9e53a..6df37ec 100644 --- a/Backend/Models/Conversations.go +++ b/Backend/Models/Conversations.go @@ -1,6 +1,8 @@ package Models import ( + "time" + "github.com/gofrs/uuid" ) @@ -34,4 +36,5 @@ type UserConversation struct { ConversationDetailID string `gorm:"not null" json:"conversation_detail_id"` // Stored encrypted Admin string `gorm:"not null" json:"admin"` // Bool if user is admin of thread, stored encrypted SymmetricKey string `gorm:"not null" json:"symmetric_key"` // Stored encrypted + CreatedAt time.Time `gorm:"not null" json:"created_at"` } diff --git a/Backend/Models/Friends.go b/Backend/Models/Friends.go index 9dc892d..6438d97 100644 --- a/Backend/Models/Friends.go +++ b/Backend/Models/Friends.go @@ -2,6 +2,7 @@ package Models import ( "database/sql" + "time" "github.com/gofrs/uuid" ) @@ -17,4 +18,5 @@ type FriendRequest struct { FriendPublicAsymmetricKey string ` json:"asymmetric_public_key"` // Stored encrypted SymmetricKey string `gorm:"not null" json:"symmetric_key"` // Stored encrypted AcceptedAt sql.NullTime ` json:"accepted_at"` + CreatedAt time.Time `gorm:"not null" json:"created_at"` } diff --git a/Backend/Models/Messages.go b/Backend/Models/Messages.go index bf05e3b..eafac22 100644 --- a/Backend/Models/Messages.go +++ b/Backend/Models/Messages.go @@ -25,6 +25,7 @@ type Message struct { MessageData MessageData ` json:"message_data"` SymmetricKey string `gorm:"not null" json:"symmetric_key"` // Stored encrypted AssociationKey string `gorm:"not null" json:"association_key"` // Stored encrypted - Expiry sql.NullTime ` json:"expiry"` + ExpiryRaw string ` json:"expiry"` + Expiry sql.NullTime ` json:"-"` CreatedAt time.Time `gorm:"not null" json:"created_at"` } diff --git a/Backend/Models/Users.go b/Backend/Models/Users.go index 811c3ab..736289e 100644 --- a/Backend/Models/Users.go +++ b/Backend/Models/Users.go @@ -2,6 +2,7 @@ package Models import ( "database/sql/driver" + "errors" "github.com/gofrs/uuid" "gorm.io/gorm" @@ -40,10 +41,35 @@ const ( MessageExpiryNoExpiry = "no_expiry" ) +// MessageExpiryValues list of all expiry values for validation +var MessageExpiryValues = []string{ + MessageExpiryFifteenMin, + MessageExpiryThirtyMin, + MessageExpiryOneHour, + MessageExpiryThreeHour, + MessageExpirySixHour, + MessageExpiryTwelveHour, + MessageExpiryOneDay, + MessageExpiryThreeDay, + MessageExpiryNoExpiry, +} + // Scan new value into MessageExpiry func (e *MessageExpiry) Scan(value interface{}) error { - *e = MessageExpiry(value.(string)) - return nil + var ( + strValue = value.(string) + m string + ) + + for _, m = range MessageExpiryValues { + if strValue != m { + continue + } + *e = MessageExpiry(strValue) + return nil + } + + return errors.New("Invalid MessageExpiry value") } // Value gets value out of MessageExpiry column @@ -51,6 +77,10 @@ func (e MessageExpiry) Value() (driver.Value, error) { return string(e), nil } +func (e MessageExpiry) String() string { + return string(e) +} + // User holds user data type User struct { Base diff --git a/Backend/Tests/Init.go b/Backend/Tests/Init.go new file mode 100644 index 0000000..dde5c4f --- /dev/null +++ b/Backend/Tests/Init.go @@ -0,0 +1,92 @@ +package Tests + +import ( + "encoding/base64" + "io/ioutil" + "log" + "net/http" + "net/http/cookiejar" + "net/http/httptest" + "net/url" + "time" + + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Api" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Api/Auth" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database/Seeder" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" + + "github.com/gorilla/mux" +) + +func InitTestCreateUser(username string) (Models.User, error) { + userKey, err := Seeder.GenerateAesKey() + if err != nil { + return Models.User{}, err + } + pubKey := Seeder.GetPubKey() + + p, _ := Auth.HashPassword("password") + + u := Models.User{ + Username: username, + Password: p, + AsymmetricPublicKey: Seeder.PublicKey, + AsymmetricPrivateKey: Seeder.EncryptedPrivateKey, + SymmetricKey: base64.StdEncoding.EncodeToString( + Seeder.EncryptWithPublicKey(userKey.Key, pubKey), + ), + } + + err = Database.CreateUser(&u) + return u, err +} + +// InitTestEnv initializes the test environment +// client is used for making authenticated requests +// ts is the testing server +// err, in case it fails ¯\_(ツ)_/¯ +func InitTestEnv() (*http.Client, *httptest.Server, error) { + log.SetOutput(ioutil.Discard) + Database.InitTest() + + r := mux.NewRouter() + Api.InitAPIEndpoints(r) + ts := httptest.NewServer(r) + + u, err := InitTestCreateUser("test") + if err != nil { + return http.DefaultClient, ts, err + } + + session := Models.Session{ + UserID: u.ID, + Expiry: time.Now().Add(12 * time.Hour), + } + + err = Database.CreateSession(&session) + if err != nil { + return http.DefaultClient, ts, err + } + + jar, err := cookiejar.New(nil) + + url, _ := url.Parse(ts.URL) + + jar.SetCookies( + url, + []*http.Cookie{ + { + Name: "session_token", + Value: session.ID.String(), + MaxAge: 300, + }, + }, + ) + + client := &http.Client{ + Jar: jar, + } + + return client, ts, err +} diff --git a/Backend/Util/Files.go b/Backend/Util/Files.go index 4ee8b81..154b1ef 100644 --- a/Backend/Util/Files.go +++ b/Backend/Util/Files.go @@ -10,21 +10,14 @@ func WriteFile(contents []byte) (string, error) { var ( fileName string filePath string - cwd string f *os.File err error ) - cwd, err = os.Getwd() - if err != nil { - return fileName, err - } - fileName = RandomString(32) filePath = fmt.Sprintf( - "%s/attachments/%s", - cwd, + "/app/attachments/%s", fileName, ) diff --git a/Backend/Util/Strings.go b/Backend/Util/Strings.go index a2d5d0f..879ca23 100644 --- a/Backend/Util/Strings.go +++ b/Backend/Util/Strings.go @@ -2,12 +2,18 @@ package Util import ( "math/rand" + "time" ) var ( letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") ) +func init() { + rand.Seed(time.Now().UnixNano()) +} + +// RandomString generates a random string func RandomString(n int) string { var ( b []rune diff --git a/Backend/Util/UserHelper.go b/Backend/Util/UserHelper.go index 32616a6..47b2569 100644 --- a/Backend/Util/UserHelper.go +++ b/Backend/Util/UserHelper.go @@ -5,8 +5,8 @@ import ( "log" "net/http" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Models" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Models" "github.com/gorilla/mux" ) diff --git a/Backend/go.mod b/Backend/go.mod index 127bb75..9db656c 100644 --- a/Backend/go.mod +++ b/Backend/go.mod @@ -1,9 +1,8 @@ -module git.tovijaeschke.xyz/tovi/Envelope/Backend +module git.tovijaeschke.xyz/tovi/Capsule/Backend go 1.18 require ( - github.com/Kangaroux/go-map-schema v0.6.1 github.com/gofrs/uuid v4.2.0+incompatible github.com/gorilla/mux v1.8.0 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 diff --git a/Backend/main.go b/Backend/main.go index e9dc701..8001402 100644 --- a/Backend/main.go +++ b/Backend/main.go @@ -5,9 +5,9 @@ import ( "log" "net/http" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Api" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database" - "git.tovijaeschke.xyz/tovi/Envelope/Backend/Database/Seeder" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Api" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database" + "git.tovijaeschke.xyz/tovi/Capsule/Backend/Database/Seeder" "github.com/gorilla/mux" ) diff --git a/README.md b/README.md index d52d837..d43ef78 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Envelope +# Capsule Encrypted messaging app diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6c89730 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,49 @@ +version: "3" + +services: + server: + build: + context: ./Backend + ports: + - "8080:8080" + volumes: + - "./Backend:/app" + links: + - postgres + - postgres-testing + depends_on: + postgres: + condition: service_healthy + depends_on: + postgres-testing: + condition: service_healthy + postgres: + image: postgres:14.5 + ports: + - "54321:5432" + environment: + POSTGRES_DB: capsule + POSTGRES_PASSWORD: password + volumes: + - /var/lib/postgres + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + postgres-testing: + image: postgres:14.5 + ports: + - "54322:5432" + environment: + POSTGRES_DB: capsule-testing + POSTGRES_PASSWORD: password + tmpfs: + - /var/lib/mysql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + + diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index c3bfaaa..26cb9e0 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -2,7 +2,7 @@ package="com.example.mobile"> deleteDb() async { - final path = join(await getDatabasesPath(), 'envelope.db'); + final path = join(await getDatabasesPath(), 'capsule.db'); deleteDatabase(path); } Future getDatabaseConnection() async { WidgetsFlutterBinding.ensureInitialized(); - final path = join(await getDatabasesPath(), 'envelope.db'); + final path = join(await getDatabasesPath(), 'capsule.db'); final database = openDatabase( path, diff --git a/mobile/lib/utils/storage/messages.dart b/mobile/lib/utils/storage/messages.dart index fd7ced7..8c07669 100644 --- a/mobile/lib/utils/storage/messages.dart +++ b/mobile/lib/utils/storage/messages.dart @@ -1,8 +1,8 @@ import 'dart:convert'; import 'dart:io'; -import 'package:Envelope/models/messages.dart'; -import 'package:Envelope/utils/storage/write_file.dart'; +import 'package:Capsule/models/messages.dart'; +import 'package:Capsule/utils/storage/write_file.dart'; import 'package:http/http.dart' as http; import 'package:sqflite/sqflite.dart'; import 'package:uuid/uuid.dart'; diff --git a/mobile/lib/views/authentication/login.dart b/mobile/lib/views/authentication/login.dart index 6dce978..d4c56a0 100644 --- a/mobile/lib/views/authentication/login.dart +++ b/mobile/lib/views/authentication/login.dart @@ -177,7 +177,7 @@ class _LoginWidgetState extends State { }).catchError((error) { print(error); showMessage( - 'Could not login to Envelope, please try again later.', + 'Could not login to Capsule, please try again later.', context, ); }); diff --git a/mobile/lib/views/authentication/signup.dart b/mobile/lib/views/authentication/signup.dart index 2a190e0..22ac9ab 100644 --- a/mobile/lib/views/authentication/signup.dart +++ b/mobile/lib/views/authentication/signup.dart @@ -1,8 +1,8 @@ import 'dart:convert'; import 'dart:typed_data'; -import 'package:Envelope/components/flash_message.dart'; -import 'package:Envelope/models/my_profile.dart'; +import 'package:Capsule/components/flash_message.dart'; +import 'package:Capsule/models/my_profile.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; @@ -176,7 +176,7 @@ class _SignupWidgetState extends State { .then((dynamic) { Navigator.of(context).popUntil((route) => route.isFirst); }).catchError((error) { - showMessage('Failed to signup to Envelope, please try again later', context); + showMessage('Failed to signup to Capsule, please try again later', context); }); }, child: const Text('Submit'), diff --git a/mobile/lib/views/authentication/unauthenticated_landing.dart b/mobile/lib/views/authentication/unauthenticated_landing.dart index 6bcd086..3a7e432 100644 --- a/mobile/lib/views/authentication/unauthenticated_landing.dart +++ b/mobile/lib/views/authentication/unauthenticated_landing.dart @@ -46,7 +46,7 @@ class _UnauthenticatedLandingWidgetState extends State