255 lines
7.1 KiB
Go
255 lines
7.1 KiB
Go
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
|
||
}
|