Files
goimgApi/accounts/handlers.go
Beyhan Oğur e6f3268c28 first commit
2026-04-26 21:48:15 +03:00

255 lines
7.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package accounts
import (
"strconv"
"strings"
"time"
"github.com/gofiber/fiber/v3"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
"goimgApi/configs"
)
type AuthReq struct {
Email string `json:"email"`
Password string `json:"password"`
}
// Register godoc
// @Summary Register a new user
// @Description Register a new user with email and password
// @Tags Auth
// @Accept multipart/form-data
// @Produce json
// @Param email formData string true "Email address"
// @Param password formData string true "Password (min 6 chars)"
// @Success 201 {object} map[string]interface{}
// @Failure 400 {object} map[string]interface{}
// @Router /auth/register [post]
func Register(c fiber.Ctx) error {
email := c.FormValue("email")
password := c.FormValue("password")
if email == "" || len(password) < 6 {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Email required and password min 6 chars"})
}
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to hash password"})
}
user := User{
Email: email,
PasswordHash: string(hash),
}
if err := configs.DB.Create(&user).Error; err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Email might be already in use"})
}
return c.Status(fiber.StatusCreated).JSON(fiber.Map{"message": "User registered", "user_id": user.ID})
}
// Login godoc
// @Summary Login
// @Description Authenticate user and get JWT
// @Tags Auth
// @Accept multipart/form-data
// @Produce json
// @Param email formData string true "Email address"
// @Param password formData string true "Password"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} map[string]interface{}
// @Router /auth/login [post]
func Login(c fiber.Ctx) error {
email := c.FormValue("email")
password := c.FormValue("password")
var user User
if err := configs.DB.Where("email = ?", email).First(&user).Error; err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid credentials"})
}
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid credentials"})
}
role := roleFromUser(user)
access, refresh, err := GenerateTokens(user.ID, role)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Token generation failed"})
}
return c.JSON(fiber.Map{
"access_token": access,
"refresh_token": refresh,
"user": fiber.Map{
"id": user.ID,
"email": user.Email,
"role": role,
},
})
}
type RefreshReq struct {
RefreshToken string `json:"refresh_token"`
}
// Refresh godoc
// @Summary Refresh JWT
// @Description Get a new access token using a valid refresh token
// @Tags Auth
// @Accept multipart/form-data
// @Produce json
// @Param refresh_token formData string true "Refresh token"
// @Success 200 {object} map[string]interface{}
// @Failure 401 {object} map[string]interface{}
// @Failure 400 {object} map[string]interface{}
// @Failure 500 {object} map[string]interface{}
// @Router /auth/refresh [post]
func Refresh(c fiber.Ctx) error {
refreshToken := c.FormValue("refresh_token")
if refreshToken == "" {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Refresh token required"})
}
userID, err := ParseRefreshToken(refreshToken)
if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid or expired refresh token"})
}
var user User
if err := configs.DB.First(&user, userID).Error; err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "User not found"})
}
acc, ref, err := GenerateTokens(userID, roleFromUser(user))
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Token generation failed"})
}
return c.JSON(fiber.Map{
"access_token": acc,
"refresh_token": ref,
})
}
// JWTMiddleware extracts user_id from access token
func JWTMiddleware(c fiber.Ctx) error {
authHeader := c.Get("Authorization")
if authHeader == "" {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Unauthorized"})
}
// Swagger UI esnekliği için "Bearer " prefix'ini isteğe bağlı kılıyoruz.
tokenStr := strings.TrimSpace(strings.TrimPrefix(authHeader, "Bearer"))
claims, err := parseAccessClaims(tokenStr)
if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid or expired token"})
}
// Set userID context
c.Locals("user_id", claims.UserID)
c.Locals("role", claims.Role)
return c.Next()
}
// AdminMiddleware ensures the logged-in user is an admin
func AdminMiddleware(c fiber.Ctx) error {
userIDVal := c.Locals("user_id")
if userIDVal == nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Unauthorized"})
}
var user User
if err := configs.DB.First(&user, userIDVal).Error; err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "User not found"})
}
if !user.IsAdmin {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "Admin privileges required"})
}
return c.Next()
}
type CreateApiTokenReq struct {
ExpiresInDays int `json:"expires_in_days"`
}
// CreateApiToken godoc
// @Summary Create API Token
// @Description Creates an API Token for a user (Admin ONLY).
// @Tags Admin
// @Accept multipart/form-data
// @Produce json
// @Security BearerAuth
// @Param id path int true "User ID"
// @Param expires_in_days formData int false "Expiration in days (0 or omit for never)"
// @Success 200 {object} map[string]interface{}
// @Failure 401 {object} map[string]interface{}
// @Failure 403 {object} map[string]interface{}
// @Failure 404 {object} map[string]interface{}
// @Router /admin/users/{id}/api-token [post]
func CreateApiToken(c fiber.Ctx) error {
targetID := c.Params("id")
expiresInDays := 0
if f := c.FormValue("expires_in_days"); f != "" {
if val, err := strconv.Atoi(f); err == nil {
expiresInDays = val
}
}
// Fallback eklendi: Eger FormData gelmediyse Query'den parametreyi deneriz
if q := c.Query("expires_in_days"); q != "" {
if val, err := strconv.Atoi(q); err == nil {
expiresInDays = val
}
}
var user User
if err := configs.DB.First(&user, targetID).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
}
// Generate and update
token := uuid.New().String()
user.ApiToken = token
if expiresInDays > 0 {
exp := time.Now().Add(time.Duration(expiresInDays) * 24 * time.Hour)
user.ApiTokenExpiresAt = &exp
} else {
user.ApiTokenExpiresAt = nil
}
if err := configs.DB.Save(&user).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to save API token"})
}
responseMap := fiber.Map{
"message": "API token created successfully",
"api_token": token,
}
if user.ApiTokenExpiresAt != nil {
responseMap["expires_at"] = user.ApiTokenExpiresAt
} else {
responseMap["expires_at"] = "never"
}
return c.JSON(responseMap)
}
func roleFromUser(user User) string {
if user.IsAdmin {
return RoleAdmin
}
return RoleUser
}