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) } }) }