Merge branch 'recwc' into 'main'

Recwc

See merge request pena-services/codeword!4
This commit is contained in:
Mikhail 2024-01-16 00:13:05 +00:00
commit b6c53beaa1
8 changed files with 212 additions and 42 deletions

25
.env

@ -1,22 +1,21 @@
# General application settings
APP_NAME=codeword
HTTP_HOST=localhost
HTTP_PORT=8000
HTTP_HOST="localhost"
HTTP_PORT="8000"
# MongoDB settings
MONGO_HOST=127.0.0.1
MONGO_PORT=27020
MONGO_USER=test
MONGO_PASSWORD=test
MONGO_DB=admin
MONGO_AUTH=admin
MONGO_HOST="127.0.0.1"
MONGO_PORT="27020"
MONGO_USER="test"
MONGO_PASSWORD="test"
MONGO_DB="admin"
MONGO_AUTH="admin"
# Redis settings
REDIS_ADDR="localhost:6379"
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-----"
@ -24,3 +23,11 @@ PRIVATE_CURVE_KEY="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIKn0BKwF3vZv
SIGN_SECRET=group
# SMTP settings
SMTP_API_URL="https://api.smtp.bz/v1/smtp/send"
SMTP_HOST="connect.mailclient.bz"
SMTP_PORT="587"
SMTP_UNAME="kotilion.95@gmail.com"
SMTP_PASS="vWwbCSg4bf0p"
SMTP_API_KEY="P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev"
SMTP_SENDER="noreply@mailing.pena.digital"

@ -4,7 +4,7 @@ services:
mongo:
image: mongo
ports:
- "${MONGO_PORT}:27017"
- "27020:27017"
environment:
- MONGO_INITDB_ROOT_USERNAME=${MONGO_USER}
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD}

