From 922222d2c8f1f63b267e4e62e629fc9799aab84f Mon Sep 17 00:00:00 2001 From: Pavel Date: Wed, 3 Jan 2024 18:45:41 +0300 Subject: [PATCH 01/12] add task struct --- .env | 3 ++ internal/app/app.go | 3 +- .../recovery/recovery_controller.go | 33 ++++++++++---- internal/initialize/config.go | 45 ++++++++++--------- internal/models/user.go | 22 ++++----- internal/repository/user_repository.go | 25 ++++++----- internal/services/recovery_service.go | 20 +++++---- .../worker/recovery_worker/recovery_worker.go | 39 ++++++++++++++-- tests/repository_test/repository_test.go | 8 ++-- 9 files changed, 130 insertions(+), 68 deletions(-) diff --git a/.env b/.env index 72eba97..4cdb4a2 100644 --- a/.env +++ b/.env @@ -31,3 +31,6 @@ SMTP_UNAME="kotilion.95@gmail.com" SMTP_PASS="vWwbCSg4bf0p" SMTP_API_KEY="P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev" SMTP_SENDER="noreply@mailing.pena.digital" + +# URL settings +DEFAULT_REDIRECTION_URL = "def.url" \ No newline at end of file diff --git a/internal/app/app.go b/internal/app/app.go index 49cc0cc..e8244c0 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -55,12 +55,13 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { EncryptService: encryptService, }) - recoveryController := controller.NewRecoveryController(logger, recoveryService) + recoveryController := controller.NewRecoveryController(logger, recoveryService, cfg.DefaultRedirectionURL) recoveryWC := recovery_worker.NewRecoveryWC(recovery_worker.Deps{ Logger: logger, Redis: rdb, EmailSender: recoveryEmailSender, + Mongo: mdb.Collection("codeword"), }) go recoveryWC.Start(ctx) diff --git a/internal/controller/recovery/recovery_controller.go b/internal/controller/recovery/recovery_controller.go index e92d318..2fa0761 100644 --- a/internal/controller/recovery/recovery_controller.go +++ b/internal/controller/recovery/recovery_controller.go @@ -8,14 +8,16 @@ import ( ) type RecoveryController struct { - logger *zap.Logger - service *services.RecoveryService + logger *zap.Logger + service *services.RecoveryService + defaultURL string } -func NewRecoveryController(logger *zap.Logger, service *services.RecoveryService) *RecoveryController { +func NewRecoveryController(logger *zap.Logger, service *services.RecoveryService, defaultRedirectionURL string) *RecoveryController { return &RecoveryController{ - logger: logger, - service: service, + logger: logger, + service: service, + defaultURL: defaultRedirectionURL, } } @@ -23,9 +25,18 @@ func (r *RecoveryController) HandlePingDB(c *fiber.Ctx) error { return r.service.Ping(c.Context()) } +// TODO add deps struct, counnt params >3 // HandleRecoveryRequest обрабатывает запрос на восстановление пароля func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { email := c.FormValue("email") + referralURL := c.Get("Referrer") + redirectionURL := c.FormValue("RedirectionURL") + + if redirectionURL == "" && referralURL != "" { + redirectionURL = referralURL + } else if redirectionURL == "" { + redirectionURL = r.defaultURL + } key, err := r.service.GenerateKey() if err != nil { @@ -39,26 +50,30 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"}) } - err = r.service.StoreRecoveryRecord(c.Context(), user.ID.Hex(), user.Email, key) + //sign := referralURL + string(key) + + id, err := r.service.StoreRecoveryRecord(c.Context(), user.ID.Hex(), user.Email, key) if err != nil { r.logger.Error("Failed to store recovery record", zap.Error(err)) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } - err = r.service.RecoveryEmailTask(c.Context(), user.ID.Hex(), email, key) + err = r.service.RecoveryEmailTask(c.Context(), user.ID.Hex(), email, key, id) if err != nil { r.logger.Error("Failed to send recovery email", zap.Error(err)) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } - return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Recovery email sent successfully"}) + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "id": id, + }) } // HandleRecoveryLink обрабатывает ссылку восстановления и обменивает ее на токены func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error { key := c.Params("sign") // тут получается - record, err := r.service.GetRecoveryRecord(c.Context(), key) + record, err := r.service.GetRecoveryRecord(c.Context(), []byte(key)) if err != nil { r.logger.Error("Failed to get recovery record", zap.Error(err)) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) diff --git a/internal/initialize/config.go b/internal/initialize/config.go index e85c719..b51bc54 100644 --- a/internal/initialize/config.go +++ b/internal/initialize/config.go @@ -7,28 +7,29 @@ import ( ) type Config struct { - AppName string `env:"APP_NAME" envDefault:"codeword"` - HTTPHost string `env:"HTTP_HOST" envDefault:"localhost"` - HTTPPort string `env:"HTTP_PORT" envDefault:"3000"` - MongoHost string `env:"MONGO_HOST" envDefault:"127.0.0.1"` - MongoPort string `env:"MONGO_PORT" envDefault:"27020"` - MongoUser string `env:"MONGO_USER" envDefault:"test"` - MongoPassword string `env:"MONGO_PASSWORD" envDefault:"test"` - MongoDatabase string `env:"MONGO_DB" envDefault:"admin"` - MongoAuth string `env:"MONGO_AUTH" envDefault:"admin"` - PublicCurveKey string `env:"PUBLIC_CURVE_KEY"` - PrivateCurveKey string `env:"PRIVATE_CURVE_KEY"` - SignSecret string `env:"SIGN_SECRET"` - RedisAddr string `env:"REDIS_ADDR" envDefault:"localhost:6379"` - RedisPassword string `env:"REDIS_PASS" envDefault:"admin"` - RedisDB int `env:"REDIS_DB" envDefault:"2"` - SmtpApiUrl string `env:"SMTP_API_URL"` - SmtpHost string `env:"SMTP_HOST"` - SmtpPort string `env:"SMTP_PORT"` - SmtpUsername string `env:"SMTP_UNAME"` - SmtpPassword string `env:"SMTP_PASS"` - SmtpApiKey string `env:"SMTP_API_KEY"` - SmtpSender string `env:"SMTP_SENDER"` + AppName string `env:"APP_NAME" envDefault:"codeword"` + HTTPHost string `env:"HTTP_HOST" envDefault:"localhost"` + HTTPPort string `env:"HTTP_PORT" envDefault:"3000"` + MongoHost string `env:"MONGO_HOST" envDefault:"127.0.0.1"` + MongoPort string `env:"MONGO_PORT" envDefault:"27020"` + MongoUser string `env:"MONGO_USER" envDefault:"test"` + MongoPassword string `env:"MONGO_PASSWORD" envDefault:"test"` + MongoDatabase string `env:"MONGO_DB" envDefault:"admin"` + MongoAuth string `env:"MONGO_AUTH" envDefault:"admin"` + PublicCurveKey string `env:"PUBLIC_CURVE_KEY"` + PrivateCurveKey string `env:"PRIVATE_CURVE_KEY"` + SignSecret string `env:"SIGN_SECRET"` + RedisAddr string `env:"REDIS_ADDR" envDefault:"localhost:6379"` + RedisPassword string `env:"REDIS_PASS" envDefault:"admin"` + RedisDB int `env:"REDIS_DB" envDefault:"2"` + SmtpApiUrl string `env:"SMTP_API_URL"` + SmtpHost string `env:"SMTP_HOST"` + SmtpPort string `env:"SMTP_PORT"` + SmtpUsername string `env:"SMTP_UNAME"` + SmtpPassword string `env:"SMTP_PASS"` + SmtpApiKey string `env:"SMTP_API_KEY"` + SmtpSender string `env:"SMTP_SENDER"` + DefaultRedirectionURL string `env:"DEFAULT_REDIRECTION_URL"` } func LoadConfig() (*Config, error) { diff --git a/internal/models/user.go b/internal/models/user.go index 544e227..aff2f7d 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -18,18 +18,18 @@ type User struct { } type RestoreRequest struct { - ID string // xid или ObjectID - CreatedAt time.Time - Sign string // подпись - Email string // email из запроса - UserID string // айдишник юзера, которого нашли по email - Sent bool - SentAt time.Time + ID primitive.ObjectID `bson:"_id,omitempty"` + CreatedAt time.Time `bson:"created_at,omitempty"` + Sign []byte `bson:"sign,omitempty"` + Email string `bson:"email,omitempty"` + UserID string `bson:"user_id,omitempty"` + Sent bool `bson:"sent"` + SentAt time.Time `bson:"sent_at"` } type RecoveryRecord struct { - UserID string `bson:"user_id"` - Email string `bson:"email"` - Key []byte `bson:"key"` - CreatedAt time.Time `bson:"created_at"` + ID string + UserID string + Email string + Key []byte } diff --git a/internal/repository/user_repository.go b/internal/repository/user_repository.go index 7c9a146..311be86 100644 --- a/internal/repository/user_repository.go +++ b/internal/repository/user_repository.go @@ -7,6 +7,7 @@ import ( "github.com/go-redis/redis/v8" "github.com/pioz/faker" "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/readpref" "time" @@ -49,31 +50,33 @@ func (r *userRepository) FindByEmail(ctx context.Context, email string) (*models return &user, nil } -func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, userID string, email string, key []byte) error { - record := models.RecoveryRecord{ +func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, userID string, email string, key []byte) (string, error) { + newID := primitive.NewObjectID() + record := models.RestoreRequest{ + ID: newID, UserID: userID, Email: email, - Key: key, + Sign: key, CreatedAt: time.Now(), } _, err := r.mdb.InsertOne(ctx, record) if err != nil { - return err + return "", err } - return nil + return newID.Hex(), nil } -func (r *codewordRepository) InsertToQueue(ctx context.Context, userID string, email string, key []byte) error { +func (r *codewordRepository) InsertToQueue(ctx context.Context, userID string, email string, key []byte, id string) error { // todo не забыть убрать потом этот цикл for i := 0; i < 10; i++ { task := models.RecoveryRecord{ - UserID: userID + faker.String(), - Email: email, - Key: key, - CreatedAt: time.Now(), + ID: id, + UserID: userID + faker.String(), + Email: email, + Key: key, } taskBytes, err := json.Marshal(task) @@ -89,7 +92,7 @@ func (r *codewordRepository) InsertToQueue(ctx context.Context, userID string, e return nil } -func (r *codewordRepository) GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) { +func (r *codewordRepository) GetRecoveryRecord(ctx context.Context, key []byte) (*models.RestoreRequest, error) { return &models.RestoreRequest{UserID: "123", Sign: key, CreatedAt: time.Now()}, nil } diff --git a/internal/services/recovery_service.go b/internal/services/recovery_service.go index 0d5ea87..588801a 100644 --- a/internal/services/recovery_service.go +++ b/internal/services/recovery_service.go @@ -8,10 +8,10 @@ import ( ) type CodewordRepository interface { - StoreRecoveryRecord(ctx context.Context, userID string, email string, key []byte) error - InsertToQueue(ctx context.Context, userID string, email string, key []byte) error + StoreRecoveryRecord(ctx context.Context, userID string, email string, key []byte) (string, error) + InsertToQueue(ctx context.Context, userID string, email string, key []byte, id string) error Ping(ctx context.Context) error - GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) + GetRecoveryRecord(ctx context.Context, key []byte) (*models.RestoreRequest, error) } type UserRepository interface { @@ -60,17 +60,21 @@ func (s *RecoveryService) FindUserByEmail(ctx context.Context, email string) (*m } // StoreRecoveryRecord сохраняет запись восстановления в базе данных -func (s *RecoveryService) StoreRecoveryRecord(ctx context.Context, userID string, email string, key []byte) error { - return s.repositoryCodeword.StoreRecoveryRecord(ctx, userID, email, key) +func (s *RecoveryService) StoreRecoveryRecord(ctx context.Context, userID string, email string, key []byte) (string, error) { + id, err := s.repositoryCodeword.StoreRecoveryRecord(ctx, userID, email, key) + if err != nil { + return "", err + } + return id, nil } // SendRecoveryEmail посылает письмо для восстановления доступа пользователю -func (s *RecoveryService) RecoveryEmailTask(ctx context.Context, userID string, email string, key []byte) error { - return s.repositoryCodeword.InsertToQueue(ctx, userID, email, key) +func (s *RecoveryService) RecoveryEmailTask(ctx context.Context, userID string, email string, key []byte, id string) error { + return s.repositoryCodeword.InsertToQueue(ctx, userID, email, key, id) } // GetRecoveryRecord получает запись восстановления из базы данных -func (s *RecoveryService) GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) { +func (s *RecoveryService) GetRecoveryRecord(ctx context.Context, key []byte) (*models.RestoreRequest, error) { return s.repositoryCodeword.GetRecoveryRecord(ctx, key) } diff --git a/internal/worker/recovery_worker/recovery_worker.go b/internal/worker/recovery_worker/recovery_worker.go index 93f495a..f6f9c4f 100644 --- a/internal/worker/recovery_worker/recovery_worker.go +++ b/internal/worker/recovery_worker/recovery_worker.go @@ -6,6 +6,9 @@ import ( "context" "encoding/json" "github.com/go-redis/redis/v8" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" "time" ) @@ -14,12 +17,14 @@ type Deps struct { Logger *zap.Logger Redis *redis.Client EmailSender *client.RecoveryEmailSender + Mongo *mongo.Collection } type recoveryWorker struct { logger *zap.Logger redis *redis.Client emailSender *client.RecoveryEmailSender + mongo *mongo.Collection } func NewRecoveryWC(deps Deps) *recoveryWorker { @@ -27,6 +32,7 @@ func NewRecoveryWC(deps Deps) *recoveryWorker { logger: deps.Logger, redis: deps.Redis, emailSender: deps.EmailSender, + mongo: deps.Mongo, } } @@ -65,19 +71,46 @@ func (wc *recoveryWorker) processTasks(ctx context.Context) { return } - err = wc.sendRecoveryTask(task) + err = wc.sendRecoveryTask(ctx, task) if err != nil { wc.logger.Error("Failed to send recovery task", zap.String("key", result[0]), zap.Error(err)) return } } -func (wc *recoveryWorker) sendRecoveryTask(task models.RecoveryRecord) error { +func (wc *recoveryWorker) sendRecoveryTask(ctx context.Context, task models.RecoveryRecord) error { err := wc.emailSender.SendRecoveryEmail(task.Email, task.Key) if err != nil { wc.logger.Error("Failed to send recovery email", zap.Error(err)) return err } - wc.logger.Info("Recovery email sent successfully", zap.String("email", task.Email)) + + update := bson.M{ + "$set": bson.M{ + "sent": true, + "sent_at": time.Now(), + }, + } + + objectID, err := primitive.ObjectIDFromHex(task.ID) + if err != nil { + wc.logger.Error("Invalid ObjectID", zap.String("ID", task.ID), zap.Error(err)) + return err + } + + filter := bson.M{"_id": objectID} + + result, err := wc.mongo.UpdateOne(ctx, filter, update) + if err != nil { + wc.logger.Error("Failed to update restore request", zap.Error(err)) + return err + } + + if result.ModifiedCount == 0 { + wc.logger.Warn("No documents were updated - this may indicate the document was not found", + zap.String("ID", task.ID)) + } + + wc.logger.Info("Recovery email sent and restore request updated successfully", zap.String("email", task.Email)) return nil } diff --git a/tests/repository_test/repository_test.go b/tests/repository_test/repository_test.go index 566dc99..ffb03a1 100644 --- a/tests/repository_test/repository_test.go +++ b/tests/repository_test/repository_test.go @@ -4,6 +4,7 @@ import ( "codeword/internal/models" "codeword/internal/repository" "context" + "fmt" "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" @@ -77,14 +78,15 @@ func TestStoreRecoveryRecord(t *testing.T) { email := faker.Email() key := []byte("test_recovery_key") - err = userRepo.StoreRecoveryRecord(ctx, userID, email, key) + id, err := userRepo.StoreRecoveryRecord(ctx, userID, email, key) assert.NoError(t, err) + fmt.Println(id) - var storedRecord models.RecoveryRecord + var storedRecord models.RestoreRequest err = codeword.FindOne(ctx, bson.M{"user_id": userID}).Decode(&storedRecord) assert.NoError(t, err) assert.Equal(t, email, storedRecord.Email) - assert.Equal(t, string(key), storedRecord.Key) + assert.Equal(t, key, storedRecord.Sign) } _ = database.Drop(ctx) From cfc90597cc1f0012d33ec1cccf4c9eb75bb185e9 Mon Sep 17 00:00:00 2001 From: Pavel Date: Wed, 3 Jan 2024 19:19:27 +0300 Subject: [PATCH 02/12] set format base64 for sign in email sender --- internal/adapters/client/mail.go | 10 ++++------ internal/worker/recovery_worker/recovery_worker.go | 2 ++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/adapters/client/mail.go b/internal/adapters/client/mail.go index 66ae333..361f392 100644 --- a/internal/adapters/client/mail.go +++ b/internal/adapters/client/mail.go @@ -2,6 +2,7 @@ package client import ( "bytes" + "encoding/base64" "fmt" "github.com/gofiber/fiber/v2" "go.uber.org/zap" @@ -34,15 +35,12 @@ func NewRecoveryEmailSender(deps RecoveryEmailSenderDeps) *RecoveryEmailSender { } func (r *RecoveryEmailSender) SendRecoveryEmail(email string, signature []byte) error { + signatureStr := base64.URLEncoding.EncodeToString(signature) url := r.deps.SmtpApiUrl - fmt.Println(email, signature) + fmt.Println(email, signatureStr) - message := fmt.Sprintf("To: %s\r\n"+ - "Subject: Восстановление доступа\r\n"+ - "\r\n"+ - "Чтобы восстановить доступ, пожалуйста, перейдите по ссылке ниже:\r\n"+ - " https://hub.pena.digital/codeword/restore/%s\r\n", email, signature) + message := fmt.Sprintf("https://hub.pena.digital/codeword/restore/%s", signatureStr) form := new(bytes.Buffer) writer := multipart.NewWriter(form) diff --git a/internal/worker/recovery_worker/recovery_worker.go b/internal/worker/recovery_worker/recovery_worker.go index f6f9c4f..6efe707 100644 --- a/internal/worker/recovery_worker/recovery_worker.go +++ b/internal/worker/recovery_worker/recovery_worker.go @@ -5,6 +5,7 @@ import ( "codeword/internal/models" "context" "encoding/json" + "fmt" "github.com/go-redis/redis/v8" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" @@ -79,6 +80,7 @@ func (wc *recoveryWorker) processTasks(ctx context.Context) { } func (wc *recoveryWorker) sendRecoveryTask(ctx context.Context, task models.RecoveryRecord) error { + fmt.Println("task.Key", task.Key) err := wc.emailSender.SendRecoveryEmail(task.Email, task.Key) if err != nil { wc.logger.Error("Failed to send recovery email", zap.Error(err)) From d944d14ec2b94bad0554d4e28277ca6aef8fd4b1 Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 4 Jan 2024 14:27:50 +0300 Subject: [PATCH 03/12] logger --- .env | 2 +- internal/adapters/client/mail.go | 28 ++++---- internal/app/app.go | 26 +------ .../recovery/recovery_controller.go | 11 +-- internal/initialize/encrypt.go | 13 ++++ internal/initialize/mail.go | 23 +++++++ internal/models/user.go | 3 +- internal/repository/user_repository.go | 45 ++++++------ internal/services/recovery_service.go | 68 +++++++++++++++---- internal/utils/encrypt/encrypt_util.go | 3 +- .../worker/recovery_worker/recovery_worker.go | 4 +- tests/repository_test/repository_test.go | 4 +- 12 files changed, 148 insertions(+), 82 deletions(-) create mode 100644 internal/initialize/encrypt.go create mode 100644 internal/initialize/mail.go diff --git a/.env b/.env index 4cdb4a2..19032cb 100644 --- a/.env +++ b/.env @@ -17,7 +17,7 @@ REDIS_PASS="admin" REDIS_DB=2 # Keys -PUBLIC_CURVE_KEY="-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyt4XuLovUY7i12K2PIMbQZOKn+wFFKUvxvKQDel049/+VMpHMx1FLolUKuyGp9zi6gOwjHsBPgc9oqr/eaXGQSh7Ult7i9f+Ht563Y0er5UU9Zc5ZPSxf9O75KYD48ruGkqiFoncDqPENK4dtUa7w0OqlN4bwVBbmIsP8B3EDC5Dof+vtiNTSHSXPx+zifKeZGyknp+nyOHVrRDhPjOhzQzCom0MSZA/sJYmps8QZgiPA0k4Z6jTupDymPOIwYeD2C57zSxnAv0AfC3/pZYJbZYH/0TszRzmy052DME3zMnhMK0ikdN4nzYqU0dkkA5kb5GtKDymspHIJ9eWbUuwgtg8Rq/LrVBj1I3UFgs0ibio40k6gqinLKslc5Y1I5mro7J3OSEP5eO/XeDLOLlOJjEqkrx4fviI1cL3m5L6QV905xmcoNZG1+RmOg7D7cZQUf27TXqM381jkbNdktm1JLTcMScxuo3vaRftnIVw70V8P8sIkaKY8S8HU1sQgE2LB9t04oog5u59htx2FHv4B13NEm8tt8Tv1PexpB4UVh7PIualF6SxdFBrKbraYej72wgjXVPQ0eGXtGGD57j8DUEzk7DK2OvIWhehlVqtiRnFdAvdBj2ynHT2/5FJ/Zpd4n5dKGJcQvy1U1qWMs+8M7AHfWyt2+nZ04s48+bK3yMCAwEAAQ==\n-----END PUBLIC KEY-----" +PUBLIC_CURVE_KEY="-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAEbnIvjIMle4rqVol6K2XUqOxHy1KJoNoZdKJrRUPKL4=\n-----END PUBLIC KEY-----" PRIVATE_CURVE_KEY="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIKn0BKwF3vZvODgWAnUIwQhd8de5oZhY48gc23EWfrfs\n-----END PRIVATE KEY-----" diff --git a/internal/adapters/client/mail.go b/internal/adapters/client/mail.go index 361f392..a3836cd 100644 --- a/internal/adapters/client/mail.go +++ b/internal/adapters/client/mail.go @@ -10,15 +10,17 @@ import ( ) type RecoveryEmailSenderDeps struct { - SmtpApiUrl string - SmtpHost string - SmtpPort string - SmtpSender string - Username string - Password string - ApiKey string - FiberClient *fiber.Client - Logger *zap.Logger + SmtpApiUrl string + SmtpHost string + SmtpPort string + SmtpSender string + Username string + Password string + ApiKey string + FiberClient *fiber.Client + Logger *zap.Logger + CodewordHost string + CodewordPort string } type RecoveryEmailSender struct { @@ -40,7 +42,7 @@ func (r *RecoveryEmailSender) SendRecoveryEmail(email string, signature []byte) fmt.Println(email, signatureStr) - message := fmt.Sprintf("https://hub.pena.digital/codeword/restore/%s", signatureStr) + message := fmt.Sprintf("http://"+r.deps.CodewordHost+":"+r.deps.CodewordPort+"/recover/%s", signatureStr) form := new(bytes.Buffer) writer := multipart.NewWriter(form) @@ -70,16 +72,16 @@ func (r *RecoveryEmailSender) SendRecoveryEmail(email string, signature []byte) statusCode, body, errs := req.Bytes() if errs != nil { - r.deps.Logger.Error("Ошибка при отправке запроса", zap.Error(errs[0])) + r.deps.Logger.Error("Error sending request", zap.Error(errs[0])) return errs[0] } if statusCode != fiber.StatusOK { - err := fmt.Errorf("SMTP сервис вернул ошибку: %s Тело ответа: %s", statusCode, body) + err := fmt.Errorf("the SMTP service returned an error: %s Response body: %s", statusCode, body) r.deps.Logger.Error("Ошибка при отправке электронной почты", zap.Error(err)) return err } - r.deps.Logger.Info("Письмо для восстановления отправлено", zap.String("email", email)) + //r.deps.Logger.Info("Recovery email sent", zap.String("email", email)) return nil } diff --git a/internal/app/app.go b/internal/app/app.go index e8244c0..1f40308 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -1,16 +1,13 @@ package app import ( - "codeword/internal/adapters/client" controller "codeword/internal/controller/recovery" "codeword/internal/initialize" "codeword/internal/repository" httpserver "codeword/internal/server/http" "codeword/internal/services" - "codeword/internal/utils/encrypt" "codeword/internal/worker/recovery_worker" "context" - "github.com/gofiber/fiber/v2" "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" "time" @@ -26,33 +23,16 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { } rdb, err := initialize.InitializeRedis(ctx, cfg) - - encryptService := encrypt.New(&encrypt.EncryptDeps{ - PublicKey: cfg.PublicCurveKey, - PrivateKey: cfg.PrivateCurveKey, - SignSecret: cfg.SignSecret, - }) - + encrypt := initialize.InitializeEncrypt(cfg) codewordRepo := repository.NewCodewordRepository(repository.Deps{Rdb: rdb, Mdb: mdb.Collection("codeword")}) userRepo := repository.NewUserRepository(repository.Deps{Rdb: nil, Mdb: mdb.Collection("users")}) - - recoveryEmailSender := client.NewRecoveryEmailSender(client.RecoveryEmailSenderDeps{ - SmtpApiUrl: cfg.SmtpApiUrl, - SmtpHost: cfg.SmtpHost, - SmtpPort: cfg.SmtpPort, - SmtpSender: cfg.SmtpSender, - Username: cfg.SmtpUsername, - Password: cfg.SmtpPassword, - ApiKey: cfg.SmtpApiKey, - FiberClient: &fiber.Client{}, - Logger: logger, - }) + recoveryEmailSender := initialize.InitializeRecoveryEmailSender(cfg, logger) recoveryService := services.NewRecoveryService(services.Deps{ Logger: logger, CodewordRepository: codewordRepo, UserRepository: userRepo, - EncryptService: encryptService, + Encrypt: encrypt, }) recoveryController := controller.NewRecoveryController(logger, recoveryService, cfg.DefaultRedirectionURL) diff --git a/internal/controller/recovery/recovery_controller.go b/internal/controller/recovery/recovery_controller.go index 2fa0761..bf16b61 100644 --- a/internal/controller/recovery/recovery_controller.go +++ b/internal/controller/recovery/recovery_controller.go @@ -2,6 +2,7 @@ package controller import ( "codeword/internal/services" + "encoding/base64" "github.com/gofiber/fiber/v2" "go.uber.org/zap" "time" @@ -50,9 +51,10 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"}) } - //sign := referralURL + string(key) + signUrl := redirectionURL + base64.URLEncoding.EncodeToString(key) + sign := base64.URLEncoding.EncodeToString(key) - id, err := r.service.StoreRecoveryRecord(c.Context(), user.ID.Hex(), user.Email, key) + id, err := r.service.StoreRecoveryRecord(c.Context(), user.ID.Hex(), user.Email, sign, signUrl) if err != nil { r.logger.Error("Failed to store recovery record", zap.Error(err)) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) @@ -72,14 +74,13 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { // HandleRecoveryLink обрабатывает ссылку восстановления и обменивает ее на токены func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error { key := c.Params("sign") - // тут получается - record, err := r.service.GetRecoveryRecord(c.Context(), []byte(key)) + + record, err := r.service.GetRecoveryRecord(c.Context(), key) if err != nil { r.logger.Error("Failed to get recovery record", zap.Error(err)) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } - // проверка на более чем 15 минут if time.Since(record.CreatedAt) > 15*time.Minute { r.logger.Error("Recovery link expired", zap.String("signature", key)) return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Recovery link expired"}) diff --git a/internal/initialize/encrypt.go b/internal/initialize/encrypt.go new file mode 100644 index 0000000..abb49ed --- /dev/null +++ b/internal/initialize/encrypt.go @@ -0,0 +1,13 @@ +package initialize + +import ( + "codeword/internal/utils/encrypt" +) + +func InitializeEncrypt(cfg Config) *encrypt.Encrypt { + return encrypt.New(&encrypt.EncryptDeps{ + PublicKey: cfg.PublicCurveKey, + PrivateKey: cfg.PrivateCurveKey, + SignSecret: cfg.SignSecret, + }) +} diff --git a/internal/initialize/mail.go b/internal/initialize/mail.go new file mode 100644 index 0000000..dcdf3c0 --- /dev/null +++ b/internal/initialize/mail.go @@ -0,0 +1,23 @@ +package initialize + +import ( + "codeword/internal/adapters/client" + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" +) + +func InitializeRecoveryEmailSender(cfg Config, logger *zap.Logger) *client.RecoveryEmailSender { + return client.NewRecoveryEmailSender(client.RecoveryEmailSenderDeps{ + SmtpApiUrl: cfg.SmtpApiUrl, + SmtpHost: cfg.SmtpHost, + SmtpPort: cfg.SmtpPort, + SmtpSender: cfg.SmtpSender, + Username: cfg.SmtpUsername, + Password: cfg.SmtpPassword, + ApiKey: cfg.SmtpApiKey, + FiberClient: &fiber.Client{}, + Logger: logger, + CodewordHost: cfg.HTTPHost, + CodewordPort: cfg.HTTPPort, + }) +} diff --git a/internal/models/user.go b/internal/models/user.go index aff2f7d..899b51d 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -20,7 +20,8 @@ type User struct { type RestoreRequest struct { ID primitive.ObjectID `bson:"_id,omitempty"` CreatedAt time.Time `bson:"created_at,omitempty"` - Sign []byte `bson:"sign,omitempty"` + Sign string `bson:"sign,omitempty"` + SignUrl string `bson:"sign_url,omitempty"` Email string `bson:"email,omitempty"` UserID string `bson:"user_id,omitempty"` Sent bool `bson:"sent"` diff --git a/internal/repository/user_repository.go b/internal/repository/user_repository.go index 311be86..d93720d 100644 --- a/internal/repository/user_repository.go +++ b/internal/repository/user_repository.go @@ -5,7 +5,6 @@ import ( "context" "encoding/json" "github.com/go-redis/redis/v8" - "github.com/pioz/faker" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" @@ -50,13 +49,14 @@ func (r *userRepository) FindByEmail(ctx context.Context, email string) (*models return &user, nil } -func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, userID string, email string, key []byte) (string, error) { +func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, userID, email, key, url string) (string, error) { newID := primitive.NewObjectID() record := models.RestoreRequest{ ID: newID, UserID: userID, Email: email, Sign: key, + SignUrl: url, CreatedAt: time.Now(), } @@ -69,31 +69,36 @@ func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, userID str } func (r *codewordRepository) InsertToQueue(ctx context.Context, userID string, email string, key []byte, id string) error { - // todo не забыть убрать потом этот цикл - for i := 0; i < 10; i++ { + task := models.RecoveryRecord{ + ID: id, + UserID: userID, + Email: email, + Key: key, + } - task := models.RecoveryRecord{ - ID: id, - UserID: userID + faker.String(), - Email: email, - Key: key, - } + taskBytes, err := json.Marshal(task) + if err != nil { + return err + } - taskBytes, err := json.Marshal(task) - if err != nil { - return err - } - - if err := r.rdb.LPush(ctx, "recoveryQueue", taskBytes).Err(); err != nil { - return err - } + if err := r.rdb.LPush(ctx, "recoveryQueue", taskBytes).Err(); err != nil { + return err } return nil } -func (r *codewordRepository) GetRecoveryRecord(ctx context.Context, key []byte) (*models.RestoreRequest, error) { - return &models.RestoreRequest{UserID: "123", Sign: key, CreatedAt: time.Now()}, nil +func (r *codewordRepository) GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) { + var restoreRequest models.RestoreRequest + + filter := bson.M{"sign": key} + + err := r.mdb.FindOne(ctx, filter).Decode(&restoreRequest) + if err != nil { + return nil, err + } + + return &restoreRequest, nil } func (r *codewordRepository) Ping(ctx context.Context) error { diff --git a/internal/services/recovery_service.go b/internal/services/recovery_service.go index 588801a..0bab162 100644 --- a/internal/services/recovery_service.go +++ b/internal/services/recovery_service.go @@ -4,14 +4,15 @@ import ( "codeword/internal/models" "codeword/internal/utils/encrypt" "context" + "encoding/base64" "go.uber.org/zap" ) type CodewordRepository interface { - StoreRecoveryRecord(ctx context.Context, userID string, email string, key []byte) (string, error) + StoreRecoveryRecord(ctx context.Context, userID, email, key, signUrl string) (string, error) InsertToQueue(ctx context.Context, userID string, email string, key []byte, id string) error Ping(ctx context.Context) error - GetRecoveryRecord(ctx context.Context, key []byte) (*models.RestoreRequest, error) + GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) } type UserRepository interface { @@ -22,14 +23,14 @@ type Deps struct { Logger *zap.Logger CodewordRepository CodewordRepository UserRepository UserRepository - EncryptService *encrypt.Encrypt + Encrypt *encrypt.Encrypt } type RecoveryService struct { logger *zap.Logger repositoryCodeword CodewordRepository repositoryUser UserRepository - encryptService *encrypt.Encrypt + encrypt *encrypt.Encrypt } func NewRecoveryService(deps Deps) *RecoveryService { @@ -37,32 +38,48 @@ func NewRecoveryService(deps Deps) *RecoveryService { logger: deps.Logger, repositoryCodeword: deps.CodewordRepository, repositoryUser: deps.UserRepository, - encryptService: deps.EncryptService, + encrypt: deps.Encrypt, } } // GenerateKey генерирует ключ, используя шифрование на основе эллиптической кривой func (s *RecoveryService) GenerateKey() ([]byte, error) { - key, err := s.encryptService.SignCommonSecret() + key, err := s.encrypt.SignCommonSecret() if err != nil { + s.logger.Error("Failed to generate unique key for user", zap.Error(err)) return nil, err } return key, nil } func (s *RecoveryService) Ping(ctx context.Context) error { - return s.repositoryCodeword.Ping(ctx) + err := s.repositoryCodeword.Ping(ctx) + if err != nil { + s.logger.Error("Failed to ping database", zap.Error(err)) + return err + } + return nil } // FindUserByEmail ищет пользователя по электронной почте func (s *RecoveryService) FindUserByEmail(ctx context.Context, email string) (*models.User, error) { - return s.repositoryUser.FindByEmail(ctx, email) + user, err := s.repositoryUser.FindByEmail(ctx, email) + if err != nil { + s.logger.Error("Failed to find user by email", zap.String("email", email), zap.Error(err)) + return nil, err + } + if user == nil { + s.logger.Info("No user found with email", zap.String("email", email)) + return nil, nil + } + return user, nil } // StoreRecoveryRecord сохраняет запись восстановления в базе данных -func (s *RecoveryService) StoreRecoveryRecord(ctx context.Context, userID string, email string, key []byte) (string, error) { - id, err := s.repositoryCodeword.StoreRecoveryRecord(ctx, userID, email, key) +func (s *RecoveryService) StoreRecoveryRecord(ctx context.Context, userID, email, key, signUrl string) (string, error) { + id, err := s.repositoryCodeword.StoreRecoveryRecord(ctx, userID, email, key, signUrl) if err != nil { + s.logger.Error("Failed save data in mongoDB for email", zap.String("email", email), zap.Error(err)) return "", err } return id, nil @@ -70,12 +87,37 @@ func (s *RecoveryService) StoreRecoveryRecord(ctx context.Context, userID string // SendRecoveryEmail посылает письмо для восстановления доступа пользователю func (s *RecoveryService) RecoveryEmailTask(ctx context.Context, userID string, email string, key []byte, id string) error { - return s.repositoryCodeword.InsertToQueue(ctx, userID, email, key, id) + err := s.repositoryCodeword.InsertToQueue(ctx, userID, email, key, id) + if err != nil { + s.logger.Error("Failed creating a task to send a worker by email", zap.String("email", email), zap.Error(err)) + return err + } + return nil } // GetRecoveryRecord получает запись восстановления из базы данных -func (s *RecoveryService) GetRecoveryRecord(ctx context.Context, key []byte) (*models.RestoreRequest, error) { - return s.repositoryCodeword.GetRecoveryRecord(ctx, key) +func (s *RecoveryService) GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) { + byteKey, err := base64.URLEncoding.DecodeString(key) + if err != nil { + s.logger.Error("Failed to decode string signature to []byte format", zap.String("signature", key), zap.Error(err)) + return nil, err + } + + result, err := s.encrypt.VerifySignature(byteKey) + if err != nil { + s.logger.Error("Failed to verify signature", zap.String("signature", key), zap.Error(err)) + return nil, err + } + + if result { + req, err := s.repositoryCodeword.GetRecoveryRecord(ctx, key) + if err != nil { + s.logger.Error("Failed to obtain signature recovery data", zap.String("signature", key), zap.Error(err)) + return nil, err + } + return req, nil + } + return nil, nil } // ExchangeForTokens обменивает ссылку восстановления на токены используя сервис аутентификации. diff --git a/internal/utils/encrypt/encrypt_util.go b/internal/utils/encrypt/encrypt_util.go index 36695e3..405c5e4 100644 --- a/internal/utils/encrypt/encrypt_util.go +++ b/internal/utils/encrypt/encrypt_util.go @@ -4,6 +4,7 @@ import ( "crypto/ed25519" "crypto/x509" "encoding/pem" + "errors" "fmt" ) @@ -46,7 +47,7 @@ func (receiver *Encrypt) VerifySignature(signature []byte) (isValid bool, err er publicKey, ok := rawPublicKey.(ed25519.PublicKey) if !ok { - return false, fmt.Errorf("failed convert to ed25519.PrivateKey on of : %w", err) + return false, errors.New("public key is not of type ed25519.PublicKey") } return ed25519.Verify(publicKey, []byte(receiver.signSecret), signature), nil diff --git a/internal/worker/recovery_worker/recovery_worker.go b/internal/worker/recovery_worker/recovery_worker.go index 6efe707..79f73f0 100644 --- a/internal/worker/recovery_worker/recovery_worker.go +++ b/internal/worker/recovery_worker/recovery_worker.go @@ -5,7 +5,6 @@ import ( "codeword/internal/models" "context" "encoding/json" - "fmt" "github.com/go-redis/redis/v8" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" @@ -80,7 +79,6 @@ func (wc *recoveryWorker) processTasks(ctx context.Context) { } func (wc *recoveryWorker) sendRecoveryTask(ctx context.Context, task models.RecoveryRecord) error { - fmt.Println("task.Key", task.Key) err := wc.emailSender.SendRecoveryEmail(task.Email, task.Key) if err != nil { wc.logger.Error("Failed to send recovery email", zap.Error(err)) @@ -113,6 +111,6 @@ func (wc *recoveryWorker) sendRecoveryTask(ctx context.Context, task models.Reco zap.String("ID", task.ID)) } - wc.logger.Info("Recovery email sent and restore request updated successfully", zap.String("email", task.Email)) + //wc.logger.Info("Recovery email sent and restore request updated successfully", zap.String("email", task.Email)) return nil } diff --git a/tests/repository_test/repository_test.go b/tests/repository_test/repository_test.go index ffb03a1..d12754c 100644 --- a/tests/repository_test/repository_test.go +++ b/tests/repository_test/repository_test.go @@ -76,9 +76,9 @@ func TestStoreRecoveryRecord(t *testing.T) { for i := 0; i < 10; i++ { userID := faker.String() email := faker.Email() - key := []byte("test_recovery_key") + key := "test_recovery_key" - id, err := userRepo.StoreRecoveryRecord(ctx, userID, email, key) + id, err := userRepo.StoreRecoveryRecord(ctx, userID, email, key, "def.url") assert.NoError(t, err) fmt.Println(id) From ebe7702d7762b042c641e237d5c5df47267bbf74 Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 4 Jan 2024 14:32:27 +0300 Subject: [PATCH 04/12] replace russian logs --- internal/adapters/client/mail.go | 2 +- internal/services/recovery_service.go | 15 ++++++--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/internal/adapters/client/mail.go b/internal/adapters/client/mail.go index a3836cd..eb3ab5f 100644 --- a/internal/adapters/client/mail.go +++ b/internal/adapters/client/mail.go @@ -78,7 +78,7 @@ func (r *RecoveryEmailSender) SendRecoveryEmail(email string, signature []byte) if statusCode != fiber.StatusOK { err := fmt.Errorf("the SMTP service returned an error: %s Response body: %s", statusCode, body) - r.deps.Logger.Error("Ошибка при отправке электронной почты", zap.Error(err)) + r.deps.Logger.Error("Error sending email", zap.Error(err)) return err } diff --git a/internal/services/recovery_service.go b/internal/services/recovery_service.go index 0bab162..10c730f 100644 --- a/internal/services/recovery_service.go +++ b/internal/services/recovery_service.go @@ -104,20 +104,17 @@ func (s *RecoveryService) GetRecoveryRecord(ctx context.Context, key string) (*m } result, err := s.encrypt.VerifySignature(byteKey) - if err != nil { + if err != nil || result == false { s.logger.Error("Failed to verify signature", zap.String("signature", key), zap.Error(err)) return nil, err } - if result { - req, err := s.repositoryCodeword.GetRecoveryRecord(ctx, key) - if err != nil { - s.logger.Error("Failed to obtain signature recovery data", zap.String("signature", key), zap.Error(err)) - return nil, err - } - return req, nil + req, err := s.repositoryCodeword.GetRecoveryRecord(ctx, key) + if err != nil { + s.logger.Error("Failed to obtain signature recovery data", zap.String("signature", key), zap.Error(err)) + return nil, err } - return nil, nil + return req, nil } // ExchangeForTokens обменивает ссылку восстановления на токены используя сервис аутентификации. From dc28f975622f5b4a46729440b9a7a93b31374630 Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 4 Jan 2024 17:57:30 +0300 Subject: [PATCH 05/12] add auth client --- .env | 5 +- internal/adapters/client/auth.go | 68 +++++++++++++++++++ internal/app/app.go | 2 + .../recovery/recovery_controller.go | 2 +- internal/initialize/{mail.go => clients.go} | 8 +++ internal/initialize/config.go | 1 + internal/models/auth.go | 11 +++ internal/services/recovery_service.go | 19 ++++-- 8 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 internal/adapters/client/auth.go rename internal/initialize/{mail.go => clients.go} (74%) create mode 100644 internal/models/auth.go diff --git a/.env b/.env index 19032cb..8ce3512 100644 --- a/.env +++ b/.env @@ -1,7 +1,7 @@ # General application settings APP_NAME=codeword HTTP_HOST="localhost" -HTTP_PORT="8000" +HTTP_PORT="8080" # MongoDB settings MONGO_HOST="127.0.0.1" @@ -33,4 +33,5 @@ SMTP_API_KEY="P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev" SMTP_SENDER="noreply@mailing.pena.digital" # URL settings -DEFAULT_REDIRECTION_URL = "def.url" \ No newline at end of file +DEFAULT_REDIRECTION_URL = "def.url" +AUTH_REFRESH_URL = "http://localhost:8000/auth/refresh" \ No newline at end of file diff --git a/internal/adapters/client/auth.go b/internal/adapters/client/auth.go new file mode 100644 index 0000000..7f501a5 --- /dev/null +++ b/internal/adapters/client/auth.go @@ -0,0 +1,68 @@ +package client + +import ( + "codeword/internal/models" + "encoding/json" + "fmt" + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" +) + +type AuthClientDeps struct { + AuthUrl string + FiberClient *fiber.Client + Logger *zap.Logger +} + +type AuthClient struct { + deps AuthClientDeps +} + +func NewAuthClient(deps AuthClientDeps) *AuthClient { + if deps.FiberClient == nil { + deps.FiberClient = fiber.AcquireClient() + } + return &AuthClient{ + deps: deps, + } +} + +func (a *AuthClient) RefreshAuthToken(userID, signature string) (*models.RefreshResponse, error) { + body := models.AuthRequestBody{ + UserID: userID, + Signature: signature, + } + + bodyBytes, err := json.Marshal(body) + if err != nil { + a.deps.Logger.Error("Failed to encode request body", zap.Error(err)) + return nil, err + } + + agent := a.deps.FiberClient.Post(a.deps.AuthUrl) + agent.Set("Content-Type", "application/json").Body(bodyBytes) + //todo надо что-то придумать с авторизаиционными токенами + agent.Set("Authorization", "Bearer "+"123") + + statusCode, resBody, errs := agent.Bytes() + if len(errs) > 0 { + for _, err := range errs { + a.deps.Logger.Error("Error in refresh auth token request", zap.Error(err)) + } + return nil, fmt.Errorf("request failed: %v", errs) + } + + if statusCode != fiber.StatusOK { + errorMessage := fmt.Sprintf("received an incorrect response from the authentication service: %d", statusCode) + a.deps.Logger.Error(errorMessage, zap.Int("status", statusCode)) + return nil, fmt.Errorf(errorMessage) + } + + var tokens models.RefreshResponse + if err := json.Unmarshal(resBody, &tokens); err != nil { + a.deps.Logger.Error("failed to unmarshal auth service response", zap.Error(err)) + return nil, err + } + + return &tokens, nil +} diff --git a/internal/app/app.go b/internal/app/app.go index 1f40308..5c33226 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -27,12 +27,14 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { codewordRepo := repository.NewCodewordRepository(repository.Deps{Rdb: rdb, Mdb: mdb.Collection("codeword")}) userRepo := repository.NewUserRepository(repository.Deps{Rdb: nil, Mdb: mdb.Collection("users")}) recoveryEmailSender := initialize.InitializeRecoveryEmailSender(cfg, logger) + authClient := initialize.InitializeAuthClient(cfg, logger) recoveryService := services.NewRecoveryService(services.Deps{ Logger: logger, CodewordRepository: codewordRepo, UserRepository: userRepo, Encrypt: encrypt, + AuthClient: authClient, }) recoveryController := controller.NewRecoveryController(logger, recoveryService, cfg.DefaultRedirectionURL) diff --git a/internal/controller/recovery/recovery_controller.go b/internal/controller/recovery/recovery_controller.go index bf16b61..9d54350 100644 --- a/internal/controller/recovery/recovery_controller.go +++ b/internal/controller/recovery/recovery_controller.go @@ -86,7 +86,7 @@ func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Recovery link expired"}) } - tokens, err := r.service.ExchangeForTokens(record.UserID) + tokens, err := r.service.ExchangeForTokens(record.UserID, record.Sign) if err != nil { r.logger.Error("Failed to exchange recovery link for tokens", zap.Error(err)) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) diff --git a/internal/initialize/mail.go b/internal/initialize/clients.go similarity index 74% rename from internal/initialize/mail.go rename to internal/initialize/clients.go index dcdf3c0..0278933 100644 --- a/internal/initialize/mail.go +++ b/internal/initialize/clients.go @@ -21,3 +21,11 @@ func InitializeRecoveryEmailSender(cfg Config, logger *zap.Logger) *client.Recov CodewordPort: cfg.HTTPPort, }) } + +func InitializeAuthClient(cfg Config, logger *zap.Logger) *client.AuthClient { + return client.NewAuthClient(client.AuthClientDeps{ + AuthUrl: cfg.AuthURL, + Logger: logger, + FiberClient: &fiber.Client{}, + }) +} diff --git a/internal/initialize/config.go b/internal/initialize/config.go index b51bc54..deca1bc 100644 --- a/internal/initialize/config.go +++ b/internal/initialize/config.go @@ -30,6 +30,7 @@ type Config struct { SmtpApiKey string `env:"SMTP_API_KEY"` SmtpSender string `env:"SMTP_SENDER"` DefaultRedirectionURL string `env:"DEFAULT_REDIRECTION_URL"` + AuthURL string `env:"AUTH_REFRESH_URL"` } func LoadConfig() (*Config, error) { diff --git a/internal/models/auth.go b/internal/models/auth.go new file mode 100644 index 0000000..a159a74 --- /dev/null +++ b/internal/models/auth.go @@ -0,0 +1,11 @@ +package models + +type AuthRequestBody struct { + UserID string `json:"userId"` + Signature string `json:"signature"` +} + +type RefreshResponse struct { + AccessToken string `json:"accessToken"` + RefreshToken string `json:"refreshToken"` +} diff --git a/internal/services/recovery_service.go b/internal/services/recovery_service.go index 10c730f..c342b27 100644 --- a/internal/services/recovery_service.go +++ b/internal/services/recovery_service.go @@ -1,6 +1,7 @@ package services import ( + "codeword/internal/adapters/client" "codeword/internal/models" "codeword/internal/utils/encrypt" "context" @@ -24,6 +25,7 @@ type Deps struct { CodewordRepository CodewordRepository UserRepository UserRepository Encrypt *encrypt.Encrypt + AuthClient *client.AuthClient } type RecoveryService struct { @@ -31,6 +33,7 @@ type RecoveryService struct { repositoryCodeword CodewordRepository repositoryUser UserRepository encrypt *encrypt.Encrypt + authClient *client.AuthClient } func NewRecoveryService(deps Deps) *RecoveryService { @@ -39,6 +42,7 @@ func NewRecoveryService(deps Deps) *RecoveryService { repositoryCodeword: deps.CodewordRepository, repositoryUser: deps.UserRepository, encrypt: deps.Encrypt, + authClient: deps.AuthClient, } } @@ -117,8 +121,15 @@ func (s *RecoveryService) GetRecoveryRecord(ctx context.Context, key string) (*m return req, nil } -// ExchangeForTokens обменивает ссылку восстановления на токены используя сервис аутентификации. -func (s *RecoveryService) ExchangeForTokens(userID string) (map[string]string, error) { - // TODO - return nil, nil +func (s *RecoveryService) ExchangeForTokens(userID string, signature string) (map[string]string, error) { + tokens, err := s.authClient.RefreshAuthToken(userID, signature) + if err != nil { + s.logger.Error("Failed to refresh auth token", zap.Error(err)) + return nil, err + } + + return map[string]string{ + "accessToken": tokens.AccessToken, + "refreshToken": tokens.RefreshToken, + }, nil } From 487dc8bc2d09d1828f5c54e8f80a033ccc0a92d3 Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 5 Jan 2024 00:34:30 +0300 Subject: [PATCH 06/12] change auth url --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 8ce3512..5d2f70b 100644 --- a/.env +++ b/.env @@ -34,4 +34,4 @@ SMTP_SENDER="noreply@mailing.pena.digital" # URL settings DEFAULT_REDIRECTION_URL = "def.url" -AUTH_REFRESH_URL = "http://localhost:8000/auth/refresh" \ No newline at end of file +AUTH_REFRESH_URL = "http://localhost:8000/auth/exchange" \ No newline at end of file From b76770216031976f548a58143be616e1ba2d3c8d Mon Sep 17 00:00:00 2001 From: Pavel Date: Fri, 5 Jan 2024 14:37:06 +0300 Subject: [PATCH 07/12] ref code, add purgeWC and base doc --- .env | 6 +- docs/openapi.yaml | 87 +++++++++++++++++++ internal/adapters/client/auth.go | 4 +- internal/app/app.go | 19 ++-- .../recovery/recovery_controller.go | 16 ++-- internal/errors/errors.go | 2 + internal/initialize/clients.go | 4 +- internal/initialize/config.go | 2 +- internal/repository/codeword_repository.go | 83 ++++++++++++++++++ internal/repository/user_repository.go | 72 +-------------- internal/services/recovery_service.go | 5 +- internal/utils/encrypt/encrypt_util.go | 1 + internal/worker/purge_worker/purge_worker.go | 56 ++++++++++++ .../worker/recovery_worker/recovery_worker.go | 12 +-- 14 files changed, 272 insertions(+), 97 deletions(-) create mode 100644 internal/repository/codeword_repository.go create mode 100644 internal/worker/purge_worker/purge_worker.go diff --git a/.env b/.env index 5d2f70b..ec4d988 100644 --- a/.env +++ b/.env @@ -21,7 +21,9 @@ PUBLIC_CURVE_KEY="-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAEbnIvjIMle4rqVol6K PRIVATE_CURVE_KEY="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIKn0BKwF3vZvODgWAnUIwQhd8de5oZhY48gc23EWfrfs\n-----END PRIVATE KEY-----" -SIGN_SECRET=group +# SIGN_SECRET="group" + +SIGN_SECRET="secret" # SMTP settings SMTP_API_URL="https://api.smtp.bz/v1/smtp/send" @@ -34,4 +36,4 @@ SMTP_SENDER="noreply@mailing.pena.digital" # URL settings DEFAULT_REDIRECTION_URL = "def.url" -AUTH_REFRESH_URL = "http://localhost:8000/auth/exchange" \ No newline at end of file +AUTH_EXCHANGE_URL = "http://localhost:8000/auth/exchange" \ No newline at end of file diff --git a/docs/openapi.yaml b/docs/openapi.yaml index e69de29..dcbd082 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -0,0 +1,87 @@ +openapi: 3.0.0 +info: + title: Codeword Recovery Service API + version: 1.0.0 + description: API for handling password recovery for the Codeword service. + + +paths: + /liveness: + get: + summary: Роут проверки активности + responses: + '200': + description: Успех – сервис запущен + + /readiness: + get: + summary: Роут проверки базы данных + responses: + '200': + description: Успех — сервис готов и соединение с БД живо + '503': + description: Служба недоступна — не удалось выполнить проверку связи с БД + + /recover: + post: + summary: Запустите процесс восстановления пароля + requestBody: + required: true + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + email: + type: string + format: email + description: Электронная почта, на которую нужно отправить инструкции по восстановлению + Referrer: + type: string + description: URL-адрес referral, если он доступен + RedirectionURL: + type: string + description: URL-адрес, на который перенаправляется пользователь после отправки электронного письма + + responses: + '200': + description: Запрос на восстановление принят, и возвращен идентификатор записи восстановления + content: + application/json: + schema: + type: object + properties: + id: + type: string + description: Идентификатор запроса на восстановление + '404': + description: Пользователь не найден по электронной почте + '500': + description: Внутренняя ошибка сервера – разные причины + + /recover/{sign}: + get: + summary: Обработать ссылку восстановления, в которой содержится подпись и обменять ее на токены + parameters: + - in: path + name: sign + required: true + schema: + type: string + description: Подпись восстановления как часть URL-адреса восстановления + responses: + '200': + description: Восстановление успешно, информация для обмена токенов возвращена + content: + application/json: + schema: + type: object + properties: + accessToken: + type: string + refreshToken: + type: string + '406': + description: NotAcceptable - срок действия ссылки для восстановления истек или она недействительна + '500': + description: Внутренняя ошибка сервера – разные причины diff --git a/internal/adapters/client/auth.go b/internal/adapters/client/auth.go index 7f501a5..225d282 100644 --- a/internal/adapters/client/auth.go +++ b/internal/adapters/client/auth.go @@ -41,13 +41,11 @@ func (a *AuthClient) RefreshAuthToken(userID, signature string) (*models.Refresh agent := a.deps.FiberClient.Post(a.deps.AuthUrl) agent.Set("Content-Type", "application/json").Body(bodyBytes) - //todo надо что-то придумать с авторизаиционными токенами - agent.Set("Authorization", "Bearer "+"123") statusCode, resBody, errs := agent.Bytes() if len(errs) > 0 { for _, err := range errs { - a.deps.Logger.Error("Error in refresh auth token request", zap.Error(err)) + a.deps.Logger.Error("Error in exchange auth token request", zap.Error(err)) } return nil, fmt.Errorf("request failed: %v", errs) } diff --git a/internal/app/app.go b/internal/app/app.go index 5c33226..7e4ca7e 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -6,6 +6,7 @@ import ( "codeword/internal/repository" httpserver "codeword/internal/server/http" "codeword/internal/services" + "codeword/internal/worker/purge_worker" "codeword/internal/worker/recovery_worker" "context" "go.mongodb.org/mongo-driver/mongo" @@ -26,8 +27,8 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { encrypt := initialize.InitializeEncrypt(cfg) codewordRepo := repository.NewCodewordRepository(repository.Deps{Rdb: rdb, Mdb: mdb.Collection("codeword")}) userRepo := repository.NewUserRepository(repository.Deps{Rdb: nil, Mdb: mdb.Collection("users")}) - recoveryEmailSender := initialize.InitializeRecoveryEmailSender(cfg, logger) - authClient := initialize.InitializeAuthClient(cfg, logger) + recoveryEmailSender := initialize.RecoveryEmailSender(cfg, logger) + authClient := initialize.AuthClient(cfg, logger) recoveryService := services.NewRecoveryService(services.Deps{ Logger: logger, @@ -46,7 +47,13 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { Mongo: mdb.Collection("codeword"), }) + purgeWC := purge_worker.NewRecoveryWC(purge_worker.Deps{ + Logger: logger, + Mongo: mdb.Collection("codeword"), + }) + go recoveryWC.Start(ctx) + go purgeWC.Start(ctx) server := httpserver.NewServer(httpserver.ServerConfig{ Logger: logger, @@ -55,7 +62,7 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { go func() { if err := server.Start(cfg.HTTPHost + ":" + cfg.HTTPPort); err != nil { - logger.Error("Ошибка запуска сервера", zap.Error(err)) + logger.Error("Server startup error", zap.Error(err)) } }() @@ -64,7 +71,7 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { if err := shutdownApp(server, mdb, logger); err != nil { return err } - logger.Info("Приложение остановлено") + logger.Info("The application has stopped") return nil } @@ -87,7 +94,7 @@ func shutdownHTTPServer(server *httpserver.Server, logger *zap.Logger) error { defer cancel() if err := server.Shutdown(ctx); err != nil { - logger.Error("Ошибка при остановке HTTP-сервера", zap.Error(err)) + logger.Error("Error stopping HTTP server", zap.Error(err)) return err } return nil @@ -98,7 +105,7 @@ func shutdownMongoDB(mdb *mongo.Database, logger *zap.Logger) error { defer cancel() if err := mdb.Client().Disconnect(ctx); err != nil { - logger.Error("Ошибка при закрытии соединения с MongoDB", zap.Error(err)) + logger.Error("Error when closing MongoDB connection", zap.Error(err)) return err } return nil diff --git a/internal/controller/recovery/recovery_controller.go b/internal/controller/recovery/recovery_controller.go index 9d54350..69d9c16 100644 --- a/internal/controller/recovery/recovery_controller.go +++ b/internal/controller/recovery/recovery_controller.go @@ -39,18 +39,18 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { redirectionURL = r.defaultURL } - key, err := r.service.GenerateKey() - if err != nil { - r.logger.Error("Failed to generate key", zap.Error(err)) - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) - } - user, err := r.service.FindUserByEmail(c.Context(), email) if err != nil || user == nil { r.logger.Error("Failed to find user by email", zap.Error(err)) return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"}) } + key, err := r.service.GenerateKey() + if err != nil { + r.logger.Error("Failed to generate key", zap.Error(err)) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) + } + signUrl := redirectionURL + base64.URLEncoding.EncodeToString(key) sign := base64.URLEncoding.EncodeToString(key) @@ -71,6 +71,8 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { }) } +// todo тут скорее всего помимо подписи будет передаваться еще что-то, например email пользователя от фронта для поиска в бд + // HandleRecoveryLink обрабатывает ссылку восстановления и обменивает ее на токены func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error { key := c.Params("sign") @@ -83,7 +85,7 @@ func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error { if time.Since(record.CreatedAt) > 15*time.Minute { r.logger.Error("Recovery link expired", zap.String("signature", key)) - return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Recovery link expired"}) + return c.Status(fiber.StatusNotAcceptable).JSON(fiber.Map{"error": "Recovery link expired"}) } tokens, err := r.service.ExchangeForTokens(record.UserID, record.Sign) diff --git a/internal/errors/errors.go b/internal/errors/errors.go index 04b3218..df9aae3 100644 --- a/internal/errors/errors.go +++ b/internal/errors/errors.go @@ -1 +1,3 @@ package errors + +// пока не нужен diff --git a/internal/initialize/clients.go b/internal/initialize/clients.go index 0278933..e7851b8 100644 --- a/internal/initialize/clients.go +++ b/internal/initialize/clients.go @@ -6,7 +6,7 @@ import ( "go.uber.org/zap" ) -func InitializeRecoveryEmailSender(cfg Config, logger *zap.Logger) *client.RecoveryEmailSender { +func RecoveryEmailSender(cfg Config, logger *zap.Logger) *client.RecoveryEmailSender { return client.NewRecoveryEmailSender(client.RecoveryEmailSenderDeps{ SmtpApiUrl: cfg.SmtpApiUrl, SmtpHost: cfg.SmtpHost, @@ -22,7 +22,7 @@ func InitializeRecoveryEmailSender(cfg Config, logger *zap.Logger) *client.Recov }) } -func InitializeAuthClient(cfg Config, logger *zap.Logger) *client.AuthClient { +func AuthClient(cfg Config, logger *zap.Logger) *client.AuthClient { return client.NewAuthClient(client.AuthClientDeps{ AuthUrl: cfg.AuthURL, Logger: logger, diff --git a/internal/initialize/config.go b/internal/initialize/config.go index deca1bc..d2205b0 100644 --- a/internal/initialize/config.go +++ b/internal/initialize/config.go @@ -30,7 +30,7 @@ type Config struct { SmtpApiKey string `env:"SMTP_API_KEY"` SmtpSender string `env:"SMTP_SENDER"` DefaultRedirectionURL string `env:"DEFAULT_REDIRECTION_URL"` - AuthURL string `env:"AUTH_REFRESH_URL"` + AuthURL string `env:"AUTH_EXCHANGE_URL"` } func LoadConfig() (*Config, error) { diff --git a/internal/repository/codeword_repository.go b/internal/repository/codeword_repository.go new file mode 100644 index 0000000..363189b --- /dev/null +++ b/internal/repository/codeword_repository.go @@ -0,0 +1,83 @@ +package repository + +import ( + "codeword/internal/models" + "context" + "encoding/json" + "github.com/go-redis/redis/v8" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/readpref" + "time" +) + +type codewordRepository struct { + mdb *mongo.Collection + rdb *redis.Client +} + +func NewCodewordRepository(deps Deps) *codewordRepository { + + return &codewordRepository{mdb: deps.Mdb, rdb: deps.Rdb} +} + +// сохраняем полученные данные о пользователе и подписи в бд +func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, userID, email, key, url string) (string, error) { + newID := primitive.NewObjectID() + record := models.RestoreRequest{ + ID: newID, + UserID: userID, + Email: email, + Sign: key, + SignUrl: url, + CreatedAt: time.Now(), + } + + _, err := r.mdb.InsertOne(ctx, record) + if err != nil { + return "", err + } + + return newID.Hex(), nil +} + +// добавляем в очередь данные для отправки на почту в редис +func (r *codewordRepository) InsertToQueue(ctx context.Context, userID string, email string, key []byte, id string) error { + task := models.RecoveryRecord{ + ID: id, + UserID: userID, + Email: email, + Key: key, + } + + taskBytes, err := json.Marshal(task) + if err != nil { + return err + } + + if err := r.rdb.LPush(ctx, "recoveryQueue", taskBytes).Err(); err != nil { + return err + } + + return nil +} + +// получаем данные юзера по подписи +func (r *codewordRepository) GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) { + var restoreRequest models.RestoreRequest + + filter := bson.M{"sign": key} + + err := r.mdb.FindOne(ctx, filter).Decode(&restoreRequest) + if err != nil { + return nil, err + } + + return &restoreRequest, nil +} + +// пингует в монгу чтобы проверить подключение +func (r *codewordRepository) Ping(ctx context.Context) error { + return r.mdb.Database().Client().Ping(ctx, readpref.Primary()) +} diff --git a/internal/repository/user_repository.go b/internal/repository/user_repository.go index d93720d..2d343f6 100644 --- a/internal/repository/user_repository.go +++ b/internal/repository/user_repository.go @@ -3,13 +3,9 @@ package repository import ( "codeword/internal/models" "context" - "encoding/json" "github.com/go-redis/redis/v8" "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/readpref" - "time" ) type Deps struct { @@ -17,10 +13,8 @@ type Deps struct { Rdb *redis.Client } -type codewordRepository struct { - mdb *mongo.Collection - rdb *redis.Client -} +// todo: возможно стоит разделить два репозитория в одном файле на два файла чтобы не было путаницы, +// а deps структуру оставить одну дабы избежать путаницу type userRepository struct { mdb *mongo.Collection @@ -31,11 +25,7 @@ func NewUserRepository(deps Deps) *userRepository { return &userRepository{mdb: deps.Mdb} } -func NewCodewordRepository(deps Deps) *codewordRepository { - - return &codewordRepository{mdb: deps.Mdb, rdb: deps.Rdb} -} - +// ищем пользователя по мейлу в коллекции users func (r *userRepository) FindByEmail(ctx context.Context, email string) (*models.User, error) { var user models.User @@ -48,59 +38,3 @@ func (r *userRepository) FindByEmail(ctx context.Context, email string) (*models } return &user, nil } - -func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, userID, email, key, url string) (string, error) { - newID := primitive.NewObjectID() - record := models.RestoreRequest{ - ID: newID, - UserID: userID, - Email: email, - Sign: key, - SignUrl: url, - CreatedAt: time.Now(), - } - - _, err := r.mdb.InsertOne(ctx, record) - if err != nil { - return "", err - } - - return newID.Hex(), nil -} - -func (r *codewordRepository) InsertToQueue(ctx context.Context, userID string, email string, key []byte, id string) error { - task := models.RecoveryRecord{ - ID: id, - UserID: userID, - Email: email, - Key: key, - } - - taskBytes, err := json.Marshal(task) - if err != nil { - return err - } - - if err := r.rdb.LPush(ctx, "recoveryQueue", taskBytes).Err(); err != nil { - return err - } - - return nil -} - -func (r *codewordRepository) GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) { - var restoreRequest models.RestoreRequest - - filter := bson.M{"sign": key} - - err := r.mdb.FindOne(ctx, filter).Decode(&restoreRequest) - if err != nil { - return nil, err - } - - return &restoreRequest, nil -} - -func (r *codewordRepository) Ping(ctx context.Context) error { - return r.mdb.Database().Client().Ping(ctx, readpref.Primary()) -} diff --git a/internal/services/recovery_service.go b/internal/services/recovery_service.go index c342b27..6eb7ae0 100644 --- a/internal/services/recovery_service.go +++ b/internal/services/recovery_service.go @@ -56,6 +56,7 @@ func (s *RecoveryService) GenerateKey() ([]byte, error) { return key, nil } +// вызывает пингование в бд func (s *RecoveryService) Ping(ctx context.Context) error { err := s.repositoryCodeword.Ping(ctx) if err != nil { @@ -89,7 +90,7 @@ func (s *RecoveryService) StoreRecoveryRecord(ctx context.Context, userID, email return id, nil } -// SendRecoveryEmail посылает письмо для восстановления доступа пользователю +// RecoveryEmailTask посылает письмо для восстановления доступа пользователю func (s *RecoveryService) RecoveryEmailTask(ctx context.Context, userID string, email string, key []byte, id string) error { err := s.repositoryCodeword.InsertToQueue(ctx, userID, email, key, id) if err != nil { @@ -107,6 +108,7 @@ func (s *RecoveryService) GetRecoveryRecord(ctx context.Context, key string) (*m return nil, err } + // сомнительный вариант но как я думаю верный, что false==err result, err := s.encrypt.VerifySignature(byteKey) if err != nil || result == false { s.logger.Error("Failed to verify signature", zap.String("signature", key), zap.Error(err)) @@ -121,6 +123,7 @@ func (s *RecoveryService) GetRecoveryRecord(ctx context.Context, key string) (*m return req, nil } +// меняет подпись на токены идя в auth сервис func (s *RecoveryService) ExchangeForTokens(userID string, signature string) (map[string]string, error) { tokens, err := s.authClient.RefreshAuthToken(userID, signature) if err != nil { diff --git a/internal/utils/encrypt/encrypt_util.go b/internal/utils/encrypt/encrypt_util.go index 405c5e4..9f2d4ac 100644 --- a/internal/utils/encrypt/encrypt_util.go +++ b/internal/utils/encrypt/encrypt_util.go @@ -53,6 +53,7 @@ func (receiver *Encrypt) VerifySignature(signature []byte) (isValid bool, err er return ed25519.Verify(publicKey, []byte(receiver.signSecret), signature), nil } +// TODO подумать над тем чтобы подпись генерилась каждый раз разгая func (receiver *Encrypt) SignCommonSecret() (signature []byte, err error) { defer func() { if recovered := recover(); recovered != nil { diff --git a/internal/worker/purge_worker/purge_worker.go b/internal/worker/purge_worker/purge_worker.go new file mode 100644 index 0000000..19c81eb --- /dev/null +++ b/internal/worker/purge_worker/purge_worker.go @@ -0,0 +1,56 @@ +package purge_worker + +import ( + "context" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.uber.org/zap" + "time" +) + +type Deps struct { + Logger *zap.Logger + Mongo *mongo.Collection +} + +type purgeWorker struct { + logger *zap.Logger + mongo *mongo.Collection +} + +func NewRecoveryWC(deps Deps) *purgeWorker { + return &purgeWorker{ + logger: deps.Logger, + mongo: deps.Mongo, + } +} + +func (wc *purgeWorker) Start(ctx context.Context) { + ticker := time.NewTicker(1 * time.Hour) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + wc.processTasks(ctx) + + case <-ctx.Done(): + return + } + } +} + +func (wc *purgeWorker) processTasks(ctx context.Context) { + wc.logger.Info("Checking cleaning records") + + oneHourAgo := time.Now().Add(-1 * time.Hour) + + filter := bson.M{"created_at": bson.M{"$lt": oneHourAgo}} + + result, err := wc.mongo.DeleteMany(ctx, filter) + if err != nil { + wc.logger.Error("Error when trying to delete old entries", zap.Error(err)) + } else { + wc.logger.Info("Deleted documents", zap.Int64("count", result.DeletedCount)) + } +} diff --git a/internal/worker/recovery_worker/recovery_worker.go b/internal/worker/recovery_worker/recovery_worker.go index 79f73f0..7237278 100644 --- a/internal/worker/recovery_worker/recovery_worker.go +++ b/internal/worker/recovery_worker/recovery_worker.go @@ -20,15 +20,15 @@ type Deps struct { Mongo *mongo.Collection } -type recoveryWorker struct { +type RecoveryWorker struct { logger *zap.Logger redis *redis.Client emailSender *client.RecoveryEmailSender mongo *mongo.Collection } -func NewRecoveryWC(deps Deps) *recoveryWorker { - return &recoveryWorker{ +func NewRecoveryWC(deps Deps) *RecoveryWorker { + return &RecoveryWorker{ logger: deps.Logger, redis: deps.Redis, emailSender: deps.EmailSender, @@ -36,7 +36,7 @@ func NewRecoveryWC(deps Deps) *recoveryWorker { } } -func (wc *recoveryWorker) Start(ctx context.Context) { +func (wc *RecoveryWorker) Start(ctx context.Context) { ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() @@ -51,7 +51,7 @@ func (wc *recoveryWorker) Start(ctx context.Context) { } } -func (wc *recoveryWorker) processTasks(ctx context.Context) { +func (wc *RecoveryWorker) processTasks(ctx context.Context) { result, err := wc.redis.BRPop(ctx, 1*time.Second, "recoveryQueue").Result() if err != nil { if err != redis.Nil { @@ -78,7 +78,7 @@ func (wc *recoveryWorker) processTasks(ctx context.Context) { } } -func (wc *recoveryWorker) sendRecoveryTask(ctx context.Context, task models.RecoveryRecord) error { +func (wc *RecoveryWorker) sendRecoveryTask(ctx context.Context, task models.RecoveryRecord) error { err := wc.emailSender.SendRecoveryEmail(task.Email, task.Key) if err != nil { wc.logger.Error("Failed to send recovery email", zap.Error(err)) From 5728ff46e4bdd10693b90d9e863112f7945817fb Mon Sep 17 00:00:00 2001 From: Pavel Date: Sun, 7 Jan 2024 14:13:07 +0300 Subject: [PATCH 08/12] add signWithID in mongo for uniq sign --- internal/adapters/client/mail.go | 8 +++----- .../recovery/recovery_controller.go | 4 +++- internal/models/user.go | 3 ++- internal/repository/codeword_repository.go | 8 +++++--- internal/services/recovery_service.go | 19 ++++++++++--------- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/internal/adapters/client/mail.go b/internal/adapters/client/mail.go index eb3ab5f..1522cf8 100644 --- a/internal/adapters/client/mail.go +++ b/internal/adapters/client/mail.go @@ -2,7 +2,6 @@ package client import ( "bytes" - "encoding/base64" "fmt" "github.com/gofiber/fiber/v2" "go.uber.org/zap" @@ -36,13 +35,12 @@ func NewRecoveryEmailSender(deps RecoveryEmailSenderDeps) *RecoveryEmailSender { } } -func (r *RecoveryEmailSender) SendRecoveryEmail(email string, signature []byte) error { - signatureStr := base64.URLEncoding.EncodeToString(signature) +func (r *RecoveryEmailSender) SendRecoveryEmail(email string, signature string) error { url := r.deps.SmtpApiUrl - fmt.Println(email, signatureStr) + fmt.Println(email, signature) - message := fmt.Sprintf("http://"+r.deps.CodewordHost+":"+r.deps.CodewordPort+"/recover/%s", signatureStr) + message := fmt.Sprintf("http://"+r.deps.CodewordHost+":"+r.deps.CodewordPort+"/recover/%s", signature) form := new(bytes.Buffer) writer := multipart.NewWriter(form) diff --git a/internal/controller/recovery/recovery_controller.go b/internal/controller/recovery/recovery_controller.go index 69d9c16..f3fc1eb 100644 --- a/internal/controller/recovery/recovery_controller.go +++ b/internal/controller/recovery/recovery_controller.go @@ -60,7 +60,9 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } - err = r.service.RecoveryEmailTask(c.Context(), user.ID.Hex(), email, key, id) + singWithID := sign + id // подпись с id записи + + err = r.service.RecoveryEmailTask(c.Context(), user.ID.Hex(), email, singWithID, id) if err != nil { r.logger.Error("Failed to send recovery email", zap.Error(err)) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) diff --git a/internal/models/user.go b/internal/models/user.go index 899b51d..dfe0a2f 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -22,6 +22,7 @@ type RestoreRequest struct { CreatedAt time.Time `bson:"created_at,omitempty"` Sign string `bson:"sign,omitempty"` SignUrl string `bson:"sign_url,omitempty"` + SignID string `bson:"sign_id"` Email string `bson:"email,omitempty"` UserID string `bson:"user_id,omitempty"` Sent bool `bson:"sent"` @@ -32,5 +33,5 @@ type RecoveryRecord struct { ID string UserID string Email string - Key []byte + Key string } diff --git a/internal/repository/codeword_repository.go b/internal/repository/codeword_repository.go index 363189b..60a040a 100644 --- a/internal/repository/codeword_repository.go +++ b/internal/repository/codeword_repository.go @@ -25,12 +25,14 @@ func NewCodewordRepository(deps Deps) *codewordRepository { // сохраняем полученные данные о пользователе и подписи в бд func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, userID, email, key, url string) (string, error) { newID := primitive.NewObjectID() + signID := key + newID.Hex() record := models.RestoreRequest{ ID: newID, UserID: userID, Email: email, Sign: key, SignUrl: url, + SignID: signID, CreatedAt: time.Now(), } @@ -43,12 +45,12 @@ func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, userID, em } // добавляем в очередь данные для отправки на почту в редис -func (r *codewordRepository) InsertToQueue(ctx context.Context, userID string, email string, key []byte, id string) error { +func (r *codewordRepository) InsertToQueue(ctx context.Context, userID string, email string, singWithID string, id string) error { task := models.RecoveryRecord{ ID: id, UserID: userID, Email: email, - Key: key, + Key: singWithID, } taskBytes, err := json.Marshal(task) @@ -67,7 +69,7 @@ func (r *codewordRepository) InsertToQueue(ctx context.Context, userID string, e func (r *codewordRepository) GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) { var restoreRequest models.RestoreRequest - filter := bson.M{"sign": key} + filter := bson.M{"sign_id": key} err := r.mdb.FindOne(ctx, filter).Decode(&restoreRequest) if err != nil { diff --git a/internal/services/recovery_service.go b/internal/services/recovery_service.go index 6eb7ae0..347e49b 100644 --- a/internal/services/recovery_service.go +++ b/internal/services/recovery_service.go @@ -11,7 +11,7 @@ import ( type CodewordRepository interface { StoreRecoveryRecord(ctx context.Context, userID, email, key, signUrl string) (string, error) - InsertToQueue(ctx context.Context, userID string, email string, key []byte, id string) error + InsertToQueue(ctx context.Context, userID string, email string, singWithID string, id string) error Ping(ctx context.Context) error GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) } @@ -91,8 +91,8 @@ func (s *RecoveryService) StoreRecoveryRecord(ctx context.Context, userID, email } // RecoveryEmailTask посылает письмо для восстановления доступа пользователю -func (s *RecoveryService) RecoveryEmailTask(ctx context.Context, userID string, email string, key []byte, id string) error { - err := s.repositoryCodeword.InsertToQueue(ctx, userID, email, key, id) +func (s *RecoveryService) RecoveryEmailTask(ctx context.Context, userID string, email string, singWithID string, id string) error { + err := s.repositoryCodeword.InsertToQueue(ctx, userID, email, singWithID, id) if err != nil { s.logger.Error("Failed creating a task to send a worker by email", zap.String("email", email), zap.Error(err)) return err @@ -102,7 +102,13 @@ func (s *RecoveryService) RecoveryEmailTask(ctx context.Context, userID string, // GetRecoveryRecord получает запись восстановления из базы данных func (s *RecoveryService) GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) { - byteKey, err := base64.URLEncoding.DecodeString(key) + req, err := s.repositoryCodeword.GetRecoveryRecord(ctx, key) + if err != nil { + s.logger.Error("Failed to obtain signature recovery data", zap.String("signature", key), zap.Error(err)) + return nil, err + } + + byteKey, err := base64.URLEncoding.DecodeString(req.Sign) if err != nil { s.logger.Error("Failed to decode string signature to []byte format", zap.String("signature", key), zap.Error(err)) return nil, err @@ -115,11 +121,6 @@ func (s *RecoveryService) GetRecoveryRecord(ctx context.Context, key string) (*m return nil, err } - req, err := s.repositoryCodeword.GetRecoveryRecord(ctx, key) - if err != nil { - s.logger.Error("Failed to obtain signature recovery data", zap.String("signature", key), zap.Error(err)) - return nil, err - } return req, nil } From 5186edf22724bc1a471477018bebcf673d84d33e Mon Sep 17 00:00:00 2001 From: Pavel Date: Sun, 7 Jan 2024 14:26:38 +0300 Subject: [PATCH 09/12] change singWithID -> signWithID --- internal/controller/recovery/recovery_controller.go | 4 ++-- internal/repository/codeword_repository.go | 4 ++-- internal/services/recovery_service.go | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/controller/recovery/recovery_controller.go b/internal/controller/recovery/recovery_controller.go index f3fc1eb..2225b84 100644 --- a/internal/controller/recovery/recovery_controller.go +++ b/internal/controller/recovery/recovery_controller.go @@ -60,9 +60,9 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) } - singWithID := sign + id // подпись с id записи + signWithID := sign + id // подпись с id записи - err = r.service.RecoveryEmailTask(c.Context(), user.ID.Hex(), email, singWithID, id) + err = r.service.RecoveryEmailTask(c.Context(), user.ID.Hex(), email, signWithID, id) if err != nil { r.logger.Error("Failed to send recovery email", zap.Error(err)) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) diff --git a/internal/repository/codeword_repository.go b/internal/repository/codeword_repository.go index 60a040a..560b925 100644 --- a/internal/repository/codeword_repository.go +++ b/internal/repository/codeword_repository.go @@ -45,12 +45,12 @@ func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, userID, em } // добавляем в очередь данные для отправки на почту в редис -func (r *codewordRepository) InsertToQueue(ctx context.Context, userID string, email string, singWithID string, id string) error { +func (r *codewordRepository) InsertToQueue(ctx context.Context, userID string, email string, signWithID string, id string) error { task := models.RecoveryRecord{ ID: id, UserID: userID, Email: email, - Key: singWithID, + Key: signWithID, } taskBytes, err := json.Marshal(task) diff --git a/internal/services/recovery_service.go b/internal/services/recovery_service.go index 347e49b..e4d3ecb 100644 --- a/internal/services/recovery_service.go +++ b/internal/services/recovery_service.go @@ -11,7 +11,7 @@ import ( type CodewordRepository interface { StoreRecoveryRecord(ctx context.Context, userID, email, key, signUrl string) (string, error) - InsertToQueue(ctx context.Context, userID string, email string, singWithID string, id string) error + InsertToQueue(ctx context.Context, userID string, email string, signWithID string, id string) error Ping(ctx context.Context) error GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) } @@ -91,8 +91,8 @@ func (s *RecoveryService) StoreRecoveryRecord(ctx context.Context, userID, email } // RecoveryEmailTask посылает письмо для восстановления доступа пользователю -func (s *RecoveryService) RecoveryEmailTask(ctx context.Context, userID string, email string, singWithID string, id string) error { - err := s.repositoryCodeword.InsertToQueue(ctx, userID, email, singWithID, id) +func (s *RecoveryService) RecoveryEmailTask(ctx context.Context, userID string, email string, signWithID string, id string) error { + err := s.repositoryCodeword.InsertToQueue(ctx, userID, email, signWithID, id) if err != nil { s.logger.Error("Failed creating a task to send a worker by email", zap.String("email", email), zap.Error(err)) return err From 9377a9fe356ce8a46c6925b0ffffdafb056ce074 Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 11 Jan 2024 14:15:28 +0300 Subject: [PATCH 10/12] add deps struct for input val >3 --- .../recovery/recovery_controller.go | 6 ++--- internal/models/deps.go | 15 +++++++++++++ internal/repository/codeword_repository.go | 22 +++++++++---------- internal/repository/user_repository.go | 3 --- internal/services/recovery_service.go | 16 +++++++------- 5 files changed, 37 insertions(+), 25 deletions(-) create mode 100644 internal/models/deps.go diff --git a/internal/controller/recovery/recovery_controller.go b/internal/controller/recovery/recovery_controller.go index 2225b84..cd10b1d 100644 --- a/internal/controller/recovery/recovery_controller.go +++ b/internal/controller/recovery/recovery_controller.go @@ -1,6 +1,7 @@ package controller import ( + "codeword/internal/models" "codeword/internal/services" "encoding/base64" "github.com/gofiber/fiber/v2" @@ -26,7 +27,6 @@ func (r *RecoveryController) HandlePingDB(c *fiber.Ctx) error { return r.service.Ping(c.Context()) } -// TODO add deps struct, counnt params >3 // HandleRecoveryRequest обрабатывает запрос на восстановление пароля func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { email := c.FormValue("email") @@ -54,7 +54,7 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { signUrl := redirectionURL + base64.URLEncoding.EncodeToString(key) sign := base64.URLEncoding.EncodeToString(key) - id, err := r.service.StoreRecoveryRecord(c.Context(), user.ID.Hex(), user.Email, sign, signUrl) + id, err := r.service.StoreRecoveryRecord(c.Context(), models.StoreRecDeps{UserID: user.ID.Hex(), Email: user.Email, Key: sign, Url: signUrl}) if err != nil { r.logger.Error("Failed to store recovery record", zap.Error(err)) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) @@ -62,7 +62,7 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error { signWithID := sign + id // подпись с id записи - err = r.service.RecoveryEmailTask(c.Context(), user.ID.Hex(), email, signWithID, id) + err = r.service.RecoveryEmailTask(c.Context(), models.RecEmailDeps{UserID: user.ID.Hex(), Email: email, SignWithID: signWithID, ID: id}) if err != nil { r.logger.Error("Failed to send recovery email", zap.Error(err)) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"}) diff --git a/internal/models/deps.go b/internal/models/deps.go new file mode 100644 index 0000000..824660d --- /dev/null +++ b/internal/models/deps.go @@ -0,0 +1,15 @@ +package models + +type StoreRecDeps struct { + UserID string + Email string + Key string + Url string +} + +type RecEmailDeps struct { + UserID string + Email string + SignWithID string + ID string +} diff --git a/internal/repository/codeword_repository.go b/internal/repository/codeword_repository.go index 560b925..e7af8a8 100644 --- a/internal/repository/codeword_repository.go +++ b/internal/repository/codeword_repository.go @@ -23,15 +23,15 @@ func NewCodewordRepository(deps Deps) *codewordRepository { } // сохраняем полученные данные о пользователе и подписи в бд -func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, userID, email, key, url string) (string, error) { +func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, deps models.StoreRecDeps) (string, error) { newID := primitive.NewObjectID() - signID := key + newID.Hex() + signID := deps.Key + newID.Hex() record := models.RestoreRequest{ ID: newID, - UserID: userID, - Email: email, - Sign: key, - SignUrl: url, + UserID: deps.UserID, + Email: deps.Email, + Sign: deps.Key, + SignUrl: deps.Url, SignID: signID, CreatedAt: time.Now(), } @@ -45,12 +45,12 @@ func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, userID, em } // добавляем в очередь данные для отправки на почту в редис -func (r *codewordRepository) InsertToQueue(ctx context.Context, userID string, email string, signWithID string, id string) error { +func (r *codewordRepository) InsertToQueue(ctx context.Context, deps models.RecEmailDeps) error { task := models.RecoveryRecord{ - ID: id, - UserID: userID, - Email: email, - Key: signWithID, + ID: deps.ID, + UserID: deps.UserID, + Email: deps.Email, + Key: deps.SignWithID, } taskBytes, err := json.Marshal(task) diff --git a/internal/repository/user_repository.go b/internal/repository/user_repository.go index 2d343f6..3b2e2c7 100644 --- a/internal/repository/user_repository.go +++ b/internal/repository/user_repository.go @@ -13,9 +13,6 @@ type Deps struct { Rdb *redis.Client } -// todo: возможно стоит разделить два репозитория в одном файле на два файла чтобы не было путаницы, -// а deps структуру оставить одну дабы избежать путаницу - type userRepository struct { mdb *mongo.Collection } diff --git a/internal/services/recovery_service.go b/internal/services/recovery_service.go index e4d3ecb..e3738f0 100644 --- a/internal/services/recovery_service.go +++ b/internal/services/recovery_service.go @@ -10,8 +10,8 @@ import ( ) type CodewordRepository interface { - StoreRecoveryRecord(ctx context.Context, userID, email, key, signUrl string) (string, error) - InsertToQueue(ctx context.Context, userID string, email string, signWithID string, id string) error + StoreRecoveryRecord(ctx context.Context, deps models.StoreRecDeps) (string, error) + InsertToQueue(ctx context.Context, deps models.RecEmailDeps) error Ping(ctx context.Context) error GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) } @@ -81,20 +81,20 @@ func (s *RecoveryService) FindUserByEmail(ctx context.Context, email string) (*m } // StoreRecoveryRecord сохраняет запись восстановления в базе данных -func (s *RecoveryService) StoreRecoveryRecord(ctx context.Context, userID, email, key, signUrl string) (string, error) { - id, err := s.repositoryCodeword.StoreRecoveryRecord(ctx, userID, email, key, signUrl) +func (s *RecoveryService) StoreRecoveryRecord(ctx context.Context, deps models.StoreRecDeps) (string, error) { + id, err := s.repositoryCodeword.StoreRecoveryRecord(ctx, models.StoreRecDeps{UserID: deps.UserID, Email: deps.Email, Key: deps.Key, Url: deps.Url}) if err != nil { - s.logger.Error("Failed save data in mongoDB for email", zap.String("email", email), zap.Error(err)) + s.logger.Error("Failed save data in mongoDB for email", zap.String("email", deps.Email), zap.Error(err)) return "", err } return id, nil } // RecoveryEmailTask посылает письмо для восстановления доступа пользователю -func (s *RecoveryService) RecoveryEmailTask(ctx context.Context, userID string, email string, signWithID string, id string) error { - err := s.repositoryCodeword.InsertToQueue(ctx, userID, email, signWithID, id) +func (s *RecoveryService) RecoveryEmailTask(ctx context.Context, deps models.RecEmailDeps) error { + err := s.repositoryCodeword.InsertToQueue(ctx, models.RecEmailDeps{UserID: deps.UserID, Email: deps.Email, SignWithID: deps.SignWithID, ID: deps.ID}) if err != nil { - s.logger.Error("Failed creating a task to send a worker by email", zap.String("email", email), zap.Error(err)) + s.logger.Error("Failed creating a task to send a worker by email", zap.String("email", deps.Email), zap.Error(err)) return err } return nil From 4c43a0d87b83611d38ddef08bcfb839980b23228 Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 11 Jan 2024 15:07:17 +0300 Subject: [PATCH 11/12] some refactor --- internal/app/app.go | 25 +++++++------------- internal/initialize/encrypt.go | 2 +- internal/initialize/mongo.go | 2 +- internal/initialize/redis.go | 2 +- internal/repository/codeword_repository.go | 5 +++- internal/server/http/http_server.go | 6 ++++- internal/services/recovery_service.go | 2 +- internal/worker/purge_worker/purge_worker.go | 10 ++++---- 8 files changed, 27 insertions(+), 27 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index 7e4ca7e..112ded4 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -11,20 +11,19 @@ import ( "context" "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" - "time" ) func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { logger.Info("Запуск приложения", zap.String("AppName", cfg.AppName)) - mdb, err := initialize.InitializeMongoDB(ctx, cfg) + mdb, err := initialize.MongoDB(ctx, cfg) if err != nil { logger.Error("Failed to initialize MongoDB", zap.Error(err)) return err } - rdb, err := initialize.InitializeRedis(ctx, cfg) - encrypt := initialize.InitializeEncrypt(cfg) + rdb, err := initialize.Redis(ctx, cfg) + encrypt := initialize.Encrypt(cfg) codewordRepo := repository.NewCodewordRepository(repository.Deps{Rdb: rdb, Mdb: mdb.Collection("codeword")}) userRepo := repository.NewUserRepository(repository.Deps{Rdb: nil, Mdb: mdb.Collection("users")}) recoveryEmailSender := initialize.RecoveryEmailSender(cfg, logger) @@ -68,7 +67,7 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { <-ctx.Done() - if err := shutdownApp(server, mdb, logger); err != nil { + if err := shutdownApp(ctx, server, mdb, logger); err != nil { return err } logger.Info("The application has stopped") @@ -77,22 +76,19 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error { // TODO возможно стоит вынести в отдельные файлы или отказаться от разделения на отдельные методы -func shutdownApp(server *httpserver.Server, mdb *mongo.Database, logger *zap.Logger) error { - if err := shutdownHTTPServer(server, logger); err != nil { +func shutdownApp(ctx context.Context, server *httpserver.Server, mdb *mongo.Database, logger *zap.Logger) error { + if err := shutdownHTTPServer(ctx, server, logger); err != nil { return err } - if err := shutdownMongoDB(mdb, logger); err != nil { + if err := shutdownMongoDB(ctx, mdb, logger); err != nil { return err } return nil } -func shutdownHTTPServer(server *httpserver.Server, logger *zap.Logger) error { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - +func shutdownHTTPServer(ctx context.Context, server *httpserver.Server, logger *zap.Logger) error { if err := server.Shutdown(ctx); err != nil { logger.Error("Error stopping HTTP server", zap.Error(err)) return err @@ -100,10 +96,7 @@ func shutdownHTTPServer(server *httpserver.Server, logger *zap.Logger) error { return nil } -func shutdownMongoDB(mdb *mongo.Database, logger *zap.Logger) error { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - +func shutdownMongoDB(ctx context.Context, mdb *mongo.Database, logger *zap.Logger) error { if err := mdb.Client().Disconnect(ctx); err != nil { logger.Error("Error when closing MongoDB connection", zap.Error(err)) return err diff --git a/internal/initialize/encrypt.go b/internal/initialize/encrypt.go index abb49ed..366147d 100644 --- a/internal/initialize/encrypt.go +++ b/internal/initialize/encrypt.go @@ -4,7 +4,7 @@ import ( "codeword/internal/utils/encrypt" ) -func InitializeEncrypt(cfg Config) *encrypt.Encrypt { +func Encrypt(cfg Config) *encrypt.Encrypt { return encrypt.New(&encrypt.EncryptDeps{ PublicKey: cfg.PublicCurveKey, PrivateKey: cfg.PrivateCurveKey, diff --git a/internal/initialize/mongo.go b/internal/initialize/mongo.go index df49819..64fea74 100644 --- a/internal/initialize/mongo.go +++ b/internal/initialize/mongo.go @@ -7,7 +7,7 @@ import ( "time" ) -func InitializeMongoDB(ctx context.Context, cfg Config) (*mongo.Database, error) { +func MongoDB(ctx context.Context, cfg Config) (*mongo.Database, error) { dbConfig := &mdb.Configuration{ MongoHost: cfg.MongoHost, MongoPort: cfg.MongoPort, diff --git a/internal/initialize/redis.go b/internal/initialize/redis.go index bd42631..d8de002 100644 --- a/internal/initialize/redis.go +++ b/internal/initialize/redis.go @@ -5,7 +5,7 @@ import ( "github.com/go-redis/redis/v8" ) -func InitializeRedis(ctx context.Context, cfg Config) (*redis.Client, error) { +func Redis(ctx context.Context, cfg Config) (*redis.Client, error) { rdb := redis.NewClient(&redis.Options{ Addr: cfg.RedisAddr, Password: cfg.RedisPassword, diff --git a/internal/repository/codeword_repository.go b/internal/repository/codeword_repository.go index e7af8a8..75d38e1 100644 --- a/internal/repository/codeword_repository.go +++ b/internal/repository/codeword_repository.go @@ -81,5 +81,8 @@ func (r *codewordRepository) GetRecoveryRecord(ctx context.Context, key string) // пингует в монгу чтобы проверить подключение func (r *codewordRepository) Ping(ctx context.Context) error { - return r.mdb.Database().Client().Ping(ctx, readpref.Primary()) + if err := r.mdb.Database().Client().Ping(ctx, readpref.Primary()); err != nil { + return err + } + return nil } diff --git a/internal/server/http/http_server.go b/internal/server/http/http_server.go index 4b0808a..ca397ff 100644 --- a/internal/server/http/http_server.go +++ b/internal/server/http/http_server.go @@ -35,7 +35,11 @@ func NewServer(config ServerConfig) *Server { } func (s *Server) Start(addr string) error { - return s.app.Listen(addr) + if err := s.app.Listen(addr); err != nil { + s.Logger.Error("Failed to start server", zap.Error(err)) + return err + } + return nil } func (s *Server) Shutdown(ctx context.Context) error { diff --git a/internal/services/recovery_service.go b/internal/services/recovery_service.go index e3738f0..664d447 100644 --- a/internal/services/recovery_service.go +++ b/internal/services/recovery_service.go @@ -116,7 +116,7 @@ func (s *RecoveryService) GetRecoveryRecord(ctx context.Context, key string) (*m // сомнительный вариант но как я думаю верный, что false==err result, err := s.encrypt.VerifySignature(byteKey) - if err != nil || result == false { + if err != nil || !result { s.logger.Error("Failed to verify signature", zap.String("signature", key), zap.Error(err)) return nil, err } diff --git a/internal/worker/purge_worker/purge_worker.go b/internal/worker/purge_worker/purge_worker.go index 19c81eb..e621641 100644 --- a/internal/worker/purge_worker/purge_worker.go +++ b/internal/worker/purge_worker/purge_worker.go @@ -13,19 +13,19 @@ type Deps struct { Mongo *mongo.Collection } -type purgeWorker struct { +type PurgeWorker struct { logger *zap.Logger mongo *mongo.Collection } -func NewRecoveryWC(deps Deps) *purgeWorker { - return &purgeWorker{ +func NewRecoveryWC(deps Deps) *PurgeWorker { + return &PurgeWorker{ logger: deps.Logger, mongo: deps.Mongo, } } -func (wc *purgeWorker) Start(ctx context.Context) { +func (wc *PurgeWorker) Start(ctx context.Context) { ticker := time.NewTicker(1 * time.Hour) defer ticker.Stop() @@ -40,7 +40,7 @@ func (wc *purgeWorker) Start(ctx context.Context) { } } -func (wc *purgeWorker) processTasks(ctx context.Context) { +func (wc *PurgeWorker) processTasks(ctx context.Context) { wc.logger.Info("Checking cleaning records") oneHourAgo := time.Now().Add(-1 * time.Hour) From 5015387857f502a417eeed74a1390b08ab8a854c Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 11 Jan 2024 15:11:18 +0300 Subject: [PATCH 12/12] some refactor --- tests/repository_test/repository_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/repository_test/repository_test.go b/tests/repository_test/repository_test.go index d12754c..6b69033 100644 --- a/tests/repository_test/repository_test.go +++ b/tests/repository_test/repository_test.go @@ -17,6 +17,8 @@ import ( "github.com/stretchr/testify/assert" ) +// todo add another tests + const mongoURI = "mongodb://test:test@127.0.0.1:27020/?authMechanism=SCRAM-SHA-256&authSource=admin&directConnection=true" func TestFindByEmail(t *testing.T) { @@ -78,7 +80,7 @@ func TestStoreRecoveryRecord(t *testing.T) { email := faker.Email() key := "test_recovery_key" - id, err := userRepo.StoreRecoveryRecord(ctx, userID, email, key, "def.url") + id, err := userRepo.StoreRecoveryRecord(ctx, models.StoreRecDeps{UserID: userID, Email: email, Key: key, Url: "def.url"}) assert.NoError(t, err) fmt.Println(id)