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

@@ -110,9 +110,31 @@ func (p *Plugin) Boot(app *backpack.App) error {
if err := p.bootBackendLinks(app); err != nil {
return err
}
if err := p.bootMCPGuard(app); err != nil {
return err
}
return classes.RegisterUserAuthorizer(app)
}
// bootMCPGuard registers the "user" MCP guard provider in the application's
// *pact.MCPGuards (publishing the registry when no one did yet) and
// publishes the same value as pact.MCPPersonalTokenResolver for the
// plugin's personal API tokens. Without the MCP module both stay unused.
func (p *Plugin) bootMCPGuard(app *backpack.App) error {
guards, ok := app.Lookup[*pact.MCPGuards]()
if !ok || guards == nil {
guards = pact.NewMCPGuards()
if err := app.Publish(guards); err != nil {
return err
}
}
guard := &classes.MCPUserGuard{App: app}
if err := guards.Register(guard); err != nil {
return err
}
return app.Publish[pact.MCPPersonalTokenResolver](guard)
}
// bootBackendLinks publishes the account link service as *classes.BackendLinks
// and as the MCP linked-backend resolver, and removes a frontend user's link
// when the user is deleted or deactivated. The request lifetime is checked