Files
UNITRONIX b8caa1e2fa fix(auth): recover missing client_sessions on RustDesk login
Log issueClientSession failures, ensure/recreate the #242 schema when absent, and document RustDesk 1.4.9 compatibility. Fixes #284. Refs #286.
2026-07-20 18:06:56 +02:00

725 lines
29 KiB
Go

// Package db defines the database interface and models for the BetterDesk server.
// Implementations: sqlite.go (default), postgres.go (PostgreSQL via pgx/v5).
package db
import (
"errors"
"time"
)
// Peer represents a registered RustDesk device.
type Peer struct {
ID string `json:"id"`
UUID string `json:"uuid"`
PK []byte `json:"pk"`
IP string `json:"ip"`
User string `json:"user,omitempty"`
Hostname string `json:"hostname,omitempty"`
OS string `json:"os,omitempty"`
Version string `json:"version,omitempty"`
Status string `json:"status"` // ONLINE, OFFLINE, DEGRADED, CRITICAL
NATType int `json:"nat_type"`
LastOnline time.Time `json:"last_online"`
CreatedAt time.Time `json:"created_at"`
Disabled bool `json:"disabled"`
Banned bool `json:"banned"`
BanReason string `json:"ban_reason,omitempty"`
BannedAt *time.Time `json:"banned_at,omitempty"`
SoftDeleted bool `json:"soft_deleted"`
DeletedAt *time.Time `json:"deleted_at,omitempty"`
Note string `json:"note,omitempty"`
Tags string `json:"tags,omitempty"`
DisplayName string `json:"display_name"` // Admin-set alias (overrides hostname in UI)
DeviceType string `json:"device_type,omitempty"` // CDAP: desktop, mobile, headless, kiosk, etc.
LinkedPeerID string `json:"linked_peer_id,omitempty"` // CDAP: paired device (e.g., mobile→desktop)
HeartbeatSeq int64 `json:"-"` // internal heartbeat counter
}
// ServerConfig stores runtime configuration in the database.
type ServerConfig struct {
Key string `json:"key"`
Value string `json:"value"`
}
// User represents an API user account.
// Authentication provider values for User.AuthProvider. Determines how an
// account is allowed to authenticate. A user bound to a non-local provider
// can ONLY authenticate via that provider — password login is rejected even
// when a username collides with a local account (Issue #148).
const (
AuthProviderLocal = "local" // local password (PBKDF2/bcrypt)
AuthProviderLDAP = "ldap" // LDAP / Active Directory
AuthProviderOIDC = "oidc" // OpenID Connect / OAuth2 SSO
)
type User struct {
ID int64 `json:"id"`
Username string `json:"username"`
PasswordHash string `json:"-"`
Role string `json:"role"` // admin, operator, viewer
IsServerAdmin bool `json:"is_server_admin"` // Phase 3: separate server admin flag
AuthProvider string `json:"auth_provider"` // local, ldap, oidc (Issue #148)
TOTPSecret string `json:"-"`
TOTPEnabled bool `json:"totp_enabled"`
TOTPRecoveryCodes string `json:"-"` // JSON array of bcrypt-hashed recovery codes (H4)
CreatedAt string `json:"created_at"`
LastLogin string `json:"last_login,omitempty"`
}
// RolePermission represents a custom permission override for a role.
// Stored in the role_permissions table. If no override exists for a role+permission,
// the default from auth.DefaultRolePermissions is used.
type RolePermission struct {
ID int64 `json:"id"`
Role string `json:"role"`
Permission string `json:"permission"` // e.g. "device.view", "user.manage"
Granted bool `json:"granted"` // true = allowed, false = denied
}
// ClientSession represents a RustDesk desktop client login session (opaque bearer token).
type ClientSession struct {
ID int64 `json:"id"`
TokenHash string `json:"-"`
UserID int64 `json:"user_id"`
ClientID string `json:"client_id"`
ClientUUID string `json:"client_uuid"`
ExpiresAt string `json:"expires_at"`
LastUsed string `json:"last_used,omitempty"`
CreatedAt string `json:"created_at"`
Revoked bool `json:"revoked"`
IPAddress string `json:"ip_address,omitempty"`
}
// APIKey represents a scoped API key for programmatic access.
type APIKey struct {
ID int64 `json:"id"`
KeyHash string `json:"-"`
KeyPrefix string `json:"key_prefix"` // First 8 chars for identification
Name string `json:"name"`
Role string `json:"role"`
CreatedAt string `json:"created_at"`
ExpiresAt string `json:"expires_at,omitempty"`
LastUsed string `json:"last_used,omitempty"`
}
// IDChangeHistory tracks peer ID changes.
type IDChangeHistory struct {
OldID string `json:"old_id"`
NewID string `json:"new_id"`
ChangedAt time.Time `json:"changed_at"`
Reason string `json:"reason,omitempty"`
}
// PeerIDState describes whether an ID is available, active, or reserved by a
// soft-deleted peer row.
type PeerIDState string
const (
PeerIDMissing PeerIDState = "missing"
PeerIDActive PeerIDState = "active"
PeerIDSoftDeleted PeerIDState = "soft_deleted"
)
var (
ErrPeerNotFound = errors.New("db: peer not found")
ErrPeerIDExists = errors.New("db: peer ID already exists")
ErrPeerIDSoftDeleted = errors.New("db: peer ID belongs to a deleted peer")
)
// PeerMetric represents a single heartbeat metric data point.
type PeerMetric struct {
ID int64 `json:"id"`
PeerID string `json:"peer_id"`
CPU float64 `json:"cpu_usage"`
Memory float64 `json:"memory_usage"`
Disk float64 `json:"disk_usage"`
CreatedAt time.Time `json:"created_at"`
}
// DeviceToken represents a unique enrollment token for device registration.
// Dual Key System: supports both global server key (backward compatible) and
// per-device tokens for enhanced security.
type DeviceToken struct {
ID int64 `json:"id"`
Token string `json:"token"` // Unique enrollment token (32 chars)
TokenHash string `json:"-"` // SHA256 hash for storage
Name string `json:"name"` // Friendly name for the token
PeerID string `json:"peer_id,omitempty"` // Bound peer ID (after enrollment)
Status string `json:"status"` // pending, active, revoked, expired
MaxUses int `json:"max_uses"` // 0 = unlimited, 1 = single-use
UseCount int `json:"use_count"` // Current use count
CreatedAt time.Time `json:"created_at"`
ExpiresAt *time.Time `json:"expires_at,omitempty"` // Optional expiration
RevokedAt *time.Time `json:"revoked_at,omitempty"`
LastUsedAt *time.Time `json:"last_used_at,omitempty"`
CreatedBy string `json:"created_by,omitempty"` // Admin who created the token
Note string `json:"note,omitempty"`
}
// DeviceTokenStatus constants
const (
TokenStatusPending = "pending" // Created, not yet used
TokenStatusActive = "active" // Bound to a peer
TokenStatusRevoked = "revoked" // Manually revoked
TokenStatusExpired = "expired" // Past expiration date
)
// ChatMessage represents a persisted chat message between devices/operators.
type ChatMessage struct {
ID int64 `json:"id"`
ConversationID string `json:"conversation_id"` // "operator", device_id, or group:<id>
FromID string `json:"from_id"` // sender device_id or "operator:<name>"
FromName string `json:"from_name"` // display name
ToID string `json:"to_id,omitempty"` // recipient device_id or group ID
Text string `json:"text"`
Read bool `json:"read"`
CreatedAt time.Time `json:"created_at"`
}
// ChatGroup represents a multi-device chat group.
type ChatGroup struct {
ID string `json:"id"`
Name string `json:"name"`
Members string `json:"members"` // comma-separated device IDs
CreatedBy string `json:"created_by"` // device_id or operator name
CreatedAt time.Time `json:"created_at"`
}
// ChatContact represents a peer visible for chat (derived from peers table).
type ChatContact struct {
ID string `json:"id"`
Name string `json:"name"`
Hostname string `json:"hostname"`
Online bool `json:"online"`
LastSeen int64 `json:"last_seen"`
Unread int `json:"unread"`
AvatarColor string `json:"avatar_color"`
}
// HelpRequest status constants.
const (
HelpStatusPending = "pending" // Raised by device, awaiting operator
HelpStatusAcknowledged = "acknowledged" // Operator picked it up
HelpStatusResolved = "resolved" // Operator closed it
HelpStatusCancelled = "cancelled" // Device cancelled it
)
// HelpRequest represents a support request raised by an agent device.
type HelpRequest struct {
ID int64 `json:"id"`
DeviceID string `json:"device_id"`
Hostname string `json:"hostname,omitempty"`
OrgID string `json:"org_id,omitempty"`
Message string `json:"message"`
Status string `json:"status"`
HandledBy string `json:"handled_by,omitempty"` // operator that acked/resolved
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// HelpRequestFilter narrows ListHelpRequests results. Empty fields match any.
type HelpRequestFilter struct {
Status string // "" = any status
DeviceID string // "" = any device
OrgID string // "" = any org (org data-scoping)
Limit int // 0 = default (100)
}
// Organization represents a customer/tenant entity.
type Organization struct {
ID string `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
LogoURL string `json:"logo_url,omitempty"`
Settings string `json:"settings,omitempty"` // JSON blob for org-level settings
CreatedAt time.Time `json:"created_at"`
}
// OrgUser represents a user account within an organization.
// Can be either a standalone org-scoped user (password_hash set) or a linked
// server-level user (server_user_id set, password_hash empty).
type OrgUser struct {
ID string `json:"id"`
OrgID string `json:"org_id"`
ServerUserID int64 `json:"server_user_id,omitempty"` // FK to users.id (0 = standalone org user)
Username string `json:"username"`
DisplayName string `json:"display_name,omitempty"`
Email string `json:"email,omitempty"`
PasswordHash string `json:"-"`
Role string `json:"role"` // owner, admin, operator, user
TOTPSecret string `json:"-"`
AvatarURL string `json:"avatar_url,omitempty"`
LastLogin *time.Time `json:"last_login,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
// OrgDevice binds a device to an organization with metadata.
type OrgDevice struct {
OrgID string `json:"org_id"`
DeviceID string `json:"device_id"`
AssignedUserID string `json:"assigned_user_id,omitempty"`
Department string `json:"department,omitempty"`
Location string `json:"location,omitempty"`
Building string `json:"building,omitempty"`
Tags string `json:"tags,omitempty"`
}
// OrgInvitation represents a pending invitation to join an organization.
type OrgInvitation struct {
ID string `json:"id"`
OrgID string `json:"org_id"`
Token string `json:"token"`
Email string `json:"email,omitempty"`
Role string `json:"role"` // default: "user"
ExpiresAt time.Time `json:"expires_at"`
UsedAt *time.Time `json:"used_at,omitempty"`
}
// OrgSetting stores a single key-value pair scoped to an organization.
type OrgSetting struct {
OrgID string `json:"org_id"`
Key string `json:"key"`
Value string `json:"value"`
}
// AccessPolicy controls unattended access for a peer device.
type AccessPolicy struct {
PeerID string `json:"peer_id"`
UnattendedEnabled bool `json:"unattended_enabled"` // Whether unattended access is allowed
PasswordHash string `json:"-"` // bcrypt hash of unattended password
PasswordSet bool `json:"password_set"` // Whether a password is configured (computed, not stored)
ScheduleEnabled bool `json:"schedule_enabled"` // Whether access schedule is active
ScheduleDays string `json:"schedule_days,omitempty"` // Comma-separated days: "mon,tue,wed,thu,fri"
ScheduleStartTime string `json:"schedule_start_time,omitempty"` // HH:MM (24h format)
ScheduleEndTime string `json:"schedule_end_time,omitempty"` // HH:MM (24h format)
ScheduleTimezone string `json:"schedule_timezone,omitempty"` // IANA timezone (e.g. "Europe/Warsaw")
AllowedOperators string `json:"allowed_operators,omitempty"` // Comma-separated operator usernames (empty = all)
UpdatedAt string `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"` // Admin/operator who last changed the policy
}
// OrgRole constants
const (
OrgRoleOwner = "owner"
OrgRoleAdmin = "admin"
OrgRoleOperator = "operator"
OrgRoleUser = "user"
)
// OrgRoleLevel returns the numeric privilege level for an org-scoped role.
// Higher = more privileges. Used for role boundary enforcement.
func OrgRoleLevel(role string) int {
switch role {
case OrgRoleOwner:
return 40
case OrgRoleAdmin:
return 30
case OrgRoleOperator:
return 20
case OrgRoleUser:
return 10
default:
return 0
}
}
// OrgCanAssignRole checks whether a caller with callerOrgRole may assign targetOrgRole.
// Owner → any; Admin → operator, user; Operator/User → none.
func OrgCanAssignRole(callerOrgRole, targetOrgRole string) bool {
switch callerOrgRole {
case OrgRoleOwner:
// Owner can assign admin, operator, user (not another owner — handled in API)
return targetOrgRole == OrgRoleAdmin || targetOrgRole == OrgRoleOperator || targetOrgRole == OrgRoleUser
case OrgRoleAdmin:
return targetOrgRole == OrgRoleOperator || targetOrgRole == OrgRoleUser
default:
return false
}
}
// ValidOrgRole returns true if the given string is a recognized org role.
func ValidOrgRole(r string) bool {
return r == OrgRoleOwner || r == OrgRoleAdmin || r == OrgRoleOperator || r == OrgRoleUser
}
// AuditConnection records a remote-control session event reported by a RustDesk client.
// Mirrors the Node.js console's audit_connections table for API-port consolidation.
type AuditConnection struct {
ID int64 `json:"id"`
HostID string `json:"host_id"`
HostUUID string `json:"host_uuid"`
PeerID string `json:"peer_id"`
PeerName string `json:"peer_name"`
Action string `json:"action"`
ConnType int `json:"conn_type"`
SessionID string `json:"session_id"`
IP string `json:"ip"`
CreatedAt string `json:"created_at"`
}
// AuditFile records a file-transfer event reported by a RustDesk client.
type AuditFile struct {
ID int64 `json:"id"`
HostID string `json:"host_id"`
HostUUID string `json:"host_uuid"`
PeerID string `json:"peer_id"`
Direction int `json:"direction"`
Path string `json:"path"`
IsFile int `json:"is_file"`
NumFiles int `json:"num_files"`
FilesJSON string `json:"files_json"`
IP string `json:"ip"`
PeerName string `json:"peer_name"`
CreatedAt string `json:"created_at"`
}
// AuditAlarm records a security alarm event reported by a RustDesk client.
type AuditAlarm struct {
ID int64 `json:"id"`
AlarmType int `json:"alarm_type"`
AlarmName string `json:"alarm_name"`
HostID string `json:"host_id"`
PeerID string `json:"peer_id"`
IP string `json:"ip"`
Details string `json:"details"`
CreatedAt string `json:"created_at"`
}
// AuditFilter holds optional filter parameters for audit list/count queries.
// A nil AlarmType means "no filter on alarm_type".
type AuditFilter struct {
HostID string
PeerID string
Action string
AlarmType *int
Limit int
Offset int
}
// UserGroup represents a named group of operator/user accounts.
// Mirrors the Node.js console's user_groups table.
type UserGroup struct {
ID int64 `json:"id"`
GUID string `json:"guid"`
Name string `json:"name"`
Note string `json:"note"`
TeamID string `json:"team_id"`
MemberCount int `json:"member_count"`
CreatedAt string `json:"created_at"`
}
// DeviceGroup represents a named group of devices.
// Mirrors the Node.js console's device_groups table.
type DeviceGroup struct {
ID int64 `json:"id"`
GUID string `json:"guid"`
Name string `json:"name"`
Note string `json:"note"`
TeamID string `json:"team_id"`
SourceType string `json:"source_type"` // "manual" or "tag"
TagFilter string `json:"tag_filter"`
MemberCount int `json:"member_count"`
CreatedAt string `json:"created_at"`
}
// Strategy maps a user group + device group to a permission policy.
// Mirrors the Node.js console's strategies table.
type Strategy struct {
ID int64 `json:"id"`
GUID string `json:"guid"`
Name string `json:"name"`
UserGroupGUID string `json:"user_group_guid"`
DeviceGroupGUID string `json:"device_group_guid"`
Enabled bool `json:"enabled"`
Permissions string `json:"permissions"` // JSON blob
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// Database is the interface for all database operations.
// Designed to support SQLite (now) and PostgreSQL (future) as drop-in implementations.
type Database interface {
// Lifecycle
Close() error
Migrate() error
// Peer operations
GetPeer(id string) (*Peer, error)
GetPeerIDState(id string) (PeerIDState, error)
// GetPeersByIDs returns active (non-soft-deleted) peers for the given IDs.
// Missing IDs are omitted; an empty ids slice returns an empty map.
GetPeersByIDs(ids []string) (map[string]*Peer, error)
GetPeerByUUID(uuid string) (*Peer, error)
UpsertPeer(p *Peer) error
DeletePeer(id string) error // soft delete
HardDeletePeer(id string) error // permanent delete
ListPeers(includeDeleted bool) ([]*Peer, error)
// ListPeersPaginated returns peers with SQL LIMIT/OFFSET plus total row count.
// limit <= 0 returns all rows (offset still applied).
ListPeersPaginated(includeDeleted bool, limit, offset int) ([]*Peer, int, error)
GetPeerCount() (total int, online int, err error)
GetBannedPeerCount() (int, error)
// Status tracking
UpdatePeerStatus(id string, status string, ip string) error
BatchUpdatePeerStatus(ids []string, status string) error
UpdatePeerSysinfo(id, hostname, os, version string) error
SetAllOffline() error
// Peer field updates
UpdatePeerFields(id string, fields map[string]string) error
// Ban system
BanPeer(id string, reason string) error
UnbanPeer(id string) error
IsPeerBanned(id string) (bool, error)
IsPeerSoftDeleted(id string) (bool, error)
// RestorePeer clears the soft_deleted flag and deleted_at timestamp,
// making a previously deleted peer visible and registrable again.
// This is an explicit admin operation — UpsertPeer must NOT do this
// implicitly (GHSA-3v82-3gf8-fxx8).
RestorePeer(id string) error
// ID change
ChangePeerID(oldID, newID, reason string) error
GetIDChangeHistory(id string) ([]*IDChangeHistory, error)
// IsRenamedPeerID returns true if the given ID was previously used and
// changed to a different one (appears as old_id in id_change_history).
IsRenamedPeerID(id string) (bool, error)
// GetLatestRenameTarget returns the most recent new_id for a renamed old_id.
GetLatestRenameTarget(oldID string) (string, error)
// ReleasePeerID clears id_change_history rows involving id so the ID can be reused.
ReleasePeerID(id string) error
// CDAP: linked device queries
GetLinkedPeers(id string) ([]*Peer, error)
// Tags
UpdatePeerTags(id, tags string) error
ListPeersByTag(tag string) ([]*Peer, error)
// Config
GetConfig(key string) (string, error)
SetConfig(key, value string) error
DeleteConfig(key string) error
ListConfigByPrefix(prefix string) ([]ServerConfig, error)
// Users
CreateUser(u *User) error
GetUser(username string) (*User, error)
GetUserByID(id int64) (*User, error)
ListUsers() ([]*User, error)
UpdateUser(u *User) error
DeleteUser(id int64) error
UpdateUserLogin(id int64) error
UserCount() (int, error)
// API Keys
CreateAPIKey(k *APIKey) error
GetAPIKeyByHash(keyHash string) (*APIKey, error)
ListAPIKeys() ([]*APIKey, error)
DeleteAPIKey(id int64) error
TouchAPIKey(id int64) error
// Device Tokens (Dual Key System)
CreateDeviceToken(t *DeviceToken) error
GetDeviceToken(id int64) (*DeviceToken, error)
GetDeviceTokenByHash(tokenHash string) (*DeviceToken, error)
GetDeviceTokenByPeerID(peerID string) (*DeviceToken, error)
ListDeviceTokens(includeRevoked bool) ([]*DeviceToken, error)
UpdateDeviceToken(t *DeviceToken) error
RevokeDeviceToken(id int64) error
BindTokenToPeer(tokenHash, peerID string) error
IncrementTokenUse(tokenHash string) error
ValidateToken(tokenHash string) (*DeviceToken, error) // Returns token if valid, nil if invalid/expired/revoked
CleanupExpiredTokens() (int64, error)
// Address Book
GetAddressBook(username, abType string) (string, error) // Returns JSON data string; abType: "legacy" or "personal"
SaveAddressBook(username, abType, data string) error
// Peer Metrics (heartbeat CPU/memory/disk)
SavePeerMetric(peerID string, cpu, memory, disk float64) error
GetPeerMetrics(peerID string, limit int) ([]*PeerMetric, error)
GetLatestPeerMetric(peerID string) (*PeerMetric, error)
CleanupOldMetrics(maxAge time.Duration) (int64, error) // Delete metrics older than maxAge
// Chat Messages
SaveChatMessage(msg *ChatMessage) (int64, error) // Returns inserted ID
GetChatHistory(conversationID string, limit int) ([]*ChatMessage, error)
GetChatHistoryBefore(conversationID string, beforeID int64, limit int) ([]*ChatMessage, error)
MarkChatRead(conversationID, readerID string) error // Mark all messages as read for reader
GetUnreadCount(deviceID string) (int, error) // Total unread messages for device
DeleteChatHistory(conversationID string) error
// Chat Groups
CreateChatGroup(g *ChatGroup) error
GetChatGroup(id string) (*ChatGroup, error)
ListChatGroups(memberID string) ([]*ChatGroup, error) // Groups containing memberID
UpdateChatGroup(g *ChatGroup) error
DeleteChatGroup(id string) error
// Help Requests
CreateHelpRequest(r *HelpRequest) (int64, error) // Returns inserted ID
GetHelpRequest(id int64) (*HelpRequest, error)
ListHelpRequests(filter HelpRequestFilter) ([]*HelpRequest, error)
UpdateHelpRequestStatus(id int64, status, handledBy string) error
PruneHelpRequests(maxAge time.Duration) (int64, error) // Delete requests older than maxAge
GetDeviceOrgID(deviceID string) (string, error) // "" if device has no org
// Organizations
CreateOrganization(o *Organization) error
GetOrganization(id string) (*Organization, error)
GetOrganizationBySlug(slug string) (*Organization, error)
ListOrganizations() ([]*Organization, error)
UpdateOrganization(o *Organization) error
DeleteOrganization(id string) error
// Org Users
CreateOrgUser(u *OrgUser) error
GetOrgUser(id string) (*OrgUser, error)
GetOrgUserByUsername(orgID, username string) (*OrgUser, error)
GetOrgUserByServerUserID(orgID string, serverUserID int64) (*OrgUser, error)
ListOrgUsers(orgID string) ([]*OrgUser, error)
UpdateOrgUser(u *OrgUser) error
DeleteOrgUser(id string) error
UpdateOrgUserLogin(id string) error
// Org User Linking (Issue #106)
LinkUserToOrg(orgID string, userID int64, role string) (*OrgUser, error)
UnlinkUserFromOrg(orgID string, serverUserID int64) error
ListUsersNotInOrg(orgID string) ([]*User, error)
ListUserOrganizations(userID int64) ([]*Organization, error)
// Org Devices
AssignDeviceToOrg(d *OrgDevice) error
UnassignDeviceFromOrg(orgID, deviceID string) error
GetOrgDevice(orgID, deviceID string) (*OrgDevice, error)
ListOrgDevices(orgID string) ([]*OrgDevice, error)
UpdateOrgDevice(d *OrgDevice) error
// Org Invitations
CreateOrgInvitation(inv *OrgInvitation) error
GetOrgInvitationByToken(token string) (*OrgInvitation, error)
ListOrgInvitations(orgID string) ([]*OrgInvitation, error)
UseOrgInvitation(token string) error
DeleteOrgInvitation(id string) error
// Org Settings
GetOrgSetting(orgID, key string) (string, error)
SetOrgSetting(orgID, key, value string) error
DeleteOrgSetting(orgID, key string) error
ListOrgSettings(orgID string) ([]*OrgSetting, error)
// Org Address Books (shared contacts for organization members)
GetOrgAddressBook(orgID, abType string) (string, error)
SaveOrgAddressBook(orgID, abType, data, updatedBy string) error
// Access Policies (unattended access management)
GetAccessPolicy(peerID string) (*AccessPolicy, error)
SaveAccessPolicy(p *AccessPolicy) error
DeleteAccessPolicy(peerID string) error
// Role Permissions (RBAC Phase 52)
ListRolePermissions(role string) ([]*RolePermission, error)
SetRolePermission(role, permission string, granted bool) error
DeleteRolePermission(role, permission string) error
HasRolePermission(role, permission string) (bool, error)
// Org-scoped device queries (RBAC Phase 52 — data scoping)
ListPeersForOrg(orgID string, includeDeleted bool) ([]*Peer, error)
ListPeersForOrgPaginated(orgID string, includeDeleted bool, limit, offset int) ([]*Peer, int, error)
// Audit logs (RustDesk client reporting — API-port consolidation Phase A)
InsertAuditConnection(a *AuditConnection) error
ListAuditConnections(f AuditFilter) ([]*AuditConnection, error)
CountAuditConnections(f AuditFilter) (int, error)
InsertAuditFile(a *AuditFile) error
ListAuditFiles(f AuditFilter) ([]*AuditFile, error)
CountAuditFiles(f AuditFilter) (int, error)
InsertAuditAlarm(a *AuditAlarm) error
ListAuditAlarms(f AuditFilter) ([]*AuditAlarm, error)
CountAuditAlarms(f AuditFilter) (int, error)
// User groups (operator/user grouping — API-port consolidation Phase A)
ListUserGroups() ([]*UserGroup, error)
GetUserGroup(guid string) (*UserGroup, error)
CreateUserGroup(g *UserGroup) error
UpdateUserGroup(guid string, g *UserGroup) error
DeleteUserGroup(guid string) error
// Device groups (device grouping — API-port consolidation Phase A)
ListDeviceGroups() ([]*DeviceGroup, error)
GetDeviceGroup(guid string) (*DeviceGroup, error)
CreateDeviceGroup(g *DeviceGroup) error
UpdateDeviceGroup(guid string, g *DeviceGroup) error
DeleteDeviceGroup(guid string) error
// Strategies (permission policies — API-port consolidation Phase A)
ListStrategies() ([]*Strategy, error)
GetStrategy(guid string) (*Strategy, error)
CreateStrategy(s *Strategy) error
UpdateStrategy(guid string, s *Strategy) error
DeleteStrategy(guid string) error
// Pro-style direct strategy assignments (device / user / device group)
AssignStrategy(strategyGUID string, peerKeys, userKeys, groupKeys []string) error
GetStrategyAssignmentSummary(strategyGUID string) (*StrategyAssignmentSummary, error)
ResolvePeerAssignmentKey(ref string) (string, error)
ResolveUserAssignmentKey(ref string) (string, error)
ResolveDeviceGroupAssignmentKey(ref string) (string, error)
EnsurePeerGUID(id string) (string, error)
GetPeerIDByGUID(guid string) (string, error)
SetStrategyEnabled(guid string, enabled bool) error
ListProDeviceRefs(idFilter string, limit, offset int) ([]ProDeviceRef, int, error)
// Billing / commercialization
CreateBillingPackage(p *BillingPackage) error
GetBillingPackage(id string) (*BillingPackage, error)
ListBillingPackages() ([]*BillingPackage, error)
UpdateBillingPackage(p *BillingPackage) error
DeleteBillingPackage(id string) error
CreateBillingOrgContract(c *BillingOrgContract) error
GetBillingOrgContract(id string) (*BillingOrgContract, error)
GetActiveBillingOrgContract(orgID string) (*BillingOrgContract, error)
ListBillingOrgContracts(filter BillingContractFilter) ([]*BillingOrgContract, error)
UpdateBillingOrgContract(c *BillingOrgContract) error
CreateBillingContract(c *BillingContract) error
GetBillingContract(id string) (*BillingContract, error)
GetActiveBillingContract(targetType, targetKey string) (*BillingContract, error)
ListBillingContracts(filter BillingContractFilter) ([]*BillingContract, error)
UpdateBillingContract(c *BillingContract) error
DeleteBillingContract(id string) error
CountBillingContractsByPackage(packageID string) (int, error)
CountBillingContractsExpiringWithin(days int) (int, error)
CreateBillingSession(s *BillingSession) error
GetBillingSession(id string) (*BillingSession, error)
GetBillingSessionByRelayUUID(relayUUID string) (*BillingSession, error)
UpdateBillingSession(s *BillingSession) error
ListBillingSessions(filter BillingSessionFilter) ([]*BillingSession, error)
InsertBillingLedgerEntry(e *BillingSessionLedger) error
ListBillingLedger(sessionID string) ([]*BillingSessionLedger, error)
CreateBillingWorkReport(r *BillingWorkReport) error
GetBillingWorkReportBySession(sessionID string) (*BillingWorkReport, error)
ListBillingWorkReports(orgID string, limit int) ([]*BillingWorkReport, error)
// RustDesk client sessions (Issue #242 — DB-backed opaque tokens with sliding expiry)
EnsureClientSessionsSchema() error
CreateClientSession(sess *ClientSession) error
GetClientSessionByTokenHash(tokenHash string) (*ClientSession, error)
GetActiveClientSessionByClient(clientID, clientUUID string) (*ClientSession, error)
TouchClientSession(id int64, expiresAt, lastUsed string) error
RevokeClientSessionByTokenHash(tokenHash string) error
RevokeClientSessionsForDevice(userID int64, clientID, clientUUID string) error
CleanupExpiredClientSessions() (int64, error)
ListBillingCurrencies() ([]*BillingCurrency, error)
UpsertBillingCurrency(c *BillingCurrency) error
DeleteBillingCurrency(code string) error
}