diff --git a/api/auth.go b/api/auth.go index 4ce6f0e..1a36603 100644 --- a/api/auth.go +++ b/api/auth.go @@ -5,7 +5,6 @@ import ( "io/ioutil" "log" "net/http" - "time" "github.com/dgrijalva/jwt-go" ) @@ -50,35 +49,6 @@ func init() { } } -func serveToken(w http.ResponseWriter, r *http.Request) error { - t := jwt.New(jwt.GetSigningMethod("RS256")) - - // Set our claims - t.Claims["AccessToken"] = "level1" - t.Claims["CustomUserInfo"] = struct { - Name string - Kind string - }{"mrdillon", "human"} - - // Set the expire time - // See http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-20#section-4.1.4 - t.Claims["exp"] = time.Now().Add(time.Minute * 1).Unix() - tokenString, err := t.SignedString(signKey) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return errWhileSigningToken - } - - http.SetCookie(w, &http.Cookie{ - Name: tokenName, - Value: tokenString, - Path: "/", - RawExpires: "0", - }) - - return writeJSON(w, Message{"success"}) -} - type authHandler func(http.ResponseWriter, *http.Request) error // Only accessible with a valid token diff --git a/api/handler.go b/api/handler.go index c13746a..2545a76 100644 --- a/api/handler.go +++ b/api/handler.go @@ -20,8 +20,9 @@ func Handler() *mux.Router { m.Get(router.User).Handler(handler(serveUser)) m.Get(router.CreateUser).Handler(handler(serveCreateUser)) m.Get(router.Users).Handler(handler(serveUsers)) + m.Get(router.GetToken).Handler(handler(serveAuthenticateUser)) - m.Get(router.Genus).Handler(handler(serveGenus)) + m.Get(router.Genus).Handler(authHandler(serveGenus)) m.Get(router.CreateGenus).Handler(handler(serveCreateGenus)) m.Get(router.Genera).Handler(handler(serveGenera)) m.Get(router.UpdateGenus).Handler(handler(serveUpdateGenus)) @@ -69,9 +70,6 @@ func Handler() *mux.Router { m.Get(router.UpdateMeasurement).Handler(handler(serveUpdateMeasurement)) m.Get(router.DeleteMeasurement).Handler(handler(serveDeleteMeasurement)) - m.Get(router.GetToken).Handler(handler(serveToken)) - m.Get(router.Restricted).Handler(authHandler(restrictedHandler)) - return m } diff --git a/api/server_for_test.go b/api/server_for_test.go index ef07063..77b2607 100644 --- a/api/server_for_test.go +++ b/api/server_for_test.go @@ -4,7 +4,9 @@ import ( "bytes" "io/ioutil" "net/http" + "net/http/cookiejar" "net/http/httptest" + "net/url" "github.com/thermokarst/bactdb/datastore" "github.com/thermokarst/bactdb/models" @@ -15,13 +17,20 @@ func init() { } var ( - serveMux = http.NewServeMux() - httpClient = http.Client{Transport: (*muxTransport)(serveMux)} - apiClient = models.NewClient(&httpClient) + serveMux = http.NewServeMux() + cookieJar, _ = cookiejar.New(nil) + httpClient = http.Client{ + Transport: (*muxTransport)(serveMux), + Jar: cookieJar, + } + apiClient = models.NewClient(&httpClient) ) func setup() { store = datastore.NewMockDatastore() + resp, _ := httpClient.PostForm(apiClient.BaseURL.String()+"authenticate/", + url.Values{"username": {"test_user"}, "password": {"password"}}) + defer resp.Body.Close() } type muxTransport http.ServeMux diff --git a/api/users.go b/api/users.go index d783449..ffd6044 100644 --- a/api/users.go +++ b/api/users.go @@ -3,7 +3,9 @@ package api import ( "encoding/json" "strconv" + "time" + "github.com/dgrijalva/jwt-go" "github.com/gorilla/mux" "net/http" @@ -59,3 +61,31 @@ func serveUsers(w http.ResponseWriter, r *http.Request) error { return writeJSON(w, users) } + +func serveAuthenticateUser(w http.ResponseWriter, r *http.Request) error { + username := r.FormValue("username") + password := r.FormValue("password") + + auth_level, err := store.Users.Authenticate(username, password) + if err != nil { + return err + } + + t := jwt.New(jwt.GetSigningMethod("RS256")) + t.Claims["AccessToken"] = auth_level + t.Claims["exp"] = time.Now().Add(time.Minute * 1).Unix() + tokenString, err := t.SignedString(signKey) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return errWhileSigningToken + } + + http.SetCookie(w, &http.Cookie{ + Name: tokenName, + Value: tokenString, + Path: "/", + RawExpires: "0", + }) + + return writeJSON(w, auth_level) +} diff --git a/api/users_test.go b/api/users_test.go index 22d3319..064fb6b 100644 --- a/api/users_test.go +++ b/api/users_test.go @@ -94,3 +94,29 @@ func TestUser_List(t *testing.T) { t.Errorf("got users %+v but wanted users %+v", users, wantUsers) } } + +func TestUser_Authenticate(t *testing.T) { + setup() + + test_user := newUser() + test_user.Username = "test_user" + + calledAuthenticate := false + store.Users.(*models.MockUsersService).Authenticate_ = func(username string, password string) (*string, error) { + calledAuthenticate = true + auth_level := "read" + return &auth_level, nil + } + + auth_level, err := apiClient.Users.Authenticate(test_user.Username, "password") + if err != nil { + t.Fatal(err) + } + + if !calledAuthenticate { + t.Error("!calledAuthenticate") + } + if *auth_level != "read" { + t.Errorf("got auth level %+v but wanted read", *auth_level) + } +} diff --git a/datastore/users.go b/datastore/users.go index ad59732..170ab93 100644 --- a/datastore/users.go +++ b/datastore/users.go @@ -51,3 +51,15 @@ func (s *usersStore) List(opt *models.UserListOptions) ([]*models.User, error) { } return users, nil } + +func (s *usersStore) Authenticate(username string, password string) (*string, error) { + var users []*models.User + if err := s.dbh.Select(&users, `SELECT * FROM users WHERE username=$1;`, username); err != nil { + return nil, err + } + if len(users) == 0 { + return nil, models.ErrUserNotFound + } + auth_level := "read" + return &auth_level, nil +} diff --git a/datastore/users_test.go b/datastore/users_test.go index 2fc655e..0180ca0 100644 --- a/datastore/users_test.go +++ b/datastore/users_test.go @@ -20,7 +20,7 @@ func insertUser(t *testing.T, tx *modl.Transaction) *models.User { } func newUser() *models.User { - return &models.User{UserName: "Test User"} + return &models.User{Username: "Test User"} } func TestUsersStore_Get_db(t *testing.T) { @@ -86,3 +86,21 @@ func TestUsersStore_List_db(t *testing.T) { t.Errorf("got users %+v, want %+v", users, want) } } + +func TestUsersStore_Authenticate_db(t *testing.T) { + tx, _ := DB.Begin() + defer tx.Rollback() + + user := insertUser(t, tx) + + d := NewDatastore(tx) + + auth_level, err := d.Users.Authenticate(user.Username, "password") + if err != nil { + t.Fatal(err) + } + + if *auth_level != "read" { + t.Errorf("expecting read, got %+v", auth_level) + } +} diff --git a/models/users.go b/models/users.go index 471aa7d..d9d0a4f 100644 --- a/models/users.go +++ b/models/users.go @@ -10,16 +10,17 @@ import ( ) // A User is a person that has administrative access to bactdb. +// Todo: add password type User struct { Id int64 `json:"id,omitempty"` - UserName string `json:"userName"` + Username string `db:"username" json:"username"` CreatedAt time.Time `db:"created_at" json:"createdAt"` UpdatedAt time.Time `db:"updated_at" json:"updatedAt"` DeletedAt NullTime `db:"deleted_at" json:"deletedAt"` } func NewUser() *User { - return &User{UserName: "Test User"} + return &User{Username: "Test User"} } // UsersService interacts with the user-related endpoints in bactdb's API. @@ -32,6 +33,9 @@ type UsersService interface { // Create a new user. The newly created user's ID is written to user.Id Create(user *User) (created bool, err error) + + // Authenticate a user, returns their access level. + Authenticate(username string, password string) (accessLevel *string, err error) } var ( @@ -109,10 +113,31 @@ func (s *usersService) List(opt *UserListOptions) ([]*User, error) { return users, nil } +func (s *usersService) Authenticate(username string, password string) (*string, error) { + url, err := s.client.url(router.GetToken, nil, nil) + if err != nil { + return nil, err + } + + req, err := s.client.NewRequest("POST", url.String(), nil) + if err != nil { + return nil, err + } + + var auth_level *string + _, err = s.client.Do(req, &auth_level) + if err != nil { + return nil, err + } + + return auth_level, nil +} + type MockUsersService struct { - Get_ func(id int64) (*User, error) - List_ func(opt *UserListOptions) ([]*User, error) - Create_ func(user *User) (bool, error) + Get_ func(id int64) (*User, error) + List_ func(opt *UserListOptions) ([]*User, error) + Create_ func(user *User) (bool, error) + Authenticate_ func(username string, password string) (*string, error) } var _ UsersService = &MockUsersService{} @@ -137,3 +162,10 @@ func (s *MockUsersService) List(opt *UserListOptions) ([]*User, error) { } return s.List_(opt) } + +func (s *MockUsersService) Authenticate(username string, password string) (*string, error) { + if s.Authenticate_ == nil { + return nil, nil + } + return s.Authenticate_(username, password) +} diff --git a/models/users_test.go b/models/users_test.go index 35b86c1..57fab81 100644 --- a/models/users_test.go +++ b/models/users_test.go @@ -54,7 +54,7 @@ func TestUsersService_Create(t *testing.T) { mux.HandleFunc(urlPath(t, router.CreateUser, nil), func(w http.ResponseWriter, r *http.Request) { called = true testMethod(t, r, "POST") - testBody(t, r, `{"id":1,"userName":"Test User","createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","deletedAt":null}`+"\n") + testBody(t, r, `{"id":1,"username":"Test User","createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","deletedAt":null}`+"\n") w.WriteHeader(http.StatusCreated) writeJSON(w, want) diff --git a/router/api.go b/router/api.go index 4ca79e4..5ba67b3 100644 --- a/router/api.go +++ b/router/api.go @@ -9,6 +9,7 @@ func API() *mux.Router { m.Path("/users").Methods("GET").Name(Users) m.Path("/users").Methods("POST").Name(CreateUser) m.Path("/users/{Id:.+}").Methods("GET").Name(User) + m.Path("/authenticate/").Methods("POST").Name(GetToken) // Genera m.Path("/genera").Methods("GET").Name(Genera) @@ -66,9 +67,5 @@ func API() *mux.Router { m.Path("/measurements/{Id:.+}").Methods("PUT").Name(UpdateMeasurement) m.Path("/measurements/{Id:.+}").Methods("DELETE").Name(DeleteMeasurement) - // Authentication - m.Path("/token/").Methods("GET").Name(GetToken) - m.Path("/restricted/").Methods("GET").Name(Restricted) - return m } diff --git a/router/routes.go b/router/routes.go index a8e92b9..0e600b6 100644 --- a/router/routes.go +++ b/router/routes.go @@ -4,6 +4,7 @@ const ( User = "users:get" CreateUser = "users:create" Users = "users:list" + GetToken = "token:get" Genus = "genus:get" CreateGenus = "genus:create" @@ -52,7 +53,4 @@ const ( Measurements = "measurements:list" UpdateMeasurement = "measurements:update" DeleteMeasurement = "measurements:delete" - - GetToken = "token:get" - Restricted = "restricted:get" )