Reviewed-on: #2develop
@ -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 | |||
} |