Skip to content

Commit

Permalink
11.02.2025, 02:41 AM
Browse files Browse the repository at this point in the history
  • Loading branch information
chempik1234 committed Feb 10, 2025
1 parent 4a7f64f commit 19b0d75
Show file tree
Hide file tree
Showing 10 changed files with 367 additions and 98 deletions.
21 changes: 17 additions & 4 deletions internal/apprunner/app_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package apprunner

import (
"context"
"fmt"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/gofiber/fiber/v2/middleware/limiter"
Expand All @@ -10,6 +11,7 @@ import (
"notes_service/internal/models"
"notes_service/internal/ports"
use_cases "notes_service/internal/usecases"
"notes_service/pkg/auth/password"
"notes_service/pkg/storage/postgres"
"notes_service/pkg/storage/redis"
"os/user"
Expand Down Expand Up @@ -47,12 +49,14 @@ func RunApp(mainConfig *config.Configs) error {
notesUseCase := use_cases.NewNoteCRUDUseCase(notesRepo)
notesHandler := http.NewNotesHandler(*notesUseCase)

usersRepo := ports.NewUsersRepoDB(db)
passwordManager := password.NewPasswordManagerBcrypt()

usersRepo := ports.NewUsersRepoDB(db, passwordManager)
usersUseCase := use_cases.NewUserCRUDUseCase(usersRepo)

usersHandler := http.NewUsersHandler(*usersUseCase)

jwtHandler := http.NewJWTHandler(*usersUseCase, *mainConfig.JWT)
jwtHandler := http.NewJWTHandler(usersUseCase, *mainConfig.JWT)

app := fiber.New()

Expand All @@ -75,9 +79,12 @@ func RunApp(mainConfig *config.Configs) error {

apiV1 := app.Group("/api/v1")

apiV1.Post("/users", jwtHandler.SignUpHandler)
apiV1.Post("/sign-up", jwtHandler.SignUpHandler)
apiV1.Post("/sign-in", jwtHandler.SignInHandler)
apiV1.Post("/refresh", jwtHandler.RefreshHandler)

protectedV1 := apiV1.Use("/protected", jwtHandler.JWTMiddleware())
protectedV1 := apiV1.Group("/protected")
protectedV1.Use(jwtHandler.JWTMiddleware())

protectedV1.Get("/notes/by-user/:id", notesHandler.ListNotesHandler)
protectedV1.Get("/notes/:id", notesHandler.GetNoteByIDHandler)
Expand All @@ -86,11 +93,17 @@ func RunApp(mainConfig *config.Configs) error {
protectedV1.Delete("/notes/:id", notesHandler.DeleteNoteHandler)
protectedV1.Get("/notes/count-by-user/:id", notesHandler.CountNotesByUserHandler)

protectedV1.Get("/users/me", usersHandler.GetCurrentUserHandler)
protectedV1.Get("/users/:id", usersHandler.GetUserByIDHandler)
protectedV1.Get("/users/by-login/:login", usersHandler.GetUserByLoginHandler)
protectedV1.Put("/users/:id", usersHandler.UpdateUserHandler)
protectedV1.Delete("/users/:id", usersHandler.DeleteUserHandler)

routes := app.GetRoutes()
for _, route := range routes {
fmt.Printf("%s %s\n", route.Method, route.Path)
}

log.Info("Listening on port " + mainConfig.HTTP.Port)
log.Info("Redis on " + mainConfig.Redis.URL)
err = app.Listen(":" + mainConfig.HTTP.Port)
Expand Down
169 changes: 130 additions & 39 deletions internal/handler/http/auth.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,37 @@
package http

import (
"errors"
"github.com/gofiber/fiber/v2"
"github.com/golang-jwt/jwt/v4"
"github.com/google/uuid"
"notes_service/config"
"notes_service/internal/handler/schemas"
"notes_service/internal/models"
"notes_service/internal/usecases"
"notes_service/pkg/auth/jwtutils"
"time"
)

// AuthUseCase Use case interface required for JWTHandler
type AuthUseCase interface {
GetUserByID(userID uuid.UUID) (models.User, bool, error)
CreateUser(user models.User) (models.User, error)
GetUserByLogin(login string) (models.User, bool, error)
GetUserByLoginAndPassword(login string, password string) (models.User, bool, error)
}

// JWTHandler is the auth header for fiber application.
// It has some values for creating JWT's and an auth use case
type JWTHandler struct {
secretKey string
accessTokenLifetime time.Duration
refreshTokenLifetime time.Duration
useCase usecases.UserUseCase
useCase AuthUseCase
}

// NewJWTHandler creates and returns a new instance of NewJWTHandler.
// It accepts the use case and a config.JWT to extract values from.
func NewJWTHandler(useCase usecases.UserUseCase, jwtConfig config.JWT) *JWTHandler {
func NewJWTHandler(useCase AuthUseCase, jwtConfig config.JWT) *JWTHandler {

return &JWTHandler{
secretKey: jwtConfig.SecretKey,
Expand All @@ -43,76 +51,159 @@ func (h *JWTHandler) JWTMiddleware() fiber.Handler {
}

tokenString := authHeader[len("Bearer "):]
token, err := jwtutils.ValidateToken(tokenString, h.secretKey)
if err != nil || !token.Valid {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid token"})
userID, _, err := h.runChecksForTokenString(tokenString, "access")
if err != nil {
return NotAuthenticatedError(c, err)
}

claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid token claims"})
}
c.Locals("userID", userID)
return c.Next()
}
}

tokenType, ok := claims["type"].(string)
if !ok || tokenType != "access" {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "token type must be access"})
}
func (h *JWTHandler) runChecksForTokenString(tokenString string, requiredTokenType string) (uuid.UUID, string, error) {
token, err := jwtutils.ValidateToken(tokenString, h.secretKey)
if err != nil || !token.Valid {
return uuid.UUID{}, "", errors.New("invalid token")
}

userIDString, ok := claims["sub"].(string)
userID, err := uuid.Parse(userIDString)
if !ok || err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid user ID in token"})
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return uuid.UUID{}, "", errors.New("invalid token claims")
}

_, err = h.useCase.GetUserByID(userID)
if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "can't find user with given user ID"})
}
tokenType, ok := claims["type"].(string)
if !ok || tokenType != requiredTokenType {
return uuid.UUID{}, "", errors.New("token type must be " + requiredTokenType)
}

c.Locals("userID", userID)
return c.Next()
userIDString, ok := claims["sub"].(string)
userID, err := uuid.Parse(userIDString)
if !ok || err != nil {
return uuid.UUID{}, "", errors.New("invalid user ID in token")
}

userLogin := claims["username"].(string)

// user, userFound, err := h.useCase.GetUserByID(userID)
// if !userFound || err != nil {
// return models.User{}, errors.New("can't find user with given user ID")
// }

return userID, userLogin, nil
}

