feat(pact): add HasFrontendTokenScopes and the shared ValidTokenScopeName rule (E-2)

- pact.FrontendTokenScope and pact.HasFrontendTokenScopes declare member
  (usr_) token scopes; the admin token catalog never reads them
- pact.ValidTokenScopeName carries cabana's scope-name pattern; cabana's
  g15_ catalog switches to it with unchanged behaviour and messages
This commit is contained in:
Jakub Zych
2026-10-23 16:15:25 +02:00
parent c14238b94b
commit 9eedfbbb29
5 changed files with 43 additions and 5 deletions

View File

@@ -6,6 +6,7 @@ import (
"io"
"io/fs"
"net/http"
"regexp"
"strings"
"time"
@@ -653,6 +654,39 @@ type HasTokenScopes interface {
TokenScopes() []TokenScope
}
// FrontendTokenScope is one scope of the member personal API tokens (usr_)
// that the user plugin issues, declared by a plugin: the name a route's
// user.scope:<name> middleware checks, with the label and description the
// member token screens show. Label and Description are phrase keys
// (containing "::") or literal text. It is the frontend counterpart of
// TokenScope and carries no permission patterns or method filter: the route
// middleware alone decides what a member scope allows.
type FrontendTokenScope struct {
Name string
Label string
Description string
}
// HasFrontendTokenScopes is implemented by plugins that declare member
// personal API token scopes; the frontend counterpart of HasTokenScopes. The
// framework's admin token catalog never reads it: the plugin that owns member
// tokens compiles it, validates the names with ValidTokenScopeName and fails
// boot on a bad one.
type HasFrontendTokenScopes interface {
FrontendTokenScopes() []FrontendTokenScope
}
var tokenScopeNameRE = regexp.MustCompile(`^[a-z][a-z0-9_-]*(:[a-z][a-z0-9_-]*)?$`)
// ValidTokenScopeName reports whether name is a valid personal API token
// scope name: lowercase, starting with a letter, letters, digits, "_" and "-",
// with at most one ":" separating two such parts (for example "blog:write").
// It is the one name rule both the admin token catalog (TokenScope) and the
// member token catalog (FrontendTokenScope) apply.
func ValidTokenScopeName(name string) bool {
return tokenScopeNameRE.MatchString(name)
}
// NavigationItem is one registerNavigation() entry. Controller is the admin
// controller ID; the SPA derives its route from that ID. Every field but
// Visible is static and compiled once at boot; Visible is the one hook the