mirror of
https://github.com/teamhanko/hanko.git
synced 2025-10-26 21:57:14 +08:00
291 lines
8.1 KiB
Go
291 lines
8.1 KiB
Go
package handler
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"github.com/gobuffalo/pop/v6"
|
|
"github.com/gofrs/uuid"
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/teamhanko/hanko/backend/v2/config"
|
|
"github.com/teamhanko/hanko/backend/v2/dto/admin"
|
|
"github.com/teamhanko/hanko/backend/v2/persistence"
|
|
"github.com/teamhanko/hanko/backend/v2/persistence/models"
|
|
"github.com/teamhanko/hanko/backend/v2/webhooks/events"
|
|
"github.com/teamhanko/hanko/backend/v2/webhooks/utils"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
type EmailAdminHandler interface {
|
|
List(ctx echo.Context) error
|
|
Create(ctx echo.Context) error
|
|
|
|
Get(ctx echo.Context) error
|
|
Delete(ctx echo.Context) error
|
|
SetPrimaryEmail(ctx echo.Context) error
|
|
}
|
|
|
|
type emailAdminHandler struct {
|
|
cfg *config.Config
|
|
persister persistence.Persister
|
|
}
|
|
|
|
const (
|
|
parseUserUuidFailureMessage = "failed to parse user_id as uuid: %w"
|
|
fetchUserFromDbFailureMessage = "failed to fetch user from db: %w"
|
|
)
|
|
|
|
func NewEmailAdminHandler(cfg *config.Config, persister persistence.Persister) EmailAdminHandler {
|
|
return &emailAdminHandler{
|
|
cfg: cfg,
|
|
persister: persister,
|
|
}
|
|
}
|
|
|
|
func (h *emailAdminHandler) List(ctx echo.Context) error {
|
|
listDto, err := loadDto[admin.ListEmailRequestDto](ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
userId, err := uuid.FromString(listDto.UserId)
|
|
if err != nil {
|
|
return fmt.Errorf(parseUserUuidFailureMessage, err)
|
|
}
|
|
|
|
emails, err := h.persister.GetEmailPersister().FindByUserId(userId)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch emails from db: %w", err)
|
|
}
|
|
|
|
response := make([]*admin.Email, len(emails))
|
|
|
|
for i := range emails {
|
|
response[i] = admin.FromEmailModel(&emails[i])
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
func (h *emailAdminHandler) Create(ctx echo.Context) error {
|
|
createDto, err := loadDto[admin.CreateEmailRequestDto](ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
userId, err := uuid.FromString(createDto.UserId)
|
|
if err != nil {
|
|
return fmt.Errorf(parseUserUuidFailureMessage, err)
|
|
}
|
|
|
|
emailCount, err := h.persister.GetEmailPersister().CountByUserId(userId)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to count user emails: %w", err)
|
|
}
|
|
|
|
if emailCount >= h.cfg.Email.Limit {
|
|
return echo.NewHTTPError(http.StatusConflict).SetInternal(errors.New("max number of email addresses reached"))
|
|
}
|
|
|
|
newEmailAddress := strings.ToLower(createDto.Address)
|
|
|
|
email, err := h.persister.GetEmailPersister().FindByAddress(newEmailAddress)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to fetch email from db: %w", err)
|
|
}
|
|
|
|
user, err := h.persister.GetUserPersister().Get(userId)
|
|
if err != nil {
|
|
return fmt.Errorf(fetchUserFromDbFailureMessage, err)
|
|
}
|
|
|
|
return h.persister.Transaction(func(tx *pop.Connection) error {
|
|
if user == nil {
|
|
return echo.NewHTTPError(http.StatusNotFound).SetInternal(errors.New("user not found"))
|
|
}
|
|
|
|
if email != nil {
|
|
// The email address already exists.
|
|
if email.UserID != nil {
|
|
// The email address exists and is assigned to a user already, therefore it can't be created.
|
|
return echo.NewHTTPError(http.StatusBadRequest).SetInternal(errors.New("email address already exists"))
|
|
}
|
|
|
|
email.UserID = &user.ID
|
|
err = h.persister.GetEmailPersisterWithConnection(tx).Update(*email)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update the existing email: %w", err)
|
|
}
|
|
} else {
|
|
email = models.NewEmail(&user.ID, newEmailAddress)
|
|
email.Verified = createDto.IsVerified
|
|
|
|
err = h.persister.GetEmailPersisterWithConnection(tx).Create(*email)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to store email to db: %w", err)
|
|
}
|
|
}
|
|
|
|
// make email primary if user had no emails prior to email creation
|
|
if len(user.Emails) == 0 {
|
|
primaryEmail := models.NewPrimaryEmail(email.ID, user.ID)
|
|
err = h.persister.GetPrimaryEmailPersisterWithConnection(tx).Create(*primaryEmail)
|
|
}
|
|
|
|
utils.NotifyUserChange(ctx, tx, h.persister, events.UserEmailCreate, userId)
|
|
|
|
return ctx.JSON(http.StatusCreated, admin.FromEmailModel(email))
|
|
})
|
|
}
|
|
|
|
func (h *emailAdminHandler) Get(ctx echo.Context) error {
|
|
getDto, err := loadDto[admin.GetEmailRequestDto](ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
userId, err := uuid.FromString(getDto.UserId)
|
|
if err != nil {
|
|
return fmt.Errorf(parseUserUuidFailureMessage, err)
|
|
}
|
|
|
|
emailId, err := uuid.FromString(getDto.EmailId)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse email_id as uuid: %w", err)
|
|
}
|
|
|
|
user, err := h.persister.GetUserPersister().Get(userId)
|
|
if err != nil {
|
|
return fmt.Errorf(fetchUserFromDbFailureMessage, err)
|
|
}
|
|
|
|
if user == nil {
|
|
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("user with id '%s' was not found", userId))
|
|
}
|
|
|
|
fetchedEmail := user.GetEmailById(emailId)
|
|
if fetchedEmail == nil {
|
|
return echo.NewHTTPError(http.StatusNotFound, fmt.Errorf("email with id '%s' was not found", emailId))
|
|
}
|
|
|
|
return ctx.JSON(http.StatusOK, admin.FromEmailModel(fetchedEmail))
|
|
}
|
|
|
|
func (h *emailAdminHandler) Delete(ctx echo.Context) error {
|
|
deleteDto, err := loadDto[admin.GetEmailRequestDto](ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
userId, err := uuid.FromString(deleteDto.UserId)
|
|
if err != nil {
|
|
return fmt.Errorf(parseUserUuidFailureMessage, err)
|
|
}
|
|
|
|
emailId, err := uuid.FromString(deleteDto.EmailId)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse email_id as uuid: %w", err)
|
|
}
|
|
|
|
user, err := h.persister.GetUserPersister().Get(userId)
|
|
if err != nil {
|
|
return fmt.Errorf(fetchUserFromDbFailureMessage, err)
|
|
}
|
|
|
|
if user == nil {
|
|
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("user with id '%s' was not found", userId))
|
|
}
|
|
|
|
emailToBeDeleted := user.GetEmailById(emailId)
|
|
if emailToBeDeleted == nil {
|
|
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("email with id '%s' was not found", emailId))
|
|
}
|
|
|
|
if emailToBeDeleted.IsPrimary() {
|
|
return echo.NewHTTPError(http.StatusConflict).SetInternal(errors.New("primary email can't be deleted"))
|
|
}
|
|
|
|
return h.persister.Transaction(func(tx *pop.Connection) error {
|
|
err = h.persister.GetEmailPersisterWithConnection(tx).Delete(*emailToBeDeleted)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete email from db: %w", err)
|
|
}
|
|
|
|
utils.NotifyUserChange(ctx, tx, h.persister, events.UserEmailDelete, userId)
|
|
|
|
return ctx.NoContent(http.StatusNoContent)
|
|
})
|
|
}
|
|
|
|
func (h *emailAdminHandler) SetPrimaryEmail(ctx echo.Context) error {
|
|
primaryDto, err := loadDto[admin.GetEmailRequestDto](ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
userId, err := uuid.FromString(primaryDto.UserId)
|
|
if err != nil {
|
|
return fmt.Errorf(parseUserUuidFailureMessage, err)
|
|
}
|
|
|
|
emailId, err := uuid.FromString(primaryDto.EmailId)
|
|
if err != nil {
|
|
return echo.NewHTTPError(http.StatusBadRequest).SetInternal(err)
|
|
}
|
|
|
|
user, err := h.persister.GetUserPersister().Get(userId)
|
|
if err != nil {
|
|
return fmt.Errorf(fetchUserFromDbFailureMessage, err)
|
|
}
|
|
|
|
if user == nil {
|
|
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("user with id '%s' was not found", userId))
|
|
}
|
|
|
|
email := user.GetEmailById(emailId)
|
|
if email == nil {
|
|
return echo.NewHTTPError(http.StatusNotFound).SetInternal(errors.New("the email address is not assigned to the current user"))
|
|
}
|
|
|
|
if email.IsPrimary() {
|
|
return ctx.NoContent(http.StatusNoContent)
|
|
}
|
|
|
|
return h.persister.Transaction(func(tx *pop.Connection) error {
|
|
err := h.makeEmailPrimary(ctx, email, user, tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
utils.NotifyUserChange(ctx, tx, h.persister, events.UserEmailPrimary, userId)
|
|
|
|
return ctx.NoContent(http.StatusNoContent)
|
|
})
|
|
}
|
|
|
|
func (h *emailAdminHandler) makeEmailPrimary(ctx echo.Context, email *models.Email, user *models.User, tx *pop.Connection) error {
|
|
var primaryEmail *models.PrimaryEmail
|
|
if e := user.Emails.GetPrimary(); e != nil {
|
|
primaryEmail = e.PrimaryEmail
|
|
}
|
|
|
|
if primaryEmail == nil {
|
|
primaryEmail = models.NewPrimaryEmail(email.ID, user.ID)
|
|
err := h.persister.GetPrimaryEmailPersisterWithConnection(tx).Create(*primaryEmail)
|
|
if err != nil {
|
|
ctx.Logger().Error(err)
|
|
return fmt.Errorf("failed to store new primary email: %w", err)
|
|
}
|
|
} else {
|
|
primaryEmail.EmailID = email.ID
|
|
err := h.persister.GetPrimaryEmailPersisterWithConnection(tx).Update(*primaryEmail)
|
|
if err != nil {
|
|
ctx.Logger().Error(err)
|
|
return fmt.Errorf("failed to change primary email: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|