Files
keywarden/internal/servers/servers.go
T
scriptos fd13e67aef
Release Docker Image / Build & Push Docker Image (release) Failing after 1m30s
Release: v0.1.0-alpha
2026-04-05 16:56:16 +02:00

777 lines
24 KiB
Go

// Keywarden - Centralized SSH Key Management and Deployment
// Copyright (C) 2026 Patrick Asmus (scriptos)
// SPDX-License-Identifier: AGPL-3.0-or-later
package servers
import (
"fmt"
"git.techniverse.net/scriptos/keywarden/internal/database"
"git.techniverse.net/scriptos/keywarden/internal/models"
)
// Service handles server management
type Service struct {
db *database.DB
}
// NewService creates a new server service
func NewService(db *database.DB) *Service {
return &Service{db: db}
}
// Create adds a new server
func (s *Service) Create(userID int64, name, hostname string, port int, username, description string) (*models.Server, error) {
if port == 0 {
port = 22
}
result, err := s.db.Exec(
`INSERT INTO servers (user_id, name, hostname, port, username, description) VALUES (?, ?, ?, ?, ?, ?)`,
userID, name, hostname, port, username, description,
)
if err != nil {
return nil, fmt.Errorf("failed to create server: %w", err)
}
id, _ := result.LastInsertId()
return &models.Server{
ID: id,
UserID: userID,
Name: name,
Hostname: hostname,
Port: port,
Username: username,
Description: description,
}, nil
}
// GetByUser returns all servers for a user
func (s *Service) GetByUser(userID int64) ([]models.Server, error) {
rows, err := s.db.Query(
`SELECT id, user_id, name, hostname, port, username, description, created_at, updated_at
FROM servers WHERE user_id = ? ORDER BY name ASC`, userID,
)
if err != nil {
return nil, fmt.Errorf("failed to query servers: %w", err)
}
defer rows.Close()
var servers []models.Server
for rows.Next() {
var srv models.Server
if err := rows.Scan(&srv.ID, &srv.UserID, &srv.Name, &srv.Hostname, &srv.Port, &srv.Username, &srv.Description, &srv.CreatedAt, &srv.UpdatedAt); err != nil {
return nil, fmt.Errorf("failed to scan server: %w", err)
}
servers = append(servers, srv)
}
return servers, nil
}
// GetByID returns a specific server
func (s *Service) GetByID(serverID, userID int64) (*models.Server, error) {
srv := &models.Server{}
err := s.db.QueryRow(
`SELECT id, user_id, name, hostname, port, username, description, created_at, updated_at
FROM servers WHERE id = ? AND user_id = ?`, serverID, userID,
).Scan(&srv.ID, &srv.UserID, &srv.Name, &srv.Hostname, &srv.Port, &srv.Username, &srv.Description, &srv.CreatedAt, &srv.UpdatedAt)
if err != nil {
return nil, fmt.Errorf("server not found: %w", err)
}
return srv, nil
}
// Update modifies a server
func (s *Service) Update(serverID, userID int64, name, hostname string, port int, username, description string) error {
result, err := s.db.Exec(
`UPDATE servers SET name=?, hostname=?, port=?, username=?, description=?, updated_at=CURRENT_TIMESTAMP
WHERE id=? AND user_id=?`,
name, hostname, port, username, description, serverID, userID,
)
if err != nil {
return fmt.Errorf("failed to update server: %w", err)
}
rows, _ := result.RowsAffected()
if rows == 0 {
return fmt.Errorf("server not found")
}
return nil
}
// Delete removes a server
func (s *Service) Delete(serverID, userID int64) error {
result, err := s.db.Exec(`DELETE FROM servers WHERE id = ? AND user_id = ?`, serverID, userID)
if err != nil {
return fmt.Errorf("failed to delete server: %w", err)
}
rows, _ := result.RowsAffected()
if rows == 0 {
return fmt.Errorf("server not found")
}
return nil
}
// --- Server Groups ---
// CreateGroup creates a new server group
func (s *Service) CreateGroup(userID int64, name, description string) (*models.ServerGroup, error) {
result, err := s.db.Exec(
`INSERT INTO server_groups (user_id, name, description) VALUES (?, ?, ?)`,
userID, name, description,
)
if err != nil {
return nil, fmt.Errorf("failed to create server group: %w", err)
}
id, _ := result.LastInsertId()
return &models.ServerGroup{
ID: id,
UserID: userID,
Name: name,
Description: description,
}, nil
}
// GetGroupsByUser returns all server groups for a user with server counts
func (s *Service) GetGroupsByUser(userID int64) ([]models.ServerGroupWithCount, error) {
rows, err := s.db.Query(
`SELECT sg.id, sg.user_id, sg.name, sg.description, sg.created_at, sg.updated_at,
COUNT(sgm.server_id) as server_count
FROM server_groups sg
LEFT JOIN server_group_members sgm ON sg.id = sgm.group_id
WHERE sg.user_id = ?
GROUP BY sg.id
ORDER BY sg.name ASC`, userID,
)
if err != nil {
return nil, fmt.Errorf("failed to query server groups: %w", err)
}
defer rows.Close()
var groups []models.ServerGroupWithCount
for rows.Next() {
var g models.ServerGroupWithCount
if err := rows.Scan(&g.ID, &g.UserID, &g.Name, &g.Description, &g.CreatedAt, &g.UpdatedAt, &g.ServerCount); err != nil {
return nil, fmt.Errorf("failed to scan server group: %w", err)
}
groups = append(groups, g)
}
return groups, nil
}
// GetGroupByID returns a specific server group
func (s *Service) GetGroupByID(groupID, userID int64) (*models.ServerGroup, error) {
g := &models.ServerGroup{}
err := s.db.QueryRow(
`SELECT id, user_id, name, description, created_at, updated_at
FROM server_groups WHERE id = ? AND user_id = ?`, groupID, userID,
).Scan(&g.ID, &g.UserID, &g.Name, &g.Description, &g.CreatedAt, &g.UpdatedAt)
if err != nil {
return nil, fmt.Errorf("server group not found: %w", err)
}
return g, nil
}
// UpdateGroup modifies a server group
func (s *Service) UpdateGroup(groupID, userID int64, name, description string) error {
result, err := s.db.Exec(
`UPDATE server_groups SET name=?, description=?, updated_at=CURRENT_TIMESTAMP
WHERE id=? AND user_id=?`,
name, description, groupID, userID,
)
if err != nil {
return fmt.Errorf("failed to update server group: %w", err)
}
rows, _ := result.RowsAffected()
if rows == 0 {
return fmt.Errorf("server group not found")
}
return nil
}
// DeleteGroup removes a server group
func (s *Service) DeleteGroup(groupID, userID int64) error {
result, err := s.db.Exec(`DELETE FROM server_groups WHERE id = ? AND user_id = ?`, groupID, userID)
if err != nil {
return fmt.Errorf("failed to delete server group: %w", err)
}
rows, _ := result.RowsAffected()
if rows == 0 {
return fmt.Errorf("server group not found")
}
return nil
}
// AddServerToGroup adds a server to a group
func (s *Service) AddServerToGroup(groupID, serverID, userID int64) error {
// Verify group belongs to user
_, err := s.GetGroupByID(groupID, userID)
if err != nil {
return fmt.Errorf("group not found: %w", err)
}
// Verify server belongs to user
_, err = s.GetByID(serverID, userID)
if err != nil {
return fmt.Errorf("server not found: %w", err)
}
_, err = s.db.Exec(
`INSERT OR IGNORE INTO server_group_members (group_id, server_id) VALUES (?, ?)`,
groupID, serverID,
)
if err != nil {
return fmt.Errorf("failed to add server to group: %w", err)
}
return nil
}
// RemoveServerFromGroup removes a server from a group
func (s *Service) RemoveServerFromGroup(groupID, serverID, userID int64) error {
// Verify group belongs to user
_, err := s.GetGroupByID(groupID, userID)
if err != nil {
return fmt.Errorf("group not found: %w", err)
}
_, err = s.db.Exec(
`DELETE FROM server_group_members WHERE group_id = ? AND server_id = ?`,
groupID, serverID,
)
if err != nil {
return fmt.Errorf("failed to remove server from group: %w", err)
}
return nil
}
// AddServerToGroupGlobal adds a server to a group without user_id check
func (s *Service) AddServerToGroupGlobal(groupID, serverID int64) error {
_, err := s.db.Exec(
`INSERT OR IGNORE INTO server_group_members (group_id, server_id) VALUES (?, ?)`,
groupID, serverID,
)
if err != nil {
return fmt.Errorf("failed to add server to group: %w", err)
}
return nil
}
// RemoveServerFromGroupGlobal removes a server from a group without user_id check
func (s *Service) RemoveServerFromGroupGlobal(groupID, serverID int64) error {
_, err := s.db.Exec(
`DELETE FROM server_group_members WHERE group_id = ? AND server_id = ?`,
groupID, serverID,
)
if err != nil {
return fmt.Errorf("failed to remove server from group: %w", err)
}
return nil
}
// GetGroupMembers returns all servers in a group
func (s *Service) GetGroupMembers(groupID, userID int64) ([]models.Server, error) {
rows, err := s.db.Query(
`SELECT s.id, s.user_id, s.name, s.hostname, s.port, s.username, s.description, s.created_at, s.updated_at
FROM servers s
JOIN server_group_members sgm ON s.id = sgm.server_id
WHERE sgm.group_id = ? AND s.user_id = ?
ORDER BY s.name ASC`, groupID, userID,
)
if err != nil {
return nil, fmt.Errorf("failed to query group members: %w", err)
}
defer rows.Close()
var servers []models.Server
for rows.Next() {
var srv models.Server
if err := rows.Scan(&srv.ID, &srv.UserID, &srv.Name, &srv.Hostname, &srv.Port, &srv.Username, &srv.Description, &srv.CreatedAt, &srv.UpdatedAt); err != nil {
return nil, fmt.Errorf("failed to scan server: %w", err)
}
servers = append(servers, srv)
}
return servers, nil
}
// GetGroupIDsForServer returns all group IDs that a server belongs to
func (s *Service) GetGroupIDsForServer(serverID, userID int64) ([]int64, error) {
rows, err := s.db.Query(
`SELECT sgm.group_id FROM server_group_members sgm
JOIN server_groups sg ON sg.id = sgm.group_id
WHERE sgm.server_id = ? AND sg.user_id = ?`, serverID, userID,
)
if err != nil {
return nil, fmt.Errorf("failed to query server groups: %w", err)
}
defer rows.Close()
var ids []int64
for rows.Next() {
var id int64
if err := rows.Scan(&id); err != nil {
continue
}
ids = append(ids, id)
}
return ids, nil
}
// SetServerGroups replaces all group memberships for a server
func (s *Service) SetServerGroups(serverID, userID int64, groupIDs []int64) error {
// Verify server belongs to user
_, err := s.GetByID(serverID, userID)
if err != nil {
return fmt.Errorf("server not found: %w", err)
}
// Remove all current group memberships for this server
_, err = s.db.Exec(`DELETE FROM server_group_members WHERE server_id = ?`, serverID)
if err != nil {
return fmt.Errorf("failed to clear group memberships: %w", err)
}
// Add new memberships
for _, gid := range groupIDs {
// Verify group belongs to user
_, err := s.GetGroupByID(gid, userID)
if err != nil {
continue
}
_, err = s.db.Exec(
`INSERT OR IGNORE INTO server_group_members (group_id, server_id) VALUES (?, ?)`,
gid, serverID,
)
if err != nil {
return fmt.Errorf("failed to add server to group: %w", err)
}
}
return nil
}
// GetGroupMemberIDs returns server IDs in a group
func (s *Service) GetGroupMemberIDs(groupID, userID int64) ([]int64, error) {
rows, err := s.db.Query(
`SELECT sgm.server_id FROM server_group_members sgm
JOIN servers s ON s.id = sgm.server_id
WHERE sgm.group_id = ? AND s.user_id = ?`, groupID, userID,
)
if err != nil {
return nil, fmt.Errorf("failed to query group member IDs: %w", err)
}
defer rows.Close()
var ids []int64
for rows.Next() {
var id int64
if err := rows.Scan(&id); err != nil {
continue
}
ids = append(ids, id)
}
return ids, nil
}
// --- Global queries (admin/owner) ---
// GetAllServers returns all servers regardless of owner
func (s *Service) GetAllServers() ([]models.Server, error) {
rows, err := s.db.Query(
`SELECT id, user_id, name, hostname, port, username, description, created_at, updated_at
FROM servers ORDER BY name ASC`,
)
if err != nil {
return nil, fmt.Errorf("failed to query all servers: %w", err)
}
defer rows.Close()
var servers []models.Server
for rows.Next() {
var srv models.Server
if err := rows.Scan(&srv.ID, &srv.UserID, &srv.Name, &srv.Hostname, &srv.Port, &srv.Username, &srv.Description, &srv.CreatedAt, &srv.UpdatedAt); err != nil {
return nil, fmt.Errorf("failed to scan server: %w", err)
}
servers = append(servers, srv)
}
return servers, nil
}
// GetAllGroups returns all server groups regardless of owner
func (s *Service) GetAllGroups() ([]models.ServerGroupWithCount, error) {
rows, err := s.db.Query(
`SELECT sg.id, sg.user_id, sg.name, sg.description, sg.created_at, sg.updated_at,
COUNT(sgm.server_id) as server_count
FROM server_groups sg
LEFT JOIN server_group_members sgm ON sg.id = sgm.group_id
GROUP BY sg.id
ORDER BY sg.name ASC`,
)
if err != nil {
return nil, fmt.Errorf("failed to query all server groups: %w", err)
}
defer rows.Close()
var groups []models.ServerGroupWithCount
for rows.Next() {
var g models.ServerGroupWithCount
if err := rows.Scan(&g.ID, &g.UserID, &g.Name, &g.Description, &g.CreatedAt, &g.UpdatedAt, &g.ServerCount); err != nil {
return nil, fmt.Errorf("failed to scan server group: %w", err)
}
groups = append(groups, g)
}
return groups, nil
}
// --- Global access functions (admin/owner) ---
// GetByIDGlobal returns a server without user_id check (admin/owner access)
func (s *Service) GetByIDGlobal(serverID int64) (*models.Server, error) {
srv := &models.Server{}
err := s.db.QueryRow(
`SELECT id, user_id, name, hostname, port, username, description, created_at, updated_at
FROM servers WHERE id = ?`, serverID,
).Scan(&srv.ID, &srv.UserID, &srv.Name, &srv.Hostname, &srv.Port, &srv.Username, &srv.Description, &srv.CreatedAt, &srv.UpdatedAt)
if err != nil {
return nil, fmt.Errorf("server not found: %w", err)
}
return srv, nil
}
// UpdateGlobal modifies a server without user_id check (admin/owner access)
func (s *Service) UpdateGlobal(serverID int64, name, hostname string, port int, username, description string) error {
result, err := s.db.Exec(
`UPDATE servers SET name=?, hostname=?, port=?, username=?, description=?, updated_at=CURRENT_TIMESTAMP
WHERE id=?`,
name, hostname, port, username, description, serverID,
)
if err != nil {
return fmt.Errorf("failed to update server: %w", err)
}
rows, _ := result.RowsAffected()
if rows == 0 {
return fmt.Errorf("server not found")
}
return nil
}
// DeleteGlobal removes a server without user_id check (admin/owner access)
func (s *Service) DeleteGlobal(serverID int64) error {
result, err := s.db.Exec(`DELETE FROM servers WHERE id = ?`, serverID)
if err != nil {
return fmt.Errorf("failed to delete server: %w", err)
}
rows, _ := result.RowsAffected()
if rows == 0 {
return fmt.Errorf("server not found")
}
return nil
}
// GetGroupByIDGlobal returns a server group without user_id check
func (s *Service) GetGroupByIDGlobal(groupID int64) (*models.ServerGroup, error) {
g := &models.ServerGroup{}
err := s.db.QueryRow(
`SELECT id, user_id, name, description, created_at, updated_at
FROM server_groups WHERE id = ?`, groupID,
).Scan(&g.ID, &g.UserID, &g.Name, &g.Description, &g.CreatedAt, &g.UpdatedAt)
if err != nil {
return nil, fmt.Errorf("server group not found: %w", err)
}
return g, nil
}
// UpdateGroupGlobal modifies a server group without user_id check
func (s *Service) UpdateGroupGlobal(groupID int64, name, description string) error {
result, err := s.db.Exec(
`UPDATE server_groups SET name=?, description=?, updated_at=CURRENT_TIMESTAMP WHERE id=?`,
name, description, groupID,
)
if err != nil {
return fmt.Errorf("failed to update server group: %w", err)
}
rows, _ := result.RowsAffected()
if rows == 0 {
return fmt.Errorf("server group not found")
}
return nil
}
// DeleteGroupGlobal removes a server group without user_id check
func (s *Service) DeleteGroupGlobal(groupID int64) error {
result, err := s.db.Exec(`DELETE FROM server_groups WHERE id = ?`, groupID)
if err != nil {
return fmt.Errorf("failed to delete server group: %w", err)
}
rows, _ := result.RowsAffected()
if rows == 0 {
return fmt.Errorf("server group not found")
}
return nil
}
// GetGroupMembersGlobal returns all servers in a group without user_id check
func (s *Service) GetGroupMembersGlobal(groupID int64) ([]models.Server, error) {
rows, err := s.db.Query(
`SELECT s.id, s.user_id, s.name, s.hostname, s.port, s.username, s.description, s.created_at, s.updated_at
FROM servers s
JOIN server_group_members sgm ON s.id = sgm.server_id
WHERE sgm.group_id = ?
ORDER BY s.name ASC`, groupID,
)
if err != nil {
return nil, fmt.Errorf("failed to query group members: %w", err)
}
defer rows.Close()
var servers []models.Server
for rows.Next() {
var srv models.Server
if err := rows.Scan(&srv.ID, &srv.UserID, &srv.Name, &srv.Hostname, &srv.Port, &srv.Username, &srv.Description, &srv.CreatedAt, &srv.UpdatedAt); err != nil {
return nil, fmt.Errorf("failed to scan server: %w", err)
}
servers = append(servers, srv)
}
return servers, nil
}
// GetGroupIDsForServerGlobal returns all group IDs that a server belongs to (without user_id check)
func (s *Service) GetGroupIDsForServerGlobal(serverID int64) ([]int64, error) {
rows, err := s.db.Query(
`SELECT sgm.group_id FROM server_group_members sgm WHERE sgm.server_id = ?`, serverID,
)
if err != nil {
return nil, fmt.Errorf("failed to query server groups: %w", err)
}
defer rows.Close()
var ids []int64
for rows.Next() {
var id int64
if err := rows.Scan(&id); err != nil {
continue
}
ids = append(ids, id)
}
return ids, nil
}
// SetServerGroupsGlobal replaces all group memberships for a server (without user_id check)
func (s *Service) SetServerGroupsGlobal(serverID int64, groupIDs []int64) error {
_, err := s.db.Exec(`DELETE FROM server_group_members WHERE server_id = ?`, serverID)
if err != nil {
return fmt.Errorf("failed to clear group memberships: %w", err)
}
for _, gid := range groupIDs {
_, err = s.db.Exec(
`INSERT OR IGNORE INTO server_group_members (group_id, server_id) VALUES (?, ?)`,
gid, serverID,
)
if err != nil {
return fmt.Errorf("failed to add server to group: %w", err)
}
}
return nil
}
// GetServersByAssignedUser returns hosts that a user has access to via assignments
func (s *Service) GetServersByAssignedUser(userID int64) ([]models.Server, error) {
rows, err := s.db.Query(
`SELECT DISTINCT s.id, s.user_id, s.name, s.hostname, s.port, s.username, s.description, s.created_at, s.updated_at
FROM servers s
WHERE s.id IN (
SELECT a.server_id FROM access_assignments a WHERE a.user_id = ? AND a.server_id > 0
UNION
SELECT sgm.server_id FROM access_assignments a
JOIN server_group_members sgm ON a.group_id = sgm.group_id
WHERE a.user_id = ? AND a.group_id > 0
)
ORDER BY s.name ASC`, userID, userID,
)
if err != nil {
return nil, fmt.Errorf("failed to query assigned servers: %w", err)
}
defer rows.Close()
var servers []models.Server
for rows.Next() {
var srv models.Server
if err := rows.Scan(&srv.ID, &srv.UserID, &srv.Name, &srv.Hostname, &srv.Port, &srv.Username, &srv.Description, &srv.CreatedAt, &srv.UpdatedAt); err != nil {
return nil, fmt.Errorf("failed to scan server: %w", err)
}
servers = append(servers, srv)
}
return servers, nil
}
// UpdateAssignmentStatus updates the sync status of an assignment
func (s *Service) UpdateAssignmentStatus(id int64, status, message string) error {
_, err := s.db.Exec(
`UPDATE access_assignments SET status=?, last_sync_at=CURRENT_TIMESTAMP, updated_at=CURRENT_TIMESTAMP WHERE id=?`,
status, id,
)
return err
}
// --- Access Assignments ---
// CreateAssignment creates a new access assignment
func (s *Service) CreateAssignment(userID, sshKeyID, serverID, groupID int64, systemUser, desiredState string, sudo, createUser bool) (*models.AccessAssignment, error) {
sudoInt := 0
if sudo {
sudoInt = 1
}
createUserInt := 0
if createUser {
createUserInt = 1
}
if desiredState == "" {
desiredState = "present"
}
result, err := s.db.Exec(
`INSERT INTO access_assignments (user_id, ssh_key_id, server_id, group_id, system_user, desired_state, sudo, create_user)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
userID, sshKeyID, serverID, groupID, systemUser, desiredState, sudoInt, createUserInt,
)
if err != nil {
return nil, fmt.Errorf("failed to create assignment: %w", err)
}
id, _ := result.LastInsertId()
return &models.AccessAssignment{
ID: id,
UserID: userID,
SSHKeyID: sshKeyID,
ServerID: serverID,
GroupID: groupID,
SystemUser: systemUser,
DesiredState: desiredState,
Sudo: sudo,
CreateUser: createUser,
Status: "pending",
}, nil
}
// GetAllAssignments returns all access assignments with resolved display names
func (s *Service) GetAllAssignments() ([]models.AccessAssignmentDisplay, error) {
rows, err := s.db.Query(
`SELECT a.id, a.user_id, a.ssh_key_id, a.server_id, a.group_id,
a.system_user, a.desired_state, a.sudo, a.create_user,
a.status, a.initial_password, a.last_sync_at, a.created_at, a.updated_at,
u.username,
COALESCE(k.name, '(deleted)'),
CASE
WHEN a.server_id > 0 THEN COALESCE(s.name, '(deleted)')
WHEN a.group_id > 0 THEN COALESCE(sg.name, '(deleted)')
ELSE '(none)'
END,
CASE
WHEN a.server_id > 0 THEN 'host'
WHEN a.group_id > 0 THEN 'group'
ELSE 'none'
END
FROM access_assignments a
LEFT JOIN users u ON a.user_id = u.id
LEFT JOIN ssh_keys k ON a.ssh_key_id = k.id
LEFT JOIN servers s ON a.server_id = s.id AND a.server_id > 0
LEFT JOIN server_groups sg ON a.group_id = sg.id AND a.group_id > 0
ORDER BY a.created_at DESC`,
)
if err != nil {
return nil, fmt.Errorf("failed to query assignments: %w", err)
}
defer rows.Close()
var assignments []models.AccessAssignmentDisplay
for rows.Next() {
var a models.AccessAssignmentDisplay
var sudo, createUser int
if err := rows.Scan(&a.ID, &a.UserID, &a.SSHKeyID, &a.ServerID, &a.GroupID,
&a.SystemUser, &a.DesiredState, &sudo, &createUser,
&a.Status, &a.InitialPassword, &a.LastSyncAt, &a.CreatedAt, &a.UpdatedAt,
&a.Username, &a.KeyName, &a.TargetName, &a.TargetType); err != nil {
return nil, fmt.Errorf("failed to scan assignment: %w", err)
}
a.Sudo = sudo == 1
a.CreateUser = createUser == 1
assignments = append(assignments, a)
}
return assignments, nil
}
// GetAssignmentsByUser returns access assignments for a specific user
func (s *Service) GetAssignmentsByUser(userID int64) ([]models.AccessAssignmentDisplay, error) {
all, err := s.GetAllAssignments()
if err != nil {
return nil, err
}
var filtered []models.AccessAssignmentDisplay
for _, a := range all {
if a.UserID == userID {
filtered = append(filtered, a)
}
}
return filtered, nil
}
// GetAssignmentByID returns a single access assignment
func (s *Service) GetAssignmentByID(id int64) (*models.AccessAssignment, error) {
a := &models.AccessAssignment{}
var sudo, createUser int
err := s.db.QueryRow(
`SELECT id, user_id, ssh_key_id, server_id, group_id, system_user, desired_state,
sudo, create_user, initial_password, status, last_sync_at, created_at, updated_at
FROM access_assignments WHERE id = ?`, id,
).Scan(&a.ID, &a.UserID, &a.SSHKeyID, &a.ServerID, &a.GroupID, &a.SystemUser, &a.DesiredState,
&sudo, &createUser, &a.InitialPassword, &a.Status, &a.LastSyncAt, &a.CreatedAt, &a.UpdatedAt)
if err != nil {
return nil, fmt.Errorf("assignment not found: %w", err)
}
a.Sudo = sudo == 1
a.CreateUser = createUser == 1
return a, nil
}
// UpdateAssignment updates an existing access assignment
func (s *Service) UpdateAssignment(id, userID, sshKeyID, serverID, groupID int64, systemUser, desiredState string, sudo, createUser bool) error {
sudoInt := 0
if sudo {
sudoInt = 1
}
createUserInt := 0
if createUser {
createUserInt = 1
}
result, err := s.db.Exec(
`UPDATE access_assignments SET user_id=?, ssh_key_id=?, server_id=?, group_id=?,
system_user=?, desired_state=?, sudo=?, create_user=?, status='pending', updated_at=CURRENT_TIMESTAMP
WHERE id=?`,
userID, sshKeyID, serverID, groupID, systemUser, desiredState, sudoInt, createUserInt, id,
)
if err != nil {
return fmt.Errorf("failed to update assignment: %w", err)
}
rows, _ := result.RowsAffected()
if rows == 0 {
return fmt.Errorf("assignment not found")
}
return nil
}
// DeleteAssignment removes an access assignment
func (s *Service) DeleteAssignment(id int64) error {
result, err := s.db.Exec(`DELETE FROM access_assignments WHERE id = ?`, id)
if err != nil {
return fmt.Errorf("failed to delete assignment: %w", err)
}
rows, _ := result.RowsAffected()
if rows == 0 {
return fmt.Errorf("assignment not found")
}
return nil
}
// UpdateAssignmentInitialPassword stores the encrypted initial password for an assignment
func (s *Service) UpdateAssignmentInitialPassword(id int64, encryptedPassword string) error {
_, err := s.db.Exec(
`UPDATE access_assignments SET initial_password=?, updated_at=CURRENT_TIMESTAMP WHERE id=?`,
encryptedPassword, id,
)
return err
}