From 723d883bc24f4d418edbe6fcda950e4a624721fe Mon Sep 17 00:00:00 2001 From: AmirMahdi Qiasvand Date: Sat, 6 Sep 2025 17:04:17 +0330 Subject: [PATCH] feat: add challenge to auth service --- go.mod | 10 ++-- go.sum | 24 ++++---- internal/domain/session.go | 13 ++++ internal/domain/user.go | 72 ++++++++++++++++++++++- internal/repository/external/.gitkeep | 0 internal/repository/storage/types/user.go | 17 +++++- internal/usecase/auth_service.go | 48 +++++++++++++++ 7 files changed, 165 insertions(+), 19 deletions(-) delete mode 100644 internal/repository/external/.gitkeep diff --git a/go.mod b/go.mod index 06ee044..fa937da 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module backend go 1.25.0 require ( + github.com/ethereum/go-ethereum v1.16.3 github.com/golang-jwt/jwt/v5 v5.3.0 github.com/google/uuid v1.6.0 github.com/spf13/viper v1.20.1 @@ -13,6 +14,7 @@ require ( require ( github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/holiman/uint256 v1.3.2 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.6.0 // indirect @@ -28,9 +30,9 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.32.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index d92b739..33667b5 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ethereum/go-ethereum v1.16.3 h1:nDoBSrmsrPbrDIVLTkDQCy1U9KdHN+F2PzvMbDoS42Q= +github.com/ethereum/go-ethereum v1.16.3/go.mod h1:Lrsc6bt9Gm9RyvhfFK53vboCia8kpF9nv+2Ukntnl+8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= @@ -13,6 +15,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= +github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -33,8 +37,8 @@ github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNH github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= @@ -58,14 +62,14 @@ go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/internal/domain/session.go b/internal/domain/session.go index 7873325..66792ea 100644 --- a/internal/domain/session.go +++ b/internal/domain/session.go @@ -26,3 +26,16 @@ type UserSession struct { UpdatedAt time.Time DeletedAt *time.Time } + +func NewSession(userID, walletID uuid.UUID, ipAddress, agent string, expiresAt time.Time) *UserSession { + return &UserSession{ + ID: uuid.New(), + UserID: userID, + WalletID: walletID, + IPaddress: ipAddress, + Agent: agent, + ExpiresAt: expiresAt, + CreatedAt: time.Now().UTC(), + UpdatedAt: time.Now().UTC(), + } +} diff --git a/internal/domain/user.go b/internal/domain/user.go index b24340f..1ff4967 100644 --- a/internal/domain/user.go +++ b/internal/domain/user.go @@ -1,12 +1,26 @@ package domain import ( + "backend/pkg/validate/national" + "backend/pkg/validate/phone" "context" + "errors" "time" + "net/mail" + + "github.com/ethereum/go-ethereum/common" "github.com/google/uuid" ) +var ( + ErrPhoneInvalid = errors.New("phone number is invalid") + ErrUserNotFound = errors.New("user not found") + ErrNationalIDInvalid = errors.New("national ID is invalid") + ErrWalletInvalid = errors.New("wallet address is invalid") + 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) @@ -16,14 +30,66 @@ type UserRepo interface { } type User struct { - ID uuid.UUID - PubKey string - Name string + 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 } + +type Challenge struct { + Message uuid.UUID + TimeStamp time.Time + ExpiresAt time.Time +} + +func NewUser(pubKey, name, lastName, phoneNumber, email, nationalID string) (*User, error) { + + _, err := phone.IsValid(phoneNumber) + if err != nil { + return nil, ErrPhoneInvalid + } + + _, err = national.IsValid(nationalID) + if err != nil { + return nil, ErrNationalIDInvalid + } + + if !common.IsHexAddress(pubKey) { + return nil, ErrWalletInvalid + } + + 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() +} diff --git a/internal/repository/external/.gitkeep b/internal/repository/external/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/internal/repository/storage/types/user.go b/internal/repository/storage/types/user.go index d143bc7..a7042de 100644 --- a/internal/repository/storage/types/user.go +++ b/internal/repository/storage/types/user.go @@ -2,14 +2,21 @@ package types import ( "backend/internal/domain" + "time" ) type User struct { Base - Name string - LastName string + PubKey string + Name string + LastName string + //TODO: add Kyc Embedded struct + //TODO: add NFT Embedded struct Phone string NationalID string + LastLogin *time.Time + Email *string + BirthDate *time.Time } func CastUserToStorage(u domain.User) *User { @@ -24,6 +31,9 @@ func CastUserToStorage(u domain.User) *User { LastName: u.LastName, Phone: u.PhoneNumber, NationalID: u.NationalID, + Email: u.Email, + BirthDate: u.BirthDate, + LastLogin: u.LastLogin, } } @@ -34,6 +44,9 @@ func CastUserToDomain(u User) *domain.User { LastName: u.LastName, PhoneNumber: u.Phone, NationalID: u.NationalID, + Email: u.Email, + BirthDate: u.BirthDate, + LastLogin: u.LastLogin, CreatedAt: u.CreatedAt, UpdatedAt: u.UpdatedAt, DeletedAt: u.DeletedAt, diff --git a/internal/usecase/auth_service.go b/internal/usecase/auth_service.go index aed2454..4f7edd2 100644 --- a/internal/usecase/auth_service.go +++ b/internal/usecase/auth_service.go @@ -1 +1,49 @@ package usecase + +import ( + "backend/internal/domain" + "context" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/google/uuid" +) + +type AuthService interface { + GenerateChallenge(ctx context.Context, pubKey string) (*domain.Challenge, error) +} + +type authService struct { + userRepo *domain.UserRepo + sessionRepo *domain.SessionRepo + challengeExp uint + tokenExp uint + refreshTokenExp uint +} + +func NewAuthService( + userRepo *domain.UserRepo, + sessionRepo *domain.SessionRepo, + challengeExp uint, + tokenExp uint, + refreshTokenExp uint) AuthService { + return &authService{ + userRepo: userRepo, + sessionRepo: sessionRepo, + challengeExp: challengeExp, + tokenExp: tokenExp, + refreshTokenExp: refreshTokenExp, + } +} + +func (s *authService) GenerateChallenge(ctx context.Context, pubKey string) (*domain.Challenge, error) { + if !common.IsHexAddress(pubKey) { + return nil, domain.ErrWalletInvalid + } + challenge := &domain.Challenge{ + Message: uuid.New(), + TimeStamp: time.Now().UTC(), + ExpiresAt: time.Now().Add(time.Duration(s.challengeExp) * time.Minute), + } + return challenge, nil +}