109 lines
3.4 KiB
Go
109 lines
3.4 KiB
Go
// codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/user/publiclookup.go
|
|
package user
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"strings"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
|
|
uc_user "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/usecase/user"
|
|
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
|
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/validation"
|
|
)
|
|
|
|
type UserPublicLookupRequestDTO struct {
|
|
Email string `json:"email"`
|
|
}
|
|
|
|
type UserPublicLookupResponseDTO struct {
|
|
UserID string `json:"user_id"`
|
|
Email string `json:"email"`
|
|
Name string `json:"name"` // Optional: for display
|
|
PublicKeyInBase64 string `json:"public_key_in_base64"` // Base64 encoded
|
|
VerificationID string `json:"verification_id"`
|
|
}
|
|
|
|
type UserPublicLookupService interface {
|
|
Execute(ctx context.Context, req *UserPublicLookupRequestDTO) (*UserPublicLookupResponseDTO, error)
|
|
}
|
|
|
|
type userPublicLookupServiceImpl struct {
|
|
config *config.Config
|
|
logger *zap.Logger
|
|
userGetByEmailUC uc_user.UserGetByEmailUseCase
|
|
}
|
|
|
|
func NewUserPublicLookupService(
|
|
cfg *config.Config,
|
|
logger *zap.Logger,
|
|
userGetByEmailUC uc_user.UserGetByEmailUseCase,
|
|
) UserPublicLookupService {
|
|
logger = logger.Named("UserPublicLookupService")
|
|
return &userPublicLookupServiceImpl{cfg, logger, userGetByEmailUC}
|
|
}
|
|
|
|
func (svc *userPublicLookupServiceImpl) Execute(ctx context.Context, req *UserPublicLookupRequestDTO) (*UserPublicLookupResponseDTO, error) {
|
|
//
|
|
// STEP 1: Sanitization of the input.
|
|
//
|
|
|
|
// Defensive Code: For security purposes we need to perform some sanitization on the inputs.
|
|
req.Email = strings.ToLower(req.Email)
|
|
req.Email = strings.ReplaceAll(req.Email, " ", "")
|
|
req.Email = strings.ReplaceAll(req.Email, "\t", "")
|
|
req.Email = strings.TrimSpace(req.Email)
|
|
|
|
svc.logger.Debug("sanitized email",
|
|
zap.String("email", validation.MaskEmail(req.Email)))
|
|
|
|
//
|
|
// STEP 2: Validation of input.
|
|
//
|
|
|
|
e := make(map[string]string)
|
|
if req.Email == "" {
|
|
e["email"] = "Email is required"
|
|
}
|
|
if len(req.Email) > 255 {
|
|
e["email"] = "Email is too long"
|
|
}
|
|
|
|
if len(e) != 0 {
|
|
svc.logger.Warn("failed validating",
|
|
zap.Any("e", e))
|
|
return nil, httperror.NewForBadRequest(&e)
|
|
}
|
|
|
|
//
|
|
// STEP 3: Lookup user by email
|
|
//
|
|
|
|
// Lookup the user in our database, else return a `400 Bad Request` error.
|
|
// Note: We return a generic error message to prevent user enumeration attacks.
|
|
u, err := svc.userGetByEmailUC.Execute(ctx, req.Email)
|
|
if err != nil {
|
|
svc.logger.Error("failed getting user by email from database",
|
|
zap.Any("error", err))
|
|
return nil, httperror.NewForBadRequestWithSingleField("email", "Unable to complete lookup")
|
|
}
|
|
if u == nil {
|
|
svc.logger.Warn("user lookup attempted for non-existent email",
|
|
zap.String("email", validation.MaskEmail(req.Email)))
|
|
// Return same error message as above to prevent user enumeration
|
|
return nil, httperror.NewForBadRequestWithSingleField("email", "Unable to complete lookup")
|
|
}
|
|
|
|
// STEP 4: Build response DTO
|
|
dto := &UserPublicLookupResponseDTO{
|
|
UserID: u.ID.String(),
|
|
Email: u.Email,
|
|
Name: u.Name,
|
|
PublicKeyInBase64: base64.StdEncoding.EncodeToString(u.SecurityData.PublicKey.Key),
|
|
VerificationID: u.SecurityData.VerificationID,
|
|
}
|
|
|
|
return dto, nil
|
|
}
|