From e2db61d510b18e9690a9ec61b16c54828b55fa3b Mon Sep 17 00:00:00 2001
From: Isaque Veras <46972789+isaqueveras@users.noreply.github.com>
Date: Sun, 15 Oct 2023 22:30:53 -0300
Subject: [PATCH 1/2] refact: configure 2-factor authentication when creating
account (#147)
* refact: remove send email, 2fa create account and creating services
* refact: big refact
* fix: test
* fix: import duplicate
* refact: remove nginx
* fix: remove unused packages
* feat: add message in i18n files
* fix: auth test
* fix: function name
* feat: validating 2-factor authentication when logging in
* refact: validate secrets by level
* fix: remove log
* refact: replace folder name
---
Makefile | 3 -
README.md | 5 -
app.json | 32 +-
application/auth/auth_business.go | 206 ------------
application/auth/business.go | 308 ++++++++++++++++++
application/auth/otp_business.go | 106 ------
application/auth/user_business.go | 91 ------
config/model.go | 58 +---
database/postgres/postgres.go | 4 +-
{interface => delivery}/grpc/auth/auth.pb.go | 0
.../grpc/auth/auth_grpc.pb.go | 0
{interface => delivery}/grpc/auth/handler.go | 2 +-
{interface => delivery}/http/auth/handler.go | 51 +--
.../http/auth/handler_test.go | 33 +-
{interface => delivery}/http/auth/router.go | 1 -
.../http/project/handler.go | 0
.../http/project/router.go | 0
docker-compose.dev.yml | 10 -
docker-compose.local.yml | 36 --
domain/auth/errors.go | 12 +-
domain/auth/interface.go | 22 +-
domain/auth/model.go | 31 +-
domain/auth/service.go | 53 +++
go.mod | 47 ++-
go.sum | 82 ++---
i18n/en_US.json | 10 +-
i18n/es_ES.json | 10 +-
i18n/pt_BR.json | 10 +-
.../persistencie/auth/auth_repository.go | 62 ----
.../persistencie/auth/flag_repository.go | 28 --
.../persistencie/auth/mail/mailer.go | 37 ---
.../persistencie/auth/otp_repository.go | 28 --
.../persistencie/auth/postgres/auth.go | 112 -------
.../persistencie/auth/postgres/data.go | 268 +++++++++++++++
.../postgres/{auth_test.go => data_test.go} | 0
.../persistencie/auth/postgres/flag.go | 32 --
.../persistencie/auth/postgres/otp.go | 40 ---
.../persistencie/auth/postgres/session.go | 91 ------
.../persistencie/auth/postgres/user.go | 89 -----
.../persistencie/auth/repository.go | 118 +++++++
.../persistencie/auth/session_repository.go | 35 --
.../persistencie/auth/user_repository.go | 40 ---
mailer/mailer.go | 16 -
mailer/sendmail.go | 77 -----
mailer/smtp.go | 90 -----
...ate_table_activate_account_tokens.down.sql | 5 -
...reate_table_activate_account_tokens.up.sql | 11 -
nginx/Dockerfile | 5 -
nginx/nginx.conf | 16 -
scripts/create_user_admin.go | 2 +-
server/grpc.go | 2 +-
server/rest.go | 15 +-
tokens/user.go | 7 +-
utils/opt.go | 14 +-
utils/otp_test.go | 10 +-
55 files changed, 954 insertions(+), 1519 deletions(-)
delete mode 100644 application/auth/auth_business.go
create mode 100644 application/auth/business.go
delete mode 100644 application/auth/otp_business.go
delete mode 100644 application/auth/user_business.go
rename {interface => delivery}/grpc/auth/auth.pb.go (100%)
rename {interface => delivery}/grpc/auth/auth_grpc.pb.go (100%)
rename {interface => delivery}/grpc/auth/handler.go (93%)
rename {interface => delivery}/http/auth/handler.go (71%)
rename {interface => delivery}/http/auth/handler_test.go (82%)
rename {interface => delivery}/http/auth/router.go (95%)
rename {interface => delivery}/http/project/handler.go (100%)
rename {interface => delivery}/http/project/router.go (100%)
create mode 100644 domain/auth/service.go
delete mode 100644 infrastructure/persistencie/auth/auth_repository.go
delete mode 100644 infrastructure/persistencie/auth/flag_repository.go
delete mode 100644 infrastructure/persistencie/auth/mail/mailer.go
delete mode 100644 infrastructure/persistencie/auth/otp_repository.go
delete mode 100644 infrastructure/persistencie/auth/postgres/auth.go
create mode 100644 infrastructure/persistencie/auth/postgres/data.go
rename infrastructure/persistencie/auth/postgres/{auth_test.go => data_test.go} (100%)
delete mode 100644 infrastructure/persistencie/auth/postgres/flag.go
delete mode 100644 infrastructure/persistencie/auth/postgres/otp.go
delete mode 100644 infrastructure/persistencie/auth/postgres/session.go
delete mode 100644 infrastructure/persistencie/auth/postgres/user.go
create mode 100644 infrastructure/persistencie/auth/repository.go
delete mode 100644 infrastructure/persistencie/auth/session_repository.go
delete mode 100644 infrastructure/persistencie/auth/user_repository.go
delete mode 100644 mailer/mailer.go
delete mode 100644 mailer/sendmail.go
delete mode 100644 mailer/smtp.go
delete mode 100644 migrations/20220812135809_create_table_activate_account_tokens.down.sql
delete mode 100644 migrations/20220812135809_create_table_activate_account_tokens.up.sql
delete mode 100644 nginx/Dockerfile
delete mode 100644 nginx/nginx.conf
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 71%
rename from interface/http/auth/handler.go
rename to delivery/http/auth/handler.go
index f9b752e..e9bfaf9 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,15 +26,20 @@ func createAccount(ctx *gin.Context) {
return
}
- if err := app.CreateAccount(ctx, &input); err != nil {
+ url, err := app.CreateAccount(ctx, &input)
+ if err != nil {
oops.Handling(ctx, err)
return
}
- ctx.JSON(http.StatusCreated, 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/activation/{token} [post]
+// @Router /v1/auth/activation/{token} [POST]
func activation(ctx *gin.Context) {
token, err := uuid.Parse(ctx.Param("token"))
if err != nil {
@@ -41,15 +47,14 @@ func activation(ctx *gin.Context) {
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 +96,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 +112,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 +123,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 +131,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 +139,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 +147,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 +155,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 +163,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 +171,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 +179,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")
From 62c8bafbaabacbf002a7b41f47997a6ded0b6350 Mon Sep 17 00:00:00 2001
From: Isaque Veras
Date: Sun, 15 Oct 2023 22:33:31 -0300
Subject: [PATCH 2/2] fix: remove code
---
delivery/http/auth/handler.go | 15 ---------------
1 file changed, 15 deletions(-)
diff --git a/delivery/http/auth/handler.go b/delivery/http/auth/handler.go
index e9bfaf9..4dd83d2 100644
--- a/delivery/http/auth/handler.go
+++ b/delivery/http/auth/handler.go
@@ -39,21 +39,6 @@ func createAccount(ctx *gin.Context) {
})
}
-// @Router /v1/auth/activation/{token} [POST]
-func activation(ctx *gin.Context) {
- token, err := uuid.Parse(ctx.Param("token"))
- if err != nil {
- oops.Handling(ctx, err)
- return
- }
-
- 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]
func login(ctx *gin.Context) {
var input domain.Login