4d34c6d31a
CI / test (push) Failing after 12m45s
- Add AdminUser model (bcrypt hashed passwords) and admin_users table - Add AdminJWTService for HS256 JWT sessions (24h TTL) - Add AdminSessionAuth middleware for /api/v1/admin/* routes - Add admin handlers: login, logout, me, change-password, users CRUD - Keys and model management routes now require admin JWT session - Remove ADMIN_TOKEN, add ADMIN_USERNAME, ADMIN_PASSWORD env vars - Update frontend: username/password login, admin_session storage, AdminUsers CRUD view
72 lines
2.0 KiB
Go
72 lines
2.0 KiB
Go
package admin
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/llamalink/llamalink/internal/auth"
|
|
"github.com/llamalink/llamalink/internal/db"
|
|
)
|
|
|
|
type LoginHandler struct {
|
|
db *gorm.DB
|
|
jwtService *auth.AdminJWTService
|
|
}
|
|
|
|
func NewLoginHandler(db *gorm.DB, jwtService *auth.AdminJWTService) *LoginHandler {
|
|
return &LoginHandler{db: db, jwtService: jwtService}
|
|
}
|
|
|
|
type LoginRequest struct {
|
|
Username string `json:"username" binding:"required"`
|
|
Password string `json:"password" binding:"required,min=8"`
|
|
}
|
|
|
|
func (h *LoginHandler) Login(c *gin.Context) {
|
|
var req LoginRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": gin.H{"code": "validation_error", "message": "username and password are required (password min 8 chars)"}})
|
|
return
|
|
}
|
|
|
|
var user db.AdminUser
|
|
if err := h.db.Where("username = ?", req.Username).First(&user).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": gin.H{"code": "invalid_credentials", "message": "Invalid username or password"}})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{"code": "internal_error", "message": "Internal server error"}})
|
|
return
|
|
}
|
|
|
|
if err := auth.ValidateAdminPassword(&user, req.Password); err != nil {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": gin.H{"code": "invalid_credentials", "message": "Invalid username or password"}})
|
|
return
|
|
}
|
|
|
|
token, expiresAt, err := h.jwtService.IssueToken(&user)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": gin.H{"code": "internal_error", "message": "Failed to issue session token"}})
|
|
return
|
|
}
|
|
|
|
h.db.Model(&user).Update("last_login_at", time.Now())
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"token": token,
|
|
"expires_at": expiresAt.Unix(),
|
|
"user": gin.H{
|
|
"id": user.ID,
|
|
"username": user.Username,
|
|
"is_active": user.IsActive,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (h *LoginHandler) Logout(c *gin.Context) {
|
|
c.Status(http.StatusOK)
|
|
}
|