2fa6f0df62
- analytics.go: UsagePeriodFilter, UsageSummary, TimeSeries, ProvidersUsage, ClientsUsage, AnalyticsBreakdown, DetailedUsage - models_config.go: handleGetModels, handleUpdateModel - Fix all import blocks with missing closing parens - Remove leftover fmt.Printf warnings in server.go
178 lines
5.0 KiB
Go
178 lines
5.0 KiB
Go
package server
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"golang.org/x/crypto/bcrypt"
|
|
"gophergate/internal/db"
|
|
"gophergate/internal/models"
|
|
"gophergate/internal/utils"
|
|
"log/slog"
|
|
|
|
"github.com/shirou/gopsutil/v3/cpu"
|
|
"github.com/shirou/gopsutil/v3/disk"
|
|
"github.com/shirou/gopsutil/v3/load"
|
|
"github.com/shirou/gopsutil/v3/mem"
|
|
"github.com/shirou/gopsutil/v3/process"
|
|
)
|
|
|
|
type ApiResponse struct {
|
|
Success bool `json:"success"`
|
|
Data interface{} `json:"data,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
func SuccessResponse(data interface{}) ApiResponse {
|
|
return ApiResponse{Success: true, Data: data}
|
|
}
|
|
|
|
func ErrorResponse(err string) ApiResponse {
|
|
return ApiResponse{Success: false, Error: err}
|
|
}
|
|
|
|
func (s *Server) adminAuthMiddleware() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
token := strings.TrimPrefix(c.GetHeader("Authorization"), "Bearer ")
|
|
if token == "" {
|
|
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrorResponse("Not authenticated"))
|
|
return
|
|
}
|
|
|
|
session, _, err := s.sessions.ValidateSession(token)
|
|
if err != nil {
|
|
c.AbortWithStatusJSON(http.StatusUnauthorized, ErrorResponse("Session expired or invalid"))
|
|
return
|
|
}
|
|
|
|
if session.Role != "admin" {
|
|
c.AbortWithStatusJSON(http.StatusForbidden, ErrorResponse("Admin access required"))
|
|
return
|
|
}
|
|
|
|
c.Set("session", session)
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
type LoginRequest struct {
|
|
Username string `json:"username" binding:"required"`
|
|
Password string `json:"password" binding:"required"`
|
|
}
|
|
|
|
func (s *Server) handleLogin(c *gin.Context) {
|
|
var req LoginRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, ErrorResponse("Invalid request"))
|
|
return
|
|
}
|
|
|
|
var user db.User
|
|
err := s.database.Get(&user, "SELECT username, password_hash, display_name, role, must_change_password FROM users WHERE username = ?", req.Username)
|
|
if err != nil {
|
|
c.JSON(http.StatusUnauthorized, ErrorResponse("Invalid username or password"))
|
|
return
|
|
}
|
|
|
|
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password)); err != nil {
|
|
c.JSON(http.StatusUnauthorized, ErrorResponse("Invalid username or password"))
|
|
return
|
|
}
|
|
|
|
displayName := user.Username
|
|
if user.DisplayName != nil {
|
|
displayName = *user.DisplayName
|
|
}
|
|
|
|
token, err := s.sessions.CreateSession(user.Username, displayName, user.Role)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, ErrorResponse("Failed to create session"))
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, SuccessResponse(gin.H{
|
|
"token": token,
|
|
"must_change_password": user.MustChangePassword,
|
|
"user": user,
|
|
}))
|
|
}
|
|
|
|
func (s *Server) handleAuthStatus(c *gin.Context) {
|
|
token := strings.TrimPrefix(c.GetHeader("Authorization"), "Bearer ")
|
|
session, _, err := s.sessions.ValidateSession(token)
|
|
if err != nil {
|
|
c.JSON(http.StatusUnauthorized, ErrorResponse("Not authenticated"))
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, SuccessResponse(gin.H{
|
|
"authenticated": true,
|
|
"user": gin.H{
|
|
"username": session.Username,
|
|
"role": session.Role,
|
|
"display_name": session.DisplayName,
|
|
},
|
|
}))
|
|
}
|
|
|
|
type ChangePasswordRequest struct {
|
|
CurrentPassword string `json:"current_password" binding:"required"`
|
|
NewPassword string `json:"new_password" binding:"required"`
|
|
}
|
|
|
|
func (s *Server) handleChangePassword(c *gin.Context) {
|
|
token := strings.TrimPrefix(c.GetHeader("Authorization"), "Bearer ")
|
|
session, _, err := s.sessions.ValidateSession(token)
|
|
if err != nil {
|
|
c.JSON(http.StatusUnauthorized, ErrorResponse("Not authenticated"))
|
|
return
|
|
}
|
|
|
|
var req ChangePasswordRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, ErrorResponse("Invalid request"))
|
|
return
|
|
}
|
|
|
|
var user db.User
|
|
err = s.database.Get(&user, "SELECT password_hash FROM users WHERE username = ?", session.Username)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, ErrorResponse("User not found"))
|
|
return
|
|
}
|
|
|
|
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.CurrentPassword)); err != nil {
|
|
c.JSON(http.StatusUnauthorized, ErrorResponse("Current password incorrect"))
|
|
return
|
|
}
|
|
|
|
newHash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), 12)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, ErrorResponse("Failed to hash new password"))
|
|
return
|
|
}
|
|
|
|
_, err = s.database.Exec("UPDATE users SET password_hash = ?, must_change_password = 0 WHERE username = ?", string(newHash), session.Username)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, ErrorResponse("Failed to update password"))
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, SuccessResponse(gin.H{"message": "Password updated successfully"}))
|
|
}
|
|
|
|
func (s *Server) handleLogout(c *gin.Context) {
|
|
token := strings.TrimPrefix(c.GetHeader("Authorization"), "Bearer ")
|
|
if err := s.sessions.RevokeSession(token); err != nil {
|
|
fmt.Printf("Error revoking session: %v\n", err)
|
|
}
|
|
c.JSON(http.StatusOK, SuccessResponse(gin.H{"message": "Logged out"}))
|
|
}
|
|
|