feat: users & authentication implementation

This commit is contained in:
Florian Sylvain
2023-08-07 12:31:29 +02:00
parent e258dccc1c
commit f33f53ca76
15 changed files with 550 additions and 18 deletions
+133
View File
@@ -0,0 +1,133 @@
package api
import (
"GohCMS2/useCases"
"encoding/json"
"github.com/go-chi/chi/v5"
"github.com/go-chi/jwtauth/v5"
"golang.org/x/crypto/bcrypt"
"net/http"
"time"
)
type UserRegister struct {
Username string `json:"username" validate:"required,min=3,max=20"`
Password string `json:"password" validate:"required,min=8"`
Email string `json:"email" validate:"required,email"`
}
type UserLogin struct {
Username string `json:"username" validate:"required,min=3,max=20"`
Password string `json:"password" validate:"required,min=8"`
}
var TokenAuth *jwtauth.JWTAuth
const logsErrorMessage = "Access to the requested resource is forbidden due to incorrect password and/or username."
const bodyErrorMessage = "The request cannot be processed due to a mismatch in the format of the body."
func SetJwtCookie(w *http.ResponseWriter, userId uint32) error {
_, tokenString, err := TokenAuth.Encode(map[string]interface{}{"user_id": userId})
if err != nil {
return err
}
http.SetCookie(*w, &http.Cookie{
Name: "jwt",
Value: tokenString,
Expires: time.Now().Add(24 * time.Hour),
Secure: false, // TODO false in dev, true in prod
HttpOnly: true,
Path: "/v1/",
})
return nil
}
func isAllowedToCreateUser() bool {
users := container.ListUsersUseCase.ListUsers()
return len(users) == 0
}
func isLoggedIn(r *http.Request) bool {
token, err := jwtauth.VerifyRequest(
TokenAuth,
r,
jwtauth.TokenFromCookie,
jwtauth.TokenFromHeader,
jwtauth.TokenFromQuery)
return token != nil && err == nil
}
func login(w http.ResponseWriter, r *http.Request) {
var user UserLogin
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
http.Error(w, bodyErrorMessage, http.StatusBadRequest)
return
}
err = validate.Struct(user)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
dbUser, err := container.GetUserByUsernameUseCase.GetUserByUsername(user.Username)
if err != nil {
http.Error(w, logsErrorMessage, http.StatusForbidden)
return
}
err = bcrypt.CompareHashAndPassword([]byte(dbUser.Password), []byte(user.Password))
if err != nil {
http.Error(w, logsErrorMessage, http.StatusForbidden)
return
}
_ = SetJwtCookie(&w, dbUser.ID)
message, _ := json.Marshal(map[string]interface{}{"message": "User logged in! HTTPonly access_token cookie created"})
_, _ = w.Write(message)
}
func register(w http.ResponseWriter, r *http.Request) {
if !isLoggedIn(r) && !isAllowedToCreateUser() {
http.Error(w, "You are not allowed to create a user. Log in or reset database.", http.StatusForbidden)
return
}
var user UserRegister
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
http.Error(w, bodyErrorMessage, http.StatusBadRequest)
return
}
err = validate.Struct(user)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
createdUser, err := container.CreateUserUseCase.CreateUser(useCases.CreateUserCommand{
Username: user.Username,
Password: user.Password,
Email: user.Email,
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = SetJwtCookie(&w, createdUser.ID)
message, _ := json.Marshal(map[string]interface{}{"message": "User registered! HTTPonly access_token cookie created"})
_, err = w.Write(message)
}
func NewAuthRouter() http.Handler {
r := chi.NewRouter()
r.Post("/register", register)
r.Post("/login", login)
return r
}
+23 -7
View File
@@ -9,9 +9,13 @@ import (
)
type Container struct {
CreateArticleUseCase *CreateArticleUseCase
GetArticleUseCase *GetArticleUseCase
ListArticlesUseCase *ListArticlesUseCase
CreateArticleUseCase *CreateArticleUseCase
GetArticleUseCase *GetArticleUseCase
ListArticlesUseCase *ListArticlesUseCase
GetUserUseCase *GetUserUseCase
GetUserByUsernameUseCase *GetUserByUsernameUseCase
CreateUserUseCase *CreateUserUseCase
ListUsersUseCase *ListUsersUseCase
}
var container *Container
@@ -20,11 +24,19 @@ func setContainer(
createArticle *CreateArticleUseCase,
getArticle *GetArticleUseCase,
listArticle *ListArticlesUseCase,
getUser *GetUserUseCase,
getUserByUsername *GetUserByUsernameUseCase,
createUser *CreateUserUseCase,
listUsers *ListUsersUseCase,
) *Container {
container = &Container{
CreateArticleUseCase: createArticle,
GetArticleUseCase: getArticle,
ListArticlesUseCase: listArticle,
CreateArticleUseCase: createArticle,
GetArticleUseCase: getArticle,
ListArticlesUseCase: listArticle,
GetUserUseCase: getUser,
GetUserByUsernameUseCase: getUserByUsername,
CreateUserUseCase: createUser,
ListUsersUseCase: listUsers,
}
return container
}
@@ -40,13 +52,17 @@ func InitContainer() {
if err != nil {
panic(err)
}
_ = db.AutoMigrate(&models.Article{})
_ = db.AutoMigrate(&models.Article{}, &models.User{})
_ = digContainer.Provide(func() *gorm.DB { return db })
_ = digContainer.Provide(func(db *gorm.DB) *CreateArticleUseCase { return NewCreateArticleUseCase(db) })
_ = digContainer.Provide(func(db *gorm.DB) *GetArticleUseCase { return NewGetArticleUseCase(db) })
_ = digContainer.Provide(func(db *gorm.DB) *ListArticlesUseCase { return NewListArticlesUseCase(db) })
_ = digContainer.Provide(func(db *gorm.DB) *GetUserUseCase { return NewGetUserUseCase(db) })
_ = digContainer.Provide(func(db *gorm.DB) *GetUserByUsernameUseCase { return NewGetUserByUsernameUseCase(db) })
_ = digContainer.Provide(func(db *gorm.DB) *CreateUserUseCase { return NewCreateUserUseCase(db) })
_ = digContainer.Provide(func(db *gorm.DB) *ListUsersUseCase { return NewListUsersUseCase(db) })
_ = digContainer.Invoke(setContainer)
}
+9
View File
@@ -0,0 +1,9 @@
package api
import "github.com/go-playground/validator/v10"
var validate *validator.Validate
func InitValidator() {
validate = validator.New()
}