@ -1,36 +1,87 @@
package client
import (
"bytes"
"fmt"
"net/smtp"
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
"mime/multipart"
)
type RecoveryEmailSender struct {
SmtpHost string
SmtpPort string
Username string
Password string
ApiKey string
type RecoveryEmailSenderDeps struct {
SmtpApiUrl string
SmtpHost string
SmtpPort string
SmtpSender string
Username string
Password string
ApiKey string
FiberClient *fiber.Client
Logger *zap.Logger
}
// SendRecoveryEmail отправляет email с подписью для восстановления доступа
func (r *RecoveryEmailSender) SendRecoveryEmail(email, signature string) error {
// прост как пример пока что
type RecoveryEmailSender struct {
deps RecoveryEmailSenderDeps
}
func NewRecoveryEmailSender(deps RecoveryEmailSenderDeps) *RecoveryEmailSender {
if deps.FiberClient == nil {
deps.FiberClient = fiber.AcquireClient()
}
return &RecoveryEmailSender{
deps: deps,
}
}
func (r *RecoveryEmailSender) SendRecoveryEmail(email string, signature []byte) error {
url := r.deps.SmtpApiUrl
fmt.Println(email, signature)
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)
auth := smtp.PlainAuth("", r.Username, r.Password, r.SmtpHost)
form := new(bytes.Buffer)
writer := multipart.NewWriter(form)
defer writer.Close()
err := smtp.SendMail(r.SmtpHost+":"+r.SmtpPort, auth, r.Username, []string{email}, []byte(message))
if err != nil {
fmt.Printf("Ошибка при отправке письма: %s\n", err)
fields := map[string]string{
"from": r.deps.SmtpSender,
"to": "pashamullin202@gmail.com",
"subject": "Восстановление доступа",
"html": message,
}
for key, value := range fields {
if err := writer.WriteField(key, value); err != nil {
return err
}
}
if err := writer.Close(); err != nil {
return err
}
fmt.Printf("Письмо для восстановления доступа отправлено на: %s\n", email)
req := r.deps.FiberClient.Post(url).Body(form.Bytes()).ContentType(writer.FormDataContentType())
if r.deps.ApiKey != "" {
req.Set("Authorization", r.deps.ApiKey)
}
statusCode, body, errs := req.Bytes()
if errs != nil {
r.deps.Logger.Error("Ошибка при отправке запроса", zap.Error(errs[0]))
return errs[0]
}
if statusCode != fiber.StatusOK {
err := fmt.Errorf("SMTP сервис вернул ошибку: %s Тело ответа: %s", statusCode, body)
r.deps.Logger.Error("Ошибка при отправке электронной почты", zap.Error(err))
return err
}
r.deps.Logger.Info("Письмо для восстановления отправлено", zap.String("email", email))
return nil
}

@ -1,13 +1,16 @@
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"
@ -33,7 +36,17 @@ 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 := &client.RecoveryEmailSender{}
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,
})
recoveryService := services.NewRecoveryService(services.Deps{
Logger: logger,
@ -44,6 +57,14 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error {
recoveryController := controller.NewRecoveryController(logger, recoveryService)
recoveryWC := recovery_worker.NewRecoveryWC(recovery_worker.Deps{
Logger: logger,
Redis: rdb,
EmailSender: recoveryEmailSender,
})
go recoveryWC.Start(ctx)
server := httpserver.NewServer(httpserver.ServerConfig{
Logger: logger,
RecoveryController: recoveryController,

@ -22,6 +22,13 @@ type Config struct {
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"`
}
func LoadConfig() (*Config, error) {

@ -30,6 +30,6 @@ type RestoreRequest struct {
type RecoveryRecord struct {
UserID string `bson:"user_id"`
Email string `bson:"email"`
Key string `bson:"key"`
Key []byte `bson:"key"`
CreatedAt time.Time `bson:"created_at"`
}

@ -4,8 +4,8 @@ import (
"codeword/internal/models"
"context"
"encoding/json"
"fmt"
"github.com/go-redis/redis/v8"
"github.com/pioz/faker"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/readpref"
@ -53,7 +53,7 @@ func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, userID str
record := models.RecoveryRecord{
UserID: userID,
Email: email,
Key: string(key),
Key: key,
CreatedAt: time.Now(),
}
@ -66,22 +66,24 @@ func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, userID str
}
func (r *codewordRepository) InsertToQueue(ctx context.Context, userID string, email string, key []byte) error {
task := models.RecoveryRecord{
UserID: userID,
Email: email,
Key: string(key),
CreatedAt: time.Now(),
}
// todo не забыть убрать потом этот цикл
for i := 0; i < 10; i++ {
taskBytes, err := json.Marshal(task)
if err != nil {
return err
}
task := models.RecoveryRecord{
UserID: userID + faker.String(),
Email: email,
Key: key,
CreatedAt: time.Now(),
}
uniqKey := fmt.Sprintf("needRecovery:%d", time.Now().UnixNano())
taskBytes, err := json.Marshal(task)
if err != nil {
return err
}
if err := r.rdb.Set(ctx, uniqKey, taskBytes, 0).Err(); err != nil {
return err
if err := r.rdb.LPush(ctx, "recoveryQueue", taskBytes).Err(); err != nil {
return err
}
}
return nil

@ -1 +1,83 @@
package recovery_worker
import (
"codeword/internal/adapters/client"
"codeword/internal/models"
"context"
"encoding/json"
"github.com/go-redis/redis/v8"
"go.uber.org/zap"
"time"
)
type Deps struct {
Logger *zap.Logger
Redis *redis.Client
EmailSender *client.RecoveryEmailSender
}
type recoveryWorker struct {
logger *zap.Logger
redis *redis.Client
emailSender *client.RecoveryEmailSender
}
func NewRecoveryWC(deps Deps) *recoveryWorker {
return &recoveryWorker{
logger: deps.Logger,
redis: deps.Redis,
emailSender: deps.EmailSender,
}
}
func (wc *recoveryWorker) Start(ctx context.Context) {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
wc.processTasks(ctx)
case <-ctx.Done():
return
}
}
}
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 {
wc.logger.Error("Failed to BRPop from the recovery queue", zap.Error(err))
}
return
}
if len(result) < 2 {
wc.logger.Error("Received unexpected number of elements from BRPop", zap.Strings("result", result))
return
}
var task models.RecoveryRecord
if err = json.Unmarshal([]byte(result[1]), &task); err != nil {
wc.logger.Error("Failed to unmarshal recovery task", zap.String("key", result[0]), zap.Error(err))
return
}
err = wc.sendRecoveryTask(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 {
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))
return nil
}