Adds auth route that checks username and password
All checks were successful
gitea-deepak/gogmagog/pipeline/head This commit looks good
All checks were successful
gitea-deepak/gogmagog/pipeline/head This commit looks good
This commit is contained in:
parent
c8b8f87f6c
commit
262321a1e2
@ -2,6 +2,7 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type notFoundError struct {
|
type notFoundError struct {
|
||||||
@ -27,3 +28,27 @@ func wrapNotFound(err error) error {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type invalidLoginError struct {
|
||||||
|
error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *invalidLoginError) InvalidLogin() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsInvalidLoginError returns true if the model deems it an invalid login error.
|
||||||
|
func IsInvalidLoginError(err error) bool {
|
||||||
|
type invalidLogin interface {
|
||||||
|
InvalidLogin() bool
|
||||||
|
}
|
||||||
|
te, ok := err.(invalidLogin)
|
||||||
|
return ok && te.InvalidLogin()
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapInvalidLogin(err error) error {
|
||||||
|
if err == sql.ErrNoRows || err == bcrypt.ErrMismatchedHashAndPassword {
|
||||||
|
return &invalidLoginError{error: err}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
@ -40,3 +40,10 @@ func TestErrorModelWrapping(t *testing.T) {
|
|||||||
_, err = m.Action(0)
|
_, err = m.Action(0)
|
||||||
assert.True(models.IsNotFoundError(err))
|
assert.True(models.IsNotFoundError(err))
|
||||||
}
|
}
|
||||||
|
func TestErrorModelInvalidLogin(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
m := getErrorModel(sql.ErrNoRows)
|
||||||
|
|
||||||
|
_, err := m.VerifyUserByUsernamePassword("duck", "duck")
|
||||||
|
assert.True(models.IsInvalidLoginError(err))
|
||||||
|
}
|
||||||
|
@ -44,7 +44,8 @@ func (ms *multiStore) SelectActionsByPlanID(plan *models.Plan) ([]*models.Action
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ms *multiStore) SelectUserByUsername(username string) (*models.User, error) {
|
func (ms *multiStore) SelectUserByUsername(username string) (*models.User, error) {
|
||||||
return &models.User{UserID: int64(1), Username: username, DisplayName: "Ted Est", Password: []byte("oh no")}, nil
|
// password is "password"
|
||||||
|
return &models.User{UserID: int64(1), Username: username, DisplayName: "Ted Est", Password: []byte("$2y$05$6SVV35GX4cB4PDPhRaDD/exsL.HV8QtMMr60YL6dLyqtX4l58q.cy")}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *multiStore) InsertUser(user *models.User) (int, error) {
|
func (ms *multiStore) InsertUser(user *models.User) (int, error) {
|
||||||
|
@ -22,13 +22,21 @@ type UserNoPassword struct {
|
|||||||
DisplayName string `json:"display_name"`
|
DisplayName string `json:"display_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserByUsername returns a single user by the unique username.
|
// VerifyUserByUsernamePassword returns a single user by the unique username, if the provided password is correct.
|
||||||
func (m *Model) UserByUsername(username string) (*UserNoPassword, error) {
|
func (m *Model) VerifyUserByUsernamePassword(username string, password string) (*UserNoPassword, error) {
|
||||||
user, err := m.SelectUserByUsername(username)
|
user, err := m.SelectUserByUsername(username)
|
||||||
if user == nil {
|
if err != nil {
|
||||||
return nil, wrapNotFound(err)
|
// throwaway to pad time
|
||||||
|
hashPassword(username)
|
||||||
|
return nil, wrapInvalidLogin(err)
|
||||||
}
|
}
|
||||||
return user.NoPassword(), wrapNotFound(err)
|
|
||||||
|
err = bcrypt.CompareHashAndPassword(user.Password, []byte(password))
|
||||||
|
if err != nil {
|
||||||
|
return nil, wrapInvalidLogin(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.NoPassword(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoPassword strips the user of password.
|
// NoPassword strips the user of password.
|
||||||
|
@ -17,16 +17,20 @@ func TestModelUsers(t *testing.T) {
|
|||||||
[]*models.Plan{p}}
|
[]*models.Plan{p}}
|
||||||
m := models.New(ss)
|
m := models.New(ss)
|
||||||
|
|
||||||
user, err := m.UserByUsername("test")
|
user, err := m.VerifyUserByUsernamePassword("test", "password")
|
||||||
assert.Nil(err)
|
assert.Nil(err)
|
||||||
assert.NotNil(user)
|
assert.NotNil(user)
|
||||||
|
|
||||||
|
user, err = m.VerifyUserByUsernamePassword("test", "wrong_password")
|
||||||
|
assert.NotNil(err)
|
||||||
|
assert.Nil(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestErrorUsers(t *testing.T) {
|
func TestErrorUsers(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
m := getErrorModel(fmt.Errorf("err"))
|
m := getErrorModel(fmt.Errorf("err"))
|
||||||
|
|
||||||
user, err := m.UserByUsername("snth")
|
user, err := m.VerifyUserByUsernamePassword("snth", "aoeu")
|
||||||
assert.Nil(user)
|
assert.Nil(user)
|
||||||
assert.NotNil(err)
|
assert.NotNil(err)
|
||||||
}
|
}
|
||||||
|
96
routes/auth.go
Normal file
96
routes/auth.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"gitea.deepak.science/deepak/gogmagog/models"
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newAuthRouter(m *models.Model) http.Handler {
|
||||||
|
router := chi.NewRouter()
|
||||||
|
router.Post("/register", postUserFunc(m))
|
||||||
|
router.Post("/tokens", createTokenFunc(m))
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
type createUserResponse struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func postUserFunc(m *models.Model) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
r.Body = http.MaxBytesReader(w, r.Body, 1024)
|
||||||
|
dec := json.NewDecoder(r.Body)
|
||||||
|
dec.DisallowUnknownFields()
|
||||||
|
var req models.CreateUserRequest
|
||||||
|
err := dec.Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
badRequestError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = dec.Decode(&struct{}{})
|
||||||
|
if err != io.EOF {
|
||||||
|
badRequestError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = m.CreateUser(&req)
|
||||||
|
if err != nil {
|
||||||
|
serverError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &createUserResponse{
|
||||||
|
Username: req.Username,
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
w.Header().Add("Content-Type", "application/json")
|
||||||
|
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||||
|
serverError(w, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type loginCreds struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTokenFunc(m *models.Model) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
r.Body = http.MaxBytesReader(w, r.Body, 1024)
|
||||||
|
dec := json.NewDecoder(r.Body)
|
||||||
|
dec.DisallowUnknownFields()
|
||||||
|
var creds loginCreds
|
||||||
|
err := dec.Decode(&creds)
|
||||||
|
if err != nil {
|
||||||
|
badRequestError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = dec.Decode(&struct{}{})
|
||||||
|
if err != io.EOF {
|
||||||
|
badRequestError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := m.VerifyUserByUsernamePassword(creds.Username, creds.Password)
|
||||||
|
if err != nil {
|
||||||
|
if models.IsInvalidLoginError(err) {
|
||||||
|
unauthorizedHandler(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
serverError(w, err)
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
w.Header().Add("Content-Type", "application/json")
|
||||||
|
if err := json.NewEncoder(w).Encode(user); err != nil {
|
||||||
|
serverError(w, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -25,3 +25,8 @@ func notFoundHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
code := http.StatusNotFound
|
code := http.StatusNotFound
|
||||||
http.Error(w, http.StatusText(code), code)
|
http.Error(w, http.StatusText(code), code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func unauthorizedHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
code := http.StatusUnauthorized
|
||||||
|
http.Error(w, http.StatusText(code), code)
|
||||||
|
}
|
||||||
|
@ -14,6 +14,7 @@ func NewRouter(m *models.Model) http.Handler {
|
|||||||
router.NotFound(notFoundHandler)
|
router.NotFound(notFoundHandler)
|
||||||
router.Mount("/plans", newPlanRouter(m))
|
router.Mount("/plans", newPlanRouter(m))
|
||||||
router.Mount("/actions", newActionRouter(m))
|
router.Mount("/actions", newActionRouter(m))
|
||||||
|
router.Mount("/auth", newAuthRouter(m))
|
||||||
router.Mount("/health", newHealthRouter(m))
|
router.Mount("/health", newHealthRouter(m))
|
||||||
router.Get("/ping", ping)
|
router.Get("/ping", ping)
|
||||||
return router
|
return router
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
package routes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"gitea.deepak.science/deepak/gogmagog/models"
|
|
||||||
"github.com/go-chi/chi"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newUserRouter(m *models.Model) http.Handler {
|
|
||||||
router := chi.NewRouter()
|
|
||||||
// router.Post("/", postUserFunc(m))
|
|
||||||
router.Get("/{username}", getUserByUsernameFunc(m))
|
|
||||||
return router
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUserByUsernameFunc(m *models.Model) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
username := chi.URLParam(r, "username")
|
|
||||||
|
|
||||||
user, err := m.UserByUsername(username)
|
|
||||||
if err != nil {
|
|
||||||
if models.IsNotFoundError(err) {
|
|
||||||
notFoundHandler(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
serverError(w, err)
|
|
||||||
return
|
|
||||||
|
|
||||||
}
|
|
||||||
w.Header().Add("Content-Type", "application/json")
|
|
||||||
if err := json.NewEncoder(w).Encode(user); err != nil {
|
|
||||||
serverError(w, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user