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 (
|
||||
"database/sql"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type notFoundError struct {
|
||||
@ -27,3 +28,27 @@ func wrapNotFound(err error) error {
|
||||
}
|
||||
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)
|
||||
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) {
|
||||
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) {
|
||||
|
@ -22,13 +22,21 @@ type UserNoPassword struct {
|
||||
DisplayName string `json:"display_name"`
|
||||
}
|
||||
|
||||
// UserByUsername returns a single user by the unique username.
|
||||
func (m *Model) UserByUsername(username string) (*UserNoPassword, error) {
|
||||
// VerifyUserByUsernamePassword returns a single user by the unique username, if the provided password is correct.
|
||||
func (m *Model) VerifyUserByUsernamePassword(username string, password string) (*UserNoPassword, error) {
|
||||
user, err := m.SelectUserByUsername(username)
|
||||
if user == nil {
|
||||
return nil, wrapNotFound(err)
|
||||
if err != nil {
|
||||
// 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.
|
||||
|
@ -17,16 +17,20 @@ func TestModelUsers(t *testing.T) {
|
||||
[]*models.Plan{p}}
|
||||
m := models.New(ss)
|
||||
|
||||
user, err := m.UserByUsername("test")
|
||||
user, err := m.VerifyUserByUsernamePassword("test", "password")
|
||||
assert.Nil(err)
|
||||
assert.NotNil(user)
|
||||
|
||||
user, err = m.VerifyUserByUsernamePassword("test", "wrong_password")
|
||||
assert.NotNil(err)
|
||||
assert.Nil(user)
|
||||
}
|
||||
|
||||
func TestErrorUsers(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
m := getErrorModel(fmt.Errorf("err"))
|
||||
|
||||
user, err := m.UserByUsername("snth")
|
||||
user, err := m.VerifyUserByUsernamePassword("snth", "aoeu")
|
||||
assert.Nil(user)
|
||||
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
|
||||
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.Mount("/plans", newPlanRouter(m))
|
||||
router.Mount("/actions", newActionRouter(m))
|
||||
router.Mount("/auth", newAuthRouter(m))
|
||||
router.Mount("/health", newHealthRouter(m))
|
||||
router.Get("/ping", ping)
|
||||
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