wip
This commit is contained in:
123
mangezmieux-backend/internal/users/handler.go
Normal file
123
mangezmieux-backend/internal/users/handler.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"mangezmieux-backend/internal/logger"
|
||||
"mangezmieux-backend/internal/middleware"
|
||||
"mangezmieux-backend/internal/responses"
|
||||
"mangezmieux-backend/internal/users/model"
|
||||
"mangezmieux-backend/internal/users/service"
|
||||
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
coreValidator "mangezmieux-backend/internal/validator"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
Service *service.Service
|
||||
Validator *validator.Validate
|
||||
}
|
||||
|
||||
func NewHandler(service *service.Service, validator *validator.Validate) *Handler {
|
||||
return &Handler{
|
||||
Service: service,
|
||||
Validator: validator,
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handler) CreateUser(context *gin.Context) {
|
||||
userEditable := model.UserEditable{}
|
||||
if err := context.BindJSON(&userEditable); err != nil {
|
||||
responses.JSONError(context.Writer, coreValidator.NewDataValidationAPIError(err))
|
||||
return
|
||||
}
|
||||
user, err := h.Service.CreateUser(&userEditable)
|
||||
if err != nil {
|
||||
logger.GetLogger().Error(err)
|
||||
var apiError *responses.APIError
|
||||
if errors.As(err, &apiError) {
|
||||
responses.JSONError(context.Writer, *apiError)
|
||||
return
|
||||
}
|
||||
responses.JSONErrorWithMessage(context.Writer, responses.ErrInternalServer, err.Error())
|
||||
return
|
||||
}
|
||||
responses.JSON(context.Writer, http.StatusCreated, user)
|
||||
}
|
||||
|
||||
func (h Handler) Login(context *gin.Context) {
|
||||
userLoginRequest := model.UserLoginRequest{}
|
||||
if err := context.BindJSON(&userLoginRequest); err != nil {
|
||||
responses.JSONError(context.Writer, coreValidator.NewDataValidationAPIError(err))
|
||||
return
|
||||
}
|
||||
token, err := h.Service.Login(userLoginRequest)
|
||||
if err != nil {
|
||||
logger.GetLogger().Error(err)
|
||||
var apiError *responses.APIError
|
||||
if errors.As(err, &apiError) {
|
||||
responses.JSONError(context.Writer, *apiError)
|
||||
return
|
||||
}
|
||||
responses.JSONErrorWithMessage(context.Writer, responses.ErrInternalServer, err.Error())
|
||||
return
|
||||
}
|
||||
context.SetCookie("token", token, 10, "/", "localhost", true, false)
|
||||
userLoginResponse := model.UserLoginResponse{
|
||||
AccessToken: token,
|
||||
TokenType: "Bearer",
|
||||
}
|
||||
responses.JSON(context.Writer, http.StatusOK, userLoginResponse)
|
||||
}
|
||||
|
||||
func (h Handler) IntrospectToken(context *gin.Context) {
|
||||
authorization := context.Request.Header.Get("Authorization")
|
||||
splitToken := strings.Split(authorization, "Bearer ")
|
||||
reqToken := splitToken[1]
|
||||
user, err := h.Service.Introspect(reqToken)
|
||||
if err != nil {
|
||||
logger.GetLogger().Error(err)
|
||||
var apiError *responses.APIError
|
||||
if errors.As(err, &apiError) {
|
||||
responses.JSONError(context.Writer, *apiError)
|
||||
return
|
||||
}
|
||||
responses.JSONErrorWithMessage(context.Writer, responses.ErrInternalServer, err.Error())
|
||||
return
|
||||
|
||||
}
|
||||
responses.JSON(context.Writer, http.StatusOK, user)
|
||||
}
|
||||
|
||||
func (h Handler) RefreshToken(context *gin.Context) {
|
||||
authorization := context.Request.Header.Get("Authorization")
|
||||
splitToken := strings.Split(authorization, "Bearer ")
|
||||
reqToken := splitToken[1]
|
||||
|
||||
refreshedToken, err := h.Service.Refresh(reqToken)
|
||||
if err != nil {
|
||||
logger.GetLogger().Error(err)
|
||||
var apiError *responses.APIError
|
||||
if errors.As(err, &apiError) {
|
||||
responses.JSONError(context.Writer, *apiError)
|
||||
return
|
||||
}
|
||||
responses.JSONErrorWithMessage(context.Writer, responses.ErrInternalServer, err.Error())
|
||||
return
|
||||
|
||||
}
|
||||
context.SetCookie("token", refreshedToken, 10, "/", "localhost", true, false)
|
||||
}
|
||||
|
||||
func (h Handler) GetMe(context *gin.Context) {
|
||||
usr, exists := context.Get(middleware.CtxUser)
|
||||
if !exists {
|
||||
responses.JSONErrorWithMessage(context.Writer, responses.ErrInternalServer, "User not found in context")
|
||||
return
|
||||
}
|
||||
responses.JSON(context.Writer, http.StatusOK, usr)
|
||||
|
||||
}
|
||||
44
mangezmieux-backend/internal/users/middleware.go
Normal file
44
mangezmieux-backend/internal/users/middleware.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gin-gonic/gin"
|
||||
model2 "mangezmieux-backend/internal/acl/model"
|
||||
"mangezmieux-backend/internal/acl/service"
|
||||
"mangezmieux-backend/internal/jwt"
|
||||
"mangezmieux-backend/internal/middleware"
|
||||
"mangezmieux-backend/internal/users/model"
|
||||
service2 "mangezmieux-backend/internal/users/service"
|
||||
)
|
||||
|
||||
var AuthMiddleware = newMiddleware()
|
||||
|
||||
type internalAuthMiddleware struct {
|
||||
Service *service2.Service
|
||||
RoleService service.Service
|
||||
UserService service.Service
|
||||
}
|
||||
|
||||
func newMiddleware() *internalAuthMiddleware {
|
||||
return &internalAuthMiddleware{}
|
||||
}
|
||||
|
||||
func (m *internalAuthMiddleware) GinMiddleware(jwtService *jwt.Service) gin.HandlerFunc {
|
||||
return middleware.GetAuthenticationMiddleware(m, jwtService)
|
||||
}
|
||||
|
||||
// delegate useful for deferred binding (when the middleware is installed, GinMiddleware() is called, the service m.Service is not yet created :-( )
|
||||
// see cmd/app.go for deferred binding at the end.
|
||||
func (m *internalAuthMiddleware) Introspect(token string) (*model.User, error) {
|
||||
return m.Service.Introspect(token)
|
||||
}
|
||||
|
||||
// delegate useful for deferred binding (when the middleware is installed, GinMiddleware() is called, the service m.Service is not yet created :-( )
|
||||
// see cmd/app.go for deferred binding at the end.
|
||||
func (m *internalAuthMiddleware) GetRole(ctx context.Context, user *model.User) (*model2.UserRight, error) {
|
||||
return m.RoleService.GetRoleForCurrentUser(user)
|
||||
}
|
||||
|
||||
func (m *internalAuthMiddleware) GetAllRole(ctx context.Context) ([]*model2.Role, error) {
|
||||
return m.RoleService.GetAllRole()
|
||||
}
|
||||
31
mangezmieux-backend/internal/users/model/model.go
Normal file
31
mangezmieux-backend/internal/users/model/model.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/gofrs/uuid"
|
||||
"time"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID *uuid.UUID `json:"ID"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt *time.Time `json:"updatedAt"`
|
||||
UserEditable
|
||||
}
|
||||
|
||||
type UserEditable struct {
|
||||
Firstname string `json:"first_name" binding:"required"`
|
||||
Lastname string `json:"last_name" binding:"required"`
|
||||
Email string `json:"email" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type UserLoginRequest struct {
|
||||
Email string `json:"email" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type UserLoginResponse struct {
|
||||
AccessToken string `json:"accessToken"`
|
||||
TokenType string `json:"tokenType"`
|
||||
ExpiresIn string `json:"expiresIn"`
|
||||
}
|
||||
98
mangezmieux-backend/internal/users/service/service.go
Normal file
98
mangezmieux-backend/internal/users/service/service.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"mangezmieux-backend/internal/jwt"
|
||||
"mangezmieux-backend/internal/responses"
|
||||
"mangezmieux-backend/internal/users/model"
|
||||
"mangezmieux-backend/internal/users/sql"
|
||||
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
dao sql.Dao
|
||||
jwt *jwt.Service
|
||||
}
|
||||
|
||||
func NewService(dao sql.Dao, jwt *jwt.Service) *Service {
|
||||
return &Service{dao: dao, jwt: jwt}
|
||||
}
|
||||
|
||||
func (s *Service) CreateUser(userEditable *model.UserEditable) (*model.User, error) {
|
||||
now := time.Now()
|
||||
user := &model.User{
|
||||
ID: nil,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: &now,
|
||||
UserEditable: model.UserEditable{
|
||||
Firstname: userEditable.Firstname,
|
||||
Lastname: userEditable.Lastname,
|
||||
Email: userEditable.Email,
|
||||
Password: userEditable.Password,
|
||||
},
|
||||
}
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(userEditable.Password), 8)
|
||||
if err != nil {
|
||||
return nil, &responses.ErrInternalServer
|
||||
}
|
||||
user.Password = string(hashedPassword)
|
||||
err = s.dao.Create(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.Password = ""
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *Service) Login(request model.UserLoginRequest) (string, error) {
|
||||
user, err := s.dao.FindByMail(request.Email)
|
||||
if err != nil {
|
||||
return "", &responses.ErrUnauthorized
|
||||
}
|
||||
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(request.Password))
|
||||
if err != nil {
|
||||
return "", &responses.ErrUnauthorized
|
||||
}
|
||||
return s.jwt.GenerateJWTToken(user.ID.String())
|
||||
}
|
||||
|
||||
func (s *Service) Introspect(token string) (*model.User, error) {
|
||||
claims, err := s.jwt.ValidateToken(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
usr, err := s.dao.FindByID(claims.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userModel := s.transformEntityToResponse(usr, false)
|
||||
|
||||
return userModel, nil
|
||||
}
|
||||
|
||||
func (s *Service) Refresh(oldToken string) (string, error) {
|
||||
return s.jwt.Refresh(oldToken)
|
||||
}
|
||||
|
||||
func (s *Service) transformEntityToResponse(user *model.User, withPassword bool) *model.User {
|
||||
password := ""
|
||||
if withPassword {
|
||||
password = user.Password
|
||||
}
|
||||
|
||||
return &model.User{
|
||||
ID: user.ID,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
UserEditable: model.UserEditable{
|
||||
Firstname: user.Firstname,
|
||||
Lastname: user.Lastname,
|
||||
Email: user.Email,
|
||||
Password: password,
|
||||
},
|
||||
}
|
||||
}
|
||||
42
mangezmieux-backend/internal/users/setup.go
Normal file
42
mangezmieux-backend/internal/users/setup.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"mangezmieux-backend/internal/ginserver"
|
||||
"mangezmieux-backend/internal/injector"
|
||||
"mangezmieux-backend/internal/jwt"
|
||||
"mangezmieux-backend/internal/postgres"
|
||||
service2 "mangezmieux-backend/internal/users/service"
|
||||
sql2 "mangezmieux-backend/internal/users/sql"
|
||||
"mangezmieux-backend/internal/validator"
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
validatorv10 "github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
const ServiceKey = "UsersService"
|
||||
|
||||
func Setup(inj *injector.Injector) {
|
||||
publicRoute := injector.Get[*gin.RouterGroup](inj, ginserver.UnsecuredRouterInjectorKey)
|
||||
validatorCli := injector.Get[*validatorv10.Validate](inj, validator.ValidatorInjectorKey)
|
||||
jwtService := injector.Get[*jwt.Service](inj, jwt.JWTKey)
|
||||
|
||||
client := injector.Get[*sql.DB](inj, postgres.DatabaseKey)
|
||||
dao := sql2.NewDao(client)
|
||||
|
||||
service := service2.NewService(dao, jwtService)
|
||||
handler := NewHandler(service, validatorCli)
|
||||
|
||||
inj.Set(ServiceKey, service)
|
||||
|
||||
publicRoute.Handle(http.MethodPost, "/api/v1/users", handler.CreateUser)
|
||||
publicRoute.Handle(http.MethodPost, "/oauth2/token", handler.Login)
|
||||
publicRoute.Handle(http.MethodPost, "/oauth2/introspect", handler.IntrospectToken)
|
||||
publicRoute.Handle(http.MethodPost, "/oauth2/refresh", handler.RefreshToken)
|
||||
|
||||
securedRoute := injector.Get[*gin.RouterGroup](inj, ginserver.SecuredRouterInjectorKey)
|
||||
securedRoute.Handle(http.MethodGet, "/users/me", handler.GetMe)
|
||||
|
||||
}
|
||||
11
mangezmieux-backend/internal/users/sql/dao.go
Normal file
11
mangezmieux-backend/internal/users/sql/dao.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package sql
|
||||
|
||||
import "mangezmieux-backend/internal/users/model"
|
||||
|
||||
type Dao interface {
|
||||
FindByMail(mail string) (*model.User, error)
|
||||
Create(user *model.User) error
|
||||
Delete(mail string) error
|
||||
FindByMailAndPassword(mail string, password string) (*model.User, error)
|
||||
FindByID(id string) (*model.User, error)
|
||||
}
|
||||
111
mangezmieux-backend/internal/users/sql/sql.go
Normal file
111
mangezmieux-backend/internal/users/sql/sql.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package sql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"mangezmieux-backend/internal/postgres"
|
||||
"mangezmieux-backend/internal/users/model"
|
||||
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
type SQLDao struct {
|
||||
client *sql.DB
|
||||
}
|
||||
|
||||
func NewDao(client *sql.DB) Dao {
|
||||
return &SQLDao{client: client}
|
||||
}
|
||||
|
||||
func (sqlDAO *SQLDao) FindByMailAndPassword(mail string, password string) (*model.User, error) {
|
||||
q := `
|
||||
SELECT u.ID, u.first_name, u.last_name, u.creation_date, u.last_update_date
|
||||
FROM mangezmieux.user u
|
||||
WHERE u.email = $1 AND u.password = $2
|
||||
`
|
||||
row := sqlDAO.client.QueryRow(q, mail, password)
|
||||
|
||||
u := model.User{}
|
||||
err := row.Scan(&u.Email, &u.Firstname, &u.Lastname, &u.CreatedAt, &u.UpdatedAt)
|
||||
var errPq *pq.Error
|
||||
if errors.As(err, &errPq) {
|
||||
return nil, postgres.HandlePgError(errPq)
|
||||
}
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
|
||||
}
|
||||
return &u, err
|
||||
}
|
||||
|
||||
func (sqlDAO *SQLDao) FindByMail(mail string) (*model.User, error) {
|
||||
q := `
|
||||
SELECT u.ID, u.email, u.first_name, u.last_name, u.creation_date, u.last_update_date, u.password
|
||||
FROM mangezmieux.user u
|
||||
WHERE u.email = $1
|
||||
`
|
||||
row := sqlDAO.client.QueryRow(q, mail)
|
||||
|
||||
u := model.User{}
|
||||
err := row.Scan(&u.ID, &u.Email, &u.Firstname, &u.Lastname, &u.CreatedAt, &u.UpdatedAt, &u.Password)
|
||||
var errPq *pq.Error
|
||||
if errors.As(err, &errPq) {
|
||||
return nil, postgres.HandlePgError(errPq)
|
||||
}
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
|
||||
}
|
||||
return &u, err
|
||||
}
|
||||
|
||||
func (sqlDAO *SQLDao) FindByID(id string) (*model.User, error) {
|
||||
q := `
|
||||
SELECT u.ID, u.email, u.first_name, u.last_name, u.creation_date, u.last_update_date
|
||||
FROM mangezmieux.user u
|
||||
WHERE u.id = $1
|
||||
`
|
||||
row := sqlDAO.client.QueryRow(q, id)
|
||||
|
||||
u := model.User{}
|
||||
err := row.Scan(&u.ID, &u.Email, &u.Firstname, &u.Lastname, &u.CreatedAt, &u.UpdatedAt)
|
||||
var errPq *pq.Error
|
||||
if errors.As(err, &errPq) {
|
||||
return nil, postgres.HandlePgError(errPq)
|
||||
}
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, postgres.NewDAOError(postgres.ErrTypeNotFound, err)
|
||||
}
|
||||
return &u, err
|
||||
}
|
||||
|
||||
func (sqlDAO *SQLDao) Create(user *model.User) error {
|
||||
q := `
|
||||
INSERT INTO mangezmieux.user
|
||||
(email, password, first_name, last_name, creation_date, last_update_date)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6)
|
||||
RETURNING id, creation_date
|
||||
`
|
||||
|
||||
err := sqlDAO.client.
|
||||
QueryRow(q, user.Email, user.Password, user.Firstname, user.Lastname, user.CreatedAt, user.UpdatedAt).
|
||||
Scan(&user.ID, &user.CreatedAt)
|
||||
var errPq *pq.Error
|
||||
if errors.As(err, &errPq) {
|
||||
return postgres.HandlePgError(errPq)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (sqlDAO *SQLDao) Delete(id string) error {
|
||||
q := `
|
||||
DELETE FROM mangezmieux.user
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
_, err := sqlDAO.client.Exec(q, id)
|
||||
var errPq *pq.Error
|
||||
if errors.As(err, &errPq) {
|
||||
return postgres.HandlePgError(errPq)
|
||||
}
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user