@ -0,0 +1,65 @@ | |||
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{ | |||
Username: 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,51 @@ | |||
package Auth | |||
import ( | |||
"errors" | |||
"net/http" | |||
"time" | |||
) | |||
var ( | |||
Sessions = map[string]Session{} | |||
) | |||
type Session struct { | |||
Username 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 | |||
} |