diff --git a/Makefile b/Makefile index be76136..f0c8f79 100644 --- a/Makefile +++ b/Makefile @@ -67,8 +67,5 @@ migrate-down: lint: golangci-lint run ./... -swag: - swag init -g main.go --output docs - check: staticcheck ./... diff --git a/README.md b/README.md index 39a2db4..757f563 100644 --- a/README.md +++ b/README.md @@ -37,17 +37,12 @@ $ make dev # Run the migrations $ make migrate-up -# Generate documentation -$ make swag - # Run the application in development mode $ go run main.go ``` ```bash - The backend will open on the port:5000 # access http://localhost:5000 -- The mailcatcher will open on the port:1080 # access http://localhost:1080 -- The documentation will open on the port:5000: # access http://localhost:5000/swagger/index.html ``` ## 😯 How to contribute to the project diff --git a/app.json b/app.json index a59d4b7..c7c72b2 100644 --- a/app.json +++ b/app.json @@ -1,14 +1,10 @@ { - "meta": { - "project_name": "PowerSSO", - "project_url": "http://localhost:3000" - }, + "project_name": "PowerSSO", "server": { "version": "1.0.0", "port": ":5000", "pprof_port": ":5555", "mode": "dev", - "jwt_secret_key": "power-sso-secret", "cookie_name": "jwt-power-sso", "ssl": false, "ctx_default_timeout": 12, @@ -20,7 +16,7 @@ "start_grpc": false, "access_log_directory": "/var/log/powersso/access.log", "error_log_directory": "/var/log/powersso/error.log", - "permission_base": "github.com/isaqueveras/power-sso", + "permission_base": "github.com/isaqueveras/powersso", "access_control_allow_origin": "*", "open_sessions_per_user": 5 }, @@ -31,30 +27,16 @@ "password": "postgres", "dbname": "power-sso", "sslmode": false, - "driver": "pgx", "max_open_conns": 60, "max_idle_conns": 30, "conn_max_life_time": 120, "conn_max_idle_time": 20, "timeout": 2 }, - "logger": { - "development": true, - "disable_caller": false, - "disable_stacktrace": false, - "encoding": "json", - "level": "info" - }, - "mailer": { - "host": "localhost", - "port": 1025, - "email": "contact@powersso.io", - "username": "PowerSSO", - "password": "password", - "tls": false - }, - "user_auth_token": { - "secret_key": "kjnfdjksdbfsdhfbdskjfnamkndkn", - "duration": 2592000 + "secrets_duration": 2592000, + "secrets_tokens": { + "user": "kjnfdjksdbfsdhfbdskjfnamkndkn", + "admin": "oCb9A8T7zHyx8MXBFb1aEouAXnBg2", + "integration": "krIhDsgYFY3ByJLony15bDSzBAsl5" } } diff --git a/application/auth/auth_business.go b/application/auth/auth_business.go deleted file mode 100644 index 15c418a..0000000 --- a/application/auth/auth_business.go +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright (c) 2022 Isaque Veras -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -package auth - -import ( - "context" - - "github.com/google/uuid" - "github.com/isaqueveras/powersso/database/postgres" - domain "github.com/isaqueveras/powersso/domain/auth" - infra "github.com/isaqueveras/powersso/infrastructure/persistencie/auth" - "github.com/isaqueveras/powersso/mailer" - "github.com/isaqueveras/powersso/oops" - "github.com/isaqueveras/powersso/tokens" - "github.com/isaqueveras/powersso/utils" -) - -// CreateAccount is the business logic for the user register -func CreateAccount(ctx context.Context, in *domain.CreateAccount) error { - tx, err := postgres.NewTransaction(ctx, false) - if err != nil { - return oops.Err(err) - } - defer tx.Rollback() - - if err = in.Prepare(); err != nil { - return oops.Err(err) - } - - repoAuth := infra.NewAuthRepository(tx, mailer.Client()) - repoUser := infra.NewUserRepository(tx) - - if err = repoUser.Exist(in.Email); err != nil { - return oops.Err(err) - } - - var userID *uuid.UUID - if userID, err = repoAuth.CreateAccount(in); err != nil { - return oops.Err(err) - } - - var token *uuid.UUID - if token, err = repoAuth.CreateAccessToken(userID); err != nil { - return oops.Err(err) - } - - if err = repoAuth.SendMailActivationAccount(in.Email, token); err != nil { - return oops.Err(err) - } - - if err = tx.Commit(); err != nil { - return oops.Err(err) - } - - return nil -} - -// Activation is the business logic for the user activation -func Activation(ctx context.Context, token *uuid.UUID) (err error) { - var tx *postgres.Transaction - if tx, err = postgres.NewTransaction(ctx, false); err != nil { - return oops.Err(err) - } - defer tx.Rollback() - - var ( - repoAuth = infra.NewAuthRepository(tx, nil) - repoUser = infra.NewUserRepository(tx) - repoRole = infra.NewFlagRepo(tx) - ) - - activeToken := &domain.ActivateAccount{ID: token} - if err = repoAuth.GetActivateAccountToken(activeToken); err != nil { - return oops.Err(err) - } - - if !activeToken.IsValid() { - return oops.Err(domain.ErrTokenIsNotValid()) - } - - user := domain.User{ID: activeToken.UserID} - if err = repoUser.Get(&user); err != nil { - return oops.Err(err) - } - - if err = repoRole.Set(user.ID, utils.Pointer(domain.FlagEnabledAccount)); err != nil { - return oops.Err(err) - } - - if err = repoAuth.MarkTokenAsUsed(activeToken.ID); err != nil { - return oops.Err(err) - } - - if err = tx.Commit(); err != nil { - return oops.Err(err) - } - - return nil -} - -// Login is the business logic for the user login -func Login(ctx context.Context, in *domain.Login) (*domain.Session, error) { - tx, err := postgres.NewTransaction(ctx, false) - if err != nil { - return nil, oops.Err(err) - } - defer tx.Rollback() - - var ( - repoAuth = infra.NewAuthRepository(tx, nil) - repoUser = infra.NewUserRepository(tx) - repoSession = infra.NewSessionRepository(tx) - ) - - user := &domain.User{Email: in.Email} - if err = repoUser.Get(user); err != nil { - return nil, oops.Err(err) - } - - if !user.HasFlag(domain.FlagEnabledAccount) { - return nil, oops.Err(domain.ErrNotHavePermissionLogin()) - } - - if !user.IsActive() { - return nil, oops.Err(domain.ErrUserNotExists()) - } - - if user.IsBlocked() { - return nil, oops.Err(domain.ErrUserBlockedTemporarily()) - } - - if err = in.ComparePasswords(user.Password, user.Key); err != nil { - if errAttempts := repoAuth.AddAttempts(user.ID); errAttempts != nil { - return nil, oops.Err(errAttempts) - } - if errAttempts := tx.Commit(); errAttempts != nil { - return nil, oops.Err(errAttempts) - } - return nil, oops.Err(err) - } - - if user.OTPConfigured() { - if err = utils.ValidateToken(user.OTPToken, in.OTP); err != nil { - return nil, oops.Err(domain.ErrOTPTokenInvalid()) - } - } - - var sessionID *uuid.UUID - if sessionID, err = repoSession.Create(user.ID, in.ClientIP, in.UserAgent); err != nil { - return nil, oops.Err(err) - } - - var token *string - if token, err = tokens.NewUserAuthToken(user, sessionID); err != nil { - return nil, oops.Err(err) - } - - if err = tx.Commit(); err != nil { - return nil, oops.Err(err) - } - - return &domain.Session{ - SessionID: sessionID, - Level: user.Level, - UserID: user.ID, - Email: user.Email, - FirstName: user.FirstName, - LastName: user.LastName, - CreatedAt: user.CreatedAt, - Token: token, - RawData: make(map[string]any), - }, nil -} - -// Logout is the business logic for the user logout -func Logout(ctx context.Context, sessionID *uuid.UUID) (err error) { - var tx *postgres.Transaction - if tx, err = postgres.NewTransaction(ctx, false); err != nil { - return oops.Err(err) - } - defer tx.Rollback() - - if err = infra.NewSessionRepository(tx).Delete(sessionID); err != nil { - return oops.Err(err) - } - - if err = tx.Commit(); err != nil { - return oops.Err(err) - } - - return -} - -// LoginSteps is the business logic needed to retrieve needed steps for log a user in -func LoginSteps(ctx context.Context, email *string) (res *domain.Steps, err error) { - var tx *postgres.Transaction - if tx, err = postgres.NewTransaction(ctx, true); err != nil { - return nil, oops.Err(err) - } - defer tx.Rollback() - - repository := infra.NewAuthRepository(tx, nil) - return repository.LoginSteps(email) -} diff --git a/application/auth/business.go b/application/auth/business.go new file mode 100644 index 0000000..b3f2b24 --- /dev/null +++ b/application/auth/business.go @@ -0,0 +1,308 @@ +// Copyright (c) 2022 Isaque Veras +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package auth + +import ( + "context" + + "github.com/google/uuid" + database "github.com/isaqueveras/powersso/database/postgres" + domain "github.com/isaqueveras/powersso/domain/auth" + infra "github.com/isaqueveras/powersso/infrastructure/persistencie/auth" + "github.com/isaqueveras/powersso/oops" + "github.com/isaqueveras/powersso/tokens" + "github.com/isaqueveras/powersso/utils" +) + +// CreateAccount is the business logic for the user register +func CreateAccount(ctx context.Context, in *domain.CreateAccount) (url *string, err error) { + var tx *database.Transaction + if tx, err = database.NewTransaction(ctx, false); err != nil { + return nil, oops.Err(err) + } + defer tx.Rollback() + + if err = in.Prepare(); err != nil { + return nil, oops.Err(err) + } + + userRepository := infra.NewUserRepository(tx) + if err = userRepository.AccountExists(in.Email); err != nil { + return nil, oops.Err(err) + } + + authRepository := infra.NewAuthRepository(tx) + var userID *uuid.UUID + if userID, err = authRepository.CreateAccount(in); err != nil { + return nil, oops.Err(err) + } + + service := domain.NewAuthService(infra.NewFlagRepo(tx), infra.NewOTPRepo(tx)) + if err = service.Configure2FA(userID); err != nil { + return nil, oops.Err(err) + } + + if url, err = service.GenerateQrCode2FA(userID); err != nil { + return nil, oops.Err(err) + } + + if err = tx.Commit(); err != nil { + return nil, oops.Err(err) + } + + return +} + +// Login is the business logic for the user login +func Login(ctx context.Context, in *domain.Login) (*domain.Session, error) { + tx, err := database.NewTransaction(ctx, false) + if err != nil { + return nil, oops.Err(err) + } + defer tx.Rollback() + + var ( + repoAuth = infra.NewAuthRepository(tx) + repoUser = infra.NewUserRepository(tx) + repoSession = infra.NewSessionRepository(tx) + ) + + user := &domain.User{Email: in.Email} + if err = repoUser.GetUser(user); err != nil { + return nil, oops.Err(err) + } + + if !user.IsActive() { + return nil, domain.ErrUserNotExists() + } + + if !user.OTPConfigured() { + return nil, domain.ErrAuthentication2factorNotConfigured() + } + + if user.IsBlocked() { + return nil, domain.ErrUserBlockedTemporarily() + } + + if err = in.ComparePasswords(user.Password, user.Key); err != nil { + if errAttempts := repoAuth.AddAttempts(user.ID); errAttempts != nil { + return nil, oops.Err(errAttempts) + } + if errAttempts := tx.Commit(); errAttempts != nil { + return nil, oops.Err(errAttempts) + } + return nil, oops.Err(err) + } + + if err = utils.ValidateToken(user.OTPToken, in.OTP); err != nil { + return nil, domain.ErrOTPTokenInvalid() + } + + var sessionID *uuid.UUID + if sessionID, err = repoSession.Create(user.ID, in.ClientIP, in.UserAgent); err != nil { + return nil, oops.Err(err) + } + + var token *string + if token, err = tokens.NewAuthToken(user, sessionID); err != nil { + return nil, oops.Err(err) + } + + if err = tx.Commit(); err != nil { + return nil, oops.Err(err) + } + + return &domain.Session{ + SessionID: sessionID, + Level: user.Level, + UserID: user.ID, + Email: user.Email, + FirstName: user.FirstName, + LastName: user.LastName, + CreatedAt: user.CreatedAt, + Token: token, + }, nil +} + +// Logout is the business logic for the user logout +func Logout(ctx context.Context, sessionID *uuid.UUID) (err error) { + var tx *database.Transaction + if tx, err = database.NewTransaction(ctx, false); err != nil { + return oops.Err(err) + } + defer tx.Rollback() + + if err = infra.NewSessionRepository(tx).Delete(sessionID); err != nil { + return oops.Err(err) + } + + if err = tx.Commit(); err != nil { + return oops.Err(err) + } + + return +} + +// LoginSteps is the business logic needed to retrieve needed steps for log a user in +func LoginSteps(ctx context.Context, email *string) (res *domain.Steps, err error) { + var tx *database.Transaction + if tx, err = database.NewTransaction(ctx, true); err != nil { + return nil, oops.Err(err) + } + defer tx.Rollback() + + return infra.NewAuthRepository(tx).LoginSteps(email) +} + +// Configure2FA performs business logic to configure otp for a user +func Configure2FA(ctx context.Context, userID *uuid.UUID) (err error) { + var tx *database.Transaction + if tx, err = database.NewTransaction(ctx, false); err != nil { + return oops.Err(err) + } + defer tx.Rollback() + + var ( + repoOTP = infra.NewOTPRepo(tx) + repoFlag = infra.NewFlagRepo(tx) + service = domain.NewAuthService(repoFlag, repoOTP) + ) + + if err = service.Configure2FA(userID); err != nil { + return oops.Err(err) + } + + if err = tx.Commit(); err != nil { + return oops.Err(err) + } + + return +} + +// Unconfigure2FA performs business logic to unconfigure otp for a user +func Unconfigure2FA(ctx context.Context, userID *uuid.UUID) (err error) { + var tx *database.Transaction + if tx, err = database.NewTransaction(ctx, false); err != nil { + return oops.Err(err) + } + defer tx.Rollback() + + repoFlag := infra.NewFlagRepo(tx) + flag, err := repoFlag.Get(userID) + if err != nil { + return oops.Err(err) + } + + if err = repoFlag.Set(userID, (domain.Flag(*flag))&(^domain.FlagOTPEnable)); err != nil { + return oops.Err(err) + } + + if err = repoFlag.Set(userID, (domain.Flag(*flag))&(^domain.FlagOTPSetup)); err != nil { + return oops.Err(err) + } + + repoOTP := infra.NewOTPRepo(tx) + if err = repoOTP.SetToken(userID, nil); err != nil { + return oops.Err(err) + } + + if err = tx.Commit(); err != nil { + return oops.Err(err) + } + + return +} + +// GetQRCode2FA performs business logic to get qrcode url +func GetQRCode2FA(ctx context.Context, userID *uuid.UUID) (url *string, err error) { + var tx *database.Transaction + if tx, err = database.NewTransaction(ctx, true); err != nil { + return nil, oops.Err(err) + } + defer tx.Rollback() + + var ( + repoFlag = infra.NewFlagRepo(tx) + repoOTP = infra.NewOTPRepo(tx) + service = domain.NewAuthService(repoFlag, repoOTP) + ) + + return service.GenerateQrCode2FA(userID) +} + +// DisableUser is the business logic for disable user +func DisableUser(ctx context.Context, userUUID *uuid.UUID) error { + tx, err := database.NewTransaction(ctx, false) + if err != nil { + return oops.Err(err) + } + defer tx.Rollback() + + repo := infra.NewUserRepository(tx) + if err = repo.DisableUser(userUUID); err != nil { + return oops.Err(err) + } + + if err = tx.Commit(); err != nil { + return oops.Err(err) + } + + return nil +} + +// ChangePassword is the busines logic for change passoword +func ChangePassword(ctx context.Context, in *domain.ChangePassword) (err error) { + tx, err := database.NewTransaction(ctx, false) + if err != nil { + return oops.Err(err) + } + defer tx.Rollback() + + repoUser := infra.NewUserRepository(tx) + repoSession := infra.NewSessionRepository(tx) + + user := domain.User{ID: in.UserID} + if err = repoUser.GetUser(&user); err != nil { + return oops.Err(err) + } + + if !user.OTPConfigured() { + return oops.New("2-factor authentication not configured") + } + + // Validate code otp + if err = utils.ValidateToken(user.OTPToken, in.CodeOTP); err != nil { + return oops.New("2-factor authentication code is invalid") + } + + // Generate new password crypto + gen := &domain.CreateAccount{Password: in.Password} + if err = gen.GeneratePassword(); err != nil { + return err + } + + // Change user password + in.Password, in.Key = gen.Password, gen.Key + if err = repoUser.ChangePassword(in); err != nil { + return oops.Err(err) + } + + // Getting all active user sessions + var sessions []*uuid.UUID + if sessions, err = repoSession.Get(in.UserID); err != nil { + return oops.Err(err) + } + + // Disabling all active user sessions + if err = repoSession.Delete(sessions...); err != nil { + return oops.Err(err) + } + + if err = tx.Commit(); err != nil { + return oops.Err(err) + } + + return +} diff --git a/application/auth/otp_business.go b/application/auth/otp_business.go deleted file mode 100644 index 78b70ad..0000000 --- a/application/auth/otp_business.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) 2023 Isaque Veras -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -package auth - -import ( - "context" - "encoding/base32" - - "github.com/google/uuid" - "github.com/isaqueveras/powersso/config" - "github.com/isaqueveras/powersso/database/postgres" - domain "github.com/isaqueveras/powersso/domain/auth" - "github.com/isaqueveras/powersso/infrastructure/persistencie/auth" - "github.com/isaqueveras/powersso/oops" - "github.com/isaqueveras/powersso/utils" -) - -// Configure performs business logic to configure otp for a user -func Configure(ctx context.Context, userID *uuid.UUID) (err error) { - var tx *postgres.Transaction - if tx, err = postgres.NewTransaction(ctx, false); err != nil { - return oops.Err(err) - } - defer tx.Rollback() - - data := []byte(utils.RandomString(26)) - dst := make([]byte, base32.StdEncoding.EncodedLen(len(data))) - base32.StdEncoding.Encode(dst, data) - - repo := auth.NewOTPRepo(tx, userID) - if err = repo.SetToken(utils.Pointer(string(dst))); err != nil { - return oops.Err(err) - } - - repoFlag := auth.NewFlagRepo(tx) - if err = repoFlag.Set(userID, utils.Pointer(domain.FlagOTPEnable)); err != nil { - return oops.Err(err) - } - - if err = repoFlag.Set(userID, utils.Pointer(domain.FlagOTPSetup)); err != nil { - return oops.Err(err) - } - - if err = tx.Commit(); err != nil { - return oops.Err(err) - } - - return -} - -// Unconfigure performs business logic to unconfigure otp for a user -func Unconfigure(ctx context.Context, userID *uuid.UUID) (err error) { - var tx *postgres.Transaction - if tx, err = postgres.NewTransaction(ctx, false); err != nil { - return oops.Err(err) - } - defer tx.Rollback() - - repoFlag := auth.NewFlagRepo(tx) - flag, err := repoFlag.Get(userID) - if err != nil { - return oops.Err(err) - } - - if err = repoFlag.Set(userID, utils.Pointer((domain.Flag(*flag))&(^domain.FlagOTPEnable))); err != nil { - return oops.Err(err) - } - - if err = repoFlag.Set(userID, utils.Pointer((domain.Flag(*flag))&(^domain.FlagOTPSetup))); err != nil { - return oops.Err(err) - } - - repoOTP := auth.NewOTPRepo(tx, userID) - if err = repoOTP.SetToken(nil); err != nil { - return oops.Err(err) - } - - if err = tx.Commit(); err != nil { - return oops.Err(err) - } - - return -} - -// GetQrCode performs business logic to get qrcode url -func GetQrCode(ctx context.Context, userID *uuid.UUID) (res *domain.QRCode, err error) { - var tx *postgres.Transaction - if tx, err = postgres.NewTransaction(ctx, true); err != nil { - return nil, oops.Err(err) - } - defer tx.Rollback() - - var userName, token *string - if userName, token, err = auth.NewOTPRepo(tx, userID).GetToken(); err != nil { - return nil, oops.Err(err) - } - - if config.Get().Server.IsModeDevelopment() { - *userName += " [DEV]" - } - - res = &domain.QRCode{Url: utils.Pointer(utils.GetUrlQrCode(*token, *userName))} - return -} diff --git a/application/auth/user_business.go b/application/auth/user_business.go deleted file mode 100644 index 48d23e9..0000000 --- a/application/auth/user_business.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) 2023 Isaque Veras -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -package auth - -import ( - "context" - - "github.com/google/uuid" - pg "github.com/isaqueveras/powersso/database/postgres" - domain "github.com/isaqueveras/powersso/domain/auth" - "github.com/isaqueveras/powersso/infrastructure/persistencie/auth" - "github.com/isaqueveras/powersso/oops" - "github.com/isaqueveras/powersso/utils" -) - -// Disable is the business logic for disable user -func Disable(ctx context.Context, userUUID *uuid.UUID) error { - tx, err := pg.NewTransaction(ctx, false) - if err != nil { - return oops.Err(err) - } - defer tx.Rollback() - - repo := auth.NewUserRepository(tx) - if err = repo.Disable(userUUID); err != nil { - return oops.Err(err) - } - - if err = tx.Commit(); err != nil { - return oops.Err(err) - } - - return nil -} - -// ChangePassword is the busines logic for change passoword -func ChangePassword(ctx context.Context, in *domain.ChangePassword) (err error) { - tx, err := pg.NewTransaction(ctx, false) - if err != nil { - return oops.Err(err) - } - defer tx.Rollback() - - repoUser := auth.NewUserRepository(tx) - repoSession := auth.NewSessionRepository(tx) - - user := domain.User{ID: in.UserID} - if err = repoUser.Get(&user); err != nil { - return oops.Err(err) - } - - if !user.OTPConfigured() { - return oops.New("2-factor authentication not configured") - } - - // Validate code otp - if err = utils.ValidateToken(user.OTPToken, in.CodeOTP); err != nil { - return oops.New("2-factor authentication code is invalid") - } - - // Generate new password crypto - gen := &domain.CreateAccount{Password: in.Password} - if err = gen.GeneratePassword(); err != nil { - return err - } - - // Change user password - in.Password, in.Key = gen.Password, gen.Key - if err = repoUser.ChangePassword(in); err != nil { - return oops.Err(err) - } - - // Getting all active user sessions - var sessions []*uuid.UUID - if sessions, err = repoSession.Get(in.UserID); err != nil { - return oops.Err(err) - } - - // Disabling all active user sessions - if err = repoSession.Delete(sessions...); err != nil { - return oops.Err(err) - } - - if err = tx.Commit(); err != nil { - return oops.Err(err) - } - - return -} diff --git a/config/model.go b/config/model.go index e955ed9..6e24185 100644 --- a/config/model.go +++ b/config/model.go @@ -4,9 +4,7 @@ package config -import ( - "time" -) +import "time" const ( // modeDevelopment represents the development environment mode @@ -15,23 +13,19 @@ const ( modeProduction string = "prod" ) -// LoggerEncodingConsole represents the encoding form that the log represents -const LoggerEncodingConsole string = "console" - // Config type represents the application settings type Config struct { - Meta MetaConfig `json:"meta"` - Server ServerConfig `json:"server"` - Logger Logger `json:"logger"` - Mailer MailerConfig `json:"mailer"` - Database DatabaseConfig `json:"database"` - UserAuthToken TokenConfig `json:"user_auth_token"` + ProjectName string `json:"project_name"` + Server ServerConfig `json:"server"` + Database DatabaseConfig `json:"database"` + + SecretsDuration int64 `json:"secrets_duration"` + SecretsTokens Secrets `json:"secrets_tokens"` } -// MetaConfig models the meta configuration -type MetaConfig struct { - ProjectName string `json:"project_name"` - ProjectURL string `json:"project_url"` +// GetSecrets returns a list of tokens +func (c *Config) GetSecrets() (keys []string) { + return []string{c.SecretsTokens.User, c.SecretsTokens.Admin, c.SecretsTokens.Integration} } // ServerConfig models a server's configuration data @@ -40,7 +34,6 @@ type ServerConfig struct { Port string `json:"port"` PprofPort string `json:"pprof_port"` Mode string `json:"mode"` - JwtSecretKey string `json:"jwt_secret_key"` CookieName string `json:"cookie_name"` AccessLogDirectory string `json:"access_log_directory"` ErrorLogDirectory string `json:"error_log_directory"` @@ -63,8 +56,7 @@ type DatabaseConfig struct { Port int `json:"port"` User string `json:"user"` Password string `json:"password"` - Dbname string `json:"dbname"` - Driver string `json:"driver"` + Name string `json:"dbname"` SSLMode bool `json:"sslmode"` MaxOpenConns int `json:"max_open_conns"` MaxIdleConns int `json:"max_idle_conns"` @@ -73,29 +65,11 @@ type DatabaseConfig struct { ConnMaxIdleTime time.Duration `json:"conn_max_idle_time"` } -// Logger models the data for the logs configuration -type Logger struct { - Development bool `json:"development"` - DisableCaller bool `json:"disable_caller"` - DisableStacktrace bool `json:"disable_stacktrace"` - Encoding string `json:"encoding"` - Level string `json:"level"` -} - -// MailerConfig models the data for the mailer configuration -type MailerConfig struct { - Host string `json:"host"` - Port int `json:"port"` - Email string `json:"email"` - Username string `json:"username"` - Password string `json:"password"` - TLS bool `json:"tls"` -} - -// TokenConfig models the data for the token configuration -type TokenConfig struct { - SecretKey string `json:"secret_key"` - Duration int64 `json:"duration"` +// Secrets models the data for the token configuration +type Secrets struct { + User string `json:"user"` + Admin string `json:"admin"` + Integration string `json:"integration"` } // IsModeDevelopment returns if in development mode diff --git a/database/postgres/postgres.go b/database/postgres/postgres.go index ed28b29..7d46445 100644 --- a/database/postgres/postgres.go +++ b/database/postgres/postgres.go @@ -28,7 +28,7 @@ func (p *postgres) open(c *config.Config) (err error) { c.Database.Host, c.Database.Port, c.Database.User, - c.Database.Dbname, + c.Database.Name, c.Database.Password, ) @@ -44,7 +44,7 @@ func (p *postgres) open(c *config.Config) (err error) { } stdlib.RegisterDriverConfig(&driverConfig) - db, err := sql.Open(c.Database.Driver, driverConfig.ConnectionString(dataSourceName)) + db, err := sql.Open("pgx", driverConfig.ConnectionString(dataSourceName)) if err != nil { return err } diff --git a/interface/grpc/auth/auth.pb.go b/delivery/grpc/auth/auth.pb.go similarity index 100% rename from interface/grpc/auth/auth.pb.go rename to delivery/grpc/auth/auth.pb.go diff --git a/interface/grpc/auth/auth_grpc.pb.go b/delivery/grpc/auth/auth_grpc.pb.go similarity index 100% rename from interface/grpc/auth/auth_grpc.pb.go rename to delivery/grpc/auth/auth_grpc.pb.go diff --git a/interface/grpc/auth/handler.go b/delivery/grpc/auth/handler.go similarity index 93% rename from interface/grpc/auth/handler.go rename to delivery/grpc/auth/handler.go index d321349..b9229d9 100644 --- a/interface/grpc/auth/handler.go +++ b/delivery/grpc/auth/handler.go @@ -20,7 +20,7 @@ type Server struct { // RegisterUser register user func (s *Server) RegisterUser(ctx context.Context, in *User) (_ *Empty, err error) { - if err = app.CreateAccount(ctx, &domain.CreateAccount{ + if _, err = app.CreateAccount(ctx, &domain.CreateAccount{ FirstName: utils.Pointer(in.FirstName), LastName: utils.Pointer(in.LastName), Email: utils.Pointer(in.Email), diff --git a/interface/http/auth/handler.go b/delivery/http/auth/handler.go similarity index 73% rename from interface/http/auth/handler.go rename to delivery/http/auth/handler.go index f9b752e..4dd83d2 100644 --- a/interface/http/auth/handler.go +++ b/delivery/http/auth/handler.go @@ -13,11 +13,12 @@ import ( app "github.com/isaqueveras/powersso/application/auth" domain "github.com/isaqueveras/powersso/domain/auth" + "github.com/isaqueveras/powersso/i18n" "github.com/isaqueveras/powersso/oops" "github.com/isaqueveras/powersso/utils" ) -// @Router /v1/auth/create_account [post] +// @Router /v1/auth/create_account [POST] func createAccount(ctx *gin.Context) { var input domain.CreateAccount if err := ctx.ShouldBindJSON(&input); err != nil { @@ -25,31 +26,20 @@ func createAccount(ctx *gin.Context) { return } - if err := app.CreateAccount(ctx, &input); err != nil { - oops.Handling(ctx, err) - return - } - - ctx.JSON(http.StatusCreated, utils.NoContent{}) -} - -// @Router /v1/auth/activation/{token} [post] -func activation(ctx *gin.Context) { - token, err := uuid.Parse(ctx.Param("token")) + url, err := app.CreateAccount(ctx, &input) if err != nil { oops.Handling(ctx, err) return } - if err := app.Activation(ctx, utils.Pointer(token)); err != nil { - oops.Handling(ctx, err) - return - } - - ctx.JSON(http.StatusOK, utils.NoContent{}) + ctx.JSON(http.StatusCreated, map[string]string{ + "url": *url, + "message": i18n.Value("create_account.message"), + "instructions": i18n.Value("create_account.instructions"), + }) } -// @Router /v1/auth/login [post] +// @Router /v1/auth/login [POST] func login(ctx *gin.Context) { var input domain.Login if err := ctx.ShouldBindJSON(&input); err != nil { @@ -91,7 +81,7 @@ func changePassword(ctx *gin.Context) { ctx.JSON(http.StatusOK, nil) } -// @Router /v1/auth/logout [delete] +// @Router /v1/auth/logout [DELETE] func logout(ctx *gin.Context) { sessionID, err := uuid.Parse(gopowersso.GetSession(ctx).SessionID) if err != nil { @@ -107,7 +97,7 @@ func logout(ctx *gin.Context) { ctx.JSON(http.StatusNoContent, utils.NoContent{}) } -// @Router /v1/auth/login/steps [get] +// @Router /v1/auth/login/steps [GET] func loginSteps(ctx *gin.Context) { res, err := app.LoginSteps(ctx, utils.Pointer(ctx.Query("email"))) if err != nil { @@ -118,7 +108,7 @@ func loginSteps(ctx *gin.Context) { ctx.JSON(http.StatusOK, res) } -// @Router /v1/auth/user/{user_uuid}/disable [put] +// @Router /v1/auth/user/{user_uuid}/disable [PUT] func disable(ctx *gin.Context) { userID, err := uuid.Parse(ctx.Param("user_uuid")) if err != nil { @@ -126,7 +116,7 @@ func disable(ctx *gin.Context) { return } - if err = app.Disable(ctx, &userID); err != nil { + if err = app.DisableUser(ctx, &userID); err != nil { oops.Handling(ctx, err) return } @@ -134,7 +124,7 @@ func disable(ctx *gin.Context) { ctx.JSON(http.StatusCreated, utils.NoContent{}) } -// @Router /v1/auth/user/{user_uuid}/otp/configure [post] +// @Router /v1/auth/user/{user_uuid}/otp/configure [POST] func configure(ctx *gin.Context) { userID, err := uuid.Parse(ctx.Param("user_uuid")) if err != nil { @@ -142,7 +132,7 @@ func configure(ctx *gin.Context) { return } - if err = app.Configure(ctx, &userID); err != nil { + if err = app.Configure2FA(ctx, &userID); err != nil { oops.Handling(ctx, err) return } @@ -150,7 +140,7 @@ func configure(ctx *gin.Context) { ctx.JSON(http.StatusCreated, utils.NoContent{}) } -// @Router /v1/auth/user/{user_uuid}/otp/unconfigure [put] +// @Router /v1/auth/user/{user_uuid}/otp/unconfigure [PUT] func unconfigure(ctx *gin.Context) { userID, err := uuid.Parse(ctx.Param("user_uuid")) if err != nil { @@ -158,7 +148,7 @@ func unconfigure(ctx *gin.Context) { return } - if err = app.Unconfigure(ctx, &userID); err != nil { + if err = app.Unconfigure2FA(ctx, &userID); err != nil { oops.Handling(ctx, err) return } @@ -166,7 +156,7 @@ func unconfigure(ctx *gin.Context) { ctx.JSON(http.StatusCreated, utils.NoContent{}) } -// @Router /v1/auth/user/{user_uuid}/otp/qrcode [get] +// @Router /v1/auth/user/{user_uuid}/otp/qrcode [GET] func qrcode(ctx *gin.Context) { userID, err := uuid.Parse(ctx.Param("user_uuid")) if err != nil { @@ -174,11 +164,11 @@ func qrcode(ctx *gin.Context) { return } - var res *domain.QRCode - if res, err = app.GetQrCode(ctx, &userID); err != nil { + var url *string + if url, err = app.GetQRCode2FA(ctx, &userID); err != nil { oops.Handling(ctx, err) return } - ctx.JSON(http.StatusOK, res) + ctx.JSON(http.StatusOK, map[string]*string{"url": url}) } diff --git a/interface/http/auth/handler_test.go b/delivery/http/auth/handler_test.go similarity index 82% rename from interface/http/auth/handler_test.go rename to delivery/http/auth/handler_test.go index b589dca..7479f15 100644 --- a/interface/http/auth/handler_test.go +++ b/delivery/http/auth/handler_test.go @@ -22,6 +22,7 @@ import ( domain "github.com/isaqueveras/powersso/domain/auth" "github.com/isaqueveras/powersso/middleware" "github.com/isaqueveras/powersso/oops" + "github.com/isaqueveras/powersso/utils" ) const sucessUserID = "9ec1b2a7-665c-47a7-b180-54f11f8a6122" @@ -49,8 +50,8 @@ func (a *testSuite) SetupSuite() { } func (a *testSuite) TestShouldCreateUser() { - monkey.Patch(auth.CreateAccount, func(_ context.Context, _ *domain.CreateAccount) error { - return nil + monkey.Patch(auth.CreateAccount, func(_ context.Context, _ *domain.CreateAccount) (*string, error) { + return utils.Pointer(""), nil }) defer monkey.Unpatch(auth.CreateAccount) @@ -103,10 +104,10 @@ func (t *testSuite) TestLoginSteps() { func (t *testSuite) TestShouldGetUrlQrCode() { t.Run("Success", func() { - monkey.Patch(auth.GetQrCode, func(_ context.Context, _ *uuid.UUID) (*domain.QRCode, error) { - return &domain.QRCode{}, nil + monkey.Patch(auth.GetQRCode2FA, func(_ context.Context, _ *uuid.UUID) (*string, error) { + return nil, nil }) - defer monkey.Unpatch(auth.GetQrCode) + defer monkey.Unpatch(auth.GetQRCode2FA) req := httptest.NewRequest(http.MethodGet, "/v1/auth/user/"+sucessUserID+"/otp/qrcode", nil) w := httptest.NewRecorder() @@ -116,10 +117,10 @@ func (t *testSuite) TestShouldGetUrlQrCode() { }) t.Run("Error::FetchAnotherUserURL", func() { - monkey.Patch(auth.GetQrCode, func(_ context.Context, _ *uuid.UUID) (*domain.QRCode, error) { - return &domain.QRCode{}, nil + monkey.Patch(auth.GetQRCode2FA, func(_ context.Context, _ *uuid.UUID) (*string, error) { + return nil, nil }) - defer monkey.Unpatch(auth.GetQrCode) + defer monkey.Unpatch(auth.GetQRCode2FA) req := httptest.NewRequest(http.MethodGet, "/v1/auth/user/"+uuid.New().String()+"/otp/qrcode", nil) w := httptest.NewRecorder() @@ -131,10 +132,10 @@ func (t *testSuite) TestShouldGetUrlQrCode() { func (t *testSuite) TestShouldConfigure() { t.Run("Success", func() { - monkey.Patch(auth.Configure, func(_ context.Context, _ *uuid.UUID) error { + monkey.Patch(auth.Configure2FA, func(_ context.Context, _ *uuid.UUID) error { return nil }) - defer monkey.Unpatch(auth.Configure) + defer monkey.Unpatch(auth.Configure2FA) req := httptest.NewRequest(http.MethodPost, "/v1/auth/user/"+sucessUserID+"/otp/configure", nil) w := httptest.NewRecorder() @@ -144,10 +145,10 @@ func (t *testSuite) TestShouldConfigure() { }) t.Run("Error::FetchAnotherUserURL", func() { - monkey.Patch(auth.Configure, func(_ context.Context, _ *uuid.UUID) error { + monkey.Patch(auth.Configure2FA, func(_ context.Context, _ *uuid.UUID) error { return nil }) - defer monkey.Unpatch(auth.Configure) + defer monkey.Unpatch(auth.Configure2FA) req := httptest.NewRequest(http.MethodPost, "/v1/auth/user/"+uuid.New().String()+"/otp/configure", nil) w := httptest.NewRecorder() @@ -159,10 +160,10 @@ func (t *testSuite) TestShouldConfigure() { func (t *testSuite) TestShouldUnconfigure() { t.Run("Success", func() { - monkey.Patch(auth.Unconfigure, func(_ context.Context, _ *uuid.UUID) error { + monkey.Patch(auth.Unconfigure2FA, func(_ context.Context, _ *uuid.UUID) error { return nil }) - defer monkey.Unpatch(auth.Unconfigure) + defer monkey.Unpatch(auth.Unconfigure2FA) var ( req = httptest.NewRequest(http.MethodPut, "/v1/auth/user/"+sucessUserID+"/otp/unconfigure", nil) @@ -174,10 +175,10 @@ func (t *testSuite) TestShouldUnconfigure() { }) t.Run("Error::FetchAnotherUserURL", func() { - monkey.Patch(auth.Unconfigure, func(_ context.Context, _ *uuid.UUID) error { + monkey.Patch(auth.Unconfigure2FA, func(_ context.Context, _ *uuid.UUID) error { return nil }) - defer monkey.Unpatch(auth.Unconfigure) + defer monkey.Unpatch(auth.Unconfigure2FA) var ( req = httptest.NewRequest(http.MethodPut, "/v1/auth/user/"+uuid.New().String()+"/otp/unconfigure", nil) diff --git a/interface/http/auth/router.go b/delivery/http/auth/router.go similarity index 95% rename from interface/http/auth/router.go rename to delivery/http/auth/router.go index d049e61..9149edf 100644 --- a/interface/http/auth/router.go +++ b/delivery/http/auth/router.go @@ -11,7 +11,6 @@ import ( // Router is the router for the auth module. func Router(r *gin.RouterGroup) { - r.POST("activation/:token", activation) r.POST("create_account", createAccount) r.POST("login", login) r.GET("login/steps", loginSteps) diff --git a/interface/http/project/handler.go b/delivery/http/project/handler.go similarity index 100% rename from interface/http/project/handler.go rename to delivery/http/project/handler.go diff --git a/interface/http/project/router.go b/delivery/http/project/router.go similarity index 100% rename from interface/http/project/router.go rename to delivery/http/project/router.go diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 8afbd88..9feb7eb 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -16,16 +16,6 @@ services: networks: - powersso - mailcatcher: - container_name: mailcatcher - image: sj26/mailcatcher - expose: - - 1025 - - 1080 - ports: - - 1025:1025 - - 1080:1080 - networks: powersso: driver: bridge diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 0e3b214..76a22c1 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -16,16 +16,6 @@ services: networks: - powersso - mailcatcher: - container_name: mailcatcher - image: sj26/mailcatcher - expose: - - 1025 - - 1080 - ports: - - 1025:1025 - - 1080:1080 - backend: build: context: . @@ -46,32 +36,6 @@ services: networks: - powersso - nginx: - build: ./nginx - ports: - - "80:80" - depends_on: - - backend - volumes: - - ./ui/build:/app/frontend/build - networks: - - powersso - - frontend: - build: - context: ./ui - dockerfile: Dockerfile - ports: - - "3000:3000" - depends_on: - - backend - environment: - - API_URL=http://localhost/api/v1/ - env_file: - - ./ui/.env - networks: - - powersso - networks: powersso: driver: bridge diff --git a/domain/auth/errors.go b/domain/auth/errors.go index 6bad62a..381e95c 100644 --- a/domain/auth/errors.go +++ b/domain/auth/errors.go @@ -21,11 +21,6 @@ func ErrTokenIsNotValid() *oops.Error { return oops.NewError(i18n.Value("errors.handling.err_token_is_not_valid"), http.StatusBadRequest) } -// ErrNotHavePermissionLogin creates and returns an error when the user does not have permission to login -func ErrNotHavePermissionLogin() *oops.Error { - return oops.NewError(i18n.Value("errors.handling.err_not_have_permission_login"), http.StatusBadRequest) -} - // ErrUserNotExists creates and returns an error when the user does not exists func ErrUserNotExists() *oops.Error { return oops.NewError(i18n.Value("errors.handling.err_user_not_exists"), http.StatusNotFound) @@ -43,5 +38,10 @@ func ErrUserBlockedTemporarily() *oops.Error { // ErrOTPTokenInvalid creates and returns an error when validate token OTP func ErrOTPTokenInvalid() *oops.Error { - return oops.NewError(i18n.Value("errors.handling.err_otp_token_invalid"), http.StatusForbidden) + return oops.NewError(i18n.Value("errors.handling.err_a2f_invalid"), http.StatusForbidden) +} + +// ErrAuthentication2factorNotConfigured user with 2-factor authentication token not configured +func ErrAuthentication2factorNotConfigured() *oops.Error { + return oops.NewError(i18n.Value("errors.handling.err_otp_token_2_factor_authentication_not_configured"), http.StatusForbidden) } diff --git a/domain/auth/interface.go b/domain/auth/interface.go index 2040ed4..9f598ed 100644 --- a/domain/auth/interface.go +++ b/domain/auth/interface.go @@ -6,13 +6,15 @@ package auth import "github.com/google/uuid" +// IAuthService defines an interface for service methods to access the data layer +type IAuthService interface { + Configure2FA(userID *uuid.UUID) error + GenerateQrCode2FA(userID *uuid.UUID) (*string, error) +} + // IAuth define an interface for data layer access methods type IAuth interface { CreateAccount(*CreateAccount) (userID *uuid.UUID, err error) - SendMailActivationAccount(email *string, token *uuid.UUID) error - GetActivateAccountToken(data *ActivateAccount) error - CreateAccessToken(userID *uuid.UUID) (*uuid.UUID, error) - MarkTokenAsUsed(token *uuid.UUID) error AddAttempts(userID *uuid.UUID) error LoginSteps(email *string) (*Steps, error) } @@ -27,19 +29,19 @@ type ISession interface { // IFlag define an interface for data layer access methods type IFlag interface { Get(userID *uuid.UUID) (*int64, error) - Set(userID *uuid.UUID, flag *Flag) error + Set(userID *uuid.UUID, flag Flag) error } // IOTP define an interface for data layer access methods type IOTP interface { - GetToken() (*string, *string, error) - SetToken(secret *string) error + GetToken(userID *uuid.UUID) (*string, *string, error) + SetToken(userID *uuid.UUID, secret *string) error } // IUser define an interface for data layer access methods type IUser interface { - Get(user *User) error - Exist(email *string) error - Disable(userUUID *uuid.UUID) error + GetUser(*User) error ChangePassword(*ChangePassword) error + AccountExists(email *string) error + DisableUser(userUUID *uuid.UUID) error } diff --git a/domain/auth/model.go b/domain/auth/model.go index 57b37e1..71bbc18 100644 --- a/domain/auth/model.go +++ b/domain/auth/model.go @@ -152,15 +152,21 @@ func (u *User) IsBlocked() bool { return u.Blocked != nil && *u.Blocked } +// OTPConfigured checks if the user has the OTP token configured func (u *User) OTPConfigured() bool { enabled := u.Flag != nil && *u.Flag&FlagOTPEnable != 0 setup := u.Flag != nil && *u.Flag&FlagOTPSetup != 0 return enabled && setup } -// QRCode wraps the data to return the qr code url -type QRCode struct { - Url *string `json:"url,omitempty"` +// GetUserLevel returns the authentication token and duration by user level +func (u *User) GetUserLevel(s *config.Secrets) string { + keys := map[Level]string{ + UserLevel: s.User, + AdminLevel: s.Admin, + IntegrationLevel: s.Integration, + } + return keys[*u.Level] } // Login models the data for the user to log in with their account @@ -212,14 +218,13 @@ func (l *Login) Validate() { // Session models the data of a user session type Session struct { - SessionID *uuid.UUID `json:"session_id,omitempty"` - UserID *uuid.UUID `json:"user_id,omitempty"` - Email *string `json:"email,omitempty"` - FirstName *string `json:"first_name,omitempty"` - LastName *string `json:"last_name,omitempty"` - Level *Level `json:"level,omitempty"` - Token *string `json:"token,omitempty"` - CreatedAt *time.Time `json:"created_at,omitempty"` - ExpiresAt *time.Time `json:"expires_at,omitempty"` - RawData map[string]any `json:"data,omitempty"` + SessionID *uuid.UUID `json:"session_id,omitempty"` + UserID *uuid.UUID `json:"user_id,omitempty"` + Email *string `json:"email,omitempty"` + FirstName *string `json:"first_name,omitempty"` + LastName *string `json:"last_name,omitempty"` + Level *Level `json:"level,omitempty"` + Token *string `json:"token,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + ExpiresAt *time.Time `json:"expires_at,omitempty"` } diff --git a/domain/auth/service.go b/domain/auth/service.go new file mode 100644 index 0000000..fe6c947 --- /dev/null +++ b/domain/auth/service.go @@ -0,0 +1,53 @@ +package auth + +import ( + "encoding/base32" + + "github.com/google/uuid" + "github.com/isaqueveras/powersso/config" + "github.com/isaqueveras/powersso/utils" +) + +// Service structure with repositories +type Service struct { + repoFlag IFlag + repoOTP IOTP +} + +// NewAuthService init new service +func NewAuthService(repoFlag IFlag, repoOTP IOTP) IAuthService { + return &Service{repoFlag: repoFlag, repoOTP: repoOTP} +} + +// Configure2FA add the flags to the configured 2fa user and generates the 2fa token +func (s *Service) Configure2FA(userID *uuid.UUID) (err error) { + if err = s.repoFlag.Set(userID, FlagOTPSetup); err != nil { + return err + } + + if err = s.repoFlag.Set(userID, FlagOTPEnable); err != nil { + return err + } + + data := []byte(utils.RandomString(26)) + dst := make([]byte, base32.StdEncoding.EncodedLen(len(data))) + base32.StdEncoding.Encode(dst, data) + return s.repoOTP.SetToken(userID, utils.Pointer(string(dst))) +} + +// GenerateQrCode2FA return the formatted url to configure 2-factor authentication +func (s *Service) GenerateQrCode2FA(userID *uuid.UUID) (url *string, err error) { + userName, token, err := s.repoOTP.GetToken(userID) + if err != nil { + return nil, err + } + + if config.Get().Server.IsModeDevelopment() { + *userName += " [DEV]" + } + + projectName := utils.Pointer(config.Get().ProjectName) + link := utils.GetUrlQrCode(projectName, token, userName) + + return utils.Pointer(link), nil +} diff --git a/go.mod b/go.mod index 109327a..6e791db 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/Masterminds/squirrel v1.5.3 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 - github.com/domodwyer/mailyak/v3 v3.3.5 github.com/gin-contrib/zap v0.1.0 github.com/gin-gonic/gin v1.8.2 github.com/golang-jwt/jwt/v4 v4.4.3 @@ -18,9 +17,8 @@ require ( github.com/isaqueveras/go-powersso v0.0.0-20230308214228-19254da208d9 github.com/isaqueveras/lingo v0.0.0-20181220065520-bfdb55fa4143 github.com/jackc/pgx v3.6.2+incompatible - github.com/microcosm-cc/bluemonday v1.0.22 github.com/pkg/errors v0.9.1 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.8.4 github.com/swaggo/files v1.0.0 github.com/swaggo/gin-swagger v1.5.3 github.com/swaggo/swag v1.8.10 @@ -33,21 +31,21 @@ require ( require ( github.com/KyleBanks/depth v1.2.1 // indirect - github.com/aymerick/douceur v0.2.0 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/cockroachdb/apd v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/spec v0.20.8 // indirect - github.com/go-openapi/swag v0.22.3 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.11.2 // indirect - github.com/goccy/go-json v0.10.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-playground/validator/v10 v10.11.1 // indirect + github.com/goccy/go-json v0.9.11 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/gorilla/css v1.0.0 // indirect github.com/gosimple/unidecode v1.0.1 // indirect github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -55,24 +53,25 @@ require ( github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/leodido/go-urn v1.2.1 // indirect - github.com/lib/pq v1.10.7 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/lib/pq v1.10.9 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect - github.com/ugorji/go/codec v1.2.9 // indirect - go.opentelemetry.io/otel v1.13.0 // indirect - go.opentelemetry.io/otel/trace v1.13.0 // indirect - go.uber.org/atomic v1.10.0 // indirect - go.uber.org/multierr v1.9.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/ugorji/go/codec v1.2.7 // indirect + go.opentelemetry.io/otel v1.10.0 // indirect + go.opentelemetry.io/otel/trace v1.10.0 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect golang.org/x/net v0.6.0 // indirect golang.org/x/sys v0.5.0 // indirect golang.org/x/text v0.7.0 // indirect - golang.org/x/tools v0.6.0 // indirect - google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc // indirect + golang.org/x/tools v0.1.12 // indirect + google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 93f8dc1..a95eb1e 100644 --- a/go.sum +++ b/go.sum @@ -8,13 +8,13 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= -github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -27,8 +27,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 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/domodwyer/mailyak/v3 v3.3.5 h1:4rQV4ADpFIizCR8VVCASO+7bLof3aPhdFGnzCrXMDRc= -github.com/domodwyer/mailyak/v3 v3.3.5/go.mod h1:lOm/u9CyCVWHeaAmHIdF4RiKVxKUT/H5XX10lIKAL6c= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -49,35 +47,28 @@ github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= -github.com/go-openapi/spec v0.20.8 h1:ubHmXNY3FCIOinT8RNrrPfGc9t7I1qhPtdOGoG2AxRU= -github.com/go-openapi/spec v0.20.8/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= -github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= +github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= +github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= -github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -99,8 +90,6 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= -github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gosimple/slug v1.13.1 h1:bQ+kpX9Qa6tHRaK+fZR0A0M2Kd7Pa5eHPPsb1JpHD+Q= github.com/gosimple/slug v1.13.1/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= @@ -140,21 +129,17 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhR github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= -github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/microcosm-cc/bluemonday v1.0.22 h1:p2tT7RNzRdCi0qmwxG+HbqD6ILkmwter1ZwVZn1oTxA= -github.com/microcosm-cc/bluemonday v1.0.22/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= @@ -187,6 +172,7 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -197,6 +183,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= github.com/swaggo/files v1.0.0 h1:1gGXVIeUFCS/dta17rnP0iOpr6CXFwKD7EO5ID233e4= github.com/swaggo/files v1.0.0/go.mod h1:N59U6URJLyU1PQgFqPM7wXLMhJx7QAolnvfQkqO13kc= @@ -206,31 +194,26 @@ github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pA github.com/swaggo/swag v1.8.10 h1:eExW4bFa52WOjqRzRD58bgWsWfdFJso50lpbeTcmTfo= github.com/swaggo/swag v1.8.10/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= -github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= -github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opentelemetry.io/otel v1.10.0 h1:Y7DTJMR6zs1xkS/upamJYk0SxxN4C9AqRd77jmZnyY4= go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= -go.opentelemetry.io/otel v1.13.0 h1:1ZAKnNQKwBBxFtww/GwxNUyTf0AxkZzrukO8MeXqe4Y= -go.opentelemetry.io/otel v1.13.0/go.mod h1:FH3RtdZCzRkJYFTCsAKDy9l/XYjMdNv6QrkFFB8DvVg= +go.opentelemetry.io/otel/trace v1.10.0 h1:npQMbR8o7mum8uF95yFbOEJffhs1sbCOfDh8zAJiH5E= go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= -go.opentelemetry.io/otel/trace v1.13.0 h1:CBgRZ6ntv+Amuj1jDsMhZtlAPT6gbyIRdaIzFhfBSdY= -go.opentelemetry.io/otel/trace v1.13.0/go.mod h1:muCvmmO9KKpvuXSf3KKAXXB2ygNYHQ+ZfI5X08d3tds= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -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= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= @@ -240,6 +223,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -250,8 +234,8 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -264,6 +248,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= @@ -321,9 +306,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -333,8 +317,8 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc h1:ijGwO+0vL2hJt5gaygqP2j6PfflOBrRot0IczKbmtio= -google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= diff --git a/i18n/en_US.json b/i18n/en_US.json index 910f6b9..31a2623 100644 --- a/i18n/en_US.json +++ b/i18n/en_US.json @@ -1,4 +1,8 @@ { + "create_account": { + "instructions": "Scan the QR Code to set up 2-factor authentication", + "message": "Account created successfully" + }, "errors": { "default": "Unknown error", "handling": { @@ -26,11 +30,11 @@ }, "err_user_exists": "There is already a user with this email", "err_token_is_not_valid": "Token is not valid or expired", - "err_not_have_permission_login": "You do not have permission to login", "err_user_not_exists": "User not found", "err_email_or_password_is_not_valid": "Email or password is not valid", "err_user_blocked_temporarily": "User is blocked temporarily", - "err_otp_token_invalid": "Token OTP invalid" + "err_a2f_invalid": "The two-factor authentication code is invalid", + "err_otp_token_2_factor_authentication_not_configured": "You don't have 2-factor authentication token configured" } } -} \ No newline at end of file +} diff --git a/i18n/es_ES.json b/i18n/es_ES.json index 3db25fe..a61a20b 100644 --- a/i18n/es_ES.json +++ b/i18n/es_ES.json @@ -1,4 +1,8 @@ { + "create_account": { + "instructions": "Escanee el código QR para configurar la autenticación de 2 factores", + "message": "Cuenta creada con éxito" + }, "errors": { "default": "Error desconocido", "handling": { @@ -26,11 +30,11 @@ }, "err_user_exists": "Ya hay un usuario con este correo electrónico", "err_token_is_not_valid": "El token no es válido o ha caducado", - "err_not_have_permission_login": "No tienes permiso para iniciar sesión", "err_user_not_exists": "El usuario no existe", "err_email_or_password_is_not_valid": "El correo electrónico o la contraseña no son válidos", "err_user_blocked_temporarily": "El usuario está bloqueado temporalmente", - "err_otp_token_invalid": "Token OTP inválido" + "err_a2f_invalid": "El código de autenticación de dos factores no es válido", + "err_otp_token_2_factor_authentication_not_configured": "No tienes configurado el token de autenticación de 2 factores" } } -} \ No newline at end of file +} diff --git a/i18n/pt_BR.json b/i18n/pt_BR.json index 18f2373..e76cf62 100644 --- a/i18n/pt_BR.json +++ b/i18n/pt_BR.json @@ -1,4 +1,8 @@ { + "create_account": { + "instructions": "Escaneia o QR Code para configurar a autenticação de 2 fatores", + "message": "Conta criada com sucesso" + }, "errors": { "default": "Erro desconhecido", "handling": { @@ -26,11 +30,11 @@ }, "err_user_exists": "Já existe um usuário com este e-mail", "err_token_is_not_valid": "O token não é válido ou expirou", - "err_not_have_permission_login": "Você não tem permissão para fazer login", "err_user_not_exists": "Usuário não encontrado", "err_email_or_password_is_not_valid": "E-mail ou senha inválidos", "err_user_blocked_temporarily": "Usuário bloqueado temporariamente", - "err_otp_token_invalid": "Token OTP inválido" + "err_a2f_invalid": "O código de autenticação de dois fatores é inválido", + "err_otp_token_2_factor_authentication_not_configured": "Você não tem o token de autenticação de 2 fatores configurado" } } -} \ No newline at end of file +} diff --git a/infrastructure/persistencie/auth/auth_repository.go b/infrastructure/persistencie/auth/auth_repository.go deleted file mode 100644 index 346a469..0000000 --- a/infrastructure/persistencie/auth/auth_repository.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2022 Isaque Veras -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -package auth - -import ( - "github.com/google/uuid" - "github.com/isaqueveras/powersso/config" - pg "github.com/isaqueveras/powersso/database/postgres" - "github.com/isaqueveras/powersso/domain/auth" - "github.com/isaqueveras/powersso/infrastructure/persistencie/auth/mail" - infra "github.com/isaqueveras/powersso/infrastructure/persistencie/auth/postgres" - "github.com/isaqueveras/powersso/mailer" -) - -var _ auth.IAuth = (*repoAuth)(nil) - -type repoAuth struct { - pg *infra.PGAuth - mailer *mail.MailerAuth -} - -// NewAuthRepository creates a new repository -func NewAuthRepository(tx *pg.Transaction, client *mailer.SmtpClient) auth.IAuth { - return &repoAuth{pg: &infra.PGAuth{DB: tx}, mailer: &mail.MailerAuth{SmtpClient: client, Cfg: config.Get()}} -} - -// CreateAccount contains the flow for the user register in database -func (r *repoAuth) CreateAccount(data *auth.CreateAccount) (userID *uuid.UUID, err error) { - return r.pg.CreateAccount(data) -} - -// SendMailActivationAccount contains the flow for the send activation account email -func (r *repoAuth) SendMailActivationAccount(email *string, token *uuid.UUID) error { - return r.mailer.SendMailActivationAccount(email, token) -} - -// GetActivateAccountToken contains the flow for the get activate account token -func (r *repoAuth) GetActivateAccountToken(data *auth.ActivateAccount) error { - return r.pg.GetActivateAccountToken(data) -} - -// CreateAccessToken contains the flow for the create access token -func (r *repoAuth) CreateAccessToken(userID *uuid.UUID) (*uuid.UUID, error) { - return r.pg.CreateAccessToken(userID) -} - -// MarkTokenAsUsed contains the flow for the mark token as used -func (r *repoAuth) MarkTokenAsUsed(token *uuid.UUID) error { - return r.pg.MarkTokenAsUsed(token) -} - -// AddAttempts contains the flow for the add number failed attempts -func (r *repoAuth) AddAttempts(userID *uuid.UUID) error { - return r.pg.AddAttempts(userID) -} - -// LoginSteps contains the flow to get the data needed to retrieve the steps required to log in a user -func (r *repoAuth) LoginSteps(email *string) (*auth.Steps, error) { - return r.pg.LoginSteps(email) -} diff --git a/infrastructure/persistencie/auth/flag_repository.go b/infrastructure/persistencie/auth/flag_repository.go deleted file mode 100644 index 9f69556..0000000 --- a/infrastructure/persistencie/auth/flag_repository.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) 2023 Isaque Veras -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -package auth - -import ( - "github.com/google/uuid" - "github.com/isaqueveras/powersso/database/postgres" - domain "github.com/isaqueveras/powersso/domain/auth" - infra "github.com/isaqueveras/powersso/infrastructure/persistencie/auth/postgres" -) - -var _ domain.IFlag = (*repoFlag)(nil) - -type repoFlag struct{ pg *infra.PGFlag } - -func NewFlagRepo(tx *postgres.Transaction) domain.IFlag { - return &repoFlag{pg: &infra.PGFlag{DB: tx}} -} - -func (r *repoFlag) Set(userID *uuid.UUID, flag *domain.Flag) error { - return r.pg.Set(userID, flag) -} - -func (r *repoFlag) Get(userID *uuid.UUID) (*int64, error) { - return r.pg.Get(userID) -} diff --git a/infrastructure/persistencie/auth/mail/mailer.go b/infrastructure/persistencie/auth/mail/mailer.go deleted file mode 100644 index 8f35e93..0000000 --- a/infrastructure/persistencie/auth/mail/mailer.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2022 Isaque Veras -// Use of this source code is governed by MIT -// license that can be found in the LICENSE file. - -package mail - -import ( - "net/mail" - - "github.com/google/uuid" - "github.com/isaqueveras/powersso/config" - "github.com/isaqueveras/powersso/mailer" -) - -// MailerAuth is the implementation -// of mailer for the auth repository -type MailerAuth struct { - SmtpClient *mailer.SmtpClient - Cfg *config.Config -} - -// SendMailActivationAccount send the activation account email -func (ma *MailerAuth) SendMailActivationAccount(email *string, token *uuid.UUID) error { - return ma.SmtpClient.Send( - mail.Address{Name: ma.Cfg.Mailer.Username, Address: ma.Cfg.Mailer.Email}, - mail.Address{Address: *email}, - "Activate your "+ma.Cfg.Meta.ProjectName+" registration", - `Click on the link below to activate your `+ma.Cfg.Meta.ProjectName+` registration: - - `+ma.Cfg.Meta.ProjectURL+`/auth/activation/`+token.String()+` - - If you have not made this request, please ignore this email. - - Yours sincerely, - `+ma.Cfg.Meta.ProjectName+` team`, nil, - ) -} diff --git a/infrastructure/persistencie/auth/otp_repository.go b/infrastructure/persistencie/auth/otp_repository.go deleted file mode 100644 index d18d62d..0000000 --- a/infrastructure/persistencie/auth/otp_repository.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) 2023 Isaque Veras -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -package auth - -import ( - "github.com/google/uuid" - "github.com/isaqueveras/powersso/database/postgres" - domain "github.com/isaqueveras/powersso/domain/auth" - infra "github.com/isaqueveras/powersso/infrastructure/persistencie/auth/postgres" -) - -var _ domain.IOTP = (*repoOTP)(nil) - -type repoOTP struct{ pg *infra.PGOTP } - -func NewOTPRepo(tx *postgres.Transaction, userID *uuid.UUID) domain.IOTP { - return &repoOTP{pg: &infra.PGOTP{DB: tx, UserID: userID}} -} - -func (r *repoOTP) GetToken() (*string, *string, error) { - return r.pg.GetToken() -} - -func (r *repoOTP) SetToken(secret *string) error { - return r.pg.SetToken(secret) -} diff --git a/infrastructure/persistencie/auth/postgres/auth.go b/infrastructure/persistencie/auth/postgres/auth.go deleted file mode 100644 index e19c993..0000000 --- a/infrastructure/persistencie/auth/postgres/auth.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2022 Isaque Veras -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -package postgres - -import ( - "database/sql" - "time" - - "github.com/Masterminds/squirrel" - "github.com/google/uuid" - - pg "github.com/isaqueveras/powersso/database/postgres" - "github.com/isaqueveras/powersso/domain/auth" - "github.com/isaqueveras/powersso/oops" - "github.com/isaqueveras/powersso/utils" -) - -// PGAuth is the implementation of transaction for the auth repository -type PGAuth struct { - DB *pg.Transaction -} - -// CreateAccount register the user in the database -func (pg *PGAuth) CreateAccount(input *auth.CreateAccount) (userID *uuid.UUID, err error) { - _cols, _vals, err := utils.FormatValuesInUp(input) - if err != nil { - return nil, oops.Err(err) - } - - if err = pg.DB.Builder. - Insert("users"). - Columns(_cols...). - Values(_vals...). - Suffix(`RETURNING "id"`). - Scan(&userID); err != nil { - return nil, oops.Err(err) - } - - return -} - -// CreateAccessToken create the access token for the user -func (pg *PGAuth) CreateAccessToken(userID *uuid.UUID) (token *uuid.UUID, err error) { - if err = pg.DB.Builder. - Insert("activate_account_tokens"). - Columns("user_id", "expires_at"). - Values(userID, time.Now().Add(30*time.Minute)). - Suffix(`RETURNING "id"`). - Scan(&token); err != nil { - return token, oops.Err(err) - } - - return -} - -// GetActivateAccountToken get the activate account token from the database -func (pg *PGAuth) GetActivateAccountToken(data *auth.ActivateAccount) (err error) { - if err = pg.DB.Builder. - Select("user_id, used, expires_at >= now(), expires_at, created_at"). - From("activate_account_tokens"). - Where("id = ?", data.ID). - Limit(1). - Scan(&data.UserID, &data.Used, &data.Valid, &data.ExpiresAt, &data.CreatedAt); err != nil && err != sql.ErrNoRows { - return oops.Err(err) - } - - return -} - -// MarkTokenAsUsed mark the token as used in the database -func (pg *PGAuth) MarkTokenAsUsed(token *uuid.UUID) (err error) { - if _, err = pg.DB.Builder. - Update("activate_account_tokens"). - Set("used", true). - Where("id = ?", token). - Exec(); err != nil { - return oops.Err(err) - } - - return -} - -func (pg *PGAuth) AddAttempts(userID *uuid.UUID) (err error) { - if _, err = pg.DB.Builder. - Update("users"). - Set("attempts", squirrel.Expr("attempts + 1")). - Set("last_failure", squirrel.Expr("NOW()")). - Where("id = ?", userID). - Exec(); err != nil && err != sql.ErrNoRows { - return oops.Err(err) - } - - return -} - -func (pg *PGAuth) LoginSteps(email *string) (steps *auth.Steps, err error) { - steps = new(auth.Steps) - if err = pg.DB.Builder. - Select("first_name"). - Column("(flag&?) <> 0 AND (flag&?) <> 0", - auth.FlagOTPEnable, auth.FlagOTPSetup). - From("users"). - Where("email = ?", email). - Limit(1). - Scan(&steps.Name, &steps.OTP); err != nil && err != sql.ErrNoRows { - return nil, oops.Err(err) - } - - return -} diff --git a/infrastructure/persistencie/auth/postgres/data.go b/infrastructure/persistencie/auth/postgres/data.go new file mode 100644 index 0000000..f7d94f6 --- /dev/null +++ b/infrastructure/persistencie/auth/postgres/data.go @@ -0,0 +1,268 @@ +package postgres + +import ( + "database/sql" + + "github.com/Masterminds/squirrel" + "github.com/google/uuid" + "github.com/isaqueveras/powersso/config" + database "github.com/isaqueveras/powersso/database/postgres" + domain "github.com/isaqueveras/powersso/domain/auth" + "github.com/isaqueveras/powersso/oops" + "github.com/isaqueveras/powersso/utils" +) + +type ( + // PGAuth is the implementation of transaction for the auth repository + PGAuth struct{ DB *database.Transaction } + + // PGOTP is the implementation of transaction for the user repository + User struct{ DB *database.Transaction } + + // OTP is the implementation of transaction for the otp repository + OTP struct{ DB *database.Transaction } + + // Session is the implementation of transaction for the session repository + Session struct{ DB *database.Transaction } + + // Flag is the implementation of transaction for the flag repository + Flag struct{ DB *database.Transaction } +) + +// CreateAccount register the user in the database +func (pg *PGAuth) CreateAccount(input *domain.CreateAccount) (userID *uuid.UUID, err error) { + _cols, _vals, err := utils.FormatValuesInUp(input) + if err != nil { + return nil, oops.Err(err) + } + + if err = pg.DB.Builder. + Insert("users"). + Columns(_cols...). + Values(_vals...). + Suffix(`RETURNING "id"`). + Scan(&userID); err != nil { + return nil, oops.Err(err) + } + + return +} + +func (pg *PGAuth) AddAttempts(userID *uuid.UUID) (err error) { + if _, err = pg.DB.Builder. + Update("users"). + Set("attempts", squirrel.Expr("attempts + 1")). + Set("last_failure", squirrel.Expr("NOW()")). + Where("id = ?", userID). + Exec(); err != nil && err != sql.ErrNoRows { + return oops.Err(err) + } + + return +} + +func (pg *PGAuth) LoginSteps(email *string) (steps *domain.Steps, err error) { + steps = new(domain.Steps) + if err = pg.DB.Builder. + Select("first_name"). + Column("(flag&?) <> 0 AND (flag&?) <> 0", + domain.FlagOTPEnable, domain.FlagOTPSetup). + From("users"). + Where("email = ?", email). + Limit(1). + Scan(&steps.Name, &steps.OTP); err != nil && err != sql.ErrNoRows { + return nil, oops.Err(err) + } + + return +} + +// AccountExists validate whether an account with the same identifier already exists +func (pg *User) AccountExists(email *string) (err error) { + var exists *bool + if err = pg.DB.Builder. + Select("COUNT(id) > 0"). + From("users"). + Where(squirrel.Eq{"email": email}). + Scan(&exists); err != nil && err != sql.ErrNoRows { + return oops.Err(err) + } + + if exists != nil && *exists { + return domain.ErrUserExists() + } + + return +} + +// GetUser fetches a user's data from the database +func (pg *User) GetUser(data *domain.User) (err error) { + cond := squirrel.Eq{"id": data.ID} + if data.Email != nil { + cond = squirrel.Eq{"email": data.Email} + } + + if err = pg.DB.Builder. + Select(`id, email, password, first_name, last_name, flag, key, active, level, otp`). + Column("attempts >= 3 AND (last_failure + '5 minutes') >= NOW() AS blocked"). + Column("(flag & ?) <> 0", domain.FlagOTPEnable). + Column("(flag & ?) <> 0", domain.FlagOTPSetup). + From("users"). + Where(cond). + Scan(&data.ID, &data.Email, &data.Password, &data.FirstName, &data.LastName, &data.Flag, &data.Key, + &data.Active, &data.Level, &data.OTPToken, &data.Blocked, &data.OTPEnable, &data.OTPSetUp); err != nil { + if err == sql.ErrNoRows { + return domain.ErrUserNotExists() + } + return oops.Err(err) + } + + return +} + +func (pg *User) Disable(userUUID *uuid.UUID) (err error) { + if err = pg.DB.Builder. + Update("users"). + Set("active", false). + Set("updated_at", squirrel.Expr("NOW()")). + Where(squirrel.Eq{"id": userUUID, "active": true}). + Suffix("RETURNING id"). + Scan(new(string)); err != nil { + return oops.Err(err) + } + + return +} + +func (pg *User) ChangePassword(in *domain.ChangePassword) error { + if err := pg.DB.Builder. + Update("users"). + Set("password", in.Password). + Set("attempts", 0). + Set("key", in.Key). + Set("last_failure", squirrel.Expr("NULL")). + Where(squirrel.Eq{"id": in.UserID, "active": true}). + Suffix("RETURNING id"). + Scan(new(string)); err != nil { + return oops.Err(err) + } + + return nil +} + +func (pg *OTP) GetToken(userID *uuid.UUID) (userName, token *string, err error) { + if err = pg.DB.Builder. + Select("CONCAT('(',first_name,' ',last_name,')'), otp"). + From("public.users"). + Where("id = ?::UUID AND otp NOTNULL", userID). + QueryRow(). + Scan(&userName, &token); err != nil { + return nil, nil, oops.Err(err) + } + + return +} + +func (pg *OTP) SetToken(userID *uuid.UUID, secret *string) (err error) { + if _, err = pg.DB.Builder. + Update("users"). + Set("otp", secret). + Where("id = ?", userID). + Exec(); err != nil { + return oops.Err(err) + } + return +} + +// Create add session of the user in database +func (pg *Session) Create(userID *uuid.UUID, clientIP, userAgent *string) (sessionID *uuid.UUID, err error) { + if err = pg.DB.Builder. + Insert("sessions"). + Columns("user_id", "expires_at", "ip", "user_agent"). + Values(userID, squirrel.Expr("NOW() + '15 minutes'"), clientIP, userAgent). + Suffix(`RETURNING "id"`). + Scan(&sessionID); err != nil { + return nil, oops.Err(err) + } + + if _, err = pg.DB.Builder. + Update("sessions"). + Set("deleted_at", squirrel.Expr("NOW()")). + Where(`id NOT IN ( + SELECT id FROM sessions + WHERE user_id = ? AND deleted_at IS NULL + ORDER BY created_at DESC + LIMIT ? + )`, userID, config.Get().Server.OpenSessionsPerUser). + Where("user_id = ?", userID). + Exec(); err != nil { + return nil, oops.Err(err) + } + + if _, err = pg.DB.Builder. + Update("users"). + Set("attempts", 0). + Set("last_login", squirrel.Expr("NOW()")). + Set("last_failure", nil). + Where("id = ?", userID). + Exec(); err != nil && err != sql.ErrNoRows { + return nil, oops.Err(err) + } + + return +} + +// Delete delete session of the user in database +func (pg *Session) Delete(ids ...*uuid.UUID) (err error) { + if _, err = pg.DB.Builder. + Update("sessions"). + Set("deleted_at", squirrel.Expr("NOW()")). + Where("deleted_at IS NULL"). + Where(squirrel.Eq{"id": ids}). + Exec(); err != nil && err != sql.ErrNoRows { + return oops.Err(err) + } + + return +} + +func (pg *Session) Get(userID *uuid.UUID) (sessions []*uuid.UUID, err error) { + query := pg.DB.Builder.Select("id").From("sessions").Where("user_id = ? AND deleted_at IS NULL", userID) + + row, err := query.Query() + if err != nil { + return nil, oops.Err(err) + } + + for row.Next() { + var sessionID *uuid.UUID + if err = row.Scan(&sessionID); err != nil { + return nil, oops.Err(err) + } + sessions = append(sessions, sessionID) + } + + return +} + +func (pg *Flag) Set(userID *uuid.UUID, flag domain.Flag) error { + if _, err := pg.DB.Builder. + Update("users"). + Set("flag", flag). + Where("id = ?::UUID", userID). + Exec(); err != nil { + return oops.Err(err) + } + return nil +} + +func (pg *Flag) Get(userID *uuid.UUID) (flag *int64, err error) { + if err = pg.DB.Builder. + Select("flag"). + From("users"). + Where("id = ?::UUID", userID). + Scan(&flag); err != nil { + return nil, oops.Err(err) + } + return +} diff --git a/infrastructure/persistencie/auth/postgres/auth_test.go b/infrastructure/persistencie/auth/postgres/data_test.go similarity index 100% rename from infrastructure/persistencie/auth/postgres/auth_test.go rename to infrastructure/persistencie/auth/postgres/data_test.go diff --git a/infrastructure/persistencie/auth/postgres/flag.go b/infrastructure/persistencie/auth/postgres/flag.go deleted file mode 100644 index 0dee8f6..0000000 --- a/infrastructure/persistencie/auth/postgres/flag.go +++ /dev/null @@ -1,32 +0,0 @@ -package postgres - -import ( - "github.com/google/uuid" - "github.com/isaqueveras/powersso/database/postgres" - "github.com/isaqueveras/powersso/domain/auth" - "github.com/isaqueveras/powersso/oops" -) - -type PGFlag struct{ DB *postgres.Transaction } - -func (pg *PGFlag) Set(userID *uuid.UUID, flag *auth.Flag) error { - if _, err := pg.DB.Builder. - Update("users"). - Set("flag", flag). - Where("id = ?::UUID", userID). - Exec(); err != nil { - return oops.Err(err) - } - return nil -} - -func (pg *PGFlag) Get(userID *uuid.UUID) (flag *int64, err error) { - if err = pg.DB.Builder. - Select("flag"). - From("users"). - Where("id = ?::UUID", userID). - Scan(&flag); err != nil { - return nil, oops.Err(err) - } - return -} diff --git a/infrastructure/persistencie/auth/postgres/otp.go b/infrastructure/persistencie/auth/postgres/otp.go deleted file mode 100644 index 6a9d3f8..0000000 --- a/infrastructure/persistencie/auth/postgres/otp.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2023 Isaque Veras -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -package postgres - -import ( - "github.com/google/uuid" - "github.com/isaqueveras/powersso/database/postgres" - "github.com/isaqueveras/powersso/oops" -) - -type PGOTP struct { - DB *postgres.Transaction - UserID *uuid.UUID -} - -func (pg *PGOTP) GetToken() (userName, token *string, err error) { - if err = pg.DB.Builder. - Select("CONCAT('(',first_name,' ',last_name,')'), otp"). - From("public.users"). - Where("id = ?::UUID AND otp NOTNULL", pg.UserID). - QueryRow(). - Scan(&userName, &token); err != nil { - return nil, nil, oops.Err(err) - } - - return -} - -func (pg *PGOTP) SetToken(secret *string) (err error) { - if _, err = pg.DB.Builder. - Update("users"). - Set("otp", secret). - Where("id = ?", pg.UserID). - Exec(); err != nil { - return oops.Err(err) - } - return -} diff --git a/infrastructure/persistencie/auth/postgres/session.go b/infrastructure/persistencie/auth/postgres/session.go deleted file mode 100644 index 525635a..0000000 --- a/infrastructure/persistencie/auth/postgres/session.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) 2023 Isaque Veras -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -package postgres - -import ( - "database/sql" - - "github.com/Masterminds/squirrel" - "github.com/google/uuid" - "github.com/isaqueveras/powersso/config" - "github.com/isaqueveras/powersso/database/postgres" - "github.com/isaqueveras/powersso/oops" -) - -// PGSession is the implementation of transaction for the session repository -type PGSession struct { - DB *postgres.Transaction -} - -// Create add session of the user in database -func (pg *PGSession) Create(userID *uuid.UUID, clientIP, userAgent *string) (sessionID *uuid.UUID, err error) { - if err = pg.DB.Builder. - Insert("sessions"). - Columns("user_id", "expires_at", "ip", "user_agent"). - Values(userID, squirrel.Expr("NOW() + '15 minutes'"), clientIP, userAgent). - Suffix(`RETURNING "id"`). - Scan(&sessionID); err != nil { - return nil, oops.Err(err) - } - - if _, err = pg.DB.Builder. - Update("sessions"). - Set("deleted_at", squirrel.Expr("NOW()")). - Where(`id NOT IN ( - SELECT id FROM sessions - WHERE user_id = ? AND deleted_at IS NULL - ORDER BY created_at DESC - LIMIT ? - )`, userID, config.Get().Server.OpenSessionsPerUser). - Where("user_id = ?", userID). - Exec(); err != nil { - return nil, oops.Err(err) - } - - if _, err = pg.DB.Builder. - Update("users"). - Set("attempts", 0). - Set("last_login", squirrel.Expr("NOW()")). - Set("last_failure", nil). - Where("id = ?", userID). - Exec(); err != nil && err != sql.ErrNoRows { - return nil, oops.Err(err) - } - - return -} - -// Delete delete session of the user in database -func (pg *PGSession) Delete(ids ...*uuid.UUID) (err error) { - if _, err = pg.DB.Builder. - Update("sessions"). - Set("deleted_at", squirrel.Expr("NOW()")). - Where("deleted_at IS NULL"). - Where(squirrel.Eq{"id": ids}). - Exec(); err != nil && err != sql.ErrNoRows { - return oops.Err(err) - } - - return -} - -func (pg *PGSession) Get(userID *uuid.UUID) (sessions []*uuid.UUID, err error) { - query := pg.DB.Builder.Select("id").From("sessions").Where("user_id = ? AND deleted_at IS NULL", userID) - - row, err := query.Query() - if err != nil { - return nil, oops.Err(err) - } - - for row.Next() { - var sessionID *uuid.UUID - if err = row.Scan(&sessionID); err != nil { - return nil, oops.Err(err) - } - sessions = append(sessions, sessionID) - } - - return -} diff --git a/infrastructure/persistencie/auth/postgres/user.go b/infrastructure/persistencie/auth/postgres/user.go deleted file mode 100644 index d45130c..0000000 --- a/infrastructure/persistencie/auth/postgres/user.go +++ /dev/null @@ -1,89 +0,0 @@ -package postgres - -import ( - "database/sql" - - "github.com/Masterminds/squirrel" - "github.com/google/uuid" - "github.com/isaqueveras/powersso/database/postgres" - domain "github.com/isaqueveras/powersso/domain/auth" - "github.com/isaqueveras/powersso/oops" -) - -type PGUser struct { - DB *postgres.Transaction -} - -func (pg *PGUser) Exist(email *string) (err error) { - var exists *bool - if err = pg.DB.Builder. - Select("COUNT(id) > 0"). - From("users"). - Where(squirrel.Eq{"email": email}). - Scan(&exists); err != nil { - if err == sql.ErrNoRows { - return domain.ErrUserNotExists() - } - return oops.Err(err) - } - - if exists != nil && *exists { - return domain.ErrUserExists() - } - - return -} - -func (pg *PGUser) Get(data *domain.User) (err error) { - cond := squirrel.Eq{"id": data.ID} - if data.Email != nil { - cond = squirrel.Eq{"email": data.Email} - } - - if err = pg.DB.Builder. - Select(`id, email, password, first_name, last_name, flag, key, active, level, otp`). - Column("attempts >= 3 AND (last_failure + '5 minutes') >= NOW() AS blocked"). - Column("(flag & ?) <> 0", domain.FlagOTPEnable). - Column("(flag & ?) <> 0", domain.FlagOTPSetup). - From("users"). - Where(cond). - Scan(&data.ID, &data.Email, &data.Password, &data.FirstName, &data.LastName, &data.Flag, &data.Key, - &data.Active, &data.Level, &data.OTPToken, &data.Blocked, &data.OTPEnable, &data.OTPSetUp); err != nil { - if err == sql.ErrNoRows { - return domain.ErrUserNotExists() - } - return oops.Err(err) - } - - return -} - -func (pg *PGUser) Disable(userUUID *uuid.UUID) (err error) { - if err = pg.DB.Builder. - Update("users"). - Set("active", false). - Set("updated_at", squirrel.Expr("NOW()")). - Where(squirrel.Eq{"id": userUUID, "active": true}). - Suffix("RETURNING id"). - Scan(new(string)); err != nil { - return oops.Err(err) - } - - return -} - -func (pg *PGUser) ChangePassword(in *domain.ChangePassword) error { - if err := pg.DB.Builder. - Update("users"). - Set("password", in.Password). - Set("attempts", 0). - Set("key", in.Key). - Set("last_failure", squirrel.Expr("NULL")). - Where(squirrel.Eq{"id": in.UserID, "active": true}). - Suffix("RETURNING id"). - Scan(new(string)); err != nil { - return oops.Err(err) - } - - return nil -} diff --git a/infrastructure/persistencie/auth/repository.go b/infrastructure/persistencie/auth/repository.go new file mode 100644 index 0000000..784728e --- /dev/null +++ b/infrastructure/persistencie/auth/repository.go @@ -0,0 +1,118 @@ +// Copyright (c) 2023 Isaque Veras +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package auth + +import ( + "github.com/google/uuid" + database "github.com/isaqueveras/powersso/database/postgres" + domain "github.com/isaqueveras/powersso/domain/auth" + infra "github.com/isaqueveras/powersso/infrastructure/persistencie/auth/postgres" +) + +var ( + _ domain.IOTP = (*repoOTP)(nil) + _ domain.IFlag = (*repoFlag)(nil) + _ domain.IAuth = (*repoAuth)(nil) + _ domain.ISession = (*repoSession)(nil) + _ domain.IUser = (*repoUser)(nil) +) + +type ( + repoOTP struct{ pg *infra.OTP } + repoFlag struct{ pg *infra.Flag } + repoAuth struct{ pg *infra.PGAuth } + repoSession struct{ pg *infra.Session } + repoUser struct{ pg *infra.User } +) + +// NewAuthRepository creates a new repository +func NewAuthRepository(tx *database.Transaction) domain.IAuth { + return &repoAuth{pg: &infra.PGAuth{DB: tx}} +} + +// CreateAccount contains the flow for the user register in database +func (r *repoAuth) CreateAccount(data *domain.CreateAccount) (userID *uuid.UUID, err error) { + return r.pg.CreateAccount(data) +} + +// AddAttempts contains the flow for the add number failed attempts +func (r *repoAuth) AddAttempts(userID *uuid.UUID) error { + return r.pg.AddAttempts(userID) +} + +// LoginSteps contains the flow to get the data needed to retrieve the steps required to log in a user +func (r *repoAuth) LoginSteps(email *string) (*domain.Steps, error) { + return r.pg.LoginSteps(email) +} + +// NewOTPRepo creates a new repository +func NewOTPRepo(tx *database.Transaction) domain.IOTP { + return &repoOTP{pg: &infra.OTP{DB: tx}} +} + +func (r *repoOTP) GetToken(userID *uuid.UUID) (*string, *string, error) { + return r.pg.GetToken(userID) +} + +func (r *repoOTP) SetToken(userID *uuid.UUID, secret *string) error { + return r.pg.SetToken(userID, secret) +} + +// NewFlagRepo creates a new repository +func NewFlagRepo(tx *database.Transaction) domain.IFlag { + return &repoFlag{pg: &infra.Flag{DB: tx}} +} + +func (r *repoFlag) Set(userID *uuid.UUID, flag domain.Flag) error { + return r.pg.Set(userID, flag) +} + +func (r *repoFlag) Get(userID *uuid.UUID) (*int64, error) { + return r.pg.Get(userID) +} + +// NewSessionRepository creates a new repository +func NewSessionRepository(tx *database.Transaction) domain.ISession { + return &repoSession{pg: &infra.Session{DB: tx}} +} + +// Create create a new session for a user +func (r *repoSession) Create(userID *uuid.UUID, clientIP, userAgent *string) (*uuid.UUID, error) { + return r.pg.Create(userID, clientIP, userAgent) +} + +// Delete delete a session for a user +func (r *repoSession) Delete(ids ...*uuid.UUID) error { + return r.pg.Delete(ids...) +} + +func (r *repoSession) Get(userID *uuid.UUID) ([]*uuid.UUID, error) { + return r.pg.Get(userID) +} + +// NewUserRepository creates a new repository +func NewUserRepository(tx *database.Transaction) domain.IUser { + return &repoUser{pg: &infra.User{DB: tx}} +} + +// GetUser manages the flow for a user's data +func (r *repoUser) GetUser(user *domain.User) error { + return r.pg.GetUser(user) +} + +// AccountExists manages the flow to check if a user with the same identifier already exists +func (r *repoUser) AccountExists(email *string) error { + return r.pg.AccountExists(email) +} + +// DisableUser manages the flow to deactivate a user's account +func (r *repoUser) DisableUser(userUUID *uuid.UUID) error { + return r.pg.Disable(userUUID) +} + +// ChangePassword manages the flow to change a user's password +func (r *repoUser) ChangePassword(in *domain.ChangePassword) error { + return r.pg.ChangePassword(in) +} diff --git a/infrastructure/persistencie/auth/session_repository.go b/infrastructure/persistencie/auth/session_repository.go deleted file mode 100644 index 3c63650..0000000 --- a/infrastructure/persistencie/auth/session_repository.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2023 Isaque Veras -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -package auth - -import ( - "github.com/google/uuid" - pg "github.com/isaqueveras/powersso/database/postgres" - domain "github.com/isaqueveras/powersso/domain/auth" - infra "github.com/isaqueveras/powersso/infrastructure/persistencie/auth/postgres" -) - -var _ domain.ISession = (*repoSession)(nil) - -type repoSession struct{ pg *infra.PGSession } - -// NewSessionRepository creates a new repository -func NewSessionRepository(tx *pg.Transaction) domain.ISession { - return &repoSession{pg: &infra.PGSession{DB: tx}} -} - -// Create create a new session for a user -func (r *repoSession) Create(userID *uuid.UUID, clientIP, userAgent *string) (*uuid.UUID, error) { - return r.pg.Create(userID, clientIP, userAgent) -} - -// Delete delete a session for a user -func (r *repoSession) Delete(ids ...*uuid.UUID) error { - return r.pg.Delete(ids...) -} - -func (r *repoSession) Get(userID *uuid.UUID) ([]*uuid.UUID, error) { - return r.pg.Get(userID) -} diff --git a/infrastructure/persistencie/auth/user_repository.go b/infrastructure/persistencie/auth/user_repository.go deleted file mode 100644 index 604b097..0000000 --- a/infrastructure/persistencie/auth/user_repository.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2023 Isaque Veras -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -package auth - -import ( - "github.com/google/uuid" - pg "github.com/isaqueveras/powersso/database/postgres" - domain "github.com/isaqueveras/powersso/domain/auth" - infra "github.com/isaqueveras/powersso/infrastructure/persistencie/auth/postgres" -) - -var _ domain.IUser = (*repoUser)(nil) - -type repoUser struct{ pg *infra.PGUser } - -// NewUserRepository creates a new repository -func NewUserRepository(tx *pg.Transaction) domain.IUser { - return &repoUser{pg: &infra.PGUser{DB: tx}} -} - -// Get get user data -func (r *repoUser) Get(user *domain.User) error { - return r.pg.Get(user) -} - -// Exist check if user already exists -func (r *repoUser) Exist(email *string) error { - return r.pg.Exist(email) -} - -// Disable deactivate a user's account -func (r *repoUser) Disable(userUUID *uuid.UUID) error { - return r.pg.Disable(userUUID) -} - -func (r *repoUser) ChangePassword(in *domain.ChangePassword) error { - return r.pg.ChangePassword(in) -} diff --git a/mailer/mailer.go b/mailer/mailer.go deleted file mode 100644 index 8103799..0000000 --- a/mailer/mailer.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2022 Isaque Veras -// Use of this source code is governed by MIT -// license that can be found in the LICENSE file. - -package mailer - -import ( - "io" - "net/mail" -) - -// Mailer defines a base mail client interface. -type Mailer interface { - // Send sends an email with HTML body to the specified recipient. - Send(fromEmail mail.Address, toEmail mail.Address, subject string, htmlBody string, attachments map[string]io.Reader) error -} diff --git a/mailer/sendmail.go b/mailer/sendmail.go deleted file mode 100644 index 7c7afbb..0000000 --- a/mailer/sendmail.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) 2022 Isaque Veras -// Use of this source code is governed by MIT -// license that can be found in the LICENSE file. - -package mailer - -import ( - "bytes" - "errors" - "io" - "mime" - "net/http" - "net/mail" - "os/exec" -) - -var _ Mailer = (*Sendmail)(nil) - -// Sendmail implements `mailer.Mailer` interface and defines a mail -// client that sends emails via the `sendmail` *nix command. -// -// This client is usually recommended only for development and testing. -type Sendmail struct{} - -// Send implements `mailer.Mailer` interface. -// -// Attachments are currently not supported. -func (m *Sendmail) Send(fromEmail mail.Address, toEmail mail.Address, subject string, htmlBody string, attachments map[string]io.Reader) (err error) { - headers := make(http.Header) - headers.Set("Subject", mime.QEncoding.Encode("utf-8", subject)) - headers.Set("From", fromEmail.String()) - headers.Set("To", toEmail.String()) - headers.Set("Content-Type", "text/html; charset=UTF-8") - - var ( - cmdPath string - sendmail *exec.Cmd - buffer bytes.Buffer - ) - - if cmdPath, err = findSendmailPath(); err != nil { - return err - } - - if err = headers.Write(&buffer); err != nil { - return err - } - - if _, err = buffer.Write([]byte("\r\n")); err != nil { - return err - } - - if _, err = buffer.Write([]byte(htmlBody)); err != nil { - return err - } - - sendmail = exec.Command(cmdPath, toEmail.Address) - sendmail.Stdin = &buffer - - return sendmail.Run() -} - -func findSendmailPath() (path string, err error) { - options := []string{ - "/usr/sbin/sendmail", - "/usr/bin/sendmail", - "sendmail", - } - - for _, option := range options { - if path, err = exec.LookPath(option); err == nil { - return path, err - } - } - - return "", errors.New("failed to locate a sendmail executable path") -} diff --git a/mailer/smtp.go b/mailer/smtp.go deleted file mode 100644 index f670649..0000000 --- a/mailer/smtp.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) 2022 Isaque Veras -// Use of this source code is governed by MIT -// license that can be found in the LICENSE file. - -package mailer - -import ( - "fmt" - "io" - "net/mail" - "net/smtp" - "regexp" - "strings" - - "github.com/domodwyer/mailyak/v3" - "github.com/microcosm-cc/bluemonday" - - "github.com/isaqueveras/powersso/config" -) - -var _ Mailer = (*SmtpClient)(nil) - -// regex to select all tabs -var tabsRegex = regexp.MustCompile(`\t+`) - -// SmtpClient defines a SMTP mail client structure that implements -type SmtpClient struct { - host string - port int - username string - password string - tls bool -} - -// NewSmtpClient creates new `SmtpClient` with the provided configuration. -func NewSmtpClient(host string, port int, username string, password string, tls bool) *SmtpClient { - return &SmtpClient{ - host: host, - port: port, - username: username, - password: password, - tls: tls, - } -} - -// Send implements `mailer.Mailer` interface. -func (m *SmtpClient) Send(fromEmail mail.Address, toEmail mail.Address, subject string, htmlBody string, attachments map[string]io.Reader) (err error) { - var ( - email *mailyak.MailYak - smtpAuth = smtp.PlainAuth("", m.username, m.password, m.host) - ) - - if m.tls { - if email, err = mailyak.NewWithTLS(fmt.Sprintf("%s:%d", m.host, m.port), smtpAuth, nil); err != nil { - return err - } - } else { - email = mailyak.New(fmt.Sprintf("%s:%d", m.host, m.port), smtpAuth) - } - - if fromEmail.Name != "" { - email.FromName(fromEmail.Name) - } - - email.From(fromEmail.Address) - email.To(toEmail.Address) - email.Subject(subject) - email.HTML().Set(htmlBody) - - policy := bluemonday.StrictPolicy() - email.Plain().Set(strings.TrimSpace(tabsRegex.ReplaceAllString(policy.Sanitize(htmlBody), ""))) - - for name, data := range attachments { - email.Attach(name, data) - } - - return email.Send() -} - -// Client returns the `SmtpClient` instance. -func Client() *SmtpClient { - cfg := config.Get() - return NewSmtpClient( - cfg.Mailer.Host, - cfg.Mailer.Port, - cfg.Mailer.Username, - cfg.Mailer.Password, - cfg.Mailer.TLS, - ) -} diff --git a/migrations/20220812135809_create_table_activate_account_tokens.down.sql b/migrations/20220812135809_create_table_activate_account_tokens.down.sql deleted file mode 100644 index 6b94818..0000000 --- a/migrations/20220812135809_create_table_activate_account_tokens.down.sql +++ /dev/null @@ -1,5 +0,0 @@ --- Copyright (c) 2022 Isaque Veras --- Use of this source code is governed by MIT style --- license that can be found in the LICENSE file. - -DROP TABLE IF EXISTS activate_account_tokens CASCADE; diff --git a/migrations/20220812135809_create_table_activate_account_tokens.up.sql b/migrations/20220812135809_create_table_activate_account_tokens.up.sql deleted file mode 100644 index 31e0797..0000000 --- a/migrations/20220812135809_create_table_activate_account_tokens.up.sql +++ /dev/null @@ -1,11 +0,0 @@ --- Copyright (c) 2022 Isaque Veras --- Use of this source code is governed by MIT style --- license that can be found in the LICENSE file. - -CREATE TABLE activate_account_tokens ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - user_id UUID NOT NULL REFERENCES users (id), - used BOOLEAN NOT NULL DEFAULT FALSE, - expires_at TIMESTAMP WITH TIME ZONE NOT NULL, - created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP -); diff --git a/nginx/Dockerfile b/nginx/Dockerfile deleted file mode 100644 index cc72299..0000000 --- a/nginx/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM nginx:1.15 - -COPY nginx.conf /etc/nginx/conf.d/default.conf - -EXPOSE 80 diff --git a/nginx/nginx.conf b/nginx/nginx.conf deleted file mode 100644 index 16f6e9a..0000000 --- a/nginx/nginx.conf +++ /dev/null @@ -1,16 +0,0 @@ -server { - listen 80; - server_name localhost; - - location / { - root /app/frontend/build; - include /etc/nginx/mime.types; - try_files $uri /index.html; - } - - location /api { - proxy_pass http://backend:5000; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - } -} diff --git a/scripts/create_user_admin.go b/scripts/create_user_admin.go index d295993..afe8ce2 100644 --- a/scripts/create_user_admin.go +++ b/scripts/create_user_admin.go @@ -29,7 +29,7 @@ func CreateUserAdmin(logg *utils.Logger) { } defer tx.Rollback() - if err = auth.CreateAccount(ctx, &domain.CreateAccount{ + if _, err = auth.CreateAccount(ctx, &domain.CreateAccount{ FirstName: utils.Pointer("User Power"), LastName: utils.Pointer("Admin"), Email: utils.Pointer("admin@powersso.io"), diff --git a/server/grpc.go b/server/grpc.go index 5f5098e..a3c02b0 100644 --- a/server/grpc.go +++ b/server/grpc.go @@ -15,7 +15,7 @@ import ( gogrpc "google.golang.org/grpc" "google.golang.org/grpc/reflection" - "github.com/isaqueveras/powersso/interface/grpc/auth" + "github.com/isaqueveras/powersso/delivery/grpc/auth" "github.com/isaqueveras/powersso/middleware" "github.com/isaqueveras/powersso/oops" "github.com/isaqueveras/powersso/utils" diff --git a/server/rest.go b/server/rest.go index 0208c5f..2e86f43 100644 --- a/server/rest.go +++ b/server/rest.go @@ -13,8 +13,8 @@ import ( "github.com/isaqueveras/endless" gopowersso "github.com/isaqueveras/go-powersso" - "github.com/isaqueveras/powersso/interface/http/auth" - "github.com/isaqueveras/powersso/interface/http/project" + "github.com/isaqueveras/powersso/delivery/http/auth" + "github.com/isaqueveras/powersso/delivery/http/project" "github.com/isaqueveras/powersso/middleware" ) @@ -38,10 +38,13 @@ func (s *Server) ServerHTTP() (err error) { middleware.GinZap(s.logg.ZapLogger(), *s.cfg), ) + // FIXME: fix "gopowersso.Authorization" to accept list of tokens + secret := &s.cfg.GetSecrets()[0] + v1 := router.Group("v1") auth.Router(v1.Group("auth")) - project.RouterAuthorization(v1.Group("project", gopowersso.Authorization(&s.cfg.UserAuthToken.SecretKey))) - auth.RouterAuthorization(v1.Group("auth", gopowersso.Authorization(&s.cfg.UserAuthToken.SecretKey))) + auth.RouterAuthorization(v1.Group("auth", gopowersso.Authorization(secret))) + project.RouterAuthorization(v1.Group("project", gopowersso.Authorization(secret))) endless.DefaultReadTimeOut = s.cfg.Server.ReadTimeout * time.Second endless.DefaultWriteTimeOut = s.cfg.Server.WriteTimeout * time.Second @@ -61,8 +64,7 @@ func (s *Server) ServerHTTP() (err error) { func (s *Server) routerDebugPProf(router *gin.Engine) { r := router.Group("debug/pprof") - r.Use(gopowersso.Authorization(&s.cfg.UserAuthToken.SecretKey), gopowersso.OnlyAdmin()) - + r.Use(gopowersso.Authorization(&s.cfg.GetSecrets()[1]), gopowersso.OnlyAdmin()) r.GET("/", func(c *gin.Context) { pprof.Index(c.Writer, c.Request) }) r.GET("/cmdline", func(c *gin.Context) { pprof.Cmdline(c.Writer, c.Request) }) r.GET("/profile", func(c *gin.Context) { pprof.Profile(c.Writer, c.Request) }) @@ -74,6 +76,5 @@ func (s *Server) routerDebugPProf(router *gin.Engine) { r.GET("/heap", func(c *gin.Context) { pprof.Handler("heap").ServeHTTP(c.Writer, c.Request) }) r.GET("/mutex", func(c *gin.Context) { pprof.Handler("mutex").ServeHTTP(c.Writer, c.Request) }) r.GET("/threadcreate", func(c *gin.Context) { pprof.Handler("threadcreate").ServeHTTP(c.Writer, c.Request) }) - s.group.Go(func() error { return endless.ListenAndServe("0.0.0.0"+s.cfg.Server.PprofPort, router) }) } diff --git a/tokens/user.go b/tokens/user.go index 50f9d33..bcaadf9 100644 --- a/tokens/user.go +++ b/tokens/user.go @@ -7,14 +7,13 @@ package tokens import ( "github.com/golang-jwt/jwt/v4" "github.com/google/uuid" - "github.com/isaqueveras/powersso/config" "github.com/isaqueveras/powersso/domain/auth" "github.com/isaqueveras/powersso/utils" ) -// NewUserAuthToken generates and returns a new user authentication token. -func NewUserAuthToken(user *auth.User, sessionID *uuid.UUID) (*string, error) { +// NewAuthToken generates and returns a new authentication token +func NewAuthToken(user *auth.User, sessionID *uuid.UUID) (*string, error) { claims := jwt.MapClaims{ "session_id": sessionID, "user_id": user.ID, @@ -24,6 +23,6 @@ func NewUserAuthToken(user *auth.User, sessionID *uuid.UUID) (*string, error) { "email": user.Email, } - token, err := utils.NewToken(claims, (config.Get().UserAuthToken.SecretKey), config.Get().UserAuthToken.Duration) + token, err := utils.NewToken(claims, user.GetUserLevel(&config.Get().SecretsTokens), config.Get().SecretsDuration) return utils.Pointer(token), err } diff --git a/utils/opt.go b/utils/opt.go index cd1009f..44dbe7c 100644 --- a/utils/opt.go +++ b/utils/opt.go @@ -14,16 +14,9 @@ import ( "strconv" "strings" "time" - - "github.com/isaqueveras/powersso/config" ) -const ( - windowSize = 5 - stepSize = 30 - - QrCodeURL = "https://chart.googleapis.com/chart?chs=200x200&cht=qr&chl=200x200&chld=M|0&cht=qr&chl=" -) +const windowSize, stepSize = 5, 30 // ValidateToken validates if the otp is valid func ValidateToken(token, otp *string) (err error) { @@ -96,6 +89,7 @@ func GenerateToken(secret string, ts int64) (otp string, err error) { } // GetUrlQrCode returns the url of qr code to configure the otp -func GetUrlQrCode(otpToken string, userName string) (url string) { - return QrCodeURL + "otpauth://totp/" + config.Get().Meta.ProjectName + " " + userName + "%3Fsecret%3D" + otpToken +func GetUrlQrCode(projectName, otpToken, userName *string) (url string) { + baseUrl := "https://chart.googleapis.com/chart?chs=200x200&cht=qr&chl=200x200&chld=M|0&cht=qr&chl=" + return baseUrl + "otpauth://totp/" + *projectName + " " + *userName + "%3Fsecret%3D" + *otpToken } diff --git a/utils/otp_test.go b/utils/otp_test.go index a6b274c..ba40c12 100644 --- a/utils/otp_test.go +++ b/utils/otp_test.go @@ -14,6 +14,8 @@ import ( "github.com/isaqueveras/powersso/utils" ) +const baseUrl = "https://chart.googleapis.com/chart?chs=200x200&cht=qr&chl=200x200&chld=M|0&cht=qr&chl=" + var tokens = []string{ "J5WGCTLVNZSG6II=", "JEQGW3TPO4QHS33VEB3W65LMMQQGIZLDN5SGKIDUNBUXGIDDN5SGKLBAPFXXKIDDOVZGS33VOMQQ====", @@ -45,8 +47,8 @@ func TestOTP(t *testing.T) { for i := range tokens { userUUID := uuid.New() - url := utils.GetUrlQrCode(tokens[i], userUUID.String()) - urlCorrect := utils.QrCodeURL + "otpauth://totp/" + config.Get().Meta.ProjectName + " " + userUUID.String() + "%3Fsecret%3D" + tokens[i] + url := utils.GetUrlQrCode(&config.Get().ProjectName, utils.Pointer(tokens[i]), utils.Pointer(userUUID.String())) + urlCorrect := baseUrl + "otpauth://totp/" + config.Get().ProjectName + " " + userUUID.String() + "%3Fsecret%3D" + tokens[i] if urlCorrect != url { t.Error("url not equal") @@ -76,8 +78,8 @@ func BenchmarkOTP(b *testing.B) { for i := range tokens { userUUID := uuid.New() - url := utils.GetUrlQrCode(tokens[i], userUUID.String()) - urlCorrect := utils.QrCodeURL + "otpauth://totp/" + config.Get().Meta.ProjectName + " " + userUUID.String() + "%3Fsecret%3D" + tokens[i] + url := utils.GetUrlQrCode(&config.Get().ProjectName, utils.Pointer(tokens[i]), utils.Pointer(userUUID.String())) + urlCorrect := baseUrl + "otpauth://totp/" + config.Get().ProjectName + " " + userUUID.String() + "%3Fsecret%3D" + tokens[i] if urlCorrect != url { b.Error("url not equal")