@@ -0,0 +1,141 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user