Files
llmgw/internal/web/roles.go
T
Ilker ManapandClaude Opus 5.5 102ca9556f An organization's admin, and forms only for those who may send them
C1. A membership of an organization may be admin as well as member and
manager. An admin sees /org as a manager does and changes the
organization from it: its teams and projects, who of the approved
accounts is in them, its members' roles, and the keys spent in its
projects. Every id a form names is checked against the organization;
limits, accounts, the organization's name and the cluster stay with the
cluster's staff, and an admin cannot take their own role away.

A group from OIDC, SCIM or a directory no longer rewrites a membership
it names as a plain member at every sign-in, which took a manager's role
away (EnsureMembership).

Pages draw a form only for someone who may send it: Nav.May asks the
staff rule staffMay keeps, and a page whose forms a person may not send
gets no form token. A viewer sees the people, limits and mirror pages
without forms; an operator the mirror's.

Forms are compact: labels close to their fields, fields as wide as what
goes in them, smaller buttons, and the short forms on one line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 11:38:06 +01:00

66 lines
2.1 KiB
Go

package web
import (
"net/http"
"strings"
"gitea.mynodes.xyz/ilker/llmgw/internal/store"
)
// staffMay reports whether a person may do this to an administration page.
//
// An administrator may do anything. A viewer may look at every page and
// change nothing. An operator may also run the cluster from them: models
// through the mirror, and a node's engines. Billing may look at usage and
// quotas. Everything that touches people, the tree, limits or keys stays
// with administrators.
func staffMay(u store.User, r *http.Request) bool {
if u.IsAdmin {
return true
}
read := r.Method == http.MethodGet || r.Method == http.MethodHead
path := r.URL.Path
switch u.Role {
case store.RoleViewer:
return read
case store.RoleOperator:
return read || staffMayChange(u.Role, path)
case store.RoleBilling:
return read && (path == "/admin/usage" || path == "/admin/quotas")
}
return false
}
// staffMayChange reports whether a staff role may send a change to an
// administration path. It is staffMay's rule for everything but reading, and
// what the pages ask before they draw a form, so nobody is shown a form whose
// answer would be "not found".
func staffMayChange(role, path string) bool {
switch role {
case store.RoleAdmin:
return true
case store.RoleOperator:
return strings.HasPrefix(path, "/admin/mirror/") ||
strings.HasPrefix(path, "/admin/nodes/") && strings.HasSuffix(path, "/engines")
}
return false
}
// csrfIfMay is the form token for a page whose forms send to path, or ""
// when this person may not send them: a page draws a form only where it has a
// token for it, so a viewer is drawn the page without its forms.
func csrfIfMay(r *http.Request, path string) string {
if !navFrom(r, "").May(path) {
return ""
}
return csrfFor(r)
}
// May reports whether the person this menu is for may send a change to an
// administration path: a template asks it before drawing a form, with the
// form's action ("/admin/mirror/fetch"); a part of the path that varies may be
// anything ("/admin/nodes/x/engines").
func (n Nav) May(path string) bool {
return n.Admin && staffMayChange(n.Role, path)
}