Documentation
¶
Index ¶
- Constants
- func SameAccess(acc1, acc2 EnvAccess) bool
- type AccessLevel
- type AdminUser
- type EnvAccess
- type TokenClaims
- type UserAccess
- type UserManager
- func (m *UserManager) All() ([]AdminUser, error)
- func (m *UserManager) AllNonService() ([]AdminUser, error)
- func (m *UserManager) AllService() ([]AdminUser, error)
- func (m *UserManager) ChangeAccess(username, environment string, access EnvAccess) error
- func (m *UserManager) ChangeAccessAll(username string, envUUIDs []string, access EnvAccess) (int, error)
- func (m *UserManager) ChangeAdmin(username string, admin bool) error
- func (m *UserManager) ChangeAuthSource(username, authSource string) error
- func (m *UserManager) ChangeEmail(username, email string) error
- func (m *UserManager) ChangeFullname(username, fullname string) error
- func (m *UserManager) ChangePassword(username, password string) error
- func (m *UserManager) ChangePermission(username, environment string, perm UserPermission) error
- func (m *UserManager) ChangePermissions(username, environment string, permissions []UserPermission) error
- func (m *UserManager) ChangeService(username string, service bool) error
- func (m *UserManager) CheckLoginCredentials(username, password string) (bool, AdminUser)
- func (m *UserManager) CheckPermissions(username string, level AccessLevel, environment string) bool
- func (m *UserManager) CheckToken(jwtSecret, tokenStr string) (TokenClaims, bool)
- func (m *UserManager) ClearToken(username string) error
- func (m *UserManager) CountAdmins() (int64, error)
- func (m *UserManager) Create(user AdminUser) error
- func (m *UserManager) CreatePermission(permission UserPermission) error
- func (m *UserManager) CreatePermissions(permissions []UserPermission) error
- func (m *UserManager) CreateToken(username, issuer string, expHours int) (string, time.Time, error)
- func (m *UserManager) Delete(username string) error
- func (m *UserManager) DeleteAllPermissions(username string) error
- func (m *UserManager) DeleteEnvPermissions(username, environment string) error
- func (m *UserManager) Exists(username string) bool
- func (m *UserManager) ExistsGet(username string) (bool, AdminUser)
- func (m *UserManager) GenEnvUserAccess(envs []string, user, query, carve, admin bool) UserAccess
- func (m *UserManager) GenPermissions(username, granted string, access UserAccess) []UserPermission
- func (m *UserManager) GenUserAccess(env environments.TLSEnvironment, envAccess EnvAccess) UserAccess
- func (m *UserManager) GenUserPermission(username, granted, env string, aType int, aValue bool) UserPermission
- func (m *UserManager) GenericAllService(service bool) ([]AdminUser, error)
- func (m *UserManager) Get(username string) (AdminUser, error)
- func (m *UserManager) GetAccess(username string) (UserAccess, error)
- func (m *UserManager) GetAllPermissions(username string) ([]UserPermission, error)
- func (m *UserManager) GetByEnvID(username string, envID uint) (AdminUser, error)
- func (m *UserManager) GetEnvAccess(username, env string) (EnvAccess, error)
- func (m *UserManager) GetEnvPermissions(username, environment string) ([]UserPermission, error)
- func (m *UserManager) GetPermission(username, environment string, aType AccessLevel) (UserPermission, error)
- func (m *UserManager) GetWithService(username string, service bool) (AdminUser, error)
- func (m *UserManager) GetWithServiceByEnvID(username string, service bool, envID uint) (AdminUser, error)
- func (m *UserManager) HashPasswordWithSalt(password string) (string, error)
- func (m *UserManager) HashTextWithSalt(text string) (string, error)
- func (m *UserManager) IsAdmin(username string) bool
- func (m *UserManager) New(username, password, email, fullname string, admin, service bool) (AdminUser, error)
- func (m *UserManager) SetEnvAdmin(username, environment string, admin bool) error
- func (m *UserManager) SetEnvCarve(username, environment string, carve bool) error
- func (m *UserManager) SetEnvLevel(username, environment string, level AccessLevel, value bool) error
- func (m *UserManager) SetEnvQuery(username, environment string, query bool) error
- func (m *UserManager) SetEnvUser(username, environment string, user bool) error
- func (m *UserManager) UpdateMetadata(ipaddress, useragent, username, csrftoken string) error
- func (m *UserManager) UpdateToken(username, token string, exp time.Time) error
- func (m *UserManager) UpdateTokenIPAddress(ipaddress, username string) error
- func (u *UserManager) WithJWT(jwtconfig *config.YAMLConfigurationJWT) *UserManager
- type UserPermission
Constants ¶
const ( DefaultTokenIssuer = "osctrl" // ActionAdd as action to add a user ActionAdd string = "add" // ActionEdit as action to edit a user ActionEdit string = "edit" // ActionRemove as action to remove a user ActionRemove string = "remove" )
const BcryptCost = 12
BcryptCost is the bcrypt work factor for password hashing. 12 is the 2026 commodity-CPU recommendation; bcrypt.DefaultCost is 10.
const MinJWTSecretBytes = 32
MinJWTSecretBytes is the minimum acceptable length of the HMAC JWT secret (RFC 7518 §3.2 recommends a key at least as wide as the hash output for HS256 ⇒ 32 bytes). Generate one with: openssl rand -base64 48
Variables ¶
This section is empty.
Functions ¶
func SameAccess ¶
Helper to compare two set of permissions
Types ¶
type AccessLevel ¶
type AccessLevel int
AccessLevel as abstraction of level of access for a user
const ( // AdminLevel for admin privileges AdminLevel AccessLevel = iota // QueryLevel for query privileges QueryLevel // CarveLevel for carve privileges CarveLevel // UserLevel for regular user privileges UserLevel // NoEnvironment to be explicit when used NoEnvironment = "" )
type AdminUser ¶
type AdminUser struct {
gorm.Model
Username string `gorm:"index"`
Email string
Fullname string
PassHash string `json:"-"`
APIToken string `json:"-"`
TokenExpire time.Time
Admin bool
Service bool
UUID string
CSRFToken string `json:"-"`
LastIPAddress string
LastUserAgent string
LastAccess time.Time
LastTokenUse time.Time
EnvironmentID uint
// AuthSource records HOW this user authenticates. Empty/""
// means password (the legacy and default path). Set to "oidc"
// when the row was JIT-provisioned by the federated-login
// callback. The field is informational — operators see it on
// the Users page so they can distinguish SSO-only rows from
// dual-auth rows. The Login flow itself doesn't gate on it
// (an OIDC user with a password set later can log in either
// way, by design).
AuthSource string
}
AdminUser to hold all users
type EnvAccess ¶
type EnvAccess struct {
User bool `json:"user"`
Query bool `json:"query"`
Carve bool `json:"carve"`
Admin bool `json:"admin"`
}
UserPermissions to abstract the permissions for a user
func GenEnvAccess ¶
Helper to convert received permissions into struct
type TokenClaims ¶
type TokenClaims struct {
Username string `json:"username"`
jwt.RegisteredClaims
}
TokenClaims to hold user claims when using JWT
type UserAccess ¶
UserAccess to provide an abstraction for user access between environment and permissions
type UserManager ¶
type UserManager struct {
DB *gorm.DB
JWTConfig *config.YAMLConfigurationJWT
}
UserManager have all users of the system
func CreateUserManager ¶
func CreateUserManager(backend *gorm.DB) *UserManager
CreateUserManager initializes the DB-backed user/permission manager. JWT signing config is attached separately via WithJWT — callers that don't mint tokens (osctrl-cli) skip it. CreateToken refuses to run without a configured JWT, so a non-token caller cannot accidentally sign anything.
func (*UserManager) AllNonService ¶ added in v0.4.5
func (m *UserManager) AllNonService() ([]AdminUser, error)
AllNonService get all non-service users
func (*UserManager) AllService ¶ added in v0.4.5
func (m *UserManager) AllService() ([]AdminUser, error)
AllService get all service users
func (*UserManager) ChangeAccess ¶
func (m *UserManager) ChangeAccess(username, environment string, access EnvAccess) error
ChangeAccess for setting user access by username and environment
func (*UserManager) ChangeAccessAll ¶ added in v0.5.3
func (m *UserManager) ChangeAccessAll(username string, envUUIDs []string, access EnvAccess) (int, error)
ChangeAccessAll applies the same EnvAccess to every environment in envUUIDs. Returns the count of envs successfully updated. On the first error, aborts and returns the partial count + the error — callers should treat any non-nil error as "operator must retry."
Why partial-count-on-error instead of full rollback: the existing ChangeAccess path is itself non-transactional (four sequential SetEnvLevel writes, each a separate UPDATE). A "transactional bulk" would need a much deeper refactor of SetEnvLevel to take a tx handle. For an MVP "give alice access to all envs" workflow, the partial-success-on-error semantics are acceptable: the SPA reports "N of M succeeded, retry?" and re-running is idempotent — re-applying the same access to an env is a no-op rewrite of the same rows.
envUUIDs is a slice of environment identifiers as they appear in user_permissions.environment (which is the env UUID — the column is named "environment" but holds the UUID; see ChangeAccess). Callers (cmd/api handler) must enumerate envs upfront and pass the UUID list; this package doesn't reach into the environments table directly to keep the dependency arrow clean.
func (*UserManager) ChangeAdmin ¶
func (m *UserManager) ChangeAdmin(username string, admin bool) error
ChangeAdmin to modify the admin setting for a user
func (*UserManager) ChangeAuthSource ¶ added in v0.5.3
func (m *UserManager) ChangeAuthSource(username, authSource string) error
ChangeAuthSource to modify the auth_source for a user
func (*UserManager) ChangeEmail ¶
func (m *UserManager) ChangeEmail(username, email string) error
ChangeEmail for user by username
func (*UserManager) ChangeFullname ¶
func (m *UserManager) ChangeFullname(username, fullname string) error
ChangeFullname for user by username
func (*UserManager) ChangePassword ¶
func (m *UserManager) ChangePassword(username, password string) error
ChangePassword for user by username
func (*UserManager) ChangePermission ¶
func (m *UserManager) ChangePermission(username, environment string, perm UserPermission) error
ChangePermissions for setting user permissions by username
func (*UserManager) ChangePermissions ¶
func (m *UserManager) ChangePermissions(username, environment string, permissions []UserPermission) error
ChangePermissions for setting user permissions by username
func (*UserManager) ChangeService ¶ added in v0.4.5
func (m *UserManager) ChangeService(username string, service bool) error
ChangeService to modify the service setting for a user
func (*UserManager) CheckLoginCredentials ¶
func (m *UserManager) CheckLoginCredentials(username, password string) (bool, AdminUser)
CheckLoginCredentials matches password hashes and, on a successful match, opportunistically re-hashes the password at the current BcryptCost when the stored hash is below it. Users created under an older cost migrate transparently on their next login. The rehash failure is non-fatal — login succeeds even if the rehash write fails (next login retries).
func (*UserManager) CheckPermissions ¶
func (m *UserManager) CheckPermissions(username string, level AccessLevel, environment string) bool
CheckPermissions to verify access for a username
func (*UserManager) CheckToken ¶
func (m *UserManager) CheckToken(jwtSecret, tokenStr string) (TokenClaims, bool)
CheckToken to verify if a token used is valid. Pins the signing algorithm to HMAC so an attacker cannot swap to `alg:none` or RS256-with-public-key (RS-vs-HS confusion) — defense-in-depth on top of the underlying library's own mitigations.
func (*UserManager) ClearToken ¶ added in v0.5.2
func (m *UserManager) ClearToken(username string) error
ClearToken empties the user's APIToken and CSRFToken so any existing JWT + CSRF cookie pair for them stops validating. Used by DELETE /api/v1/users/{username}/token. We use a map-update so the empty strings actually land (GORM's struct-Updates skips zero-value fields).
func (*UserManager) CountAdmins ¶ added in v0.5.2
func (m *UserManager) CountAdmins() (int64, error)
CountAdmins returns the number of active admin (Admin=true) users. Used by the permissions API to refuse demoting the last super-admin (which would lock the system out — no remaining super-admin = no one can promote anyone else).
func (*UserManager) CreatePermission ¶
func (m *UserManager) CreatePermission(permission UserPermission) error
CreatePermission new permission
func (*UserManager) CreatePermissions ¶
func (m *UserManager) CreatePermissions(permissions []UserPermission) error
CreatePermissions to iterate through a slice of permissions
func (*UserManager) CreateToken ¶
CreateToken to create a new JWT token for a given user. Stamps a random jti (JWT ID) on every token so two issuances for the same user — even in the same second with the same expiry — produce distinct token strings. Without the jti, claims are deterministic (username + ExpiresAt + Issuer) and HMAC-SHA256 is deterministic for the same key+payload: re-issuing in the same second would return the same bytes, silently undoing token rotation. The jti is a 16-byte random hex string.
func (*UserManager) Delete ¶
func (m *UserManager) Delete(username string) error
Delete user by username
func (*UserManager) DeleteAllPermissions ¶
func (m *UserManager) DeleteAllPermissions(username string) error
DeleteAllPermissions to delete all permissions by username
func (*UserManager) DeleteEnvPermissions ¶
func (m *UserManager) DeleteEnvPermissions(username, environment string) error
DeleteEnvPermissions to delete all permissions by username and environment
func (*UserManager) Exists ¶
func (m *UserManager) Exists(username string) bool
Exists checks if user exists
func (*UserManager) ExistsGet ¶
func (m *UserManager) ExistsGet(username string) (bool, AdminUser)
ExistsGet checks if user exists and returns the user
func (*UserManager) GenEnvUserAccess ¶
func (m *UserManager) GenEnvUserAccess(envs []string, user, query, carve, admin bool) UserAccess
GenEnvUserAccess to generate the struct with empty access
func (*UserManager) GenPermissions ¶
func (m *UserManager) GenPermissions(username, granted string, access UserAccess) []UserPermission
GenPermission to generate the struct with empty permissions FIXME this probably can be implemented in a better way
func (*UserManager) GenUserAccess ¶
func (m *UserManager) GenUserAccess(env environments.TLSEnvironment, envAccess EnvAccess) UserAccess
GenUserAccess to generate the struct with empty access
func (*UserManager) GenUserPermission ¶
func (m *UserManager) GenUserPermission(username, granted, env string, aType int, aValue bool) UserPermission
GenUserPermission Helper to generate struct
func (*UserManager) GenericAllService ¶ added in v0.4.5
func (m *UserManager) GenericAllService(service bool) ([]AdminUser, error)
GenericAllService get all users with a specific service
func (*UserManager) Get ¶
func (m *UserManager) Get(username string) (AdminUser, error)
Get user by username including service users
func (*UserManager) GetAccess ¶
func (m *UserManager) GetAccess(username string) (UserAccess, error)
GetAccess to extract all access by username
func (*UserManager) GetAllPermissions ¶
func (m *UserManager) GetAllPermissions(username string) ([]UserPermission, error)
GetAllPermissions to extract permissions by username
func (*UserManager) GetByEnvID ¶ added in v0.4.6
func (m *UserManager) GetByEnvID(username string, envID uint) (AdminUser, error)
Get user by username and by environment ID, including service users
func (*UserManager) GetEnvAccess ¶
func (m *UserManager) GetEnvAccess(username, env string) (EnvAccess, error)
GetEnvAccess to get the access for a user and a specific environment
func (*UserManager) GetEnvPermissions ¶
func (m *UserManager) GetEnvPermissions(username, environment string) ([]UserPermission, error)
GetPermissions to extract permissions by username and environment
func (*UserManager) GetPermission ¶
func (m *UserManager) GetPermission(username, environment string, aType AccessLevel) (UserPermission, error)
GetPermission to extract permission by username and environment
func (*UserManager) GetWithService ¶ added in v0.4.5
func (m *UserManager) GetWithService(username string, service bool) (AdminUser, error)
Get user by username and service
func (*UserManager) GetWithServiceByEnvID ¶ added in v0.4.6
func (m *UserManager) GetWithServiceByEnvID(username string, service bool, envID uint) (AdminUser, error)
Get user by username, service and environment ID
func (*UserManager) HashPasswordWithSalt ¶
func (m *UserManager) HashPasswordWithSalt(password string) (string, error)
HashPasswordWithSalt to hash a password before store it
func (*UserManager) HashTextWithSalt ¶
func (m *UserManager) HashTextWithSalt(text string) (string, error)
HashTextWithSalt to hash text before store it
func (*UserManager) IsAdmin ¶
func (m *UserManager) IsAdmin(username string) bool
IsAdmin checks if user is an admin
func (*UserManager) New ¶
func (m *UserManager) New(username, password, email, fullname string, admin, service bool) (AdminUser, error)
New empty user
func (*UserManager) SetEnvAdmin ¶
func (m *UserManager) SetEnvAdmin(username, environment string, admin bool) error
SetEnvAdmin to change the admin access for a user and environment
func (*UserManager) SetEnvCarve ¶
func (m *UserManager) SetEnvCarve(username, environment string, carve bool) error
SetEnvCarve to change the carve access for a user and environment
func (*UserManager) SetEnvLevel ¶
func (m *UserManager) SetEnvLevel(username, environment string, level AccessLevel, value bool) error
SetEnvLevel changes the (username, environment, level) permission row's access_value, or creates it if no row exists yet. The upsert branch is what makes ChangeAccess work for a user who has never had any permission rows in this env — historically a "GetPermission record not found" was returned as an error here, which made the single-env Save button (and now the bulk-grant endpoint) fail on first use.
Notes:
- GORM's `errors.Is(err, gorm.ErrRecordNotFound)` is the canonical check; the .Error() string is also "record not found" but we don't string-match.
- EnvironmentID is set to 0 on insert. The numeric FK isn't used by any read path (GetAccess / GetEnvAccess switch on AccessType + Environment string), and the existing GenUserPermission helper also leaves it zero. Consistent.
- granted_by is unset on insert via this path — there's no caller-side "who granted" context at SetEnvLevel's layer. The audit log captures the granting operator at the handler.
func (*UserManager) SetEnvQuery ¶
func (m *UserManager) SetEnvQuery(username, environment string, query bool) error
SetEnvQuery to change the query access for a user and environment
func (*UserManager) SetEnvUser ¶
func (m *UserManager) SetEnvUser(username, environment string, user bool) error
SetEnvUser to change the user access for a user and environment
func (*UserManager) UpdateMetadata ¶
func (m *UserManager) UpdateMetadata(ipaddress, useragent, username, csrftoken string) error
UpdateMetadata updates IP, User Agent and Last Access for a given user
func (*UserManager) UpdateToken ¶
func (m *UserManager) UpdateToken(username, token string, exp time.Time) error
UpdateToken for user by username
func (*UserManager) UpdateTokenIPAddress ¶
func (m *UserManager) UpdateTokenIPAddress(ipaddress, username string) error
UpdateTokenIPAddress updates IP and Last Access for a user's token
func (*UserManager) WithJWT ¶ added in v0.5.2
func (u *UserManager) WithJWT(jwtconfig *config.YAMLConfigurationJWT) *UserManager
WithJWT attaches JWT signing config to the user manager. The secret is validated here — at the point where token-issuing capability is granted — not at DB-manager construction time. Returns the manager so calls can chain: users.CreateUserManager(db).WithJWT(cfg).