feat: applied auth on go services

This commit is contained in:
Jean Jacques Avril 2025-01-04 14:10:20 +00:00
parent 02af66b585
commit 55edac6abe
No known key found for this signature in database
8 changed files with 182 additions and 112 deletions

View File

@ -31,10 +31,10 @@ func main() {
authRepo := repository.NewInMemoryAuthRepositoryImpl("secret")
// Initialize services
userService := services.NewUserService(userRepo)
projectService := services.NewProjectService(projectRepo)
projectTaskService := services.NewProjectTaskService(projectTaskRepo)
timeEntryService := services.NewTimeEntryService(timeEntryRepo)
userService := services.NewUserService(authRepo, userRepo)
projectService := services.NewProjectService(authRepo, projectRepo)
projectTaskService := services.NewProjectTaskService(authRepo, projectTaskRepo)
timeEntryService := services.NewTimeEntryService(authRepo, timeEntryRepo)
authService := services.NewAuthService(authRepo, userRepo)
// Initialize and start the server

View File

@ -67,40 +67,34 @@ func (s *AuthService) Login(c *gin.Context) {
// Validate handles token validation.
func (s *AuthService) Validate(c *gin.Context) {
var tokenRequest dto.TokenRequestDTO
if err := c.ShouldBindJSON(&tokenRequest); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
F.Pipe2(
s.authRepository.ValidateToken(c.Request.Context())(tokenRequest.Token),
E.Map[error](func(userID string) dto.TokenResponseDTO {
return dto.TokenResponseDTO{
Token: tokenRequest.Token,
UserID: userID,
}
F.Pipe4(
ReadSessionToken(c),
E.FromOption[string, error](
F.Constant[error](app_error.NewUnauthorizedError("No session token found"))),
E.Chain(
s.authRepository.ValidateToken(c.Request.Context()),
),
E.Map[error](func(userID string) gin.H {
return gin.H{"message": "Token validated", "user_id": userID}
}),
E.Fold(
HandleError(c),
HandleSuccess[dto.TokenResponseDTO](c, http.StatusOK),
HandleSuccess[gin.H](c, http.StatusOK),
),
)
}
// Logout and token revocation.
func (s *AuthService) Logout(c *gin.Context) {
var tokenRequest dto.TokenRequestDTO
if err := c.ShouldBindJSON(&tokenRequest); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
deleteSessionTokenCookie(c)
F.Pipe2(
s.authRepository.RevokeToken(c.Request.Context())(tokenRequest.Token),
F.Pipe4(
ReadSessionToken(c),
E.FromOption[string, error](
F.Constant[error](app_error.NewUnauthorizedError("No session token found"))),
E.Chain(
s.authRepository.RevokeToken(c.Request.Context()),
),
E.Map[error](func(userID string) gin.H {
deleteSessionTokenCookie(c)
return gin.H{"message": "Token revoked", "user_id": userID}
}),
E.Fold(

View File

@ -6,11 +6,6 @@ type TokenResponseDTO struct {
UserID string `json:"user_id"`
}
// TokenRequestDTO represents a request for operations involving tokens.
type TokenRequestDTO struct {
Token string `json:"token"`
}
// LoginRequestDTO represents the login request.
type LoginRequestDTO struct {
Email string `json:"email"`

View File

@ -2,8 +2,12 @@ package services
import (
"actatempus_backend/internal/domain/app_error"
"actatempus_backend/internal/domain/repository"
"net/http"
E "github.com/IBM/fp-go/either"
F "github.com/IBM/fp-go/function"
O "github.com/IBM/fp-go/option"
"github.com/gin-gonic/gin"
)
@ -41,3 +45,29 @@ func HandleSuccess[T any](c *gin.Context, statusCode int) func(T) any {
return nil
}
}
func ReadSessionToken(c *gin.Context) O.Option[string] {
sessionToken, err := c.Cookie("session_token")
if err != nil || sessionToken == "" {
return O.None[string]()
}
return O.Some(sessionToken)
}
func CheckAuth(c *gin.Context, authRepository repository.AuthRepository) E.Either[error, string] {
return F.Pipe2(
ReadSessionToken(c),
E.FromOption[string, error](
F.Constant[error](app_error.NewUnauthorizedError("No session token found"))),
E.Chain(
authRepository.ValidateToken(c.Request.Context()),
),
//E.MapLeft[string](
// func(e error) error {
// HandleError(c)(e)
// return e
// },
//),
)
}

View File

@ -14,12 +14,13 @@ import (
// ProjectService handles project-related HTTP requests.
type ProjectService struct {
repository repository.ProjectRepository
authRepository repository.AuthRepository
repository repository.ProjectRepository
}
// NewProjectService creates a new instance of ProjectService.
func NewProjectService(repo repository.ProjectRepository) *ProjectService {
return &ProjectService{repository: repo}
func NewProjectService(authRepo repository.AuthRepository, repo repository.ProjectRepository) *ProjectService {
return &ProjectService{authRepository: authRepo, repository: repo}
}
// RegisterRoutes registers the project-related routes with Gin.
@ -38,9 +39,11 @@ func (s *ProjectService) CreateProject(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
F.Pipe3(
mappers.MapCreateDTOToProject(projectCreateDTO),
s.repository.Create(c.Request.Context()),
F.Pipe5(
CheckAuth(c, s.authRepository),
E.Map[error](F.Constant1[string](projectCreateDTO)),
E.Map[error](mappers.MapCreateDTOToProject),
E.Chain(s.repository.Create(c.Request.Context())),
E.Map[error](mappers.MapProjectToDTO),
E.Fold(
HandleError(c),
@ -51,9 +54,10 @@ func (s *ProjectService) CreateProject(c *gin.Context) {
// GetProjectByID handles fetching a project by its ID.
func (s *ProjectService) GetProjectByID(c *gin.Context) {
F.Pipe3(
c.Param("id"),
s.repository.FindByID(c.Request.Context()),
F.Pipe4(
CheckAuth(c, s.authRepository),
E.Map[error](F.Constant1[string](c.Param("id"))),
E.Chain(s.repository.FindByID(c.Request.Context())),
E.Map[error](mappers.MapProjectToDTO),
E.Fold(
HandleError(c),
@ -64,8 +68,9 @@ func (s *ProjectService) GetProjectByID(c *gin.Context) {
// GetAllProjects handles fetching all projects.
func (s *ProjectService) GetAllProjects(c *gin.Context) {
F.Pipe2(
s.repository.FindAll(c.Request.Context()),
F.Pipe3(
CheckAuth(c, s.authRepository),
E.Chain(F.Constant1[string](s.repository.FindAll(c.Request.Context()))),
E.Map[error](A.Map(mappers.MapProjectToDTO)),
E.Fold(
HandleError(c),
@ -76,7 +81,6 @@ func (s *ProjectService) GetAllProjects(c *gin.Context) {
// UpdateProject handles updating an existing project.
func (s *ProjectService) UpdateProject(c *gin.Context) {
id := c.Param("id")
var projectUpdateDTO dto.ProjectUpdateDTO
if err := c.ShouldBindJSON(&projectUpdateDTO); err != nil {
@ -84,9 +88,13 @@ func (s *ProjectService) UpdateProject(c *gin.Context) {
return
}
F.Pipe3(
mappers.MapUpdateDTOToProject(projectUpdateDTO, id),
s.repository.Update(c.Request.Context()),
F.Pipe5(
CheckAuth(c, s.authRepository),
E.Map[error](F.Constant1[string](c.Param("id"))),
E.Map[error](
F.Curry2(mappers.MapUpdateDTOToProject)(projectUpdateDTO),
),
E.Chain(s.repository.Update(c.Request.Context())),
E.Map[error](mappers.MapProjectToDTO),
E.Fold(
HandleError(c),
@ -97,9 +105,10 @@ func (s *ProjectService) UpdateProject(c *gin.Context) {
// DeleteProject handles deleting a project by its ID.
func (s *ProjectService) DeleteProject(c *gin.Context) {
F.Pipe3(
c.Param("id"),
s.repository.Delete(c.Request.Context()),
F.Pipe4(
CheckAuth(c, s.authRepository),
E.Map[error](F.Constant1[string](c.Param("id"))),
E.Chain(s.repository.Delete(c.Request.Context())),
E.Map[error](mappers.MapProjectToDTO),
E.Fold(
HandleError(c),

View File

@ -14,12 +14,13 @@ import (
// ProjectTaskService handles project task-related HTTP requests.
type ProjectTaskService struct {
repository repository.ProjectTaskRepository
authRepository repository.AuthRepository
repository repository.ProjectTaskRepository
}
// NewProjectTaskService creates a new instance of ProjectTaskService.
func NewProjectTaskService(repo repository.ProjectTaskRepository) *ProjectTaskService {
return &ProjectTaskService{repository: repo}
func NewProjectTaskService(authRepo repository.AuthRepository, repo repository.ProjectTaskRepository) *ProjectTaskService {
return &ProjectTaskService{authRepository: authRepo, repository: repo}
}
// RegisterRoutes registers the project task-related routes with Gin.
@ -39,9 +40,13 @@ func (s *ProjectTaskService) CreateTask(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
F.Pipe3(
mappers.MapCreateDTOToProjectTask(taskCreateDTO),
s.repository.Create(c.Request.Context()),
F.Pipe5(
CheckAuth(c, s.authRepository),
E.Map[error](F.Constant1[string](taskCreateDTO)),
E.Map[error](mappers.MapCreateDTOToProjectTask),
E.Chain(
s.repository.Create(c.Request.Context()),
),
E.Map[error](mappers.MapProjectTaskToDTO),
E.Fold(
HandleError(c),
@ -52,9 +57,10 @@ func (s *ProjectTaskService) CreateTask(c *gin.Context) {
// GetTaskByID handles fetching a project task by its ID.
func (s *ProjectTaskService) GetTaskByID(c *gin.Context) {
F.Pipe3(
c.Param("id"),
s.repository.FindByID(c.Request.Context()),
F.Pipe4(
CheckAuth(c, s.authRepository),
E.Map[error](F.Constant1[string](c.Param("id"))),
E.Chain(s.repository.FindByID(c.Request.Context())),
E.Map[error](mappers.MapProjectTaskToDTO),
E.Fold(
HandleError(c),
@ -65,9 +71,10 @@ func (s *ProjectTaskService) GetTaskByID(c *gin.Context) {
// GetTasksByProjectID handles fetching project tasks by project ID.
func (s *ProjectTaskService) GetTasksByProjectID(c *gin.Context) {
F.Pipe3(
c.Param("projectID"),
s.repository.FindByProjectID(c.Request.Context()),
F.Pipe4(
CheckAuth(c, s.authRepository),
E.Map[error](F.Constant1[string](c.Param("projectID"))),
E.Chain(s.repository.FindByProjectID(c.Request.Context())),
E.Map[error](A.Map(mappers.MapProjectTaskToDTO)),
E.Fold(
HandleError(c),
@ -78,8 +85,9 @@ func (s *ProjectTaskService) GetTasksByProjectID(c *gin.Context) {
// GetAllTasks handles fetching all project tasks.
func (s *ProjectTaskService) GetAllTasks(c *gin.Context) {
F.Pipe2(
s.repository.FindAll(c.Request.Context()),
F.Pipe3(
CheckAuth(c, s.authRepository),
E.Chain(F.Constant1[string](s.repository.FindAll(c.Request.Context()))),
E.Map[error](A.Map(mappers.MapProjectTaskToDTO)),
E.Fold(
HandleError(c),
@ -90,7 +98,6 @@ func (s *ProjectTaskService) GetAllTasks(c *gin.Context) {
// UpdateTask handles updating an existing project task.
func (s *ProjectTaskService) UpdateTask(c *gin.Context) {
id := c.Param("id")
var taskUpdateDTO dto.ProjectTaskUpdateDTO
if err := c.ShouldBindJSON(&taskUpdateDTO); err != nil {
@ -98,9 +105,15 @@ func (s *ProjectTaskService) UpdateTask(c *gin.Context) {
return
}
F.Pipe3(
mappers.MapUpdateDTOToProjectTask(taskUpdateDTO, id),
s.repository.Update(c.Request.Context()),
F.Pipe5(
CheckAuth(c, s.authRepository),
E.Map[error](F.Constant1[string](c.Param("id"))),
E.Map[error](
F.Curry2(mappers.MapUpdateDTOToProjectTask)(taskUpdateDTO),
),
E.Chain(
s.repository.Update(c.Request.Context()),
),
E.Map[error](mappers.MapProjectTaskToDTO),
E.Fold(
HandleError(c),
@ -111,9 +124,10 @@ func (s *ProjectTaskService) UpdateTask(c *gin.Context) {
// DeleteTask handles deleting a project task by its ID and returns the deleted object.
func (s *ProjectTaskService) DeleteTask(c *gin.Context) {
F.Pipe3(
c.Param("id"),
s.repository.Delete(c.Request.Context()),
F.Pipe4(
CheckAuth(c, s.authRepository),
E.Map[error](F.Constant1[string](c.Param("id"))),
E.Chain(s.repository.Delete(c.Request.Context())),
E.Map[error](mappers.MapProjectTaskToDTO),
E.Fold(
HandleError(c),

View File

@ -14,12 +14,13 @@ import (
// TimeEntryService handles time entry-related HTTP requests.
type TimeEntryService struct {
repository repository.TimeEntryRepository
authRepository repository.AuthRepository
repository repository.TimeEntryRepository
}
// NewTimeEntryService creates a new instance of TimeEntryService.
func NewTimeEntryService(repo repository.TimeEntryRepository) *TimeEntryService {
return &TimeEntryService{repository: repo}
func NewTimeEntryService(authRepo repository.AuthRepository, repo repository.TimeEntryRepository) *TimeEntryService {
return &TimeEntryService{authRepository: authRepo, repository: repo}
}
// RegisterRoutes registers the time entry-related routes with Gin.
@ -40,9 +41,11 @@ func (s *TimeEntryService) CreateTimeEntry(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
F.Pipe3(
mappers.MapCreateDTOToTimeEntry(timeEntryCreateDTO),
s.repository.Create(c.Request.Context()),
F.Pipe5( // disabled for creating the initial user
CheckAuth(c, s.authRepository),
E.Map[error](F.Constant1[string](timeEntryCreateDTO)),
E.Map[error](mappers.MapCreateDTOToTimeEntry),
E.Chain(s.repository.Create(c.Request.Context())),
E.Map[error](mappers.MapTimeEntryToDTO),
E.Fold(
HandleError(c),
@ -53,9 +56,10 @@ func (s *TimeEntryService) CreateTimeEntry(c *gin.Context) {
// GetTimeEntryByID handles fetching a time entry by its ID.
func (s *TimeEntryService) GetTimeEntryByID(c *gin.Context) {
F.Pipe3(
c.Param("id"),
s.repository.FindByID(c.Request.Context()),
F.Pipe4(
CheckAuth(c, s.authRepository),
E.Map[error](F.Constant1[string](c.Param("id"))),
E.Chain(s.repository.FindByID(c.Request.Context())),
E.Map[error](mappers.MapTimeEntryToDTO),
E.Fold(
HandleError(c),
@ -66,9 +70,10 @@ func (s *TimeEntryService) GetTimeEntryByID(c *gin.Context) {
// GetTimeEntriesByUserID handles fetching time entries by user ID.
func (s *TimeEntryService) GetTimeEntriesByUserID(c *gin.Context) {
F.Pipe3(
c.Param("userID"),
s.repository.FindByUserID(c.Request.Context()),
F.Pipe4(
CheckAuth(c, s.authRepository),
E.Map[error](F.Constant1[string](c.Param("userID"))),
E.Chain(s.repository.FindByUserID(c.Request.Context())),
E.Map[error](A.Map(mappers.MapTimeEntryToDTO)),
E.Fold(
HandleError(c),
@ -79,9 +84,10 @@ func (s *TimeEntryService) GetTimeEntriesByUserID(c *gin.Context) {
// GetTimeEntriesByProjectID handles fetching time entries by project ID.
func (s *TimeEntryService) GetTimeEntriesByProjectID(c *gin.Context) {
F.Pipe3(
c.Param("projectID"),
s.repository.FindByProjectID(c.Request.Context()),
F.Pipe4(
CheckAuth(c, s.authRepository),
E.Map[error](F.Constant1[string](c.Param("projectID"))),
E.Chain(s.repository.FindByProjectID(c.Request.Context())),
E.Map[error](A.Map(mappers.MapTimeEntryToDTO)),
E.Fold(
HandleError(c),
@ -92,8 +98,9 @@ func (s *TimeEntryService) GetTimeEntriesByProjectID(c *gin.Context) {
// GetAllTimeEntries handles fetching all time entries.
func (s *TimeEntryService) GetAllTimeEntries(c *gin.Context) {
F.Pipe2(
s.repository.FindAll(c.Request.Context()),
F.Pipe3(
CheckAuth(c, s.authRepository),
E.Chain(F.Constant1[string](s.repository.FindAll(c.Request.Context()))),
E.Map[error](A.Map(mappers.MapTimeEntryToDTO)),
E.Fold(
HandleError(c),
@ -104,7 +111,6 @@ func (s *TimeEntryService) GetAllTimeEntries(c *gin.Context) {
// UpdateTimeEntry handles updating an existing time entry.
func (s *TimeEntryService) UpdateTimeEntry(c *gin.Context) {
id := c.Param("id")
var timeEntryUpdateDTO dto.TimeEntryUpdateDTO
if err := c.ShouldBindJSON(&timeEntryUpdateDTO); err != nil {
@ -112,9 +118,12 @@ func (s *TimeEntryService) UpdateTimeEntry(c *gin.Context) {
return
}
F.Pipe3(
mappers.MapUpdateDTOToTimeEntry(timeEntryUpdateDTO, id),
s.repository.Update(c.Request.Context()),
F.Pipe5(
CheckAuth(c, s.authRepository),
E.Map[error](F.Constant1[string](c.Param("id"))),
E.Map[error](
F.Curry2(mappers.MapUpdateDTOToTimeEntry)(timeEntryUpdateDTO)),
E.Chain(s.repository.Update(c.Request.Context())),
E.Map[error](mappers.MapTimeEntryToDTO),
E.Fold(
HandleError(c),
@ -125,9 +134,10 @@ func (s *TimeEntryService) UpdateTimeEntry(c *gin.Context) {
// DeleteTimeEntry handles deleting a time entry by its ID and returns the deleted object.
func (s *TimeEntryService) DeleteTimeEntry(c *gin.Context) {
F.Pipe3(
c.Param("id"),
s.repository.Delete(c.Request.Context()),
F.Pipe4(
CheckAuth(c, s.authRepository),
E.Map[error](F.Constant1[string](c.Param("id"))),
E.Chain(s.repository.Delete(c.Request.Context())),
E.Map[error](mappers.MapTimeEntryToDTO),
E.Fold(
HandleError(c),

View File

@ -14,12 +14,13 @@ import (
// UserService handles user-related HTTP requests.
type UserService struct {
repository repository.UserRepository
authRepository repository.AuthRepository
repository repository.UserRepository
}
// NewUserService creates a new instance of UserService.
func NewUserService(repo repository.UserRepository) *UserService {
return &UserService{repository: repo}
func NewUserService(authRepo repository.AuthRepository, repo repository.UserRepository) *UserService {
return &UserService{authRepository: authRepo, repository: repo}
}
// RegisterRoutes registers the user-related routes with Gin.
@ -47,13 +48,28 @@ func (s *UserService) CreateUser(c *gin.Context) {
HandleSuccess[dto.UserDTO](c, http.StatusCreated),
),
)
//F.Pipe5( // disabled for creating the initial user
// CheckAuth(c, s.authRepository),
// E.Map[error](F.Constant1[string](userCreateDTO)),
// E.Map[error](mappers.MapCreateDTOToUser),
// E.Chain(
// s.repository.Create(c.Request.Context()),
// ),
// E.Map[error](mappers.MapUserToDTO),
// E.Fold(
// HandleError(c),
// HandleSuccess[dto.UserDTO](c, http.StatusCreated),
// ),
//)
}
// GetUserByID handles fetching a user by their ID.
func (s *UserService) GetUserByID(c *gin.Context) {
F.Pipe3(
c.Param("id"),
s.repository.FindByID(c.Request.Context()),
F.Pipe4(
CheckAuth(c, s.authRepository),
E.Map[error](F.Constant1[string](c.Param("id"))),
E.Chain(s.repository.FindByID(c.Request.Context())),
E.Map[error](mappers.MapUserToDTO),
E.Fold(
HandleError(c),
@ -64,8 +80,9 @@ func (s *UserService) GetUserByID(c *gin.Context) {
// GetAllUsers handles fetching all users.
func (s *UserService) GetAllUsers(c *gin.Context) {
F.Pipe2(
s.repository.FindAll(c.Request.Context()),
F.Pipe3(
CheckAuth(c, s.authRepository),
E.Chain(F.Constant1[string](s.repository.FindAll(c.Request.Context()))),
E.Map[error](A.Map(mappers.MapUserToDTO)),
E.Fold(
HandleError(c),
@ -76,17 +93,17 @@ func (s *UserService) GetAllUsers(c *gin.Context) {
// UpdateUser handles updating an existing user.
func (s *UserService) UpdateUser(c *gin.Context) {
id := c.Param("id")
var userUpdateDTO dto.UserUpdateDTO
if err := c.ShouldBindJSON(&userUpdateDTO); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
F.Pipe3(
mappers.MapUpdateDTOToUser(userUpdateDTO, id),
s.repository.Update(c.Request.Context()),
F.Pipe5(
CheckAuth(c, s.authRepository),
E.Map[error](F.Constant1[string](c.Param("id"))),
E.Map[error](F.Curry2(mappers.MapUpdateDTOToUser)(userUpdateDTO)),
E.Chain(s.repository.Update(c.Request.Context())),
E.Map[error](mappers.MapUserToDTO),
E.Fold(
HandleError(c),
@ -97,9 +114,10 @@ func (s *UserService) UpdateUser(c *gin.Context) {
// DeleteUser handles deleting a user by their ID.
func (s *UserService) DeleteUser(c *gin.Context) {
F.Pipe3(
c.Param("id"),
s.repository.Delete(c.Request.Context()),
F.Pipe4(
CheckAuth(c, s.authRepository),
E.Map[error](F.Constant1[string](c.Param("id"))),
E.Chain(s.repository.Delete(c.Request.Context())),
E.Map[error](mappers.MapUserToDTO),
E.Fold(
HandleError(c),