func (h *JWTHandler) SignUpHandler(c *fiber.Ctx) error {
func (h *JWTHandler) parseUser(c *fiber.Ctx) (models.User, error) {
var body schemas.UserBodySchema
if err := c.BodyParser(&body); err != nil {
return BadRequest(c, "invalid request body")
return models.User{}, err
}

user := models.User{
Login: body.Login,
Password: body.Password,
}
createdUser, err := h.useCase.CreateUser(user)
if err != nil {
return InternalServerError(c, err)
}
return user, nil
}

func (h *JWTHandler) createTokensForUser(userID uuid.UUID, userLogin string) (fiber.Map, error) {
accessToken, err := jwtutils.GenerateToken(
createdUser.ID,
createdUser.Login,
userID,
userLogin,
"access",
h.accessTokenLifetime,
h.secretKey,
)
if err != nil {
return InternalServerError(c, err)
return nil, err
}

refreshToken, err := jwtutils.GenerateToken(
createdUser.ID,
createdUser.Login,
userID,
userLogin,
"refresh",
h.refreshTokenLifetime,
h.secretKey,
)
if err != nil {
return InternalServerError(c, err)
return nil, err
}

return c.Status(fiber.StatusOK).JSON(fiber.Map{
return fiber.Map{
"accessToken": accessToken,
"refreshToken": refreshToken,
})
}, nil
}

// SignUpHandler HTTP handler for creating a new user and retrieving a new token
func (h *JWTHandler) SignUpHandler(c *fiber.Ctx) error {
user, err := h.parseUser(c)
if err != nil {
return BadRequest(c, "invalid user data")
}

_, found, err := h.useCase.GetUserByLogin(user.Login)
if err != nil {
return InternalServerError(c, err)
}
if found {
return BadRequest(c, "username already exists")
}

createdUser, err := h.useCase.CreateUser(user)
if err != nil {
return InternalServerError(c, err)
}

returnData, err := h.createTokensForUser(createdUser.ID, createdUser.Login)
if err != nil {
return InternalServerError(c, err)
}

return c.Status(fiber.StatusOK).JSON(returnData)
}

// SignInHandler HTTP Handler for retrieving a new token by login and password
func (h *JWTHandler) SignInHandler(c *fiber.Ctx) error {
user, err := h.parseUser(c)
if err != nil {
return BadRequest(c, "invalid user data")
}

foundUser, userFound, err := h.useCase.GetUserByLoginAndPassword(user.Login, user.Password)
if err != nil {
return InternalServerError(c, err)
}
if !userFound {
return NotAuthenticatedError(c, errors.New("user not found"))
}

returnData, err := h.createTokensForUser(foundUser.ID, foundUser.Login)
if err != nil {
return InternalServerError(c, err)
}

return c.JSON(returnData)
}

// RefreshHandler HTTP handler for refreshing JWT's
func (h *JWTHandler) RefreshHandler(c *fiber.Ctx) error {
var body schemas.RefreshTokenSchema
if err := c.BodyParser(&body); err != nil {
return BadRequest(c, "invalid refresh token data")
}

tokenString := body.RefreshToken

userID, userLogin, err := h.runChecksForTokenString(tokenString, "refresh")
if err != nil {
return NotAuthenticatedError(c, err)
}

returnData, err := h.createTokensForUser(userID, userLogin)
if err != nil {
return InternalServerError(c, err)
}

return c.Status(fiber.StatusOK).JSON(returnData)
}
22 changes: 19 additions & 3 deletions internal/handler/http/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,34 @@ import (

func BadRequest(c *fiber.Ctx, message string) error {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": message,
"error": message,
})
}

func InternalServerError(c *fiber.Ctx, err error) error {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": err.Error(),
"error": err.Error(),
})
}

func NotAuthenticatedError(c *fiber.Ctx, err error) error {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": err.Error(),
})
}

func NotFoundError(c *fiber.Ctx, message string) error {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": message,
})
}

func ForbiddenError(c *fiber.Ctx) error {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{})
}

func ParseUUID(c *fiber.Ctx, fieldName string) (uuid.UUID, error) {
uuidField, err := uuid.Parse(c.Params("id"))
uuidField, err := uuid.Parse(c.Params(fieldName))
if err != nil {
return uuid.UUID{}, err
}
Expand Down
Loading

0 comments on commit 19b0d75

Please sign in to comment.