2024-01-11 18:20:33 +00:00
|
|
|
|
package recovery
|
2023-12-29 11:30:20 +00:00
|
|
|
|
|
|
|
|
|
import (
|
2024-01-18 16:49:55 +00:00
|
|
|
|
"codeword/internal/models"
|
2024-01-17 09:37:41 +00:00
|
|
|
|
"codeword/internal/repository"
|
2023-12-29 11:30:20 +00:00
|
|
|
|
"codeword/internal/services"
|
2024-01-18 16:49:55 +00:00
|
|
|
|
"encoding/base64"
|
2024-01-17 09:37:41 +00:00
|
|
|
|
"errors"
|
2024-01-15 08:43:55 +00:00
|
|
|
|
"fmt"
|
2023-12-29 11:30:20 +00:00
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type RecoveryController struct {
|
2024-01-03 15:45:41 +00:00
|
|
|
|
logger *zap.Logger
|
|
|
|
|
service *services.RecoveryService
|
|
|
|
|
defaultURL string
|
2023-12-29 11:30:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-01-03 15:45:41 +00:00
|
|
|
|
func NewRecoveryController(logger *zap.Logger, service *services.RecoveryService, defaultRedirectionURL string) *RecoveryController {
|
2023-12-29 11:30:20 +00:00
|
|
|
|
return &RecoveryController{
|
2024-01-03 15:45:41 +00:00
|
|
|
|
logger: logger,
|
|
|
|
|
service: service,
|
|
|
|
|
defaultURL: defaultRedirectionURL,
|
2023-12-29 11:30:20 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-25 12:00:05 +00:00
|
|
|
|
func (r *RecoveryController) HandleLiveness(c *fiber.Ctx) error {
|
|
|
|
|
return c.SendStatus(fiber.StatusOK)
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-31 12:22:03 +00:00
|
|
|
|
func (r *RecoveryController) HandlePingDB(c *fiber.Ctx) error {
|
2024-01-15 08:43:55 +00:00
|
|
|
|
startTime := time.Now()
|
|
|
|
|
if err := r.service.Ping(c.Context()); err != nil {
|
|
|
|
|
r.logger.Error("Failed to ping the database", zap.Error(err))
|
|
|
|
|
return c.Status(fiber.StatusServiceUnavailable).SendString("DB ping failed")
|
|
|
|
|
}
|
|
|
|
|
duration := time.Since(startTime)
|
|
|
|
|
|
|
|
|
|
durationMillis := duration.Milliseconds()
|
|
|
|
|
responseMessage := fmt.Sprintf("DB ping success - Time taken: %d ms", durationMillis)
|
|
|
|
|
|
|
|
|
|
return c.Status(fiber.StatusOK).SendString(responseMessage)
|
2024-01-18 17:04:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-12-29 11:30:20 +00:00
|
|
|
|
func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error {
|
2024-01-15 08:43:55 +00:00
|
|
|
|
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"})
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-22 19:36:19 +00:00
|
|
|
|
if req.Email == "" {
|
|
|
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "email is required"})
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-03 15:45:41 +00:00
|
|
|
|
referralURL := c.Get("Referrer")
|
|
|
|
|
|
2024-01-15 08:43:55 +00:00
|
|
|
|
if req.RedirectionURL == "" && referralURL != "" {
|
|
|
|
|
req.RedirectionURL = referralURL
|
|
|
|
|
} else if req.RedirectionURL == "" {
|
|
|
|
|
req.RedirectionURL = r.defaultURL
|
2024-01-03 15:45:41 +00:00
|
|
|
|
}
|
2023-12-29 11:30:20 +00:00
|
|
|
|
|
2024-01-15 08:43:55 +00:00
|
|
|
|
user, err := r.service.FindUserByEmail(c.Context(), req.Email)
|
2023-12-31 12:22:03 +00:00
|
|
|
|
if err != nil || user == nil {
|
2023-12-29 18:02:50 +00:00
|
|
|
|
r.logger.Error("Failed to find user by email", zap.Error(err))
|
2024-01-18 15:13:02 +00:00
|
|
|
|
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-18 17:25:40 +00:00
|
|
|
|
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"})
|
|
|
|
|
}
|
2024-01-18 16:49:55 +00:00
|
|
|
|
|
2024-03-14 09:51:48 +00:00
|
|
|
|
<<<<<<< HEAD
|
2024-01-18 22:01:15 +00:00
|
|
|
|
signUrl := redirectionURL
|
2024-03-14 09:51:48 +00:00
|
|
|
|
=======
|
2024-01-19 12:10:54 +00:00
|
|
|
|
signUrl := req.RedirectionURL
|
2024-03-14 09:51:48 +00:00
|
|
|
|
>>>>>>> dev
|
2024-01-18 16:49:55 +00:00
|
|
|
|
sign := base64.URLEncoding.EncodeToString(key)
|
|
|
|
|
|
2024-01-18 22:01:15 +00:00
|
|
|
|
id, err := r.service.StoreRecoveryRecord(c.Context(), models.StoreRecDeps{
|
|
|
|
|
UserID: user.ID.Hex(),
|
|
|
|
|
Email: user.Email,
|
|
|
|
|
Key: sign,
|
|
|
|
|
Url: signUrl,
|
|
|
|
|
})
|
2024-01-18 16:49:55 +00:00
|
|
|
|
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 записи
|
|
|
|
|
|
2024-01-15 08:43:55 +00:00
|
|
|
|
err = r.service.RecoveryEmailTask(c.Context(), models.RecEmailDeps{UserID: user.ID.Hex(), Email: req.Email, SignWithID: signWithID, ID: id})
|
2024-01-18 16:49:55 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
r.logger.Error("Failed to send recovery email", zap.Error(err))
|
2024-01-17 09:37:41 +00:00
|
|
|
|
|
|
|
|
|
if errors.Is(err, repository.ErrAlreadyReported) {
|
|
|
|
|
return c.Status(fiber.StatusAlreadyReported).JSON(fiber.Map{"error": "already reported"})
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-18 16:49:55 +00:00
|
|
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-17 08:28:33 +00:00
|
|
|
|
return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Recovery email sent successfully"})
|
2023-12-29 11:30:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *RecoveryController) HandleRecoveryLink(c *fiber.Ctx) error {
|
2024-01-15 13:32:46 +00:00
|
|
|
|
sign := c.Params("sign")
|
2024-01-04 11:27:50 +00:00
|
|
|
|
|
2024-01-15 13:32:46 +00:00
|
|
|
|
record, err := r.service.GetRecoveryRecord(c.Context(), sign)
|
2023-12-29 11:30:20 +00:00
|
|
|
|
if err != nil {
|
2024-01-18 22:01:15 +00:00
|
|
|
|
r.logger.Error("Recovery link expired", zap.String("signature", key))
|
|
|
|
|
return c.Redirect("https://shub.pena.digital/recover/expired")
|
2023-12-29 11:30:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if time.Since(record.CreatedAt) > 15*time.Minute {
|
2023-12-31 12:22:03 +00:00
|
|
|
|
r.logger.Error("Recovery link expired", zap.String("signature", key))
|
2024-01-18 22:01:15 +00:00
|
|
|
|
return c.Redirect(record.SignUrl+"/expired")
|
|
|
|
|
}
|
2023-12-29 11:30:20 +00:00
|
|
|
|
|
2024-01-04 14:57:30 +00:00
|
|
|
|
tokens, err := r.service.ExchangeForTokens(record.UserID, record.Sign)
|
2023-12-29 11:30:20 +00:00
|
|
|
|
if err != nil {
|
2023-12-29 18:02:50 +00:00
|
|
|
|
r.logger.Error("Failed to exchange recovery link for tokens", zap.Error(err))
|
2023-12-29 11:30:20 +00:00
|
|
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal Server Error"})
|
|
|
|
|
}
|
2024-01-18 22:01:15 +00:00
|
|
|
|
c.Cookie(&fiber.Cookie{
|
|
|
|
|
Name: "refreshToken",
|
|
|
|
|
Value: tokens["refreshToken"],
|
|
|
|
|
Domain: ".pena.digital",
|
|
|
|
|
Expires: time.Now().Add(30 * 24 * time.Hour),
|
|
|
|
|
Secure: true,
|
|
|
|
|
HTTPOnly: true,
|
|
|
|
|
})
|
|
|
|
|
|
2024-01-22 19:36:19 +00:00
|
|
|
|
c.Cookie(&fiber.Cookie{
|
|
|
|
|
Name: "refreshToken",
|
|
|
|
|
Value: tokens["refreshToken"],
|
|
|
|
|
Domain: ".pena.digital",
|
|
|
|
|
Expires: time.Now().Add(30 * 24 * time.Hour),
|
|
|
|
|
Secure: true,
|
|
|
|
|
HTTPOnly: true,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return c.Redirect(record.SignUrl + "?auth=" + tokens["accessToken"])
|
2023-12-29 11:30:20 +00:00
|
|
|
|
}
|