ref code, add purgeWC and base doc
This commit is contained in:
parent
487dc8bc2d
commit
b767702160
6
.env
6
.env
@ -21,7 +21,9 @@ PUBLIC_CURVE_KEY="-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAEbnIvjIMle4rqVol6K
|
|||||||
|
|
||||||
PRIVATE_CURVE_KEY="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIKn0BKwF3vZvODgWAnUIwQhd8de5oZhY48gc23EWfrfs\n-----END PRIVATE KEY-----"
|
PRIVATE_CURVE_KEY="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIKn0BKwF3vZvODgWAnUIwQhd8de5oZhY48gc23EWfrfs\n-----END PRIVATE KEY-----"
|
||||||
|
|
||||||
SIGN_SECRET=group
|
# SIGN_SECRET="group"
|
||||||
|
|
||||||
|
SIGN_SECRET="secret"
|
||||||
|
|
||||||
# SMTP settings
|
# SMTP settings
|
||||||
SMTP_API_URL="https://api.smtp.bz/v1/smtp/send"
|
SMTP_API_URL="https://api.smtp.bz/v1/smtp/send"
|
||||||
@ -34,4 +36,4 @@ SMTP_SENDER="noreply@mailing.pena.digital"
|
|||||||
|
|
||||||
# URL settings
|
# URL settings
|
||||||
DEFAULT_REDIRECTION_URL = "def.url"
|
DEFAULT_REDIRECTION_URL = "def.url"
|
||||||
AUTH_REFRESH_URL = "http://localhost:8000/auth/exchange"
|
AUTH_EXCHANGE_URL = "http://localhost:8000/auth/exchange"
|
@ -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: Внутренняя ошибка сервера – разные причины
|
@ -41,13 +41,11 @@ func (a *AuthClient) RefreshAuthToken(userID, signature string) (*models.Refresh
|
|||||||
|
|
||||||
agent := a.deps.FiberClient.Post(a.deps.AuthUrl)
|
agent := a.deps.FiberClient.Post(a.deps.AuthUrl)
|
||||||
agent.Set("Content-Type", "application/json").Body(bodyBytes)
|
agent.Set("Content-Type", "application/json").Body(bodyBytes)
|
||||||
//todo надо что-то придумать с авторизаиционными токенами
|
|
||||||
agent.Set("Authorization", "Bearer "+"123")
|
|
||||||
|
|
||||||
statusCode, resBody, errs := agent.Bytes()
|
statusCode, resBody, errs := agent.Bytes()
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
for _, err := range errs {
|
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)
|
return nil, fmt.Errorf("request failed: %v", errs)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"codeword/internal/repository"
|
"codeword/internal/repository"
|
||||||
httpserver "codeword/internal/server/http"
|
httpserver "codeword/internal/server/http"
|
||||||
"codeword/internal/services"
|
"codeword/internal/services"
|
||||||
|
"codeword/internal/worker/purge_worker"
|
||||||
"codeword/internal/worker/recovery_worker"
|
"codeword/internal/worker/recovery_worker"
|
||||||
"context"
|
"context"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"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)
|
encrypt := initialize.InitializeEncrypt(cfg)
|
||||||
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 := initialize.InitializeRecoveryEmailSender(cfg, logger)
|
recoveryEmailSender := initialize.RecoveryEmailSender(cfg, logger)
|
||||||
authClient := initialize.InitializeAuthClient(cfg, logger)
|
authClient := initialize.AuthClient(cfg, logger)
|
||||||
|
|
||||||
recoveryService := services.NewRecoveryService(services.Deps{
|
recoveryService := services.NewRecoveryService(services.Deps{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
@ -46,7 +47,13 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error {
|
|||||||
Mongo: mdb.Collection("codeword"),
|
Mongo: mdb.Collection("codeword"),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
purgeWC := purge_worker.NewRecoveryWC(purge_worker.Deps{
|
||||||
|
Logger: logger,
|
||||||
|
Mongo: mdb.Collection("codeword"),
|
||||||
|
})
|
||||||
|
|
||||||
go recoveryWC.Start(ctx)
|
go recoveryWC.Start(ctx)
|
||||||
|
go purgeWC.Start(ctx)
|
||||||
|
|
||||||
server := httpserver.NewServer(httpserver.ServerConfig{
|
server := httpserver.NewServer(httpserver.ServerConfig{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
@ -55,7 +62,7 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := server.Start(cfg.HTTPHost + ":" + cfg.HTTPPort); err != nil {
|
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 {
|
if err := shutdownApp(server, mdb, logger); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logger.Info("Приложение остановлено")
|
logger.Info("The application has stopped")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +94,7 @@ func shutdownHTTPServer(server *httpserver.Server, logger *zap.Logger) error {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := server.Shutdown(ctx); err != nil {
|
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 err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -98,7 +105,7 @@ func shutdownMongoDB(mdb *mongo.Database, logger *zap.Logger) error {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := mdb.Client().Disconnect(ctx); err != nil {
|
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 err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -39,18 +39,18 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error {
|
|||||||
redirectionURL = r.defaultURL
|
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)
|
user, err := r.service.FindUserByEmail(c.Context(), email)
|
||||||
if err != nil || user == nil {
|
if err != nil || user == nil {
|
||||||
r.logger.Error("Failed to find user by email", zap.Error(err))
|
r.logger.Error("Failed to find user by email", zap.Error(err))
|
||||||
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
|
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)
|
signUrl := redirectionURL + base64.URLEncoding.EncodeToString(key)
|
||||||
sign := base64.URLEncoding.EncodeToString(key)
|
sign := base64.URLEncoding.EncodeToString(key)
|
||||||
|
|
||||||
@ -71,6 +71,8 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo тут скорее всего помимо подписи будет передаваться еще что-то, например email пользователя от фронта для поиска в бд
|
||||||
|
|
||||||
// HandleRecoveryLink обрабатывает ссылку восстановления и обменивает ее на токены
|
// HandleRecoveryLink обрабатывает ссылку восстановления и обменивает ее на токены
|
||||||
func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error {
|
func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error {
|
||||||
key := c.Params("sign")
|
key := c.Params("sign")
|
||||||
@ -83,7 +85,7 @@ func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
if time.Since(record.CreatedAt) > 15*time.Minute {
|
if time.Since(record.CreatedAt) > 15*time.Minute {
|
||||||
r.logger.Error("Recovery link expired", zap.String("signature", key))
|
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)
|
tokens, err := r.service.ExchangeForTokens(record.UserID, record.Sign)
|
||||||
|
@ -1 +1,3 @@
|
|||||||
package errors
|
package errors
|
||||||
|
|
||||||
|
// пока не нужен
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"go.uber.org/zap"
|
"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{
|
return client.NewRecoveryEmailSender(client.RecoveryEmailSenderDeps{
|
||||||
SmtpApiUrl: cfg.SmtpApiUrl,
|
SmtpApiUrl: cfg.SmtpApiUrl,
|
||||||
SmtpHost: cfg.SmtpHost,
|
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{
|
return client.NewAuthClient(client.AuthClientDeps{
|
||||||
AuthUrl: cfg.AuthURL,
|
AuthUrl: cfg.AuthURL,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
|
@ -30,7 +30,7 @@ type Config struct {
|
|||||||
SmtpApiKey string `env:"SMTP_API_KEY"`
|
SmtpApiKey string `env:"SMTP_API_KEY"`
|
||||||
SmtpSender string `env:"SMTP_SENDER"`
|
SmtpSender string `env:"SMTP_SENDER"`
|
||||||
DefaultRedirectionURL string `env:"DEFAULT_REDIRECTION_URL"`
|
DefaultRedirectionURL string `env:"DEFAULT_REDIRECTION_URL"`
|
||||||
AuthURL string `env:"AUTH_REFRESH_URL"`
|
AuthURL string `env:"AUTH_EXCHANGE_URL"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadConfig() (*Config, error) {
|
func LoadConfig() (*Config, error) {
|
||||||
|
83
internal/repository/codeword_repository.go
Normal file
83
internal/repository/codeword_repository.go
Normal file
@ -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())
|
||||||
|
}
|
@ -3,13 +3,9 @@ package repository
|
|||||||
import (
|
import (
|
||||||
"codeword/internal/models"
|
"codeword/internal/models"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"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"
|
||||||
"go.mongodb.org/mongo-driver/mongo/readpref"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Deps struct {
|
type Deps struct {
|
||||||
@ -17,10 +13,8 @@ type Deps struct {
|
|||||||
Rdb *redis.Client
|
Rdb *redis.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
type codewordRepository struct {
|
// todo: возможно стоит разделить два репозитория в одном файле на два файла чтобы не было путаницы,
|
||||||
mdb *mongo.Collection
|
// а deps структуру оставить одну дабы избежать путаницу
|
||||||
rdb *redis.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
type userRepository struct {
|
type userRepository struct {
|
||||||
mdb *mongo.Collection
|
mdb *mongo.Collection
|
||||||
@ -31,11 +25,7 @@ func NewUserRepository(deps Deps) *userRepository {
|
|||||||
return &userRepository{mdb: deps.Mdb}
|
return &userRepository{mdb: deps.Mdb}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCodewordRepository(deps Deps) *codewordRepository {
|
// ищем пользователя по мейлу в коллекции users
|
||||||
|
|
||||||
return &codewordRepository{mdb: deps.Mdb, rdb: deps.Rdb}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *userRepository) FindByEmail(ctx context.Context, email string) (*models.User, error) {
|
func (r *userRepository) FindByEmail(ctx context.Context, email string) (*models.User, error) {
|
||||||
var user models.User
|
var user models.User
|
||||||
|
|
||||||
@ -48,59 +38,3 @@ func (r *userRepository) FindByEmail(ctx context.Context, email string) (*models
|
|||||||
}
|
}
|
||||||
return &user, nil
|
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())
|
|
||||||
}
|
|
||||||
|
@ -56,6 +56,7 @@ func (s *RecoveryService) GenerateKey() ([]byte, error) {
|
|||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// вызывает пингование в бд
|
||||||
func (s *RecoveryService) Ping(ctx context.Context) error {
|
func (s *RecoveryService) Ping(ctx context.Context) error {
|
||||||
err := s.repositoryCodeword.Ping(ctx)
|
err := s.repositoryCodeword.Ping(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -89,7 +90,7 @@ func (s *RecoveryService) StoreRecoveryRecord(ctx context.Context, userID, email
|
|||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendRecoveryEmail посылает письмо для восстановления доступа пользователю
|
// RecoveryEmailTask посылает письмо для восстановления доступа пользователю
|
||||||
func (s *RecoveryService) RecoveryEmailTask(ctx context.Context, userID string, email string, key []byte, id string) error {
|
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)
|
err := s.repositoryCodeword.InsertToQueue(ctx, userID, email, key, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -107,6 +108,7 @@ func (s *RecoveryService) GetRecoveryRecord(ctx context.Context, key string) (*m
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// сомнительный вариант но как я думаю верный, что false==err
|
||||||
result, err := s.encrypt.VerifySignature(byteKey)
|
result, err := s.encrypt.VerifySignature(byteKey)
|
||||||
if err != nil || result == false {
|
if err != nil || result == false {
|
||||||
s.logger.Error("Failed to verify signature", zap.String("signature", key), zap.Error(err))
|
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
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// меняет подпись на токены идя в auth сервис
|
||||||
func (s *RecoveryService) ExchangeForTokens(userID string, signature string) (map[string]string, error) {
|
func (s *RecoveryService) ExchangeForTokens(userID string, signature string) (map[string]string, error) {
|
||||||
tokens, err := s.authClient.RefreshAuthToken(userID, signature)
|
tokens, err := s.authClient.RefreshAuthToken(userID, signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -53,6 +53,7 @@ func (receiver *Encrypt) VerifySignature(signature []byte) (isValid bool, err er
|
|||||||
return ed25519.Verify(publicKey, []byte(receiver.signSecret), signature), nil
|
return ed25519.Verify(publicKey, []byte(receiver.signSecret), signature), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO подумать над тем чтобы подпись генерилась каждый раз разгая
|
||||||
func (receiver *Encrypt) SignCommonSecret() (signature []byte, err error) {
|
func (receiver *Encrypt) SignCommonSecret() (signature []byte, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if recovered := recover(); recovered != nil {
|
if recovered := recover(); recovered != nil {
|
||||||
|
56
internal/worker/purge_worker/purge_worker.go
Normal file
56
internal/worker/purge_worker/purge_worker.go
Normal file
@ -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))
|
||||||
|
}
|
||||||
|
}
|
@ -20,15 +20,15 @@ type Deps struct {
|
|||||||
Mongo *mongo.Collection
|
Mongo *mongo.Collection
|
||||||
}
|
}
|
||||||
|
|
||||||
type recoveryWorker struct {
|
type RecoveryWorker struct {
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
redis *redis.Client
|
redis *redis.Client
|
||||||
emailSender *client.RecoveryEmailSender
|
emailSender *client.RecoveryEmailSender
|
||||||
mongo *mongo.Collection
|
mongo *mongo.Collection
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRecoveryWC(deps Deps) *recoveryWorker {
|
func NewRecoveryWC(deps Deps) *RecoveryWorker {
|
||||||
return &recoveryWorker{
|
return &RecoveryWorker{
|
||||||
logger: deps.Logger,
|
logger: deps.Logger,
|
||||||
redis: deps.Redis,
|
redis: deps.Redis,
|
||||||
emailSender: deps.EmailSender,
|
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)
|
ticker := time.NewTicker(1 * time.Second)
|
||||||
defer ticker.Stop()
|
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()
|
result, err := wc.redis.BRPop(ctx, 1*time.Second, "recoveryQueue").Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != redis.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)
|
err := wc.emailSender.SendRecoveryEmail(task.Email, task.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wc.logger.Error("Failed to send recovery email", zap.Error(err))
|
wc.logger.Error("Failed to send recovery email", zap.Error(err))
|
||||||
|
Loading…
Reference in New Issue
Block a user