Files
Luciano Milani 5de37bb512 Inicial
2026-07-02 12:47:55 +01:00

171 lines
5.0 KiB
Go

package auth
import (
"context"
"strings"
"time"
"github.com/gofiber/fiber/v2"
goredis "github.com/redis/go-redis/v9"
"github.com/techxcar/backend/internal/config"
redispkg "github.com/techxcar/backend/pkg/redis"
)
// LoginRepository is a subset of tenant.Repository used by auth handlers.
// Uses local types to avoid a circular import with the tenant package.
type LoginRepository interface {
GetSuperAdminByEmail(ctx context.Context, email string) (*LoginSuperAdmin, error)
GetTenantBySlug(ctx context.Context, slug string) (*LoginTenant, error)
GetTenantUserByEmail(ctx context.Context, tenantID, email string) (*LoginUser, error)
}
type LoginSuperAdmin struct {
ID string
Email string
PasswordHash string
}
type LoginTenant struct {
ID string
Status string
}
type LoginUser struct {
ID string
Email string
PasswordHash string
Role string
Name string
Active bool
}
const refreshCookieName = "refresh_token"
const refreshCookieTTL = 30 * 24 * time.Hour
func SetRefreshCookie(c *fiber.Ctx, token string, cfg *config.Config) {
c.Cookie(&fiber.Cookie{
Name: refreshCookieName,
Value: token,
MaxAge: int(refreshCookieTTL.Seconds()),
HTTPOnly: true,
Secure: cfg.AppEnv == "production",
SameSite: "Strict",
Path: "/api/v1/auth",
})
}
type loginRequest struct {
Email string `json:"email"`
Password string `json:"password"`
TenantSlug string `json:"tenant_slug"`
}
func LoginHandler(repo LoginRepository, rdb *redispkg.Redis, cfg *config.Config) fiber.Handler {
return func(c *fiber.Ctx) error {
var req loginRequest
if err := c.BodyParser(&req); err != nil {
return fiber.NewError(400, "corpo do pedido inválido")
}
req.Email = strings.TrimSpace(strings.ToLower(req.Email))
if req.Email == "" || req.Password == "" {
return fiber.NewError(400, "email e password são obrigatórios")
}
var userID, tenantID, role string
userPayload := fiber.Map{}
if req.TenantSlug == "" {
admin, err := repo.GetSuperAdminByEmail(c.Context(), req.Email)
if err != nil || admin == nil || !VerifyPassword(req.Password, admin.PasswordHash) {
return fiber.NewError(401, "credenciais inválidas")
}
userID, tenantID, role = admin.ID, "", "super_admin"
userPayload = fiber.Map{
"id": admin.ID, "email": admin.Email,
"name": admin.Email, "role": "super_admin",
}
} else {
ten, err := repo.GetTenantBySlug(c.Context(), req.TenantSlug)
if err != nil || ten == nil || ten.Status != "active" {
return fiber.NewError(401, "credenciais inválidas")
}
user, err := repo.GetTenantUserByEmail(c.Context(), ten.ID, req.Email)
if err != nil || user == nil || !user.Active || !VerifyPassword(req.Password, user.PasswordHash) {
return fiber.NewError(401, "credenciais inválidas")
}
userID, tenantID, role = user.ID, ten.ID, user.Role
userPayload = fiber.Map{
"id": user.ID, "email": user.Email,
"name": user.Name, "role": user.Role,
"tenantId": ten.ID,
}
}
access, err := GenerateAccessToken(userID, tenantID, role, cfg.JWTSecret)
if err != nil {
return fiber.NewError(500, "erro ao gerar token")
}
refresh, err := GenerateRefreshToken(userID, tenantID, role, cfg.JWTSecret)
if err != nil {
return fiber.NewError(500, "erro ao gerar token")
}
if rdb != nil {
ctx := c.Context()
rdb.Client.Set(ctx, "refresh:"+userID, refresh, refreshCookieTTL)
}
SetRefreshCookie(c, refresh, cfg)
return c.JSON(fiber.Map{"data": fiber.Map{"access_token": access, "user": userPayload}, "error": nil})
}
}
func RefreshHandler(rdb *redispkg.Redis, cfg *config.Config) fiber.Handler {
return func(c *fiber.Ctx) error {
refreshToken := c.Cookies(refreshCookieName)
if refreshToken == "" {
return fiber.NewError(401, "refresh token em falta")
}
claims, err := ValidateToken(refreshToken, cfg.JWTSecret)
if err != nil {
return fiber.NewError(401, "refresh token inválido ou expirado")
}
if rdb != nil {
stored, err := rdb.Client.Get(c.Context(), "refresh:"+claims.UserID).Result()
if err == goredis.Nil || stored != refreshToken {
return fiber.NewError(401, "sessão inválida")
}
}
access, err := GenerateAccessToken(claims.UserID, claims.TenantID, claims.Role, cfg.JWTSecret)
if err != nil {
return fiber.NewError(500, "erro ao renovar token")
}
newRefresh, err := GenerateRefreshToken(claims.UserID, claims.TenantID, claims.Role, cfg.JWTSecret)
if err != nil {
return fiber.NewError(500, "erro ao renovar token")
}
if rdb != nil {
rdb.Client.Set(c.Context(), "refresh:"+claims.UserID, newRefresh, refreshCookieTTL)
}
SetRefreshCookie(c, newRefresh, cfg)
return c.JSON(fiber.Map{"data": fiber.Map{"access_token": access}, "error": nil})
}
}
func LogoutHandler(rdb *redispkg.Redis) fiber.Handler {
return func(c *fiber.Ctx) error {
c.Cookie(&fiber.Cookie{
Name: refreshCookieName,
Value: "",
MaxAge: -1,
HTTPOnly: true,
Path: "/api/v1/auth",
})
return c.JSON(fiber.Map{"data": nil, "error": nil})
}
}