| @ -0,0 +1,66 @@ | |||||
| package Auth | |||||
| import ( | |||||
| "encoding/json" | |||||
| "net/http" | |||||
| "time" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" | |||||
| "github.com/gofrs/uuid" | |||||
| ) | |||||
| type Credentials struct { | |||||
| Email string `json:"email"` | |||||
| Password string `json:"password"` | |||||
| } | |||||
| func Login(w http.ResponseWriter, r *http.Request) { | |||||
| var ( | |||||
| creds Credentials | |||||
| userData Models.User | |||||
| sessionToken uuid.UUID | |||||
| expiresAt time.Time | |||||
| err error | |||||
| ) | |||||
| err = json.NewDecoder(r.Body).Decode(&creds) | |||||
| if err != nil { | |||||
| w.WriteHeader(http.StatusBadRequest) | |||||
| return | |||||
| } | |||||
| userData, err = Database.GetUserByEmail(creds.Email) | |||||
| if err != nil { | |||||
| w.WriteHeader(http.StatusUnauthorized) | |||||
| return | |||||
| } | |||||
| if !CheckPasswordHash(creds.Password, userData.Password) { | |||||
| w.WriteHeader(http.StatusUnauthorized) | |||||
| return | |||||
| } | |||||
| sessionToken, err = uuid.NewV4() | |||||
| if err != nil { | |||||
| w.WriteHeader(http.StatusInternalServerError) | |||||
| return | |||||
| } | |||||
| expiresAt = time.Now().Add(1 * time.Hour) | |||||
| Sessions[sessionToken.String()] = Session{ | |||||
| UserID: userData.ID.String(), | |||||
| Email: userData.Email, | |||||
| Expiry: expiresAt, | |||||
| } | |||||
| http.SetCookie(w, &http.Cookie{ | |||||
| Name: "session_token", | |||||
| Value: sessionToken.String(), | |||||
| Expires: expiresAt, | |||||
| }) | |||||
| w.WriteHeader(http.StatusOK) | |||||
| } | |||||
| @ -0,0 +1,111 @@ | |||||
| package Auth | |||||
| import ( | |||||
| "fmt" | |||||
| "math/rand" | |||||
| "net/http" | |||||
| "net/http/httptest" | |||||
| "os" | |||||
| "path" | |||||
| "runtime" | |||||
| "strings" | |||||
| "testing" | |||||
| "time" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" | |||||
| "github.com/gorilla/mux" | |||||
| ) | |||||
| var ( | |||||
| r *mux.Router | |||||
| letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") | |||||
| ) | |||||
| func init() { | |||||
| // Fix working directory for tests | |||||
| _, filename, _, _ := runtime.Caller(0) | |||||
| dir := path.Join(path.Dir(filename), "..") | |||||
| err := os.Chdir(dir) | |||||
| if err != nil { | |||||
| panic(err) | |||||
| } | |||||
| Database.InitTest() | |||||
| r = mux.NewRouter() | |||||
| } | |||||
| func randString(n int) string { | |||||
| b := make([]rune, n) | |||||
| for i := range b { | |||||
| b[i] = letterRunes[rand.Intn(len(letterRunes))] | |||||
| } | |||||
| return string(b) | |||||
| } | |||||
| func createTestUser(random bool) (Models.User, error) { | |||||
| now := time.Now() | |||||
| email := "email@email.com" | |||||
| if random { | |||||
| email = fmt.Sprintf("%s@email.com", randString(16)) | |||||
| } | |||||
| password, err := HashPassword("password") | |||||
| if err != nil { | |||||
| return Models.User{}, err | |||||
| } | |||||
| userData := Models.User{ | |||||
| Email: email, | |||||
| Password: password, | |||||
| LastLogin: &now, | |||||
| FirstName: "Hugh", | |||||
| LastName: "Mann", | |||||
| } | |||||
| err = Database.CreateUser(&userData) | |||||
| return userData, err | |||||
| } | |||||
| func Test_Login(t *testing.T) { | |||||
| t.Log("Testing Login...") | |||||
| r.HandleFunc("/admin/login", Login).Methods("POST") | |||||
| ts := httptest.NewServer(r) | |||||
| defer ts.Close() | |||||
| userData, err := createTestUser(true) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| t.FailNow() | |||||
| } | |||||
| postJson := ` | |||||
| { | |||||
| "email": "%s", | |||||
| "password": "password" | |||||
| } | |||||
| ` | |||||
| postJson = fmt.Sprintf(postJson, userData.Email) | |||||
| res, err := http.Post(ts.URL+"/admin/login", "application/json", strings.NewReader(postJson)) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| return | |||||
| } | |||||
| if res.StatusCode != http.StatusOK { | |||||
| t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode) | |||||
| return | |||||
| } | |||||
| if len(res.Cookies()) != 1 { | |||||
| t.Errorf("Expected cookies len 1, recieved %d", len(res.Cookies())) | |||||
| return | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,34 @@ | |||||
| package Auth | |||||
| import ( | |||||
| "net/http" | |||||
| "time" | |||||
| ) | |||||
| func Logout(w http.ResponseWriter, r *http.Request) { | |||||
| var ( | |||||
| c *http.Cookie | |||||
| sessionToken string | |||||
| err error | |||||
| ) | |||||
| c, err = r.Cookie("session_token") | |||||
| if err != nil { | |||||
| if err == http.ErrNoCookie { | |||||
| w.WriteHeader(http.StatusUnauthorized) | |||||
| return | |||||
| } | |||||
| w.WriteHeader(http.StatusBadRequest) | |||||
| return | |||||
| } | |||||
| sessionToken = c.Value | |||||
| delete(Sessions, sessionToken) | |||||
| http.SetCookie(w, &http.Cookie{ | |||||
| Name: "session_token", | |||||
| Value: "", | |||||
| Expires: time.Now(), | |||||
| }) | |||||
| } | |||||
| @ -0,0 +1,90 @@ | |||||
| package Auth | |||||
| import ( | |||||
| "fmt" | |||||
| "net/http" | |||||
| "net/http/httptest" | |||||
| "os" | |||||
| "path" | |||||
| "runtime" | |||||
| "strings" | |||||
| "testing" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" | |||||
| "github.com/gorilla/mux" | |||||
| ) | |||||
| func init() { | |||||
| // Fix working directory for tests | |||||
| _, filename, _, _ := runtime.Caller(0) | |||||
| dir := path.Join(path.Dir(filename), "..") | |||||
| err := os.Chdir(dir) | |||||
| if err != nil { | |||||
| panic(err) | |||||
| } | |||||
| Database.InitTest() | |||||
| r = mux.NewRouter() | |||||
| } | |||||
| func Test_Logout(t *testing.T) { | |||||
| t.Log("Testing Logout...") | |||||
| r.HandleFunc("/admin/login", Logout).Methods("POST") | |||||
| r.HandleFunc("/admin/logout", Logout).Methods("GET") | |||||
| ts := httptest.NewServer(r) | |||||
| defer ts.Close() | |||||
| userData, err := createTestUser(true) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| t.FailNow() | |||||
| } | |||||
| postJson := ` | |||||
| { | |||||
| "email": "%s", | |||||
| "password": "password" | |||||
| } | |||||
| ` | |||||
| postJson = fmt.Sprintf(postJson, userData.Email) | |||||
| res, err := http.Post(ts.URL+"/admin/login", "application/json", strings.NewReader(postJson)) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| return | |||||
| } | |||||
| if res.StatusCode != http.StatusOK { | |||||
| t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode) | |||||
| return | |||||
| } | |||||
| if len(res.Cookies()) != 1 { | |||||
| t.Errorf("Expected cookies len 1, recieved %d", len(res.Cookies())) | |||||
| return | |||||
| } | |||||
| req, err := http.NewRequest("GET", ts.URL+"/admin/logout", nil) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| return | |||||
| } | |||||
| req.AddCookie(res.Cookies()[0]) | |||||
| res, err = http.DefaultClient.Do(req) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| return | |||||
| } | |||||
| if res.StatusCode != http.StatusOK { | |||||
| t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode) | |||||
| return | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,22 @@ | |||||
| package Auth | |||||
| import ( | |||||
| "golang.org/x/crypto/bcrypt" | |||||
| ) | |||||
| func HashPassword(password string) (string, error) { | |||||
| var ( | |||||
| bytes []byte | |||||
| err error | |||||
| ) | |||||
| bytes, err = bcrypt.GenerateFromPassword([]byte(password), 14) | |||||
| return string(bytes), err | |||||
| } | |||||
| func CheckPasswordHash(password, hash string) bool { | |||||
| var ( | |||||
| err error | |||||
| ) | |||||
| err = bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) | |||||
| return err == nil | |||||
| } | |||||
| @ -0,0 +1,79 @@ | |||||
| package Auth | |||||
| import ( | |||||
| "errors" | |||||
| "net/http" | |||||
| "time" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Util" | |||||
| ) | |||||
| var ( | |||||
| Sessions = map[string]Session{} | |||||
| ) | |||||
| type Session struct { | |||||
| UserID string | |||||
| Email string | |||||
| Expiry time.Time | |||||
| } | |||||
| func (s Session) IsExpired() bool { | |||||
| return s.Expiry.Before(time.Now()) | |||||
| } | |||||
| func CheckCookie(r *http.Request) (Session, error) { | |||||
| var ( | |||||
| c *http.Cookie | |||||
| sessionToken string | |||||
| userSession Session | |||||
| exists bool | |||||
| err error | |||||
| ) | |||||
| c, err = r.Cookie("session_token") | |||||
| if err != nil { | |||||
| return userSession, err | |||||
| } | |||||
| sessionToken = c.Value | |||||
| // We then get the session from our session map | |||||
| userSession, exists = Sessions[sessionToken] | |||||
| if !exists { | |||||
| return userSession, errors.New("Cookie not found") | |||||
| } | |||||
| // If the session is present, but has expired, we can delete the session, and return | |||||
| // an unauthorized status | |||||
| if userSession.IsExpired() { | |||||
| delete(Sessions, sessionToken) | |||||
| return userSession, errors.New("Cookie expired") | |||||
| } | |||||
| return userSession, nil | |||||
| } | |||||
| func CheckCookieCurrentUser(w http.ResponseWriter, r *http.Request) (Models.User, error) { | |||||
| var ( | |||||
| userSession Session | |||||
| userData Models.User | |||||
| err error | |||||
| ) | |||||
| userSession, err = CheckCookie(r) | |||||
| if err != nil { | |||||
| return userData, err | |||||
| } | |||||
| userData, err = Util.GetUserById(w, r) | |||||
| if err != nil { | |||||
| return userData, err | |||||
| } | |||||
| if userData.ID.String() != userSession.UserID { | |||||
| return userData, errors.New("Is not current user") | |||||
| } | |||||
| return userData, nil | |||||
| } | |||||
| @ -0,0 +1,53 @@ | |||||
| package Auth | |||||
| import ( | |||||
| "encoding/json" | |||||
| "net/http" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" | |||||
| ) | |||||
| type ChangePassword struct { | |||||
| Password string `json:"password"` | |||||
| ConfirmPassword string `json:"confirm_password"` | |||||
| } | |||||
| func UpdatePassword(w http.ResponseWriter, r *http.Request) { | |||||
| var ( | |||||
| changePasswd ChangePassword | |||||
| userData Models.User | |||||
| err error | |||||
| ) | |||||
| userData, err = CheckCookieCurrentUser(w, r) | |||||
| if err != nil { | |||||
| w.WriteHeader(http.StatusUnauthorized) | |||||
| return | |||||
| } | |||||
| err = json.NewDecoder(r.Body).Decode(&changePasswd) | |||||
| if err != nil { | |||||
| w.WriteHeader(http.StatusBadRequest) | |||||
| return | |||||
| } | |||||
| if changePasswd.Password != changePasswd.ConfirmPassword { | |||||
| w.WriteHeader(http.StatusBadRequest) | |||||
| return | |||||
| } | |||||
| userData.Password, err = HashPassword(changePasswd.Password) | |||||
| if err != nil { | |||||
| w.WriteHeader(http.StatusInternalServerError) | |||||
| return | |||||
| } | |||||
| err = Database.UpdateUser(userData.ID.String(), &userData) | |||||
| if err != nil { | |||||
| w.WriteHeader(http.StatusInternalServerError) | |||||
| return | |||||
| } | |||||
| w.WriteHeader(http.StatusOK) | |||||
| } | |||||
| @ -0,0 +1,100 @@ | |||||
| package Auth | |||||
| import ( | |||||
| "fmt" | |||||
| "net/http" | |||||
| "net/http/httptest" | |||||
| "os" | |||||
| "path" | |||||
| "runtime" | |||||
| "strings" | |||||
| "testing" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" | |||||
| "github.com/gorilla/mux" | |||||
| ) | |||||
| func init() { | |||||
| // Fix working directory for tests | |||||
| _, filename, _, _ := runtime.Caller(0) | |||||
| dir := path.Join(path.Dir(filename), "..") | |||||
| err := os.Chdir(dir) | |||||
| if err != nil { | |||||
| panic(err) | |||||
| } | |||||
| Database.InitTest() | |||||
| r = mux.NewRouter() | |||||
| } | |||||
| func Test_UpdatePassword(t *testing.T) { | |||||
| t.Log("Testing UpdatePassword...") | |||||
| r.HandleFunc("/admin/login", Logout).Methods("POST") | |||||
| r.HandleFunc("/admin/user/{userID}/update-password", UpdatePassword).Methods("PUT") | |||||
| ts := httptest.NewServer(r) | |||||
| defer ts.Close() | |||||
| userData, err := createTestUser(true) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| t.FailNow() | |||||
| } | |||||
| postJson := ` | |||||
| { | |||||
| "email": "%s", | |||||
| "password": "password" | |||||
| } | |||||
| ` | |||||
| postJson = fmt.Sprintf(postJson, userData.Email) | |||||
| res, err := http.Post(ts.URL+"/admin/login", "application/json", strings.NewReader(postJson)) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| return | |||||
| } | |||||
| if res.StatusCode != http.StatusOK { | |||||
| t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode) | |||||
| return | |||||
| } | |||||
| if len(res.Cookies()) != 1 { | |||||
| t.Errorf("Expected cookies len 1, recieved %d", len(res.Cookies())) | |||||
| return | |||||
| } | |||||
| postJson = ` | |||||
| { | |||||
| "password": "new_password", | |||||
| "confirm_password": "new_password" | |||||
| } | |||||
| ` | |||||
| req, err := http.NewRequest("PUT", fmt.Sprintf( | |||||
| "%s/admin/user/%s/update-password", | |||||
| ts.URL, | |||||
| userData.ID, | |||||
| ), strings.NewReader(postJson)) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| return | |||||
| } | |||||
| req.AddCookie(res.Cookies()[0]) | |||||
| res, err = http.DefaultClient.Do(req) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| return | |||||
| } | |||||
| if res.StatusCode != http.StatusOK { | |||||
| t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode) | |||||
| return | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,76 @@ | |||||
| package JsonSerialization | |||||
| import ( | |||||
| "encoding/json" | |||||
| "errors" | |||||
| "fmt" | |||||
| "strings" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" | |||||
| schema "github.com/Kangaroux/go-map-schema" | |||||
| ) | |||||
| func DeserializeUser(data []byte, allowMissing []string, allowAllMissing bool) (Models.User, error) { | |||||
| var ( | |||||
| postData 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( | |||||
| &postData, | |||||
| jsonStructureTest, | |||||
| &schema.CompareOpts{ | |||||
| ConvertibleFunc: CanConvert, | |||||
| TypeNameFunc: schema.DetailedTypeName, | |||||
| }) | |||||
| if err != nil { | |||||
| return postData, err | |||||
| } | |||||
| if len(jsonStructureTestResults.MismatchedFields) > 0 { | |||||
| return postData, 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 postData, errors.New(fmt.Sprintf( | |||||
| "MissingFields found when deserializing data: %s", | |||||
| strings.Join(missingFields, ", "), | |||||
| )) | |||||
| } | |||||
| // Deserialize the JSON into the struct | |||||
| err = json.Unmarshal(data, &postData) | |||||
| if err != nil { | |||||
| return postData, err | |||||
| } | |||||
| return postData, err | |||||
| } | |||||
| @ -0,0 +1,221 @@ | |||||
| package Api | |||||
| import ( | |||||
| "encoding/json" | |||||
| "io/ioutil" | |||||
| "log" | |||||
| "net/http" | |||||
| "net/url" | |||||
| "strconv" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api/Auth" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api/JsonSerialization" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Util" | |||||
| ) | |||||
| func getUsers(w http.ResponseWriter, r *http.Request) { | |||||
| var ( | |||||
| users []Models.User | |||||
| returnJson []byte | |||||
| values url.Values | |||||
| page, pageSize int | |||||
| err error | |||||
| ) | |||||
| _, err = Auth.CheckCookie(r) | |||||
| if err != nil { | |||||
| w.WriteHeader(http.StatusUnauthorized) | |||||
| return | |||||
| } | |||||
| values = r.URL.Query() | |||||
| page, err = strconv.Atoi(values.Get("page")) | |||||
| if err != nil { | |||||
| log.Println("Could not parse page url argument") | |||||
| Util.JsonReturn(w, 500, "An error occured") | |||||
| return | |||||
| } | |||||
| page, err = strconv.Atoi(values.Get("pageSize")) | |||||
| if err != nil { | |||||
| log.Println("Could not parse pageSize url argument") | |||||
| Util.JsonReturn(w, 500, "An error occured") | |||||
| return | |||||
| } | |||||
| users, err = Database.GetUsers(page, pageSize) | |||||
| if err != nil { | |||||
| log.Printf("An error occured: %s\n", err.Error()) | |||||
| Util.JsonReturn(w, 500, "An error occured") | |||||
| return | |||||
| } | |||||
| returnJson, err = json.MarshalIndent(users, "", " ") | |||||
| if err != nil { | |||||
| Util.JsonReturn(w, 500, "An error occured") | |||||
| return | |||||
| } | |||||
| // Return updated json | |||||
| w.WriteHeader(http.StatusOK) | |||||
| w.Write(returnJson) | |||||
| } | |||||
| func getUser(w http.ResponseWriter, r *http.Request) { | |||||
| var ( | |||||
| userData Models.User | |||||
| returnJson []byte | |||||
| err error | |||||
| ) | |||||
| _, err = Auth.CheckCookie(r) | |||||
| if err != nil { | |||||
| w.WriteHeader(http.StatusUnauthorized) | |||||
| return | |||||
| } | |||||
| userData, err = Util.GetUserById(w, r) | |||||
| if err != nil { | |||||
| return | |||||
| } | |||||
| returnJson, err = json.MarshalIndent(userData, "", " ") | |||||
| if err != nil { | |||||
| Util.JsonReturn(w, 500, "An error occured") | |||||
| return | |||||
| } | |||||
| // Return updated json | |||||
| w.WriteHeader(http.StatusOK) | |||||
| w.Write(returnJson) | |||||
| } | |||||
| func createUser(w http.ResponseWriter, r *http.Request) { | |||||
| var ( | |||||
| userData Models.User | |||||
| requestBody []byte | |||||
| err error | |||||
| ) | |||||
| requestBody, err = ioutil.ReadAll(r.Body) | |||||
| if err != nil { | |||||
| log.Printf("Error encountered reading POST body: %s\n", err.Error()) | |||||
| Util.JsonReturn(w, 500, "An error occured") | |||||
| return | |||||
| } | |||||
| userData, err = JsonSerialization.DeserializeUser(requestBody, []string{ | |||||
| "id", | |||||
| "last_login", | |||||
| }, false) | |||||
| if err != nil { | |||||
| log.Printf("Invalid data provided to user API: %s\n", err.Error()) | |||||
| Util.JsonReturn(w, 405, "Invalid data") | |||||
| return | |||||
| } | |||||
| err = Database.CheckUniqueEmail(userData.Email) | |||||
| if err != nil { | |||||
| Util.JsonReturn(w, 405, "invalid_email") | |||||
| return | |||||
| } | |||||
| if userData.Password != userData.ConfirmPassword { | |||||
| Util.JsonReturn(w, 405, "invalid_password") | |||||
| return | |||||
| } | |||||
| userData.Password, err = Auth.HashPassword(userData.Password) | |||||
| if err != nil { | |||||
| Util.JsonReturn(w, 500, "An error occured") | |||||
| return | |||||
| } | |||||
| err = Database.CreateUser(&userData) | |||||
| if err != nil { | |||||
| Util.JsonReturn(w, 500, "An error occured") | |||||
| return | |||||
| } | |||||
| // Return updated json | |||||
| w.WriteHeader(http.StatusOK) | |||||
| } | |||||
| func updateUser(w http.ResponseWriter, r *http.Request) { | |||||
| var ( | |||||
| currentUserData Models.User | |||||
| userData Models.User | |||||
| requestBody []byte | |||||
| returnJson []byte | |||||
| err error | |||||
| ) | |||||
| currentUserData, err = Auth.CheckCookieCurrentUser(w, r) | |||||
| if err != nil { | |||||
| w.WriteHeader(http.StatusUnauthorized) | |||||
| return | |||||
| } | |||||
| requestBody, err = ioutil.ReadAll(r.Body) | |||||
| if err != nil { | |||||
| log.Printf("Error encountered reading POST body: %s\n", err.Error()) | |||||
| Util.JsonReturn(w, 500, "An error occured") | |||||
| return | |||||
| } | |||||
| userData, err = JsonSerialization.DeserializeUser(requestBody, []string{}, true) | |||||
| if err != nil { | |||||
| log.Printf("Invalid data provided to users API: %s\n", err.Error()) | |||||
| Util.JsonReturn(w, 405, "Invalid data") | |||||
| return | |||||
| } | |||||
| err = Database.UpdateUser(currentUserData.ID.String(), &userData) | |||||
| if err != nil { | |||||
| log.Printf("An error occured: %s\n", err.Error()) | |||||
| Util.JsonReturn(w, 500, "An error occured") | |||||
| return | |||||
| } | |||||
| returnJson, err = json.MarshalIndent(userData, "", " ") | |||||
| if err != nil { | |||||
| log.Printf("An error occured: %s\n", err.Error()) | |||||
| Util.JsonReturn(w, 500, "An error occured") | |||||
| return | |||||
| } | |||||
| // Return updated json | |||||
| w.WriteHeader(http.StatusOK) | |||||
| w.Write(returnJson) | |||||
| } | |||||
| func deleteUser(w http.ResponseWriter, r *http.Request) { | |||||
| var ( | |||||
| userData Models.User | |||||
| err error | |||||
| ) | |||||
| _, err = Auth.CheckCookie(r) | |||||
| if err != nil { | |||||
| w.WriteHeader(http.StatusUnauthorized) | |||||
| return | |||||
| } | |||||
| userData, err = Util.GetUserById(w, r) | |||||
| if err != nil { | |||||
| w.WriteHeader(http.StatusNotFound) | |||||
| return | |||||
| } | |||||
| err = Database.DeleteUser(&userData) | |||||
| if err != nil { | |||||
| log.Printf("An error occured: %s\n", err.Error()) | |||||
| Util.JsonReturn(w, 500, "An error occured") | |||||
| return | |||||
| } | |||||
| // Return updated json | |||||
| w.WriteHeader(http.StatusOK) | |||||
| } | |||||
| @ -0,0 +1,372 @@ | |||||
| package Api | |||||
| import ( | |||||
| "encoding/json" | |||||
| "errors" | |||||
| "fmt" | |||||
| "math/rand" | |||||
| "net/http" | |||||
| "net/http/httptest" | |||||
| "os" | |||||
| "path" | |||||
| "runtime" | |||||
| "strings" | |||||
| "testing" | |||||
| "time" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Api/Auth" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" | |||||
| "github.com/gorilla/mux" | |||||
| ) | |||||
| func init() { | |||||
| // Fix working directory for tests | |||||
| _, filename, _, _ := runtime.Caller(0) | |||||
| dir := path.Join(path.Dir(filename), "..") | |||||
| err := os.Chdir(dir) | |||||
| if err != nil { | |||||
| panic(err) | |||||
| } | |||||
| Database.InitTest() | |||||
| r = mux.NewRouter() | |||||
| } | |||||
| var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") | |||||
| func randString(n int) string { | |||||
| b := make([]rune, n) | |||||
| for i := range b { | |||||
| b[i] = letterRunes[rand.Intn(len(letterRunes))] | |||||
| } | |||||
| return string(b) | |||||
| } | |||||
| func createTestUser(random bool) (Models.User, error) { | |||||
| now := time.Now() | |||||
| email := "email@email.com" | |||||
| if random { | |||||
| email = fmt.Sprintf("%s@email.com", randString(16)) | |||||
| } | |||||
| password, err := Auth.HashPassword("password") | |||||
| if err != nil { | |||||
| return Models.User{}, err | |||||
| } | |||||
| userData := Models.User{ | |||||
| Email: email, | |||||
| Password: password, | |||||
| LastLogin: &now, | |||||
| FirstName: "Hugh", | |||||
| LastName: "Mann", | |||||
| } | |||||
| err = Database.CreateUser(&userData) | |||||
| return userData, err | |||||
| } | |||||
| func login() (*http.Cookie, Models.User, error) { | |||||
| var ( | |||||
| c *http.Cookie | |||||
| u Models.User | |||||
| ) | |||||
| r.HandleFunc("/admin/login", Auth.Login).Methods("POST") | |||||
| ts := httptest.NewServer(r) | |||||
| defer ts.Close() | |||||
| u, err := createTestUser(true) | |||||
| if err != nil { | |||||
| return c, u, err | |||||
| } | |||||
| postJson := ` | |||||
| { | |||||
| "email": "%s", | |||||
| "password": "password" | |||||
| } | |||||
| ` | |||||
| postJson = fmt.Sprintf(postJson, u.Email) | |||||
| res, err := http.Post(ts.URL+"/admin/login", "application/json", strings.NewReader(postJson)) | |||||
| if err != nil { | |||||
| return c, u, err | |||||
| } | |||||
| if res.StatusCode != http.StatusOK { | |||||
| return c, u, errors.New("Invalid res.StatusCode") | |||||
| } | |||||
| if len(res.Cookies()) != 1 { | |||||
| return c, u, errors.New("Invalid cookies length") | |||||
| } | |||||
| return res.Cookies()[0], u, nil | |||||
| } | |||||
| func Test_getUser(t *testing.T) { | |||||
| t.Log("Testing getUser...") | |||||
| r.HandleFunc("/user/{userID}", getUser).Methods("GET") | |||||
| ts := httptest.NewServer(r) | |||||
| defer ts.Close() | |||||
| c, u, err := login() | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| t.FailNow() | |||||
| } | |||||
| req, err := http.NewRequest("GET", fmt.Sprintf( | |||||
| "%s/user/%s", | |||||
| ts.URL, | |||||
| u.ID, | |||||
| ), nil) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| t.FailNow() | |||||
| } | |||||
| req.AddCookie(c) | |||||
| res, err := http.DefaultClient.Do(req) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| t.FailNow() | |||||
| } | |||||
| if res.StatusCode != http.StatusOK { | |||||
| t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode) | |||||
| t.FailNow() | |||||
| } | |||||
| getUserData := new(Models.User) | |||||
| err = json.NewDecoder(res.Body).Decode(getUserData) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| t.FailNow() | |||||
| } | |||||
| if getUserData.Email != u.Email { | |||||
| t.Errorf("Expected email \"%s\", recieved %s", u.Email, getUserData.Email) | |||||
| t.FailNow() | |||||
| } | |||||
| if getUserData.FirstName != u.FirstName { | |||||
| t.Errorf("Expected email \"%s\", recieved %s", u.FirstName, getUserData.FirstName) | |||||
| t.FailNow() | |||||
| } | |||||
| if getUserData.LastName != u.LastName { | |||||
| t.Errorf("Expected email \"%s\", recieved %s", u.LastName, getUserData.LastName) | |||||
| t.FailNow() | |||||
| } | |||||
| } | |||||
| func Test_getUsers(t *testing.T) { | |||||
| t.Log("Testing getUsers...") | |||||
| r.HandleFunc("/user", getUsers).Methods("GET") | |||||
| ts := httptest.NewServer(r) | |||||
| defer ts.Close() | |||||
| c, _, err := login() | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| t.FailNow() | |||||
| } | |||||
| for i := 0; i < 20; i++ { | |||||
| createTestUser(true) | |||||
| } | |||||
| req, err := http.NewRequest("GET", ts.URL+"/user?page=1&pageSize=10", nil) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| t.FailNow() | |||||
| } | |||||
| req.AddCookie(c) | |||||
| res, err := http.DefaultClient.Do(req) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| t.FailNow() | |||||
| } | |||||
| if res.StatusCode != http.StatusOK { | |||||
| t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode) | |||||
| t.FailNow() | |||||
| } | |||||
| getUsersData := new([]Models.User) | |||||
| err = json.NewDecoder(res.Body).Decode(getUsersData) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| t.FailNow() | |||||
| } | |||||
| if len(*getUsersData) != 10 { | |||||
| t.Errorf("Expected 10, recieved %d", len(*getUsersData)) | |||||
| t.FailNow() | |||||
| } | |||||
| } | |||||
| func Test_createUser(t *testing.T) { | |||||
| t.Log("Testing createUser...") | |||||
| r.HandleFunc("/user", createUser).Methods("POST") | |||||
| ts := httptest.NewServer(r) | |||||
| defer ts.Close() | |||||
| email := fmt.Sprintf("%s@email.com", randString(16)) | |||||
| postJson := ` | |||||
| { | |||||
| "email": "%s", | |||||
| "password": "password", | |||||
| "confirm_password": "password", | |||||
| "first_name": "Hugh", | |||||
| "last_name": "Mann" | |||||
| } | |||||
| ` | |||||
| postJson = fmt.Sprintf(postJson, email) | |||||
| res, err := http.Post(ts.URL+"/user", "application/json", strings.NewReader(postJson)) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| return | |||||
| } | |||||
| if res.StatusCode != http.StatusOK { | |||||
| t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode) | |||||
| return | |||||
| } | |||||
| } | |||||
| func Test_updateUser(t *testing.T) { | |||||
| t.Log("Testing updateUser...") | |||||
| r.HandleFunc("/user/{userID}", updateUser).Methods("PUT") | |||||
| ts := httptest.NewServer(r) | |||||
| defer ts.Close() | |||||
| c, u, err := login() | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| t.FailNow() | |||||
| } | |||||
| email := fmt.Sprintf("%s@email.com", randString(16)) | |||||
| postJson := ` | |||||
| { | |||||
| "email": "%s", | |||||
| "first_name": "first", | |||||
| "last_name": "last" | |||||
| } | |||||
| ` | |||||
| postJson = fmt.Sprintf(postJson, email) | |||||
| req, err := http.NewRequest("PUT", fmt.Sprintf( | |||||
| "%s/user/%s", | |||||
| ts.URL, | |||||
| u.ID, | |||||
| ), strings.NewReader(postJson)) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| } | |||||
| req.AddCookie(c) | |||||
| // Fetch Request | |||||
| res, err := http.DefaultClient.Do(req) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| } | |||||
| defer res.Body.Close() | |||||
| if res.StatusCode != http.StatusOK { | |||||
| t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode) | |||||
| } | |||||
| updateUserData := new(Models.User) | |||||
| err = json.NewDecoder(res.Body).Decode(updateUserData) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| } | |||||
| if updateUserData.Email != email { | |||||
| t.Errorf("Expected email \"%s\", recieved %s", email, updateUserData.Email) | |||||
| } | |||||
| if updateUserData.FirstName != "first" { | |||||
| t.Errorf("Expected FirstName \"first\", recieved %s", updateUserData.FirstName) | |||||
| } | |||||
| if updateUserData.LastName != "last" { | |||||
| t.Errorf("Expected LastName \"last\", recieved %s", updateUserData.LastName) | |||||
| } | |||||
| } | |||||
| func Test_deleteUser(t *testing.T) { | |||||
| t.Log("Testing deleteUser...") | |||||
| r.HandleFunc("/user/{userID}", deleteUser).Methods("DELETE") | |||||
| ts := httptest.NewServer(r) | |||||
| defer ts.Close() | |||||
| c, _, err := login() | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| t.FailNow() | |||||
| } | |||||
| userData, err := createTestUser(true) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| t.FailNow() | |||||
| } | |||||
| req, err := http.NewRequest("DELETE", fmt.Sprintf( | |||||
| "%s/user/%s", | |||||
| ts.URL, | |||||
| userData.ID, | |||||
| ), nil) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| } | |||||
| req.AddCookie(c) | |||||
| // Fetch Request | |||||
| res, err := http.DefaultClient.Do(req) | |||||
| if err != nil { | |||||
| t.Errorf("Expected nil, recieved %s", err.Error()) | |||||
| return | |||||
| } | |||||
| defer res.Body.Close() | |||||
| if res.StatusCode != http.StatusOK { | |||||
| t.Errorf("Expected %d, recieved %d", http.StatusOK, res.StatusCode) | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,127 @@ | |||||
| package Database | |||||
| import ( | |||||
| "errors" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" | |||||
| "gorm.io/gorm" | |||||
| "gorm.io/gorm/clause" | |||||
| ) | |||||
| func GetUserById(id string) (Models.User, error) { | |||||
| var ( | |||||
| userData Models.User | |||||
| err error | |||||
| ) | |||||
| err = DB.Preload(clause.Associations). | |||||
| First(&userData, "id = ?", id). | |||||
| Error | |||||
| userData.Password = "" | |||||
| return userData, err | |||||
| } | |||||
| func GetUserByEmail(email string) (Models.User, error) { | |||||
| var ( | |||||
| userData Models.User | |||||
| err error | |||||
| ) | |||||
| err = DB.Preload(clause.Associations). | |||||
| First(&userData, "email = ?", email). | |||||
| Error | |||||
| return userData, err | |||||
| } | |||||
| func GetUsers(page, pageSize int) ([]Models.User, error) { | |||||
| var ( | |||||
| users []Models.User | |||||
| i int | |||||
| err error | |||||
| ) | |||||
| if page == 0 { | |||||
| page = 1 | |||||
| } | |||||
| switch { | |||||
| case pageSize > 100: | |||||
| pageSize = 100 | |||||
| case pageSize <= 0: | |||||
| pageSize = 10 | |||||
| } | |||||
| err = DB.Offset(page). | |||||
| Limit(pageSize). | |||||
| Find(&users). | |||||
| Error | |||||
| for i, _ = range users { | |||||
| users[i].Password = "" | |||||
| } | |||||
| return users, err | |||||
| } | |||||
| func CheckUniqueEmail(email string) error { | |||||
| var ( | |||||
| exists bool | |||||
| err error | |||||
| ) | |||||
| err = DB.Model(Models.User{}). | |||||
| Select("count(*) > 0"). | |||||
| Where("email = ?", email). | |||||
| Find(&exists). | |||||
| Error | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| if exists { | |||||
| return errors.New("Invalid email") | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func CreateUser(userData *Models.User) error { | |||||
| var ( | |||||
| err error | |||||
| ) | |||||
| err = DB.Session(&gorm.Session{FullSaveAssociations: true}). | |||||
| Create(userData). | |||||
| Error | |||||
| userData.Password = "" | |||||
| return err | |||||
| } | |||||
| func UpdateUser(id string, userData *Models.User) error { | |||||
| var ( | |||||
| err error | |||||
| ) | |||||
| err = DB.Model(&Models.User{}). | |||||
| Select("*"). | |||||
| Omit("id", "created_at", "updated_at", "deleted_at"). | |||||
| Where("id = ?", id). | |||||
| Updates(userData). | |||||
| Error | |||||
| userData.Password = "" | |||||
| return err | |||||
| } | |||||
| func DeleteUser(userData *Models.User) error { | |||||
| return DB.Session(&gorm.Session{FullSaveAssociations: true}). | |||||
| Delete(userData). | |||||
| Error | |||||
| } | |||||
| @ -0,0 +1,15 @@ | |||||
| package Models | |||||
| import ( | |||||
| "time" | |||||
| ) | |||||
| type User struct { | |||||
| Base | |||||
| Email string `gorm:"not null;unique" json:"email"` | |||||
| Password string `gorm:"not null" json:"password,omitempty"` | |||||
| ConfirmPassword string `gorm:"-" json:"confirm_password"` | |||||
| LastLogin *time.Time `json:"last_login"` | |||||
| FirstName string `gorm:"not null" json:"first_name"` | |||||
| LastName string `gorm:"not null" json:"last_name"` | |||||
| } | |||||
| @ -0,0 +1,51 @@ | |||||
| package Util | |||||
| import ( | |||||
| "errors" | |||||
| "log" | |||||
| "net/http" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Database" | |||||
| "git.tovijaeschke.xyz/tovi/SuddenImpactRecords/Models" | |||||
| "github.com/gorilla/mux" | |||||
| ) | |||||
| func GetUserId(r *http.Request) (string, error) { | |||||
| var ( | |||||
| urlVars map[string]string | |||||
| id string | |||||
| ok bool | |||||
| ) | |||||
| urlVars = mux.Vars(r) | |||||
| id, ok = urlVars["userID"] | |||||
| if !ok { | |||||
| return id, errors.New("Could not get id") | |||||
| } | |||||
| return id, nil | |||||
| } | |||||
| func GetUserById(w http.ResponseWriter, r *http.Request) (Models.User, error) { | |||||
| var ( | |||||
| postData Models.User | |||||
| id string | |||||
| err error | |||||
| ) | |||||
| id, err = GetUserId(r) | |||||
| if err != nil { | |||||
| log.Printf("Error encountered getting id\n") | |||||
| JsonReturn(w, 500, "An error occured") | |||||
| return postData, err | |||||
| } | |||||
| postData, err = Database.GetUserById(id) | |||||
| if err != nil { | |||||
| log.Printf("Could not find pet with id %s\n", id) | |||||
| JsonReturn(w, 404, "Not found") | |||||
| return postData, err | |||||
| } | |||||
| return postData, nil | |||||
| } | |||||