added: auth methods

This commit is contained in:
nquidox 2025-07-07 17:49:09 +03:00
parent a176522bbb
commit 28da81ea59
5 changed files with 223 additions and 44 deletions

View file

@ -8,6 +8,8 @@ import (
"merch-parser-api/internal/api/user" "merch-parser-api/internal/api/user"
"merch-parser-api/internal/app" "merch-parser-api/internal/app"
"merch-parser-api/internal/interfaces" "merch-parser-api/internal/interfaces"
"merch-parser-api/internal/provider/auth"
"merch-parser-api/internal/provider/token"
"merch-parser-api/internal/router" "merch-parser-api/internal/router"
"merch-parser-api/pkg/db" "merch-parser-api/pkg/db"
"merch-parser-api/pkg/utils" "merch-parser-api/pkg/utils"
@ -15,45 +17,58 @@ import (
// @Title Merch Parser // @Title Merch Parser
// @BasePath /api/v2 // @BasePath /api/v2
// @Version 2.0.0-alpha // @Version 2.0.0-alpha
// @SecurityDefinitions.apikey BearerAuth // @SecurityDefinitions.apikey BearerAuth
// @In header // @In header
// @Name Authorization // @Name Authorization
// @Description Введите "Bearer {your_token}" для аутентификации // @Description Введите "Bearer {your_token}" для аутентификации
func main() { func main() {
log.Debug("Starting merch-parser-api")
//setup config //setup config
//c := config.NewConfig() //c := config.NewConfig()
c := config.DevConfig() c := config.DevConfig()
ctx := context.Background() ctx := context.Background()
//log level
config.LogSetup(c.AppConf.LogLvl)
database, err := db.Connection(c) database, err := db.Connection(c)
if err != nil { if err != nil {
log.WithError(err).Fatal("Main | Error connecting to database") log.WithError(err).Fatal("Main | Error connecting to database")
} }
_ = database //base providers
jwtProvider := token.NewJWT(token.Deps{
SecretKey: c.JWTConf.Secret,
Issuer: c.JWTConf.Issuer,
AccessExpire: c.JWTConf.AccessExpire,
RefreshExpire: c.JWTConf.RefreshExpire,
})
log.Debug("JWT provider initialized")
utilsProvider := utils.NewUtils()
log.Debug("Utils provider initialized")
//deps providers
routerHandler := router.NewRouter(router.Deps{ routerHandler := router.NewRouter(router.Deps{
ApiPrefix: c.AppConf.ApiPrefix, ApiPrefix: c.AppConf.ApiPrefix,
GinMode: c.AppConf.GinMode, GinMode: c.AppConf.GinMode,
TokenProv: jwtProvider,
}) })
log.Debug("Router handler initialized")
//base providers authProvider := auth.NewHandler(auth.Deps{
//jwtProv := token.NewJWT(token.Deps{ DB: database,
// SecretKey: c.JWTConf.Secret, JwtProvider: jwtProvider,
// Issuer: c.JWTConf.Issuer, Utils: utilsProvider,
// AccessExpire: c.JWTConf.AccessExpire, })
// RefreshExpire: c.JWTConf.RefreshExpire, log.Debug("Auth provider initialized")
//})
utilsProv := utils.NewUtils()
//deps providers
//register app modules //register app modules
users := user.NewHandler(user.Deps{ users := user.NewHandler(user.Deps{
Auth: authProvider,
DB: database, DB: database,
Utils: utilsProv, Utils: utilsProvider,
}) })
//collect modules //collect modules

View file

@ -28,23 +28,28 @@ func (h *Handler) RegisterRoutes(r *gin.RouterGroup) {
userGroup.GET("/", h.controller.get) userGroup.GET("/", h.controller.get)
userGroup.PUT("/", h.controller.update) userGroup.PUT("/", h.controller.update)
userGroup.DELETE("/", h.controller.delete) userGroup.DELETE("/", h.controller.delete)
//auth
userGroup.POST("/login", h.controller.login)
userGroup.POST("/logout", h.controller.logout)
userGroup.POST("/refresh", h.controller.refresh)
} }
func (h *Handler) ExcludeRoutes() []shared.ExcludeRoute { func (h *Handler) ExcludeRoutes() []shared.ExcludeRoute {
return []shared.ExcludeRoute{ return []shared.ExcludeRoute{
{Route: "/user", Method: http.MethodPost}, {Route: "/user", Method: http.MethodPost},
{Route: "/user/login", Method: http.MethodPost},
} }
} }
// @Summary Регистрация нового пользователя // @Summary Регистрация нового пользователя
// @Description Регистрация нового пользователя // @Description Регистрация нового пользователя
// @Tags Users // @Tags Users
// @Security BearerAuth
// @Accept json // @Accept json
// @Param body body Register true "новый пользователь" // @Param body body Register true "новый пользователь"
// @Success 200 // @Success 200
// @Failure 400 {object} responses.ErrorResponse400 // @Failure 400 {object} responses.ErrorResponse400
// @Failure 500 {object} responses.ErrorResponse500 // @Failure 500 {object} responses.ErrorResponse500
// @Router /user [post] // @Router /user [post]
func (co *controller) register(c *gin.Context) { func (co *controller) register(c *gin.Context) {
var register Register var register Register
@ -63,14 +68,14 @@ func (co *controller) register(c *gin.Context) {
c.Status(http.StatusOK) c.Status(http.StatusOK)
} }
// @Summary Получить информацию о пользователе // @Summary Получить информацию о пользователе
// @Description Получает информацию о пользователе по его uuid из токена // @Description Получает информацию о пользователе по его uuid из токена
// @Tags Users // @Tags Users
// @Security BearerAuth // @Security BearerAuth
// @Success 200 {object} Info // @Success 200 {object} Info
// @Failure 400 {object} responses.ErrorResponse400 // @Failure 400 {object} responses.ErrorResponse400
// @Failure 401 {object} responses.ErrorResponse401 // @Failure 401 {object} responses.ErrorResponse401
// @Failure 500 {object} responses.ErrorResponse500 // @Failure 500 {object} responses.ErrorResponse500
// @Router /user [get] // @Router /user [get]
func (co *controller) get(c *gin.Context) { func (co *controller) get(c *gin.Context) {
userUuid, err := co.utils.GetUserUuidFromContext(c) userUuid, err := co.utils.GetUserUuidFromContext(c)
@ -90,16 +95,16 @@ func (co *controller) get(c *gin.Context) {
c.JSON(http.StatusOK, response) c.JSON(http.StatusOK, response)
} }
// @Summary Обновить информацию о пользователе // @Summary Обновить информацию о пользователе
// @Description Обновить информацию о пользователе по его uuid из токена // @Description Обновить информацию о пользователе по его uuid из токена
// @Tags Users // @Tags Users
// @Security BearerAuth // @Security BearerAuth
// @Accept json // @Accept json
// @Param body body Update true "изменения" // @Param body body Update true "изменения"
// @Success 200 // @Success 200
// @Failure 400 {object} responses.ErrorResponse400 // @Failure 400 {object} responses.ErrorResponse400
// @Failure 401 {object} responses.ErrorResponse401 // @Failure 401 {object} responses.ErrorResponse401
// @Failure 500 {object} responses.ErrorResponse500 // @Failure 500 {object} responses.ErrorResponse500
// @Router /user [put] // @Router /user [put]
func (co *controller) update(c *gin.Context) { func (co *controller) update(c *gin.Context) {
userUuid, err := co.utils.GetUserUuidFromContext(c) userUuid, err := co.utils.GetUserUuidFromContext(c)
@ -125,14 +130,14 @@ func (co *controller) update(c *gin.Context) {
c.Status(http.StatusOK) c.Status(http.StatusOK)
} }
// @Summary Удалить пользователя // @Summary Удалить пользователя
// @Description Помечает пользователя как удаленного по его uuid из токена // @Description Помечает пользователя как удаленного по его uuid из токена
// @Tags Users // @Tags Users
// @Security BearerAuth // @Security BearerAuth
// @Success 200 // @Success 200
// @Failure 400 {object} responses.ErrorResponse400 // @Failure 400 {object} responses.ErrorResponse400
// @Failure 401 {object} responses.ErrorResponse401 // @Failure 401 {object} responses.ErrorResponse401
// @Failure 500 {object} responses.ErrorResponse500 // @Failure 500 {object} responses.ErrorResponse500
// @Router /user [delete] // @Router /user [delete]
func (co *controller) delete(c *gin.Context) { func (co *controller) delete(c *gin.Context) {
userUuid, err := co.utils.GetUserUuidFromContext(c) userUuid, err := co.utils.GetUserUuidFromContext(c)
@ -150,3 +155,82 @@ func (co *controller) delete(c *gin.Context) {
c.Status(http.StatusOK) c.Status(http.StatusOK)
} }
// @Summary Логин
// @Description Логин
// @Tags Users - auth
// @Accept json
// @Param body body Login true "логин"
// @Success 200
// @Failure 400 {object} responses.ErrorResponse400
// @Failure 500 {object} responses.ErrorResponse500
// @Router /user/login [post]
func (co *controller) login(c *gin.Context) {
var login Login
if err := c.ShouldBindJSON(&login); err != nil {
c.JSON(http.StatusBadRequest, responses.ErrorResponse400{Error: err.Error()})
log.WithError(err).Error("User | Failed to bind JSON on login")
return
}
response, err := co.service.login(login)
if err != nil {
c.JSON(http.StatusInternalServerError, responses.ErrorResponse500{Error: err.Error()})
log.WithError(err).Error("User | Failed to login")
return
}
c.JSON(http.StatusOK, response)
}
// @Summary Логаут
// @Description Логаут. Для логаута надо передать refresh token, он будет инвалидирован.
// @Tags Users - auth
// @Security BearerAuth
// @Success 200
// @Failure 400 {object} responses.ErrorResponse400
// @Failure 500 {object} responses.ErrorResponse500
// @Router /user/logout [post]
func (co *controller) logout(c *gin.Context) {
userUuid, tokenUuid, err := co.utils.GetUserAndTokenUuidFromContext(c)
if err != nil {
c.JSON(http.StatusBadRequest, responses.ErrorResponse400{Error: err.Error()})
log.WithError(err).Error("User | Failed to get uuids from context on refresh")
return
}
if err = co.service.logout(userUuid, tokenUuid); err != nil {
c.JSON(http.StatusInternalServerError, responses.ErrorResponse500{Error: err.Error()})
log.WithError(err).Error("User | Failed to logout")
return
}
c.Status(http.StatusOK)
}
// @Summary Обновление аксесс токена по рефреш токену.
// @Description Принимает рефреш токен в заголовке Authorization
// @Tags Users - auth
// @Security BearerAuth
// @Success 200
// @Failure 400 {object} responses.ErrorResponse400s
// @Failure 500 {object} responses.ErrorResponse500
// @Router /user/refresh [post]
func (co *controller) refresh(c *gin.Context) {
//токены будут помещены в контекст при срабатывании мидлвари авторизации
userUuid, tokenUuid, err := co.utils.GetUserAndTokenUuidFromContext(c)
if err != nil {
c.JSON(http.StatusBadRequest, responses.ErrorResponse400{Error: err.Error()})
log.WithError(err).Error("User | Failed to get uuids from context on refresh")
return
}
response, err := co.service.refresh(userUuid, tokenUuid)
if err != nil {
c.JSON(http.StatusInternalServerError, responses.ErrorResponse500{Error: err.Error()})
log.WithError(err).Error("User | Failed to refresh user info")
return
}
c.JSON(http.StatusOK, response)
}

View file

@ -12,13 +12,14 @@ type Handler struct {
} }
type Deps struct { type Deps struct {
Auth interfaces.Auth
DB *gorm.DB DB *gorm.DB
Utils interfaces.Utils Utils interfaces.Utils
} }
func NewHandler(deps Deps) *Handler { func NewHandler(deps Deps) *Handler {
r := newRepo(deps.DB) r := newRepo(deps.DB)
s := newService(r) s := newService(deps.Auth, r, deps.Utils)
c := newController(s, deps.Utils) c := newController(s, deps.Utils)
return &Handler{ return &Handler{

View file

@ -14,12 +14,22 @@ func newRepo(db *gorm.DB) *repo {
} }
type UserRepo interface { type UserRepo interface {
userCrud
userAuth
}
type userCrud interface {
register(user User) error register(user User) error
getByUuid(userUuid string) (User, error) getByUuid(userUuid string) (User, error)
update(user map[string]any) error update(user map[string]any) error
delete(userUuid string) error delete(userUuid string) error
} }
type userAuth interface {
getUserByEmail(email string) (User, error)
}
// user CRUD methods
func (r *repo) register(user User) error { func (r *repo) register(user User) error {
return r.db.Create(&user).Error return r.db.Create(&user).Error
} }
@ -36,3 +46,9 @@ func (r *repo) update(user map[string]any) error {
func (r *repo) delete(userUuid string) error { func (r *repo) delete(userUuid string) error {
return r.db.Model(&User{}).Where("uuid = ?", userUuid).Update("deleted_at", time.Now().UTC()).Error return r.db.Model(&User{}).Where("uuid = ?", userUuid).Update("deleted_at", time.Now().UTC()).Error
} }
// methods for auth
func (r *repo) getUserByEmail(email string) (user User, err error) {
err = r.db.Where("email = ?", email).First(&user).Error
return user, err
}

View file

@ -2,27 +2,54 @@ package user
import ( import (
"database/sql" "database/sql"
"errors"
"github.com/google/uuid" "github.com/google/uuid"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"merch-parser-api/internal/interfaces"
"merch-parser-api/internal/shared"
"time" "time"
) )
type service struct { type service struct {
repo UserRepo auth interfaces.Auth
repo UserRepo
utils interfaces.Utils
} }
func newService(repo UserRepo) *service { func newService(auth interfaces.Auth, repo UserRepo, utils interfaces.Utils) *service {
return &service{repo: repo} return &service{
auth: auth,
repo: repo,
utils: utils,
}
} }
func (s *service) register(dto Register) error { func (s *service) register(dto Register) error {
if !s.utils.IsEmail(dto.Email) {
return errors.New("email isn't valid")
}
if len(dto.Password) < 1 {
return errors.New("password can't be empty")
}
if len(dto.Username) > 255 {
return errors.New("username can't be longer than 255 characters")
}
hashedPass, err := s.utils.HashPassword(dto.Password)
if err != nil {
log.WithError(err).Error("User | Failed to hash password on register")
return err
}
user := User{ user := User{
CreatedAt: time.Now().UTC(), CreatedAt: time.Now().UTC(),
UpdatedAt: sql.NullTime{Valid: false}, UpdatedAt: sql.NullTime{Valid: false},
DeletedAt: sql.NullTime{Valid: false}, DeletedAt: sql.NullTime{Valid: false},
Uuid: uuid.NewString(), Uuid: uuid.NewString(),
Username: dto.Username, Username: dto.Username,
Password: dto.Password, Password: hashedPass,
Email: dto.Email, Email: dto.Email,
Verified: 0, Verified: 0,
} }
@ -64,3 +91,39 @@ func (s *service) update(userUuid string, update Update) error {
func (s *service) delete(userUuid string) error { func (s *service) delete(userUuid string) error {
return s.repo.delete(userUuid) return s.repo.delete(userUuid)
} }
func (s *service) login(login Login) (shared.AuthData, error) {
if !s.utils.IsEmail(login.Email) {
return shared.AuthData{}, errors.New("email isn't valid")
}
if len(login.Password) < 1 {
return shared.AuthData{}, errors.New("password can't be empty")
}
user, err := s.repo.getUserByEmail(login.Email)
if err != nil {
log.WithError(err).Error("User | Failed to get user by email")
return shared.AuthData{}, errors.New("invalid email or password")
}
if err = s.utils.ComparePasswords(user.Password, login.Password); err != nil {
return shared.AuthData{}, errors.New("invalid email or password")
}
authData, err := s.auth.Login(user.Uuid)
if err != nil {
log.WithError(err).Error("User | Failed to generate auth data")
return shared.AuthData{}, err
}
return authData, nil
}
func (s *service) logout(userUuid string, refreshUuid string) error {
return s.auth.Logout(userUuid, refreshUuid)
}
func (s *service) refresh(userUuid string, refreshUuid string) (shared.AuthData, error) {
return s.auth.Refresh(userUuid, refreshUuid)
}