ref project
This commit is contained in:
parent
b4ce07515c
commit
34debbdd3e
21
go.mod
21
go.mod
@ -2,11 +2,21 @@ module codeword
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/caarlos0/env/v8 v8.0.0
|
||||
github.com/gofiber/fiber/v2 v2.51.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
go.mongodb.org/mongo-driver v1.13.1
|
||||
go.uber.org/zap v1.26.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/caarlos0/env/v8 v8.0.0 // indirect
|
||||
github.com/gofiber/fiber/v2 v2.51.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/go-faker/faker/v4 v4.2.0 // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||
github.com/golang/snappy v0.0.1 // indirect
|
||||
github.com/google/uuid v1.4.0 // indirect
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
@ -14,6 +24,8 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
||||
github.com/pioz/faker v1.7.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.50.0 // indirect
|
||||
@ -22,11 +34,10 @@ require (
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||
go.mongodb.org/mongo-driver v1.13.1 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
go.uber.org/zap v1.26.0 // indirect
|
||||
golang.org/x/crypto v0.7.0 // indirect
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
33
go.sum
33
go.sum
@ -2,13 +2,22 @@ github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0=
|
||||
github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/go-faker/faker/v4 v4.2.0 h1:dGebOupKwssrODV51E0zbMrv5e2gO9VWSLNC1WDCpWg=
|
||||
github.com/go-faker/faker/v4 v4.2.0/go.mod h1:F/bBy8GH9NxOxMInug5Gx4WYeG6fHJZ8Ol/dhcpRub4=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/gofiber/fiber/v2 v2.51.0 h1:JNACcZy5e2tGApWB2QrRpenTWn0fq0hkFm6k0C86gKQ=
|
||||
github.com/gofiber/fiber/v2 v2.51.0/go.mod h1:xaQRZQJGqnKOQnbQw+ltvku3/h8QxvNi8o6JiJ7Ll0U=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@ -24,8 +33,20 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/pioz/faker v1.7.3 h1:Tez8Emuq0UN+/d6mo3a9m/9ZZ/zdfJk0c5RtRatrceM=
|
||||
github.com/pioz/faker v1.7.3/go.mod h1:xSpay5w/oz1a6+ww0M3vfpe40pSIykeUPeWEc3TvVlc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M=
|
||||
@ -43,6 +64,8 @@ github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7Jul
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk=
|
||||
go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=
|
||||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||
@ -84,4 +107,10 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -1,13 +1,36 @@
|
||||
package client
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
)
|
||||
|
||||
type RecoveryEmailSender struct{}
|
||||
type RecoveryEmailSender struct {
|
||||
SmtpHost string
|
||||
SmtpPort string
|
||||
Username string
|
||||
Password string
|
||||
ApiKey string
|
||||
}
|
||||
|
||||
// SendRecoveryEmail отправляет email с подписью для восстановления доступа
|
||||
func (r *RecoveryEmailSender) SendRecoveryEmail(email, signature string) error {
|
||||
// прост как пример пока что
|
||||
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)
|
||||
|
||||
fmt.Printf("Отправляем письмо для восстановления доступа на: %s с подписью: %s\n", email, signature)
|
||||
auth := smtp.PlainAuth("", r.Username, r.Password, r.SmtpHost)
|
||||
|
||||
err := smtp.SendMail(r.SmtpHost+":"+r.SmtpPort, auth, r.Username, []string{email}, []byte(message))
|
||||
if err != nil {
|
||||
fmt.Printf("Ошибка при отправке письма: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Письмо для восстановления доступа отправлено на: %s\n", email)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"codeword/internal/adapters/client"
|
||||
controller "codeword/internal/controller/recovery"
|
||||
"codeword/internal/initialize"
|
||||
"codeword/internal/repository"
|
||||
@ -9,7 +8,6 @@ import (
|
||||
"codeword/internal/services"
|
||||
"codeword/internal/utils/encrypt"
|
||||
"context"
|
||||
"fmt"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
@ -24,20 +22,23 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error {
|
||||
return err
|
||||
}
|
||||
|
||||
rdb, err := initialize.InitializeRedis(ctx, cfg)
|
||||
|
||||
encryptService := encrypt.New(&encrypt.EncryptDeps{
|
||||
PublicKey: cfg.PublicCurveKey,
|
||||
PrivateKey: cfg.PrivateCurveKey,
|
||||
SignSecret: cfg.SignSecret,
|
||||
})
|
||||
|
||||
userRepo := repository.NewUserRepository(mdb)
|
||||
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.RecoveryEmailSender{}
|
||||
|
||||
recoveryService := services.NewRecoveryService(services.Deps{
|
||||
Logger: logger,
|
||||
Repository: userRepo,
|
||||
Email: recoveryEmailSender,
|
||||
CodewordRepository: codewordRepo,
|
||||
UserRepository: userRepo,
|
||||
EncryptService: encryptService,
|
||||
})
|
||||
|
||||
@ -45,7 +46,6 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error {
|
||||
|
||||
server := httpserver.NewServer(httpserver.ServerConfig{
|
||||
Logger: logger,
|
||||
Repo: userRepo,
|
||||
RecoveryController: recoveryController,
|
||||
})
|
||||
|
||||
@ -57,8 +57,6 @@ func Run(ctx context.Context, cfg initialize.Config, logger *zap.Logger) error {
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
fmt.Println("<-ctx.Done()")
|
||||
|
||||
if err := shutdownApp(server, mdb, logger); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package controller
|
||||
|
||||
import (
|
||||
"codeword/internal/services"
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
@ -20,6 +19,10 @@ func NewRecoveryController(logger *zap.Logger, service *services.RecoveryService
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RecoveryController) HandlePingDB(c *fiber.Ctx) error {
|
||||
return r.service.Ping(c.Context())
|
||||
}
|
||||
|
||||
// HandleRecoveryRequest обрабатывает запрос на восстановление пароля
|
||||
func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error {
|
||||
email := c.FormValue("email")
|
||||
@ -29,24 +32,20 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error {
|
||||
r.logger.Error("Failed to generate key", zap.Error(err))
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
|
||||
}
|
||||
fmt.Println(key)
|
||||
|
||||
user, err := r.service.FindUserByEmail(email)
|
||||
if err != nil {
|
||||
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"})
|
||||
}
|
||||
fmt.Println(user)
|
||||
// сохраняем в бд
|
||||
signature, err := r.service.StoreRecoveryRecord("user")
|
||||
|
||||
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.SendRecoveryEmail(email, signature)
|
||||
|
||||
err = r.service.RecoveryEmailTask(c.Context(), user.ID.Hex(), email, key)
|
||||
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"})
|
||||
@ -57,9 +56,9 @@ func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error {
|
||||
|
||||
// HandleRecoveryLink обрабатывает ссылку восстановления и обменивает ее на токены
|
||||
func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error {
|
||||
signature := c.Params("sign")
|
||||
key := c.Params("sign")
|
||||
// тут получается
|
||||
record, err := r.service.GetRecoveryRecord(signature)
|
||||
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"})
|
||||
@ -67,7 +66,7 @@ func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error {
|
||||
|
||||
// проверка на более чем 15 минут
|
||||
if time.Since(record.CreatedAt) > 15*time.Minute {
|
||||
r.logger.Error("Recovery link expired", zap.String("signature", signature))
|
||||
r.logger.Error("Recovery link expired", zap.String("signature", key))
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Recovery link expired"})
|
||||
}
|
||||
|
||||
|
@ -8,15 +8,18 @@ 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:"localhost"`
|
||||
MongoPort string `env:"MONGO_PORT" envDefault:"27017"`
|
||||
MongoUser string `env:"MONGO_USER" envDefault:"admin"`
|
||||
MongoPassword string `env:"MONGO_PASSWORD" envDefault:"admin"`
|
||||
MongoDatabase string `env:"MONGO_DB" envDefault:"codeword_db"`
|
||||
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" envDefault:"test"`
|
||||
PrivateCurveKey string `env:"PRIVATE_CURVE_KEY" envDefault:"test"`
|
||||
SignSecret string `env:"SIGN_SECRET" envDefault:"test"`
|
||||
PublicCurveKey string `env:"PUBLIC_CURVE_KEY" envDefault:"localhost:6379"`
|
||||
PrivateCurveKey string `env:"PRIVATE_CURVE_KEY" envDefault:"localhost:6379"`
|
||||
SignSecret string `env:"SIGN_SECRET" envDefault:"localhost:6379"`
|
||||
RedisAddr string `env:"REDIS_ADDR" envDefault:"localhost:6379"`
|
||||
RedisPassword string `env:"REDIS_PASS" envDefault:"admin"`
|
||||
RedisDB int `env:"REDIS_DB" envDefault:"2"`
|
||||
}
|
||||
|
||||
func LoadConfig() (*Config, error) {
|
||||
|
21
internal/initialize/redis.go
Normal file
21
internal/initialize/redis.go
Normal file
@ -0,0 +1,21 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
func InitializeRedis(ctx context.Context, cfg Config) (*redis.Client, error) {
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: cfg.RedisAddr,
|
||||
Password: cfg.RedisPassword,
|
||||
DB: cfg.RedisDB,
|
||||
})
|
||||
|
||||
err := rdb.Ping(ctx)
|
||||
if err != nil {
|
||||
return nil, err.Err()
|
||||
}
|
||||
|
||||
return rdb, nil
|
||||
}
|
@ -1,9 +1,20 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"time"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
// получаем данные из другого сервиса
|
||||
ID primitive.ObjectID `bson:"_id,omitempty"`
|
||||
Login string `bson:"login,omitempty"`
|
||||
Email string `bson:"email,omitempty"`
|
||||
Password string `bson:"password,omitempty"`
|
||||
PhoneNumber string `bson:"phoneNumber,omitempty"`
|
||||
IsDeleted bool `bson:"isDeleted,omitempty"`
|
||||
CreatedAt time.Time `bson:"createdAt,omitempty"`
|
||||
UpdatedAt time.Time `bson:"updatedAt,omitempty"`
|
||||
DeletedAt *time.Time `bson:"deletedAt,omitempty"`
|
||||
}
|
||||
|
||||
type RestoreRequest struct {
|
||||
@ -15,3 +26,10 @@ type RestoreRequest struct {
|
||||
Sent bool
|
||||
SentAt time.Time
|
||||
}
|
||||
|
||||
type RecoveryRecord struct {
|
||||
UserID string `bson:"user_id"`
|
||||
Email string `bson:"email"`
|
||||
Key string `bson:"key"`
|
||||
CreatedAt time.Time `bson:"created_at"`
|
||||
}
|
||||
|
@ -3,36 +3,94 @@ package repository
|
||||
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/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/readpref"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UserRepository struct {
|
||||
db *mongo.Database
|
||||
type Deps struct {
|
||||
Mdb *mongo.Collection
|
||||
Rdb *redis.Client
|
||||
}
|
||||
|
||||
//todo реализовать
|
||||
|
||||
func NewUserRepository(db *mongo.Database) *UserRepository {
|
||||
return &UserRepository{db}
|
||||
type codewordRepository struct {
|
||||
mdb *mongo.Collection
|
||||
rdb *redis.Client
|
||||
}
|
||||
|
||||
func (r *UserRepository) FindByEmail(email string) (*models.User, error) {
|
||||
//todo
|
||||
return &models.User{}, nil
|
||||
type userRepository struct {
|
||||
mdb *mongo.Collection
|
||||
}
|
||||
|
||||
func (r *UserRepository) StoreRecoveryRecord(userID, signature string, createdAt time.Time) error {
|
||||
//todo
|
||||
func NewUserRepository(deps Deps) *userRepository {
|
||||
|
||||
return &userRepository{mdb: deps.Mdb}
|
||||
}
|
||||
|
||||
func NewCodewordRepository(deps Deps) *codewordRepository {
|
||||
|
||||
return &codewordRepository{mdb: deps.Mdb, rdb: deps.Rdb}
|
||||
}
|
||||
|
||||
func (r *userRepository) FindByEmail(ctx context.Context, email string) (*models.User, error) {
|
||||
var user models.User
|
||||
|
||||
err := r.mdb.FindOne(ctx, bson.M{"email": email}).Decode(&user)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (r *codewordRepository) StoreRecoveryRecord(ctx context.Context, userID string, email string, key []byte) error {
|
||||
record := models.RecoveryRecord{
|
||||
UserID: userID,
|
||||
Email: email,
|
||||
Key: string(key),
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
_, err := r.mdb.InsertOne(ctx, record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetRecoveryRecord(signature string) (*models.RestoreRequest, error) {
|
||||
return &models.RestoreRequest{UserID: "123", Sign: signature, CreatedAt: time.Now()}, nil
|
||||
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(),
|
||||
}
|
||||
|
||||
taskBytes, err := json.Marshal(task)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uniqKey := fmt.Sprintf("needRecovery:%d", time.Now().UnixNano())
|
||||
|
||||
if err := r.rdb.Set(ctx, uniqKey, taskBytes, 0).Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *UserRepository) Ping(ctx context.Context) error {
|
||||
return r.db.Client().Ping(ctx, readpref.Primary())
|
||||
func (r *codewordRepository) GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) {
|
||||
return &models.RestoreRequest{UserID: "123", Sign: key, CreatedAt: time.Now()}, nil
|
||||
}
|
||||
|
||||
func (r *codewordRepository) Ping(ctx context.Context) error {
|
||||
return r.mdb.Database().Client().Ping(ctx, readpref.Primary())
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package http
|
||||
|
||||
import (
|
||||
controller "codeword/internal/controller/recovery"
|
||||
"codeword/internal/repository"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
@ -12,13 +11,11 @@ import (
|
||||
|
||||
type ServerConfig struct {
|
||||
Logger *zap.Logger
|
||||
Repo *repository.UserRepository
|
||||
RecoveryController *controller.RecoveryController
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Logger *zap.Logger
|
||||
Repo *repository.UserRepository
|
||||
RecoveryController *controller.RecoveryController
|
||||
app *fiber.App
|
||||
}
|
||||
@ -28,7 +25,6 @@ func NewServer(config ServerConfig) *Server {
|
||||
|
||||
s := &Server{
|
||||
Logger: config.Logger,
|
||||
Repo: config.Repo,
|
||||
RecoveryController: config.RecoveryController,
|
||||
app: app,
|
||||
}
|
||||
@ -61,7 +57,7 @@ func (s *Server) handleLiveness(c *fiber.Ctx) error {
|
||||
|
||||
func (s *Server) handleReadiness(c *fiber.Ctx) error {
|
||||
startTime := time.Now()
|
||||
if err := s.Repo.Ping(c.Context()); err != nil {
|
||||
if err := s.RecoveryController.HandlePingDB(c); err != nil {
|
||||
s.Logger.Error("Failed to ping the database", zap.Error(err))
|
||||
return c.Status(fiber.StatusServiceUnavailable).SendString("DB ping failed")
|
||||
}
|
||||
|
@ -3,70 +3,75 @@ package services
|
||||
import (
|
||||
"codeword/internal/models"
|
||||
"codeword/internal/utils/encrypt"
|
||||
"context"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UserRepository interface {
|
||||
FindByEmail(email string) (*models.User, error)
|
||||
StoreRecoveryRecord(userID string, signature string, createdAt time.Time) error
|
||||
GetRecoveryRecord(signature string) (*models.RestoreRequest, error)
|
||||
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
|
||||
Ping(ctx context.Context) error
|
||||
GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error)
|
||||
}
|
||||
|
||||
type EmailSender interface {
|
||||
SendRecoveryEmail(email, signature string) error
|
||||
type UserRepository interface {
|
||||
FindByEmail(ctx context.Context, email string) (*models.User, error)
|
||||
}
|
||||
|
||||
type Deps struct {
|
||||
Logger *zap.Logger
|
||||
Repository UserRepository
|
||||
Email EmailSender
|
||||
CodewordRepository CodewordRepository
|
||||
UserRepository UserRepository
|
||||
EncryptService *encrypt.Encrypt
|
||||
}
|
||||
|
||||
type RecoveryService struct {
|
||||
logger *zap.Logger
|
||||
repository UserRepository
|
||||
email EmailSender
|
||||
repositoryCodeword CodewordRepository
|
||||
repositoryUser UserRepository
|
||||
encryptService *encrypt.Encrypt
|
||||
}
|
||||
|
||||
func NewRecoveryService(deps Deps) *RecoveryService {
|
||||
return &RecoveryService{
|
||||
logger: deps.Logger,
|
||||
repository: deps.Repository,
|
||||
email: deps.Email,
|
||||
repositoryCodeword: deps.CodewordRepository,
|
||||
repositoryUser: deps.UserRepository,
|
||||
encryptService: deps.EncryptService,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateKey генерирует ключ, используя шифрование на основе эллиптической кривой
|
||||
func (s *RecoveryService) GenerateKey() (string, error) {
|
||||
// TODO
|
||||
return "", nil
|
||||
func (s *RecoveryService) GenerateKey() ([]byte, error) {
|
||||
key, err := s.encryptService.SignCommonSecret()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (s *RecoveryService) Ping(ctx context.Context) error {
|
||||
return s.repositoryCodeword.Ping(ctx)
|
||||
}
|
||||
|
||||
// FindUserByEmail ищет пользователя по электронной почте
|
||||
func (s *RecoveryService) FindUserByEmail(email string) (*models.User, error) {
|
||||
return s.repository.FindByEmail(email)
|
||||
func (s *RecoveryService) FindUserByEmail(ctx context.Context, email string) (*models.User, error) {
|
||||
return s.repositoryUser.FindByEmail(ctx, email)
|
||||
}
|
||||
|
||||
// StoreRecoveryRecord сохраняет запись восстановления в базе данных
|
||||
func (s *RecoveryService) StoreRecoveryRecord(userID string) (string, error) {
|
||||
signature := ""
|
||||
createdAt := time.Now()
|
||||
err := s.repository.StoreRecoveryRecord(userID, signature, createdAt)
|
||||
return signature, err
|
||||
}
|
||||
|
||||
// GetRecoveryRecord получает запись восстановления из базы данных
|
||||
func (s *RecoveryService) GetRecoveryRecord(signature string) (*models.RestoreRequest, error) {
|
||||
return s.repository.GetRecoveryRecord(signature)
|
||||
func (s *RecoveryService) StoreRecoveryRecord(ctx context.Context, userID string, email string, key []byte) error {
|
||||
return s.repositoryCodeword.StoreRecoveryRecord(ctx, userID, email, key)
|
||||
}
|
||||
|
||||
// SendRecoveryEmail посылает письмо для восстановления доступа пользователю
|
||||
func (s *RecoveryService) SendRecoveryEmail(email string, signature string) error {
|
||||
return s.email.SendRecoveryEmail(email, signature)
|
||||
func (s *RecoveryService) RecoveryEmailTask(ctx context.Context, userID string, email string, key []byte) error {
|
||||
return s.repositoryCodeword.InsertToQueue(ctx, userID, email, key)
|
||||
}
|
||||
|
||||
// GetRecoveryRecord получает запись восстановления из базы данных
|
||||
func (s *RecoveryService) GetRecoveryRecord(ctx context.Context, key string) (*models.RestoreRequest, error) {
|
||||
return s.repositoryCodeword.GetRecoveryRecord(ctx, key)
|
||||
}
|
||||
|
||||
// ExchangeForTokens обменивает ссылку восстановления на токены используя сервис аутентификации.
|
||||
|
@ -7,11 +7,11 @@ import (
|
||||
)
|
||||
|
||||
type Configuration struct {
|
||||
MongoHost string `env:"MONGO_HOST" envDefault:"localhost"`
|
||||
MongoPort string `env:"MONGO_PORT" envDefault:"27017"`
|
||||
MongoUser string `env:"MONGO_USER" envDefault:"admin"`
|
||||
MongoPassword string `env:"MONGO_PASSWORD" envDefault:"admin"`
|
||||
MongoDatabase string `env:"MONGO_DB" envDefault:"codeword_db"`
|
||||
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"`
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ func Connect(ctx context.Context, deps *ConnectDeps) (*mongo.Database, error) {
|
||||
connectionOptions := options.Client().
|
||||
ApplyURI(mongoURI.String()).
|
||||
SetAuth(options.Credential{
|
||||
AuthMechanism: "SCRAM-SHA-1",
|
||||
AuthMechanism: "SCRAM-SHA-256",
|
||||
AuthSource: deps.Configuration.MongoAuth,
|
||||
Username: deps.Configuration.MongoUser,
|
||||
Password: deps.Configuration.MongoPassword,
|
||||
|
91
tests/repository_test/repository_test.go
Normal file
91
tests/repository_test/repository_test.go
Normal file
@ -0,0 +1,91 @@
|
||||
package repository_test
|
||||
|
||||
import (
|
||||
"codeword/internal/models"
|
||||
"codeword/internal/repository"
|
||||
"context"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pioz/faker"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const mongoURI = "mongodb://test:test@127.0.0.1:27020/?authMechanism=SCRAM-SHA-256&authSource=admin&directConnection=true"
|
||||
|
||||
func TestFindByEmail(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to connect to MongoDB: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := client.Disconnect(ctx); err != nil {
|
||||
log.Fatalf("Failed to disconnect MongoDB client: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := client.Ping(ctx, nil); err != nil {
|
||||
log.Fatalf("Failed to ping MongoDB: %v", err)
|
||||
}
|
||||
|
||||
db := client.Database("admin")
|
||||
|
||||
userRepo := repository.NewUserRepository(repository.Deps{Rdb: nil, Mdb: db.Collection("users")})
|
||||
|
||||
t.Run("FindByEmail - existing user", func(t *testing.T) {
|
||||
user, err := userRepo.FindByEmail(ctx, "email@mail.ru")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, user)
|
||||
assert.Equal(t, "email@mail.ru", user.Email)
|
||||
})
|
||||
|
||||
t.Run("FindByEmail - non-existing user", func(t *testing.T) {
|
||||
user, err := userRepo.FindByEmail(ctx, "nonexisting@example.com")
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, user)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestStoreRecoveryRecord(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
mongoClient, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoURI))
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
_ = mongoClient.Disconnect(ctx)
|
||||
}()
|
||||
|
||||
database := mongoClient.Database("admin")
|
||||
codeword := database.Collection("codeword")
|
||||
_ = codeword.Drop(ctx)
|
||||
|
||||
userRepo := repository.NewCodewordRepository(repository.Deps{Rdb: nil, Mdb: codeword})
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
userID := faker.String()
|
||||
email := faker.Email()
|
||||
key := []byte("test_recovery_key")
|
||||
|
||||
err = userRepo.StoreRecoveryRecord(ctx, userID, email, key)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var storedRecord models.RecoveryRecord
|
||||
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)
|
||||
}
|
||||
|
||||
_ = database.Drop(ctx)
|
||||
}
|
Loading…
Reference in New Issue
Block a user