- golem15_user_backend_links (1:1, both sides unique) and one-shot hashed
golem15_user_backend_link_requests (golem15.user.backend_links.request_ttl, 10m)
- classes.BackendLinks: CreateLinkRequest, ConfirmLinkRequest (row lock,
JWT iat not before the request), SetLink, RemoveLink, LinkedBackendUser;
published as *classes.BackendLinks and pact.MCPLinkedBackendResolver
- GET /_user/api/v1/backend-link/{code} and POST /backend-link/confirm (jwt.auth)
- My account section frontend_account with link and unlink
- BackendLinkedEvent, BackendUnlinkedEvent and pact.MCPSubjectChanged after commit;
deleting or deactivating a frontend user removes its link
- golem15.user.frontend_url (falls back to app.url)
56 lines
2.1 KiB
Go
56 lines
2.1 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"
|
|
)
|
|
|
|
// FireSubjectChanged fires one *pact.MCPSubjectChanged (guard, user id,
|
|
// kind) through the application's event bus at once, so the MCP endpoint
|
|
// re-lists the subject's tools or ends its sessions. A missing application
|
|
// or bus, or a zero id, 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 FireSubjectChanged(ctx context.Context, app *backpack.App, guard string, userID uint, kind string) {
|
|
if app == nil || app.Events == nil || userID == 0 || kind == "" {
|
|
return
|
|
}
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
event := &pact.MCPSubjectChanged{Guard: guard, UserID: userID, Kind: kind}
|
|
if err := app.Events.Fire(ctx, event); err != nil {
|
|
pluginLog.WarnContext(ctx, "golem15.user: subject changed listener failed", "guard", guard, "user_id", userID, "kind", kind, "err", err)
|
|
}
|
|
}
|
|
|
|
// FireSubjectChangedAfterCommit is FireSubjectChanged 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 FireSubjectChangedAfterCommit(ctx context.Context, app *backpack.App, tx *gorm.DB, guard string, userID uint, kind string) {
|
|
if app == nil || app.Events == nil || userID == 0 || kind == "" {
|
|
return
|
|
}
|
|
if tx == nil {
|
|
FireSubjectChanged(ctx, app, guard, userID, kind)
|
|
return
|
|
}
|
|
lagoon.AfterCommit(ctx, tx, func(ctx context.Context, _ *gorm.DB) {
|
|
FireSubjectChanged(ctx, app, guard, userID, kind)
|
|
})
|
|
}
|