Merge branch 'recwc' into 'main'
Recwc See merge request pena-services/codeword!4
This commit is contained in:
commit
b6c53beaa1
25
.env
25
.env
@ -1,22 +1,21 @@
|
|||||||
# General application settings
|
# General application settings
|
||||||
APP_NAME=codeword
|
APP_NAME=codeword
|
||||||
HTTP_HOST=localhost
|
HTTP_HOST="localhost"
|
||||||
HTTP_PORT=8000
|
HTTP_PORT="8000"
|
||||||
|
|
||||||
# MongoDB settings
|
# MongoDB settings
|
||||||
MONGO_HOST=127.0.0.1
|
MONGO_HOST="127.0.0.1"
|
||||||
MONGO_PORT=27020
|
MONGO_PORT="27020"
|
||||||
MONGO_USER=test
|
MONGO_USER="test"
|
||||||
MONGO_PASSWORD=test
|
MONGO_PASSWORD="test"
|
||||||
MONGO_DB=admin
|
MONGO_DB="admin"
|
||||||
MONGO_AUTH=admin
|
MONGO_AUTH="admin"
|
||||||
|
|
||||||
# Redis settings
|
# Redis settings
|
||||||
REDIS_ADDR="localhost:6379"
|
REDIS_ADDR="localhost:6379"
|
||||||
REDIS_PASS="admin"
|
REDIS_PASS="admin"
|
||||||
REDIS_DB=2
|
REDIS_DB=2
|
||||||
|
|
||||||
|
|
||||||
# Keys
|
# 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-----\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
|
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:
|
mongo:
|
||||||
image: mongo
|
image: mongo
|
||||||
ports:
|
ports:
|
||||||
- "${MONGO_PORT}:27017"
|
- "27020:27017"
|
||||||
environment:
|
environment:
|
||||||
- MONGO_INITDB_ROOT_USERNAME=${MONGO_USER}
|
- MONGO_INITDB_ROOT_USERNAME=${MONGO_USER}
|
||||||
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD}
|
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD}
|
||||||
|
@ -1,36 +1,87 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/smtp"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"mime/multipart"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RecoveryEmailSender struct {
|
type RecoveryEmailSenderDeps struct {
|
||||||
SmtpHost string
|
SmtpApiUrl string
|
||||||
SmtpPort string
|
SmtpHost string
|
||||||
Username string
|
SmtpPort string
|
||||||
Password string
|
SmtpSender string
|
||||||
ApiKey string
|
Username string
|
||||||
|
Password string
|
||||||
|
ApiKey string
|
||||||
|
FiberClient *fiber.Client
|
||||||
|
Logger *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendRecoveryEmail отправляет email с подписью для восстановления доступа
|
type RecoveryEmailSender struct {
|
||||||
func (r *RecoveryEmailSender) SendRecoveryEmail(email, signature string) error {
|
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"+
|
message := fmt.Sprintf("To: %s\r\n"+
|
||||||
"Subject: Восстановление доступа\r\n"+
|
"Subject: Восстановление доступа\r\n"+
|
||||||
"\r\n"+
|
"\r\n"+
|
||||||
"Чтобы восстановить доступ, пожалуйста, перейдите по ссылке ниже:\r\n"+
|
"Чтобы восстановить доступ, пожалуйста, перейдите по ссылке ниже:\r\n"+
|
||||||
" https://hub.pena.digital/codeword/restore/%s\r\n", email, signature)
|
" 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))
|
fields := map[string]string{
|
||||||
if err != nil {
|
"from": r.deps.SmtpSender,
|
||||||
fmt.Printf("Ошибка при отправке письма: %s\n", err)
|
"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
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"codeword/internal/adapters/client"
|
||||||
controller "codeword/internal/controller/recovery"
|
controller "codeword/internal/controller/recovery"
|
||||||
"codeword/internal/initialize"
|
"codeword/internal/initialize"
|
||||||
"codeword/internal/repository"
|
"codeword/internal/repository"
|
||||||
httpserver "codeword/internal/server/http"
|
httpserver "codeword/internal/server/http"
|
||||||
"codeword/internal/services"
|
"codeword/internal/services"
|
||||||
"codeword/internal/utils/encrypt"
|
"codeword/internal/utils/encrypt"
|
||||||
|
"codeword/internal/worker/recovery_worker"
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"time"
|
"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")})
|
codewordRepo := repository.NewCodewordRepository(repository.Deps{Rdb: rdb, Mdb: mdb.Collection("codeword")})
|
||||||
userRepo := repository.NewUserRepository(repository.Deps{Rdb: nil, Mdb: mdb.Collection("users")})
|
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{
|
recoveryService := services.NewRecoveryService(services.Deps{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
@ -44,6 +57,14 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error {
|
|||||||
|
|
||||||
recoveryController := controller.NewRecoveryController(logger, recoveryService)
|
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{
|
server := httpserver.NewServer(httpserver.ServerConfig{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
RecoveryController: recoveryController,
|
RecoveryController: recoveryController,
|
||||||
|
@ -22,6 +22,13 @@ type Config struct {
|
|||||||
RedisAddr string `env:"REDIS_ADDR" envDefault:"localhost:6379"`
|
RedisAddr string `env:"REDIS_ADDR" envDefault:"localhost:6379"`
|
||||||
RedisPassword string `env:"REDIS_PASS" envDefault:"admin"`
|
RedisPassword string `env:"REDIS_PASS" envDefault:"admin"`
|
||||||
RedisDB int `env:"REDIS_DB" envDefault:"2"`
|
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) {
|
func LoadConfig() (*Config, error) {
|
||||||
|
@ -30,6 +30,6 @@ type RestoreRequest struct {
|
|||||||
type RecoveryRecord struct {
|
type RecoveryRecord struct {
|
||||||
UserID string `bson:"user_id"`
|
UserID string `bson:"user_id"`
|
||||||
Email string `bson:"email"`
|
Email string `bson:"email"`
|
||||||
Key string `bson:"key"`
|
Key []byte `bson:"key"`
|
||||||
CreatedAt time.Time `bson:"created_at"`
|
CreatedAt time.Time `bson:"created_at"`
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,8 @@ import (
|
|||||||
"codeword/internal/models"
|
"codeword/internal/models"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
|
"github.com/pioz/faker"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
"go.mongodb.org/mongo-driver/mongo/readpref"
|
"go.mongodb.org/mongo-driver/mongo/readpref"
|
||||||
@ -53,7 +53,7 @@ func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, userID str
|
|||||||
record := models.RecoveryRecord{
|
record := models.RecoveryRecord{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Email: email,
|
Email: email,
|
||||||
Key: string(key),
|
Key: key,
|
||||||
CreatedAt: time.Now(),
|
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 {
|
func (r *codewordRepository) InsertToQueue(ctx context.Context, userID string, email string, key []byte) error {
|
||||||
task := models.RecoveryRecord{
|
// todo не забыть убрать потом этот цикл
|
||||||
UserID: userID,
|
for i := 0; i < 10; i++ {
|
||||||
Email: email,
|
|
||||||
Key: string(key),
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}
|
|
||||||
|
|
||||||
taskBytes, err := json.Marshal(task)
|
task := models.RecoveryRecord{
|
||||||
if err != nil {
|
UserID: userID + faker.String(),
|
||||||
return err
|
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 {
|
if err := r.rdb.LPush(ctx, "recoveryQueue", taskBytes).Err(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -1 +1,83 @@
|
|||||||
package recovery_worker
|
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
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user