Files
GopherGate/internal/server/dashboard.go
T
hobokenchicken e598150d90
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Build (push) Has been cancelled
fix: trim imports per file — no unused imports in split files
2026-04-26 14:58:53 -04:00

170 lines
4.7 KiB
Go

package server
import (
"database/sql"
"fmt"
"net/http"
"strings"
"time"
"log/slog"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
"gophergate/internal/db"
"gophergate/internal/utils"
)
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"}))
}