Files
hanko/backend/handler/password.go
2022-06-09 14:36:00 +02:00

136 lines
3.7 KiB
Go

package handler
import (
"errors"
"fmt"
"github.com/gobuffalo/pop/v6"
"github.com/gofrs/uuid"
"github.com/labstack/echo/v4"
"github.com/lestrrat-go/jwx/v2/jwt"
"github.com/teamhanko/hanko/dto"
"github.com/teamhanko/hanko/persistence"
"github.com/teamhanko/hanko/persistence/models"
"github.com/teamhanko/hanko/session"
"golang.org/x/crypto/bcrypt"
"net/http"
)
type PasswordHandler struct {
persister persistence.Persister
sessionManager session.Manager
}
func NewPasswordHandler(persister persistence.Persister, sessionManager session.Manager) *PasswordHandler {
return &PasswordHandler{persister: persister, sessionManager: sessionManager}
}
type PasswordSetBody struct {
UserID string `json:"user_id" validate:"required,uuid4"`
Password string `json:"password" validate:"required"`
}
func (h *PasswordHandler) Set(c echo.Context) error {
var body PasswordSetBody
if err := (&echo.DefaultBinder{}).BindBody(c, &body); err != nil {
return c.JSON(http.StatusBadRequest, err)
}
if err := c.Validate(body); err != nil {
return c.JSON(http.StatusBadRequest, err)
}
sessionToken, ok := c.Get("session").(jwt.Token)
if !ok {
return errors.New("missing or malformed jwt")
}
sessionUserId, err := uuid.FromString(sessionToken.Subject())
if err != nil {
return fmt.Errorf("failed to parse userId from JWT subject: %w", err)
}
return h.persister.Transaction(func(tx *pop.Connection) error {
user, err := h.persister.GetUserPersisterWithConnection(tx).Get(uuid.FromStringOrNil(body.UserID))
if err != nil {
return fmt.Errorf("failed to get user: %w", err)
}
if user == nil {
return c.JSON(http.StatusUnauthorized, dto.NewApiError(http.StatusUnauthorized))
}
if sessionUserId != user.ID {
return c.JSON(http.StatusForbidden, dto.NewApiError(http.StatusForbidden))
}
pwPersister := h.persister.GetPasswordCredentialPersisterWithConnection(tx)
pw, err := pwPersister.GetByUserID(user.ID)
if err != nil {
return fmt.Errorf("failed to get credential: %w", err)
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(body.Password), 12)
if err != nil {
return errors.New("failed to create credential")
}
newPw := models.PasswordCredential{
UserId: uuid.FromStringOrNil(body.UserID),
Password: string(hashedPassword),
}
if pw == nil {
err = pwPersister.Create(newPw)
if err != nil {
return fmt.Errorf("failed to create password: %w", err)
} else {
return c.JSON(http.StatusCreated, nil)
}
} else {
newPw.ID = pw.ID
err = pwPersister.Update(newPw)
if err != nil {
return fmt.Errorf("failed to set password: %w", err)
} else {
return c.JSON(http.StatusOK, nil)
}
}
})
}
type PasswordLoginBody struct {
UserId string `json:"user_id" validate:"required,uuid4"`
Password string `json:"password" validate:"required"`
}
func (h *PasswordHandler) Login(c echo.Context) error {
var body PasswordLoginBody
if err := (&echo.DefaultBinder{}).BindBody(c, &body); err != nil {
return c.JSON(http.StatusBadRequest, err)
}
if err := c.Validate(body); err != nil {
return c.JSON(http.StatusBadRequest, err)
}
pw, err := h.persister.GetPasswordCredentialPersister().GetByUserID(uuid.FromStringOrNil(body.UserId))
if pw == nil {
return c.JSON(http.StatusUnauthorized, dto.NewApiError(http.StatusUnauthorized))
}
if err != nil {
return fmt.Errorf("error retrieving credential: %w", err)
}
if err = bcrypt.CompareHashAndPassword([]byte(pw.Password), []byte(body.Password)); err != nil {
return c.JSON(http.StatusUnauthorized, dto.NewApiError(http.StatusUnauthorized))
}
cookie, err := h.sessionManager.GenerateCookie(pw.UserId)
if err != nil {
return fmt.Errorf("failed to create session cookie: %w", err)
}
c.SetCookie(cookie)
return c.String(http.StatusOK, "")
}