- pact.MCPSubjectChanged and pier.Event gain TokenID and ClientID; a narrowed revoked event closes only matching sessions and stateless servers - cabana fires the admin token revoke with the token row id - towel.WithClientIP/ClientIP; pier puts the address (surf applies http.trusted_proxies) on the resolve context and ResolveBackendToken logs it
66 lines
2.5 KiB
Go
66 lines
2.5 KiB
Go
package cabana
|
|
|
|
import (
|
|
"context"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/backpack"
|
|
"git.golem15.com/golem15/summercms/modules/lagoon"
|
|
"git.golem15.com/golem15/summercms/modules/pact"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// backendSubjectGuard is the guard name backend subject events carry: the
|
|
// admin's own bouncer guard.
|
|
const backendSubjectGuard = "backend"
|
|
|
|
// fireSubjectChanged fires one pact.MCPSubjectChanged of the given kind for
|
|
// every administrator id through the application's event bus, so the MCP
|
|
// endpoint re-lists a linked admin's tools or ends their sessions. The event
|
|
// is a *pact.MCPSubjectChanged, the type pier listens for. Inside a
|
|
// transaction it fires only after the commit, so a rolled-back save fires
|
|
// nothing; outside one it fires at once. A missing application or bus is a
|
|
// no-op and a listener's error is logged: the save that caused the event
|
|
// never fails because of it.
|
|
func fireSubjectChanged(ctx context.Context, app *backpack.App, kind string, adminIDs ...uint) {
|
|
if app == nil || app.Events == nil {
|
|
return
|
|
}
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
tx, _ := TxFromContext(ctx)
|
|
lagoon.AfterCommit(ctx, tx, func(ctx context.Context, _ *gorm.DB) {
|
|
for _, id := range adminIDs {
|
|
if id == 0 {
|
|
continue
|
|
}
|
|
event := &pact.MCPSubjectChanged{Guard: backendSubjectGuard, UserID: id, Kind: kind}
|
|
if err := app.Events.Fire(ctx, event); err != nil {
|
|
moduleLog.WarnContext(ctx, "cabana: subject changed listener failed", "kind", kind, "admin_id", id, "error", err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// fireTokenRevoked fires one pact.MCPSubjectRevoked for an administrator's
|
|
// personal token, narrowed to that token with TokenID: the MCP endpoint ends
|
|
// only the sessions opened with this token, not the admin's other sessions
|
|
// (another token, or an OAuth grant acting as the linked admin). It fires
|
|
// after the commit like fireSubjectChanged and never fails the revoke.
|
|
func fireTokenRevoked(ctx context.Context, app *backpack.App, adminID, tokenID uint) {
|
|
if app == nil || app.Events == nil || adminID == 0 || tokenID == 0 {
|
|
return
|
|
}
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
tx, _ := TxFromContext(ctx)
|
|
lagoon.AfterCommit(ctx, tx, func(ctx context.Context, _ *gorm.DB) {
|
|
event := &pact.MCPSubjectChanged{Guard: backendSubjectGuard, UserID: adminID, Kind: pact.MCPSubjectRevoked, TokenID: tokenID}
|
|
if err := app.Events.Fire(ctx, event); err != nil {
|
|
moduleLog.WarnContext(ctx, "cabana: subject changed listener failed", "kind", pact.MCPSubjectRevoked, "admin_id", adminID, "token_id", tokenID, "error", err)
|
|
}
|
|
})
|
|
}
|