- Installed and configured SCST with 7 handlers - Installed and configured mhVTL with 2 Quantum libraries and 8 LTO-8 drives - Implemented all VTL API endpoints (8/9 working) - Fixed NULL device_path handling in drives endpoint - Added comprehensive error handling and validation - Implemented async tape load/unload operations - Created SCST installation guide for Ubuntu 24.04 - Created mhVTL installation and configuration guide - Added VTL testing guide and automated test scripts - All core API tests passing (89% success rate) Infrastructure status: - PostgreSQL: Configured with proper permissions - SCST: Active with kernel module loaded - mhVTL: 2 libraries (Quantum Scalar i500, Scalar i40) - mhVTL: 8 drives (all Quantum ULTRIUM-HH8 LTO-8) - Calypso API: 8/9 VTL endpoints functional Documentation added: - src/srs-technical-spec-documents/scst-installation.md - src/srs-technical-spec-documents/mhvtl-installation.md - VTL-TESTING-GUIDE.md - scripts/test-vtl.sh Co-Authored-By: Warp <agent@warp.dev>
224 lines
5.7 KiB
Go
224 lines
5.7 KiB
Go
package iam
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/atlasos/calypso/internal/common/database"
|
|
"github.com/atlasos/calypso/internal/common/logger"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// Handler handles IAM-related requests
|
|
type Handler struct {
|
|
db *database.DB
|
|
logger *logger.Logger
|
|
}
|
|
|
|
// NewHandler creates a new IAM handler
|
|
func NewHandler(db *database.DB, log *logger.Logger) *Handler {
|
|
return &Handler{
|
|
db: db,
|
|
logger: log,
|
|
}
|
|
}
|
|
|
|
// ListUsers lists all users
|
|
func (h *Handler) ListUsers(c *gin.Context) {
|
|
query := `
|
|
SELECT id, username, email, full_name, is_active, is_system,
|
|
created_at, updated_at, last_login_at
|
|
FROM users
|
|
ORDER BY username
|
|
`
|
|
|
|
rows, err := h.db.Query(query)
|
|
if err != nil {
|
|
h.logger.Error("Failed to list users", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list users"})
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
var users []map[string]interface{}
|
|
for rows.Next() {
|
|
var u struct {
|
|
ID string
|
|
Username string
|
|
Email string
|
|
FullName string
|
|
IsActive bool
|
|
IsSystem bool
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
LastLoginAt *string
|
|
}
|
|
if err := rows.Scan(&u.ID, &u.Username, &u.Email, &u.FullName,
|
|
&u.IsActive, &u.IsSystem, &u.CreatedAt, &u.UpdatedAt, &u.LastLoginAt); err != nil {
|
|
h.logger.Error("Failed to scan user", "error", err)
|
|
continue
|
|
}
|
|
|
|
users = append(users, map[string]interface{}{
|
|
"id": u.ID,
|
|
"username": u.Username,
|
|
"email": u.Email,
|
|
"full_name": u.FullName,
|
|
"is_active": u.IsActive,
|
|
"is_system": u.IsSystem,
|
|
"created_at": u.CreatedAt,
|
|
"updated_at": u.UpdatedAt,
|
|
"last_login_at": u.LastLoginAt,
|
|
})
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"users": users})
|
|
}
|
|
|
|
// GetUser retrieves a single user
|
|
func (h *Handler) GetUser(c *gin.Context) {
|
|
userID := c.Param("id")
|
|
|
|
user, err := GetUserByID(h.db, userID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
|
return
|
|
}
|
|
|
|
roles, _ := GetUserRoles(h.db, userID)
|
|
permissions, _ := GetUserPermissions(h.db, userID)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"id": user.ID,
|
|
"username": user.Username,
|
|
"email": user.Email,
|
|
"full_name": user.FullName,
|
|
"is_active": user.IsActive,
|
|
"is_system": user.IsSystem,
|
|
"roles": roles,
|
|
"permissions": permissions,
|
|
"created_at": user.CreatedAt,
|
|
"updated_at": user.UpdatedAt,
|
|
})
|
|
}
|
|
|
|
// CreateUser creates a new user
|
|
func (h *Handler) CreateUser(c *gin.Context) {
|
|
var req struct {
|
|
Username string `json:"username" binding:"required"`
|
|
Email string `json:"email" binding:"required,email"`
|
|
Password string `json:"password" binding:"required"`
|
|
FullName string `json:"full_name"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
|
|
return
|
|
}
|
|
|
|
// TODO: Hash password with Argon2id
|
|
passwordHash := req.Password // Placeholder
|
|
|
|
query := `
|
|
INSERT INTO users (username, email, password_hash, full_name)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING id
|
|
`
|
|
|
|
var userID string
|
|
err := h.db.QueryRow(query, req.Username, req.Email, passwordHash, req.FullName).Scan(&userID)
|
|
if err != nil {
|
|
h.logger.Error("Failed to create user", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create user"})
|
|
return
|
|
}
|
|
|
|
h.logger.Info("User created", "user_id", userID, "username", req.Username)
|
|
c.JSON(http.StatusCreated, gin.H{"id": userID, "username": req.Username})
|
|
}
|
|
|
|
// UpdateUser updates an existing user
|
|
func (h *Handler) UpdateUser(c *gin.Context) {
|
|
userID := c.Param("id")
|
|
|
|
var req struct {
|
|
Email *string `json:"email"`
|
|
FullName *string `json:"full_name"`
|
|
IsActive *bool `json:"is_active"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
|
|
return
|
|
}
|
|
|
|
// Build update query dynamically
|
|
updates := []string{"updated_at = NOW()"}
|
|
args := []interface{}{}
|
|
argPos := 1
|
|
|
|
if req.Email != nil {
|
|
updates = append(updates, fmt.Sprintf("email = $%d", argPos))
|
|
args = append(args, *req.Email)
|
|
argPos++
|
|
}
|
|
if req.FullName != nil {
|
|
updates = append(updates, fmt.Sprintf("full_name = $%d", argPos))
|
|
args = append(args, *req.FullName)
|
|
argPos++
|
|
}
|
|
if req.IsActive != nil {
|
|
updates = append(updates, fmt.Sprintf("is_active = $%d", argPos))
|
|
args = append(args, *req.IsActive)
|
|
argPos++
|
|
}
|
|
|
|
if len(updates) == 1 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "no fields to update"})
|
|
return
|
|
}
|
|
|
|
args = append(args, userID)
|
|
query := "UPDATE users SET " + strings.Join(updates, ", ") + fmt.Sprintf(" WHERE id = $%d", argPos)
|
|
|
|
_, err := h.db.Exec(query, args...)
|
|
if err != nil {
|
|
h.logger.Error("Failed to update user", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update user"})
|
|
return
|
|
}
|
|
|
|
h.logger.Info("User updated", "user_id", userID)
|
|
c.JSON(http.StatusOK, gin.H{"message": "user updated successfully"})
|
|
}
|
|
|
|
// DeleteUser deletes a user
|
|
func (h *Handler) DeleteUser(c *gin.Context) {
|
|
userID := c.Param("id")
|
|
|
|
// Check if user is system user
|
|
var isSystem bool
|
|
err := h.db.QueryRow("SELECT is_system FROM users WHERE id = $1", userID).Scan(&isSystem)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
|
|
return
|
|
}
|
|
|
|
if isSystem {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "cannot delete system user"})
|
|
return
|
|
}
|
|
|
|
_, err = h.db.Exec("DELETE FROM users WHERE id = $1", userID)
|
|
if err != nil {
|
|
h.logger.Error("Failed to delete user", "error", err)
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete user"})
|
|
return
|
|
}
|
|
|
|
h.logger.Info("User deleted", "user_id", userID)
|
|
c.JSON(http.StatusOK, gin.H{"message": "user deleted successfully"})
|
|
}
|
|
|