codeword/internal/controller/client/client_recovery/recovery_controller.go

153 lines
4.5 KiB
Go
Raw Normal View History

package client_recovery
2023-12-29 11:30:20 +00:00
import (
2024-01-18 16:49:55 +00:00
"encoding/base64"
"errors"
2024-11-22 11:22:08 +00:00
"gitea.pena/PenaSide/codeword/internal/models"
"gitea.pena/PenaSide/codeword/internal/repository"
"gitea.pena/PenaSide/codeword/internal/services"
2023-12-29 11:30:20 +00:00
"github.com/gofiber/fiber/v2"
"go.uber.org/zap"
2024-11-22 11:32:32 +00:00
"gitea.pena/PenaSide/common/log_mw"
2023-12-29 11:30:20 +00:00
"time"
2025-03-22 20:47:39 +00:00
"strings"
2023-12-29 11:30:20 +00:00
)
2024-05-28 11:05:31 +00:00
type Deps struct {
Logger *zap.Logger
Service *services.RecoveryService
DefaultURL string
RecoveryURL string
}
2023-12-29 11:30:20 +00:00
type RecoveryController struct {
2024-05-28 11:05:31 +00:00
logger *zap.Logger
service *services.RecoveryService
defaultURL string
recoveryURL string
2023-12-29 11:30:20 +00:00
}
2024-05-28 11:05:31 +00:00
func NewRecoveryController(deps Deps) *RecoveryController {
2023-12-29 11:30:20 +00:00
return &RecoveryController{
2024-05-28 11:05:31 +00:00
logger: deps.Logger,
service: deps.Service,
defaultURL: deps.DefaultURL,
recoveryURL: deps.RecoveryURL,
2023-12-29 11:30:20 +00:00
}
}
func (r *RecoveryController) HandleRecoveryRequest(c *fiber.Ctx) error {
hlogger := log_mw.ExtractLogger(c)
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
2025-03-23 21:51:36 +00:00
signUrl := referralURL + req.RedirectionURL
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{
2024-05-28 11:05:31 +00:00
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 записи
2025-03-23 21:51:36 +00:00
err = r.service.RecoveryEmailTask(c.Context(), models.RecEmailDeps{UserID: user.ID.Hex(), Email: req.Email, SignWithID: strings.Replace(signUrl, "/changepwd","",1) + "/"+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))
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-05-28 11:05:31 +00:00
hlogger.Emit(models.InfoPasswordRestorationRequested{
CtxID: id,
CtxUserID: user.ID.Hex(),
CtxReturnURL: r.recoveryURL + signWithID,
CtxEmail: req.Email,
})
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 {
hlogger := log_mw.ExtractLogger(c)
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-03-14 09:55:33 +00:00
r.logger.Error("Recovery link expired", zap.String("signature", sign))
2025-03-23 21:51:36 +00:00
return c.Redirect("https://hub.pena.digital/recover/expired")
2023-12-29 11:30:20 +00:00
}
if time.Since(record.CreatedAt) > 15*time.Minute {
2024-03-14 09:55:33 +00:00
r.logger.Error("Recovery link expired", zap.String("signature", sign))
2024-05-28 11:05:31 +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-05-28 11:05:31 +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-18 22:01:15 +00:00
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,
})
2024-05-28 11:05:31 +00:00
hlogger.Emit(models.InfoPasswordRestored{
CtxID: record.ID.String(),
CtxUserID: record.UserID,
2024-05-28 11:05:31 +00:00
})
2024-01-22 19:36:19 +00:00
return c.Redirect(record.SignUrl + "?auth=" + tokens["accessToken"])
2023-12-29 11:30:20 +00:00
}