Changeset 0.24.2 (#158)
This commit is contained in:
@@ -681,31 +681,52 @@ COMMENT ON COLUMN groups.permissions IS 'Array of permission strings granted to
|
||||
COMMENT ON COLUMN groups.allowed_models IS 'Array of model_id strings this group can use. NULL = unrestricted.';
|
||||
```
|
||||
|
||||
### The Everyone Group
|
||||
|
||||
Rather than hardcoding base permissions in application code, the system
|
||||
seeds a special **Everyone** group at migration time. Every authenticated
|
||||
user implicitly receives its permissions without explicit membership.
|
||||
|
||||
Properties:
|
||||
- `id` = `00000000-0000-0000-0000-000000000001` (stable, well-known)
|
||||
- `name` = `"Everyone"`
|
||||
- `scope` = `"global"`
|
||||
- `source` = `"system"` (guards against deletion)
|
||||
|
||||
The Everyone group is editable in the admin UI — an org that wants
|
||||
locked-down defaults empties it; an open install leaves it permissive.
|
||||
`DefaultUserPerms` via `global_config` (previously in ROADMAP) is
|
||||
superseded by this approach: it was a one-time init value anyway.
|
||||
The migration seeds the group; subsequent changes are live data.
|
||||
|
||||
`Delete` on a `source=system` group returns `400 Bad Request`. The guard
|
||||
lives in the store layer, not the handler, so it can't be bypassed.
|
||||
|
||||
### Permission Resolution
|
||||
|
||||
A user's effective permissions are the **union** of all their groups'
|
||||
permissions, plus base permissions that every active user gets.
|
||||
A user's effective permissions are the **union** of the Everyone group's
|
||||
permissions plus all their explicitly assigned groups' permissions.
|
||||
|
||||
```go
|
||||
// server/auth/permissions.go
|
||||
|
||||
// Base permissions granted to all active users (no group needed)
|
||||
var basePermissions = map[string]bool{
|
||||
"model.use": true,
|
||||
"kb.read": true,
|
||||
"channel.create": true,
|
||||
}
|
||||
const EveryoneGroupID = "00000000-0000-0000-0000-000000000001"
|
||||
|
||||
// ResolvePermissions returns the effective permission set for a user.
|
||||
// Always includes the Everyone group regardless of membership.
|
||||
// Admin users bypass this entirely — callers should check role first.
|
||||
func ResolvePermissions(ctx context.Context, stores store.Stores, userID string) (map[string]bool, error) {
|
||||
perms := make(map[string]bool)
|
||||
|
||||
// Start with base permissions
|
||||
for k, v := range basePermissions {
|
||||
perms[k] = v
|
||||
// Always apply Everyone group
|
||||
everyone, err := stores.Groups.GetByID(ctx, EveryoneGroupID)
|
||||
if err == nil {
|
||||
for _, p := range everyone.Permissions {
|
||||
perms[p] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Union all group permissions
|
||||
// Union all explicit group memberships
|
||||
groups, err := stores.Groups.ListForUser(ctx, userID)
|
||||
if err != nil {
|
||||
return perms, err
|
||||
@@ -723,6 +744,21 @@ func ResolvePermissions(ctx context.Context, stores store.Stores, userID string)
|
||||
Admin users (`role=admin`) bypass permission checks entirely, same
|
||||
as they bypass team membership checks today.
|
||||
|
||||
### BYOK Budget Bypass
|
||||
|
||||
Token budget enforcement only applies when the completion draws from a
|
||||
**team or global provider**. If `providerScope == "personal"` the request
|
||||
is the user's own money — budget pre-flight is skipped entirely. The
|
||||
`providerScope` value is already resolved by `resolveConfig()` before
|
||||
the completion handler begins streaming.
|
||||
|
||||
### Anonymous Sessions
|
||||
|
||||
v0.24.3 session participants carry `SessionClaims`, not `UserClaims`.
|
||||
They never enter group-based permission resolution. Their capability is
|
||||
bounded to their channel and the persona bound to it by the workflow
|
||||
definition. Do not conflate the two paths.
|
||||
|
||||
### Middleware Helper
|
||||
|
||||
```go
|
||||
@@ -813,9 +849,15 @@ Group edit form gains:
|
||||
|
||||
### What Ships
|
||||
|
||||
- Migration 019 (group permissions, token budgets, model allowlist)
|
||||
- `server/auth/permissions.go` (resolution logic)
|
||||
- `middleware/permissions.go` (`RequirePermission()`)
|
||||
- Migration 020 (group permissions, token budgets, model allowlist, Everyone group seed)
|
||||
- `server/auth/permissions.go` (constants, resolution logic, budget resolution)
|
||||
- `middleware/permissions.go` (`RequirePermission()` with request-context cache)
|
||||
- Permission-gated handler routes
|
||||
- Token budget pre-flight in completion handler (skipped for personal/BYOK providers)
|
||||
- Model access filtering in model list endpoint
|
||||
- Admin UI: group permissions editor, user effective permissions view
|
||||
- Both Postgres and SQLite store implementations
|
||||
- Everyone group: seeded in migration, guarded from deletion in store layer
|
||||
- Permission-gated handler routes
|
||||
- Token budget pre-flight in completion handler
|
||||
- Model access filtering in model list endpoint
|
||||
|
||||
@@ -1430,15 +1430,15 @@ group infrastructure.
|
||||
Depends on: auth abstraction (v0.24.0). Parallel to v0.24.1.
|
||||
See [DESIGN-0.24.0.md](DESIGN-0.24.0.md) §v0.24.2 for full spec.
|
||||
|
||||
- [ ] Permission constants (`domain.action` convention, code-level enum)
|
||||
- [ ] `permissions` JSONB column on groups
|
||||
- [ ] `ResolvePermissions()`: union of group permissions + `DefaultUserPerms`
|
||||
- [ ] `RequirePermission()` middleware
|
||||
- [ ] Token budgets: per-group daily/monthly ceilings, enforced in completion handler
|
||||
- [ ] Model access control: per-group model allowlists
|
||||
- [ ] Admin UI: permission checklist, budget fields, model picker on group edit
|
||||
- [ ] `DefaultUserPerms` override via `global_config` key `default_permissions`
|
||||
- [ ] Migration 020: permissions, budgets, allowed_models on groups
|
||||
- [x] Permission constants (`domain.action` convention, code-level enum)
|
||||
- [x] `permissions` JSONB column on groups
|
||||
- [x] `ResolvePermissions()`: union of group permissions + Everyone group
|
||||
- [x] `RequirePermission()` middleware
|
||||
- [x] Token budgets: per-group daily/monthly ceilings, enforced in completion handler
|
||||
- [x] Model access control: per-group model allowlists
|
||||
- [x] Admin UI: permission checklist, budget fields, model picker on group edit
|
||||
- [x] Everyone group supersedes `DefaultUserPerms` (seeded in migration, editable in admin)
|
||||
- [x] Migration 020: permissions, budgets, allowed_models on groups
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user