diff --git a/backend/.vscode/launch.json b/backend/.vscode/launch.json index 5f0bf69..29f0023 100644 --- a/backend/.vscode/launch.json +++ b/backend/.vscode/launch.json @@ -5,7 +5,7 @@ "version": "0.2.0", "configurations": [ { - "name": "debug", + "name": "blockd", "type": "go", "request": "launch", "mode": "auto", @@ -14,6 +14,7 @@ "-log-level=debug", "-log-local=true", "-log-add-source=true", + "-jwt-secret=local_jwt_secret", "-rest-address=localhost:8080", "-db-host=localhost:8432", diff --git a/backend/Makefile b/backend/Makefile index bac4cb0..542ad58 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -30,7 +30,8 @@ run.local: bin.build -db-database=blockd \ -db-user=blockd \ -db-secret=blockd \ - -db-enable-tls=false + -db-enable-tls=false \ + -jwt-secret=local_jwt_secret .PHONY: run.debug run.debug: bin.build diff --git a/backend/cmd/main.go b/backend/cmd/main.go index 0b726bb..c20be3c 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -34,6 +34,9 @@ func main() { Name: "log-add-source", Value: true, }, + &cli.StringFlag{ + Name: "jwt-secret", + }, // rest &cli.StringFlag{ @@ -77,6 +80,7 @@ func main() { LogLocal: c.Bool("log-local"), LogFile: c.String("log-file"), LogAddSource: c.Bool("log-add-source"), + JWTSecret: []byte(c.String("jwt-secret")), }, Rest: config.RestConfig{ Address: c.String("rest-address"), diff --git a/backend/internal/factory/interactors.go b/backend/internal/factory/interactors.go new file mode 100644 index 0000000..f3ea68c --- /dev/null +++ b/backend/internal/factory/interactors.go @@ -0,0 +1,21 @@ +package factory + +import ( + "log/slog" + + "github.com/emochka2007/block-accounting/internal/pkg/config" + "github.com/emochka2007/block-accounting/internal/usecase/interactors/jwt" + "github.com/emochka2007/block-accounting/internal/usecase/interactors/users" + urepo "github.com/emochka2007/block-accounting/internal/usecase/repository/users" +) + +func provideUsersInteractor( + log *slog.Logger, + usersRepo urepo.Repository, +) users.UsersInteractor { + return users.NewUsersInteractor(log.WithGroup("users-interactor"), usersRepo) +} + +func provideJWTInteractor(c config.Config) jwt.JWTInteractor { + return jwt.NewWardenJWT(c.Common.JWTSecret) +} diff --git a/backend/internal/factory/interface.go b/backend/internal/factory/interface.go index fcb79fe..b82c1ab 100644 --- a/backend/internal/factory/interface.go +++ b/backend/internal/factory/interface.go @@ -4,13 +4,22 @@ import ( "log/slog" "os" + "github.com/google/wire" + "github.com/emochka2007/block-accounting/internal/interface/rest" "github.com/emochka2007/block-accounting/internal/interface/rest/controllers" "github.com/emochka2007/block-accounting/internal/interface/rest/presenters" "github.com/emochka2007/block-accounting/internal/pkg/config" "github.com/emochka2007/block-accounting/internal/pkg/logger" + "github.com/emochka2007/block-accounting/internal/usecase/interactors/jwt" "github.com/emochka2007/block-accounting/internal/usecase/interactors/users" - urepo "github.com/emochka2007/block-accounting/internal/usecase/repository/users" +) + +var interfaceSet wire.ProviderSet = wire.NewSet( + provideAuthController, + provideControllers, + + provideAuthPresenter, ) func provideLogger(c config.Config) *slog.Logger { @@ -36,20 +45,31 @@ func provideLogger(c config.Config) *slog.Logger { return lb.Build() } +func provideAuthPresenter( + jwtInteractor jwt.JWTInteractor, +) presenters.AuthPresenter { + return presenters.NewAuthPresenter(jwtInteractor) +} + +func provideAuthController( + log *slog.Logger, + usersInteractor users.UsersInteractor, + authPresenter presenters.AuthPresenter, +) controllers.AuthController { + return controllers.NewAuthController( + log.WithGroup("auth-controller"), + authPresenter, + usersInteractor, + ) +} + func provideControllers( log *slog.Logger, - usersRepo urepo.Repository, + authController controllers.AuthController, ) *controllers.RootController { return &controllers.RootController{ Ping: controllers.NewPingController(log.WithGroup("ping-controller")), - Auth: controllers.NewAuthController( - log.WithGroup("auth-controller"), - presenters.NewAuthPresenter(), - users.NewUsersInteractor( - log.WithGroup("users-interactor"), - usersRepo, - ), - ), + Auth: authController, } } diff --git a/backend/internal/factory/wire.go b/backend/internal/factory/wire.go index ccd520c..623b0d9 100644 --- a/backend/internal/factory/wire.go +++ b/backend/internal/factory/wire.go @@ -11,9 +11,11 @@ import ( func ProvideService(c config.Config) (service.Service, func(), error) { wire.Build( - provideUsersRepository, provideLogger, - provideControllers, + provideUsersRepository, + provideUsersInteractor, + provideJWTInteractor, + interfaceSet, provideRestServer, service.NewService, ) diff --git a/backend/internal/factory/wire_gen.go b/backend/internal/factory/wire_gen.go index 2ce4f6e..9959819 100644 --- a/backend/internal/factory/wire_gen.go +++ b/backend/internal/factory/wire_gen.go @@ -19,7 +19,11 @@ func ProvideService(c config.Config) (service.Service, func(), error) { if err != nil { return nil, nil, err } - rootController := provideControllers(logger, repository) + usersInteractor := provideUsersInteractor(logger, repository) + jwtInteractor := provideJWTInteractor(c) + authPresenter := provideAuthPresenter(jwtInteractor) + authController := provideAuthController(logger, usersInteractor, authPresenter) + rootController := provideControllers(logger, authController) server := provideRestServer(logger, rootController, c) serviceService := service.NewService(logger, server) return serviceService, func() { diff --git a/backend/internal/interface/rest/controllers/auth.go b/backend/internal/interface/rest/controllers/auth.go index dfacdf4..3364ea2 100644 --- a/backend/internal/interface/rest/controllers/auth.go +++ b/backend/internal/interface/rest/controllers/auth.go @@ -10,6 +10,7 @@ import ( "github.com/emochka2007/block-accounting/internal/interface/rest/presenters" "github.com/emochka2007/block-accounting/internal/pkg/bip32" + "github.com/emochka2007/block-accounting/internal/usecase/interactors/jwt" "github.com/emochka2007/block-accounting/internal/usecase/interactors/users" ) @@ -28,6 +29,7 @@ type authController struct { log *slog.Logger presenter presenters.AuthPresenter usersInteractor users.UsersInteractor + jwtInteractor jwt.JWTInteractor } func NewAuthController( @@ -45,27 +47,32 @@ func NewAuthController( func (c *authController) Join(w http.ResponseWriter, req *http.Request) error { request, err := c.presenter.CreateJoinRequest(req) if err != nil { - return fmt.Errorf("error create join request. %w", err) + return c.presenter.ResponseJoin( + w, nil, fmt.Errorf("error create join request. %w", err), + ) } c.log.Debug("join request", slog.String("mnemonic", request.Mnemonic)) if !bip32.IsMnemonicValid(request.Mnemonic) { - return fmt.Errorf("error invalid mnemonic. %w", ErrorAuthInvalidMnemonic) + return c.presenter.ResponseJoin( + w, nil, fmt.Errorf("error invalid mnemonic. %w", ErrorAuthInvalidMnemonic), + ) } ctx, cancel := context.WithTimeout(req.Context(), 5*time.Second) defer cancel() - if _, err = c.usersInteractor.Create(ctx, users.CreateParams{ + user, err := c.usersInteractor.Create(ctx, users.CreateParams{ Mnemonic: request.Mnemonic, IsAdmin: true, Activate: true, - }); err != nil { - return fmt.Errorf("error create new user. %w", err) + }) + if err != nil { + return c.presenter.ResponseJoin(w, nil, fmt.Errorf("error create new user. %w", err)) } - return nil + return c.presenter.ResponseJoin(w, user, nil) } func (c *authController) JoinWithInvite(w http.ResponseWriter, req *http.Request) error { diff --git a/backend/internal/interface/rest/domain/dto.go b/backend/internal/interface/rest/domain/dto.go index ef99894..3d246cd 100644 --- a/backend/internal/interface/rest/domain/dto.go +++ b/backend/internal/interface/rest/domain/dto.go @@ -9,6 +9,27 @@ type JoinRequest struct { Mnemonic string `json:"mnemonic"` } +type JoinResponse struct { + Ok bool `json:"ok"` + Token string `json:"token,omitempty"` + Error *Error `json:"error,omitempty"` +} + +type LoginRequest struct { + Mnemonc string `json:"mnemonic"` +} + +type LoginResponse struct { + Ok bool `json:"ok"` + Token string `json:"token,omitempty"` + Error *Error `json:"error,omitempty"` +} + +type Error struct { + Code int `json:"code"` + Message string `json:"message"` +} + func BuildRequest[T any](data []byte) (*T, error) { var req T diff --git a/backend/internal/interface/rest/presenters/auth.go b/backend/internal/interface/rest/presenters/auth.go index 597997b..f3a95c6 100644 --- a/backend/internal/interface/rest/presenters/auth.go +++ b/backend/internal/interface/rest/presenters/auth.go @@ -5,19 +5,28 @@ import ( "fmt" "io" "net/http" + "time" "github.com/emochka2007/block-accounting/internal/interface/rest/domain" + "github.com/emochka2007/block-accounting/internal/pkg/models" + "github.com/emochka2007/block-accounting/internal/usecase/interactors/jwt" ) type AuthPresenter interface { CreateJoinRequest(r *http.Request) (*domain.JoinRequest, error) - // ResponseJoin(w http.ResponseWriter, mnemonic string) error + ResponseJoin(w http.ResponseWriter, user *models.User, err error) error } -type authPresenter struct{} +type authPresenter struct { + jwtInteractor jwt.JWTInteractor +} -func NewAuthPresenter() AuthPresenter { - return &authPresenter{} +func NewAuthPresenter( + jwtInteractor jwt.JWTInteractor, +) AuthPresenter { + return &authPresenter{ + jwtInteractor: jwtInteractor, + } } func (p *authPresenter) CreateJoinRequest(r *http.Request) (*domain.JoinRequest, error) { @@ -37,17 +46,29 @@ func (p *authPresenter) CreateJoinRequest(r *http.Request) (*domain.JoinRequest, return &request, nil } -// func (p *authPresenter) ResponseJoin(w http.ResponseWriter, mnemonic string) error { -// out, err := json.Marshal(domain.JoinResponse{ -// Mnemonic: mnemonic, -// }) -// if err != nil { -// return fmt.Errorf("error marshal join response. %w", err) -// } +func (p *authPresenter) ResponseJoin(w http.ResponseWriter, user *models.User, err error) error { + resp := new(domain.JoinResponse) -// if _, err = w.Write(out); err != nil { -// return fmt.Errorf("error write response. %w", err) -// } + if err != nil { + // todo map error + } else { + token, err := p.jwtInteractor.NewToken(user, 24*time.Hour) + if err != nil { + return fmt.Errorf("error create access token. %w", err) + } -// return nil -// } + resp.Ok = true + resp.Token = token + } + + out, err := json.Marshal(resp) + if err != nil { + return fmt.Errorf("error marshal join response. %w", err) + } + + if _, err = w.Write(out); err != nil { + return fmt.Errorf("error write response. %w", err) + } + + return nil +} diff --git a/backend/internal/pkg/config/config.go b/backend/internal/pkg/config/config.go index a05b2bc..c2cbd4d 100644 --- a/backend/internal/pkg/config/config.go +++ b/backend/internal/pkg/config/config.go @@ -12,6 +12,8 @@ type CommonConfig struct { LogLocal bool LogFile string LogAddSource bool + + JWTSecret []byte } type RestConfig struct { diff --git a/backend/internal/usecase/interactors/smartcontract/contract.go b/backend/internal/usecase/interactors/smartcontract/contract.go index cccb96c..bea406a 100644 --- a/backend/internal/usecase/interactors/smartcontract/contract.go +++ b/backend/internal/usecase/interactors/smartcontract/contract.go @@ -6,7 +6,6 @@ import ( "github.com/emochka2007/block-accounting/internal/pkg/models" "github.com/emochka2007/block-accounting/internal/usecase/interactors/users" "github.com/emochka2007/block-accounting/internal/usecase/repository/transactions" - "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/ethclient" "github.com/google/uuid" ) @@ -32,5 +31,7 @@ type smartContractInteractor struct { // todo func (s *smartContractInteractor) SignTransaction(ctx context.Context, params SignTransactionParams) error { - s.client.CallContract(ctx, ethereum.CallMsg{}, nil) + // s.client.CallContract(ctx, ethereum.CallMsg{}, nil) + + return nil } diff --git a/backend/internal/usecase/repository/users/repository.go b/backend/internal/usecase/repository/users/repository.go index 1196429..708ed34 100644 --- a/backend/internal/usecase/repository/users/repository.go +++ b/backend/internal/usecase/repository/users/repository.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "time" sq "github.com/Masterminds/squirrel" "github.com/emochka2007/block-accounting/internal/pkg/models" @@ -21,7 +22,7 @@ type GetParams struct { type Repository interface { Get(ctx context.Context, params GetParams) (*models.User, error) Create(ctx context.Context, user *models.User) error - Activate(ctx context.Context, id string) error + Activate(ctx context.Context, id uuid.UUID) error Update(ctx context.Context, user *models.User) error Delete(ctx context.Context, id string) error } @@ -89,8 +90,19 @@ func (r *repositorySQL) Create(ctx context.Context, user *models.User) error { return nil } -func (r *repositorySQL) Activate(ctx context.Context, id string) error { +func (r *repositorySQL) Activate(ctx context.Context, id uuid.UUID) error { if err := sqltools.Transaction(ctx, r.db, func(ctx context.Context) error { + query := sq.Update("users"). + SetMap(sq.Eq{ + "activated_at": time.Now(), + }). + Where(sq.Eq{ + "id": id, + }) + + if _, err := query.RunWith(r.Conn(ctx)).ExecContext(ctx); err != nil { + return fmt.Errorf("error mark user as activated in database. %w", err) + } return nil }); err != nil {