142 lines
5.1 KiB
Go
142 lines
5.1 KiB
Go
package storage
|
|
|
|
import (
|
|
"time"
|
|
|
|
"gardomatic.kleiax.de/internal/auth"
|
|
"gardomatic.kleiax.de/internal/platform/validate"
|
|
)
|
|
|
|
// UserModelInterface persists accounts and resolves token ownership.
|
|
type UserModelInterface interface {
|
|
Insert(user User) (User, error)
|
|
GetByID(id int) (User, error)
|
|
GetByEmail(email string) (User, error)
|
|
Update(user User) (User, error)
|
|
GetForToken(tokenScope, tokenPlaintext string) (User, error)
|
|
CreateEmailChange(userID int, email string, ttl time.Duration) (string, error)
|
|
ConfirmEmailChange(tokenPlaintext string, userID int) (User, error)
|
|
GetAll() ([]User, error)
|
|
UpdateRole(userID int, role ApplicationRole) (User, error)
|
|
Delete(userID int) error
|
|
}
|
|
|
|
// ApplicationRole groups garden-independent permissions for an account.
|
|
type ApplicationRole string
|
|
|
|
// ApplicationPermission identifies one garden-independent capability.
|
|
type ApplicationPermission string
|
|
|
|
// Built-in application roles and permissions.
|
|
const (
|
|
ApplicationRoleUser ApplicationRole = "application:user"
|
|
ApplicationRoleAdmin ApplicationRole = "application:admin"
|
|
|
|
ApplicationPermissionGlobalSpeciesWrite ApplicationPermission = "global_species:write"
|
|
ApplicationPermissionUsersManage ApplicationPermission = "users:manage"
|
|
ApplicationPermissionSettingsWrite ApplicationPermission = "application_settings:write"
|
|
ApplicationPermissionRolesManage ApplicationPermission = "roles:manage"
|
|
ApplicationPermissionGardensCreate ApplicationPermission = "gardens:create"
|
|
)
|
|
|
|
// Valid reports whether role is assignable to an account.
|
|
func (role ApplicationRole) Valid() bool {
|
|
return role == ApplicationRoleUser || role == ApplicationRoleAdmin
|
|
}
|
|
|
|
// Can reports whether an application role grants a permission.
|
|
func (role ApplicationRole) Can(permission ApplicationPermission) bool {
|
|
if permission == ApplicationPermissionGardensCreate {
|
|
return role == ApplicationRoleUser || role == ApplicationRoleAdmin
|
|
}
|
|
return role == ApplicationRoleAdmin && (permission == ApplicationPermissionGlobalSpeciesWrite ||
|
|
permission == ApplicationPermissionUsersManage ||
|
|
permission == ApplicationPermissionSettingsWrite ||
|
|
permission == ApplicationPermissionRolesManage)
|
|
}
|
|
|
|
// Permissions returns the capabilities bundled into the role.
|
|
func (role ApplicationRole) Permissions() []ApplicationPermission {
|
|
permissions := []ApplicationPermission{}
|
|
for _, permission := range []ApplicationPermission{
|
|
ApplicationPermissionGlobalSpeciesWrite,
|
|
ApplicationPermissionUsersManage,
|
|
ApplicationPermissionSettingsWrite,
|
|
ApplicationPermissionRolesManage,
|
|
ApplicationPermissionGardensCreate,
|
|
} {
|
|
if role.Can(permission) {
|
|
permissions = append(permissions, permission)
|
|
}
|
|
}
|
|
return permissions
|
|
}
|
|
|
|
// AllApplicationPermissions returns a copy of the supported application-level
|
|
// permissions.
|
|
func AllApplicationPermissions() []ApplicationPermission {
|
|
return []ApplicationPermission{
|
|
ApplicationPermissionGlobalSpeciesWrite,
|
|
ApplicationPermissionUsersManage,
|
|
ApplicationPermissionSettingsWrite,
|
|
ApplicationPermissionRolesManage,
|
|
ApplicationPermissionGardensCreate,
|
|
}
|
|
}
|
|
|
|
// ValidApplicationPermission reports whether permission is application-scoped
|
|
// or the global wildcard.
|
|
func ValidApplicationPermission(permission string) bool {
|
|
for _, candidate := range AllApplicationPermissions() {
|
|
if string(candidate) == permission {
|
|
return true
|
|
}
|
|
}
|
|
return permission == "*"
|
|
}
|
|
|
|
// User is an account that can own or join gardens.
|
|
type User struct {
|
|
ID int `json:"id"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
Name string `json:"name"`
|
|
Email string `json:"email"`
|
|
Color string `json:"color"`
|
|
Password auth.Password `json:"-"`
|
|
Activated bool `json:"activated"`
|
|
Role ApplicationRole `json:"role"`
|
|
Permissions []ApplicationPermission `json:"permissions"`
|
|
Version int `json:"-"`
|
|
}
|
|
|
|
// Can reports whether the user's resolved permissions grant permission. It
|
|
// falls back to the built-in role only when no resolved list is present.
|
|
func (user User) Can(permission ApplicationPermission) bool {
|
|
if user.Permissions != nil {
|
|
for _, granted := range user.Permissions {
|
|
if granted == permission || granted == "*" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
return user.Role.Can(permission)
|
|
}
|
|
|
|
// ValidateEmail applies the accepted email-address rules.
|
|
func ValidateEmail(v *validate.Validator, email string) {
|
|
v.Check(email != "", "email", "must be provided")
|
|
v.Check(validate.Matches(email, validate.EmailRX), "email", "must be a valid email address")
|
|
}
|
|
|
|
// ValidateUser applies account and password validation rules.
|
|
func ValidateUser(v *validate.Validator, user User) {
|
|
v.Check(user.Name != "", "name", "must be provided")
|
|
v.Check(len(user.Name) <= 500, "name", "must not be more than 500 bytes long")
|
|
|
|
ValidateEmail(v, user.Email)
|
|
v.Check(user.Color == "" || validate.Matches(user.Color, validate.ColorRX), "color", "must be a valid hex color")
|
|
user.Password.Validate(v)
|
|
}
|