- AccessRevokedEvent and FireAccessRevoked: fired in the write's transaction
by the ban action (newly banned users only), the password reset, an
administrator setting a password and the self-service password change;
a revoked subject event per user after commit
- LinkedBackendUser answers no administrator for a banned user
- FireSubjectEvent / FireSubjectEventAfterCommit carry TokenID or ClientID;
DELETE /tokens/{id} ends only that token's MCP sessions
- the MCP personal-token path stamps last_used_ip from towel.ClientIP
- BackendLinksFor replaces the duplicate lookups; requestUsable uses
IsUsableAt
70 lines
2.9 KiB
Go
70 lines
2.9 KiB
Go
package classes
|
|
|
|
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"
|
|
)
|
|
|
|
// Guard names the plugin's pact.MCPSubjectChanged events carry: the frontend
|
|
// user guard this plugin provides and the admin's guard (for the backend
|
|
// side of an account link).
|
|
const (
|
|
SubjectGuardUser = "user"
|
|
SubjectGuardBackend = "backend"
|
|
)
|
|
|
|
// FireSubjectEvent fires ev through the application's event bus at once, so
|
|
// the MCP endpoint re-lists the subject's tools or ends its sessions. A
|
|
// revoked event may carry TokenID (only the sessions one personal token
|
|
// opened end) or ClientID with UserID (only that OAuth client's sessions
|
|
// end). A missing application or bus, a zero UserID or an empty Kind is a
|
|
// no-op; a listener's error is logged and never returned, so the write that
|
|
// caused the event never fails because of it (every MCP request re-checks
|
|
// anyway).
|
|
func FireSubjectEvent(ctx context.Context, app *backpack.App, ev pact.MCPSubjectChanged) {
|
|
if app == nil || app.Events == nil || ev.UserID == 0 || ev.Kind == "" {
|
|
return
|
|
}
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
event := ev
|
|
if err := app.Events.Fire(ctx, &event); err != nil {
|
|
pluginLog.WarnContext(ctx, "golem15.user: subject changed listener failed", "guard", ev.Guard, "user_id", ev.UserID, "kind", ev.Kind, "token_id", ev.TokenID, "client_id", ev.ClientID, "err", err)
|
|
}
|
|
}
|
|
|
|
// FireSubjectEventAfterCommit is FireSubjectEvent once the write
|
|
// transaction tx commits (lagoon.AfterCommit), so a listener that reads the
|
|
// database sees the change; nothing is fired when the transaction rolls
|
|
// back. A nil tx fires at once. ctx must be the context the transaction
|
|
// runs under (the one cabana hands a hook or an action).
|
|
func FireSubjectEventAfterCommit(ctx context.Context, app *backpack.App, tx *gorm.DB, ev pact.MCPSubjectChanged) {
|
|
if app == nil || app.Events == nil || ev.UserID == 0 || ev.Kind == "" {
|
|
return
|
|
}
|
|
if tx == nil {
|
|
FireSubjectEvent(ctx, app, ev)
|
|
return
|
|
}
|
|
lagoon.AfterCommit(ctx, tx, func(ctx context.Context, _ *gorm.DB) {
|
|
FireSubjectEvent(ctx, app, ev)
|
|
})
|
|
}
|
|
|
|
// FireSubjectChanged fires one *pact.MCPSubjectChanged (guard, user id,
|
|
// kind) at once: FireSubjectEvent for the whole subject.
|
|
func FireSubjectChanged(ctx context.Context, app *backpack.App, guard string, userID uint, kind string) {
|
|
FireSubjectEvent(ctx, app, pact.MCPSubjectChanged{Guard: guard, UserID: userID, Kind: kind})
|
|
}
|
|
|
|
// FireSubjectChangedAfterCommit is FireSubjectChanged once the write
|
|
// transaction tx commits: FireSubjectEventAfterCommit for the whole subject.
|
|
func FireSubjectChangedAfterCommit(ctx context.Context, app *backpack.App, tx *gorm.DB, guard string, userID uint, kind string) {
|
|
FireSubjectEventAfterCommit(ctx, app, tx, pact.MCPSubjectChanged{Guard: guard, UserID: userID, Kind: kind})
|
|
}
|