152 lines
4.4 KiB
Go
152 lines
4.4 KiB
Go
package client_recovery
|
||
|
||
import (
|
||
"codeword/internal/models"
|
||
"codeword/internal/repository"
|
||
"codeword/internal/services"
|
||
"encoding/base64"
|
||
"errors"
|
||
"github.com/gofiber/fiber/v2"
|
||
"go.uber.org/zap"
|
||
"penahub.gitlab.yandexcloud.net/backend/penahub_common/log_mw"
|
||
"time"
|
||
)
|
||
|
||
type Deps struct {
|
||
Logger *zap.Logger
|
||
Service *services.RecoveryService
|
||
DefaultURL string
|
||
RecoveryURL string
|
||
}
|
||
|
||
type RecoveryController struct {
|
||
logger *zap.Logger
|
||
service *services.RecoveryService
|
||
defaultURL string
|
||
recoveryURL string
|
||
}
|
||
|
||
func NewRecoveryController(deps Deps) *RecoveryController {
|
||
return &RecoveryController{
|
||
logger: deps.Logger,
|
||
service: deps.Service,
|
||
defaultURL: deps.DefaultURL,
|
||
recoveryURL: deps.RecoveryURL,
|
||
}
|
||
}
|
||
|
||
func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error {
|
||
hlogger := log_mw.ExtractLogger(c)
|
||
var req models.RecoveryRequest
|
||
if err := c.BodyParser(&req); err != nil {
|
||
r.logger.Error("Failed to parse recovery request", zap.Error(err))
|
||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Bad Request"})
|
||
}
|
||
|
||
if req.Email == "" {
|
||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "email is required"})
|
||
}
|
||
|
||
referralURL := c.Get("Referrer")
|
||
|
||
if req.RedirectionURL == "" && referralURL != "" {
|
||
req.RedirectionURL = referralURL
|
||
} else if req.RedirectionURL == "" {
|
||
req.RedirectionURL = r.defaultURL
|
||
}
|
||
|
||
user, err := r.service.FindUserByEmail(c.Context(), req.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 := req.RedirectionURL
|
||
sign := base64.URLEncoding.EncodeToString(key)
|
||
|
||
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"})
|
||
}
|
||
|
||
signWithID := sign + id // подпись с id записи
|
||
|
||
err = r.service.RecoveryEmailTask(c.Context(), models.RecEmailDeps{UserID: user.ID.Hex(), Email: req.Email, SignWithID: signWithID, ID: id})
|
||
if err != nil {
|
||
r.logger.Error("Failed to send recovery email", zap.Error(err))
|
||
|
||
if errors.Is(err, repository.ErrAlreadyReported) {
|
||
return c.Status(fiber.StatusAlreadyReported).JSON(fiber.Map{"error": "already reported"})
|
||
}
|
||
|
||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
|
||
}
|
||
|
||
hlogger.Emit(models.InfoPasswordRestorationRequested{
|
||
CtxID: id,
|
||
CtxUserID: user.ID.Hex(),
|
||
CtxReturnURL: r.recoveryURL + signWithID,
|
||
CtxEmail: req.Email,
|
||
})
|
||
|
||
return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Recovery email sent successfully"})
|
||
}
|
||
|
||
func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error {
|
||
hlogger := log_mw.ExtractLogger(c)
|
||
sign := c.Params("sign")
|
||
|
||
record, err := r.service.GetRecoveryRecord(c.Context(), sign)
|
||
if err != nil {
|
||
r.logger.Error("Recovery link expired", zap.String("signature", sign))
|
||
return c.Redirect("https://shub.pena.digital/recover/expired")
|
||
}
|
||
|
||
if time.Since(record.CreatedAt) > 15*time.Minute {
|
||
r.logger.Error("Recovery link expired", zap.String("signature", sign))
|
||
return c.Redirect(record.SignUrl + "/expired")
|
||
}
|
||
|
||
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"})
|
||
}
|
||
c.Cookie(&fiber.Cookie{
|
||
Name: "refreshToken",
|
||
Value: tokens["refreshToken"],
|
||
Domain: ".pena.digital",
|
||
Expires: time.Now().Add(30 * 24 * time.Hour),
|
||
Secure: true,
|
||
HTTPOnly: true,
|
||
})
|
||
|
||
c.Cookie(&fiber.Cookie{
|
||
Name: "refreshToken",
|
||
Value: tokens["refreshToken"],
|
||
Domain: ".pena.digital",
|
||
Expires: time.Now().Add(30 * 24 * time.Hour),
|
||
Secure: true,
|
||
HTTPOnly: true,
|
||
})
|
||
|
||
hlogger.Emit(models.InfoPasswordRestored{
|
||
CtxID: record.ID.String(),
|
||
CtxUserID: record.UserID,
|
||
})
|
||
|
||
return c.Redirect(record.SignUrl + "?auth=" + tokens["accessToken"])
|
||
}
|