feat: user MCP guard provider, plugin frontend permissions and the user_me tool

- classes.FrontendPermissionCatalog: the seeded rows, then codes activated
  plugins declare through pact.HasFrontendPermissions (seeded row wins);
  both permission editors offer it
- classes.MCPUserGuard (guard "user"): reads user.api_token, user.scope:*
  and user.permission:*, allows with the route's checks (session scopes,
  group grants, every code), dispatches with an in-memory token credential,
  resolves usr_ personal tokens; registered in *pact.MCPGuards and published
  as pact.MCPPersonalTokenResolver
- GET /_user/api/v1/token/me (user.api_token, user.scope:read) and the MCP
  tool me (user_me)
- pact.MCPSubjectChanged on token revoke, Users saves and User Groups saves
  and deletes
This commit is contained in:
Jakub Zych
2026-10-23 12:08:38 +02:00
parent 7ac80c9aea
commit 0f545f5c81
13 changed files with 734 additions and 38 deletions

View File

@@ -163,6 +163,13 @@ func adminEnvConfig(t *testing.T) *compass.Config {
// newAdminEnv boots the plugin alone with the admin API mounted. configure
// functions adjust the config before the plugin is activated.
func newAdminEnv(t *testing.T, configure ...func(*compass.Config)) *adminEnv {
t.Helper()
return newAdminEnvWith(t, nil, configure...)
}
// newAdminEnvWith is newAdminEnv with more registered plugins activated
// after golem15.user, by ID.
func newAdminEnvWith(t *testing.T, extra []string, configure ...func(*compass.Config)) *adminEnv {
t.Helper()
gdb, sqlDB := adminHarnessDB(t)
app := backpack.New(adminEnvConfig(t))
@@ -180,7 +187,7 @@ func newAdminEnv(t *testing.T, configure ...func(*compass.Config)) *adminEnv {
}
// The registered plugin value is shared by every boot in this binary;
// its blacklist sweep is started at most once and is harmless here.
plugins, err := party.Activate(app, []string{"golem15.user"})
plugins, err := party.Activate(app, append([]string{"golem15.user"}, extra...))
if err != nil {
t.Fatal(err)
}