track/pkg/handler/user.go

156 lines
3.1 KiB
Go

package handler
import (
"encoding/json"
"net/mail"
"strings"
"github.com/alexedwards/scs/v2"
"github.com/labstack/echo/v4"
"gitrepo.ru/neonxp/track/pkg/models"
"go.etcd.io/bbolt"
"go.neonxp.ru/objectid"
"golang.org/x/crypto/bcrypt"
)
var (
ErrInvalidPasswordLen = echo.NewHTTPError(400, "Неверная длина пароля (должно быть от 8 до 32 символов)")
ErrPasswordsNotSame = echo.NewHTTPError(400, "Пароли не совпадают")
ErrInvalidEmailOrPassword = echo.NewHTTPError(400, "Неверный email или пароль")
)
type User struct {
db *bbolt.DB
sessions *scs.SessionManager
}
func NewUser(db *bbolt.DB, sessions *scs.SessionManager) *User {
return &User{
db: db,
sessions: sessions,
}
}
func (u *User) Register(c echo.Context) error {
req := new(RegisterRequest)
if err := c.Bind(req); err != nil {
return err
}
if _, err := mail.ParseAddress(req.Email); err != nil {
return echo.NewHTTPError(400, err.Error())
}
if len(req.Password) < 8 || len(req.Password) > 32 {
return ErrInvalidPasswordLen
}
if req.Password != req.Password2 {
return ErrPasswordsNotSame
}
password, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
return err
}
user := &models.User{
ID: objectid.New(),
Email: req.Email,
Password: password,
}
err = u.db.Update(func(tx *bbolt.Tx) error {
users, err := tx.CreateBucketIfNotExists([]byte("users"))
if err != nil {
return err
}
jb, err := json.Marshal(user)
if err != nil {
return err
}
if err := users.Put([]byte(strings.ToLower(user.Email)), jb); err != nil {
return err
}
return nil
})
if err != nil {
return err
}
return c.JSON(201, UserResponse{
ID: user.ID,
Email: user.Email,
})
}
func (u *User) Login(c echo.Context) error {
req := new(LoginRequest)
if err := c.Bind(req); err != nil {
return err
}
user := new(models.User)
err := u.db.View(func(tx *bbolt.Tx) error {
users := tx.Bucket([]byte("users"))
jb := users.Get([]byte(strings.ToLower(req.Email)))
if jb == nil {
return ErrInvalidEmailOrPassword
}
if err := json.Unmarshal(jb, user); err != nil {
return err
}
if err := bcrypt.CompareHashAndPassword(user.Password, []byte(req.Password)); err != nil {
return ErrInvalidEmailOrPassword
}
return nil
})
if err != nil {
return err
}
u.sessions.Put(c.Request().Context(), "user", user)
return c.JSON(200, UserResponse{
ID: user.ID,
Email: user.Email,
})
}
func (u *User) User(c echo.Context) error {
uu := u.sessions.Get(c.Request().Context(), "user")
if uu == nil {
return echo.ErrForbidden
}
user, ok := uu.(*models.User)
if !ok {
return echo.ErrForbidden
}
return c.JSON(200, UserResponse{
ID: user.ID,
Email: user.Email,
})
}
type RegisterRequest struct {
Email string `json:"email"`
Password string `json:"password"`
Password2 string `json:"password2"`
}
type LoginRequest struct {
Email string `json:"email"`
Password string `json:"password"`
}
type UserResponse struct {
ID objectid.ID `json:"id"`
Email string `json:"email"`
}