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 }