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>
66 lines
2.1 KiB
Go
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)
|
|
}
|