Add auth package
This commit is contained in:
parent
34c965d8ee
commit
bb9a5789d7
98
auth/auth.go
Normal file
98
auth/auth.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/sessions"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrInvalidPassword returned when password and hash don't match
|
||||||
|
ErrInvalidPassword = fmt.Errorf("Invalid password")
|
||||||
|
// ErrCorrupted returned when session have been corrupted
|
||||||
|
ErrCorrupted = fmt.Errorf("Corrupted session")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Authorizer handle sesssion
|
||||||
|
type Authorizer struct {
|
||||||
|
cookiejar *sessions.CookieStore
|
||||||
|
cookieName string
|
||||||
|
peeper string
|
||||||
|
cost int
|
||||||
|
}
|
||||||
|
|
||||||
|
// New Authorizer peeper is like a salt but not stored in database,
|
||||||
|
// cost is the bcrypt cost for hashing the password
|
||||||
|
func New(peeper, cookieName, cookieKey string, cost int) *Authorizer {
|
||||||
|
return &Authorizer{
|
||||||
|
cookiejar: sessions.NewCookieStore([]byte(cookieKey)),
|
||||||
|
cookieName: cookieName,
|
||||||
|
peeper: peeper,
|
||||||
|
cost: cost,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenHash generates a new hash from a password
|
||||||
|
func (a *Authorizer) GenHash(password string) (string, error) {
|
||||||
|
b, err := bcrypt.GenerateFromPassword([]byte(password+a.peeper), a.cost)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login cheks password and updates cookie info
|
||||||
|
func (a *Authorizer) Login(rw http.ResponseWriter, req *http.Request, username, hash, password string) error {
|
||||||
|
cookie, err := a.cookiejar.Get(req, a.cookieName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bcrypt.CompareHashAndPassword([]byte(hash), []byte(password+a.peeper))
|
||||||
|
if err != nil {
|
||||||
|
return ErrInvalidPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie.Values["username"] = username
|
||||||
|
err = cookie.Save(req, rw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logout remove cookie info
|
||||||
|
func (a *Authorizer) Logout(rw http.ResponseWriter, req *http.Request) error {
|
||||||
|
cookie, err := a.cookiejar.Get(req, a.cookieName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cookie.Values["username"] = ""
|
||||||
|
cookie.Options.MaxAge = -1 // kill the cookie
|
||||||
|
err = cookie.Save(req, rw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentUser returns the logged in username from session
|
||||||
|
func (a *Authorizer) CurrentUser(rw http.ResponseWriter, req *http.Request) (string, error) {
|
||||||
|
cookie, err := a.cookiejar.Get(req, a.cookieName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
username := cookie.Values["username"]
|
||||||
|
|
||||||
|
if !cookie.IsNew && username != nil {
|
||||||
|
str, ok := username.(string)
|
||||||
|
if !ok {
|
||||||
|
return "", ErrCorrupted
|
||||||
|
}
|
||||||
|
return str, nil
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
161
auth/auth_test.go
Normal file
161
auth/auth_test.go
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/kr/pretty"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
peeper = "polp"
|
||||||
|
key = "plop"
|
||||||
|
cookie = "auth"
|
||||||
|
cost = 10
|
||||||
|
|
||||||
|
username = "plop"
|
||||||
|
password = "ploppwd"
|
||||||
|
hash = "$2a$10$eVye8xbs6nj4TWnlTmifRuBsAU3F2hkxEcFz9WXdYjUuE6uKLVuzK"
|
||||||
|
)
|
||||||
|
|
||||||
|
func login(w http.ResponseWriter, r *http.Request) {
|
||||||
|
a := New(peeper, cookie, key, cost)
|
||||||
|
err := a.Login(w, r, username, hash, password)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(w, "%s", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
|
}
|
||||||
|
func logout(w http.ResponseWriter, r *http.Request) {
|
||||||
|
a := New(peeper, cookie, key, cost)
|
||||||
|
err := a.Logout(w, r)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(w, "%s", err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
func check(w http.ResponseWriter, r *http.Request) {
|
||||||
|
a := New(peeper, cookie, key, cost)
|
||||||
|
u, err := a.CurrentUser(w, r)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
fmt.Fprintf(w, "%s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, "%s", u)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlers() *mux.Router {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/login", login).Methods("GET")
|
||||||
|
r.HandleFunc("/logout", logout).Methods("GET")
|
||||||
|
r.HandleFunc("/check", check).Methods("GET")
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuth(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(handlers())
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
cookieJar, _ := cookiejar.New(nil)
|
||||||
|
client := &http.Client{
|
||||||
|
Jar: cookieJar,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check no user logged in =
|
||||||
|
res, err := client.Get(ts.URL + "/check")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
t.Fatal(body)
|
||||||
|
}
|
||||||
|
if string(body) != "" {
|
||||||
|
t.Fatalf("No user logged in expected but found: %s", body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login
|
||||||
|
res, err = client.Get(ts.URL + "/login")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
t.Fatal(string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks we are logged in
|
||||||
|
res, err = client.Get(ts.URL + "/check")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
pretty.Println(res.StatusCode)
|
||||||
|
t.Fatal(body)
|
||||||
|
}
|
||||||
|
if string(body) != username {
|
||||||
|
t.Fatalf("We expect be logged in as %s but we got: %s", username, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logout
|
||||||
|
res, err = client.Get(ts.URL + "/logout")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
t.Fatal(string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check no username logged in anymore
|
||||||
|
res, err = client.Get(ts.URL + "/check")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err = ioutil.ReadAll(res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
t.Fatal(body)
|
||||||
|
}
|
||||||
|
if string(body) != "" {
|
||||||
|
t.Fatalf("No user logged in expected but found: %s", body)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user