152 lines
3.6 KiB
Go
152 lines
3.6 KiB
Go
package domain
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"net/mail"
|
|
|
|
validate "backend/pkg/validate/common"
|
|
|
|
"github.com/ethereum/go-ethereum/accounts"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
var (
|
|
ErrUserNotFound = errors.New("user not found")
|
|
ErrEmailInvalid = errors.New("email is invalid")
|
|
)
|
|
|
|
type UserRepo interface {
|
|
Create(ctx context.Context, user *User) error
|
|
GetByID(ctx context.Context, id uuid.UUID) (*User, error)
|
|
GetByPhone(ctx context.Context, phone string) (*User, error)
|
|
GetByPubKey(ctx context.Context, pubKey string) (*User, error)
|
|
GetOrCreateUserByPubKey(ctx context.Context, pubKey string) (*User, error)
|
|
Update(ctx context.Context, user *User) error
|
|
Delete(ctx context.Context, id uuid.UUID) error
|
|
}
|
|
|
|
type ChallengeRepo interface {
|
|
Create(ctx context.Context, pubKey string, challenge *Challenge) error
|
|
GetByPubKey(ctx context.Context, pubKey string) (*Challenge, error)
|
|
Delete(ctx context.Context, pubKey string) error
|
|
}
|
|
|
|
type User struct {
|
|
ID uuid.UUID
|
|
PubKey string
|
|
Name string
|
|
//TODO: add Kyc Embedded struct
|
|
//TODO: add NFT Embedded struct
|
|
LastName string
|
|
PhoneNumber string
|
|
Email *string
|
|
NationalID string
|
|
BirthDate *time.Time
|
|
LastLogin *time.Time
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
DeletedAt *time.Time
|
|
}
|
|
|
|
// TODO: move to another file?
|
|
type Challenge struct {
|
|
Message string
|
|
TimeStamp time.Time
|
|
ExpiresAt time.Time
|
|
}
|
|
|
|
func (c *Challenge) IsExpired() bool {
|
|
return time.Now().After(c.ExpiresAt)
|
|
}
|
|
|
|
func (c *Challenge) Verify(address string, signedMsg string) (bool, error) {
|
|
if c.IsExpired() {
|
|
return false, errors.New("challenge has expired")
|
|
}
|
|
|
|
signature, err := hexutil.Decode(signedMsg)
|
|
if err != nil {
|
|
return false, fmt.Errorf("decode signature: %w", err)
|
|
}
|
|
|
|
challengeBytes := []byte(c.Message)
|
|
|
|
return c.VerifySignedBytes(address, challengeBytes, signature)
|
|
}
|
|
|
|
func (c *Challenge) VerifySignedBytes(expectedAddress string, msg []byte, sig []byte) (bool, error) {
|
|
msgHash := accounts.TextHash(msg)
|
|
|
|
if len(sig) != 65 {
|
|
return false, fmt.Errorf("invalid signature length: expected 65 bytes, got %d", len(sig))
|
|
}
|
|
|
|
if sig[crypto.RecoveryIDOffset] == 27 || sig[crypto.RecoveryIDOffset] == 28 {
|
|
sig[crypto.RecoveryIDOffset] -= 27
|
|
}
|
|
|
|
recovered, err := crypto.SigToPub(msgHash, sig)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to recover public key: %w", err)
|
|
}
|
|
recoveredAddr := crypto.PubkeyToAddress(*recovered)
|
|
|
|
if !common.IsHexAddress(expectedAddress) {
|
|
return false, fmt.Errorf("invalid Ethereum address format: %s", expectedAddress)
|
|
}
|
|
expectedAddr := common.HexToAddress(expectedAddress)
|
|
|
|
return strings.EqualFold(expectedAddr.Hex(), recoveredAddr.Hex()), nil
|
|
}
|
|
|
|
func NewUser(pubKey, name, lastName, phoneNumber, email, nationalID string) (*User, error) {
|
|
|
|
_, err := validate.IsValidPhone(phoneNumber)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err = validate.IsValidNationalID(nationalID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, err = validate.IsValidPublicKey(pubKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &User{
|
|
ID: uuid.New(),
|
|
PubKey: pubKey,
|
|
Name: name,
|
|
LastName: lastName,
|
|
PhoneNumber: phoneNumber,
|
|
NationalID: nationalID,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}, nil
|
|
}
|
|
|
|
func (u *User) UpdateLastLogin() {
|
|
now := time.Now().UTC()
|
|
u.LastLogin = &now
|
|
u.UpdatedAt = now
|
|
}
|
|
|
|
func (u *User) UpdateInfo(email string) {
|
|
addr, err := mail.ParseAddress(email)
|
|
if err == nil {
|
|
u.Email = &addr.Address
|
|
}
|
|
u.UpdatedAt = time.Now().UTC()
|
|
}
|