Files
sm-user-plugin/updates/202610220001_create_backend_links.go
Jakub Zych 7ac80c9aea feat: verified frontend account link for administrators
- 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)
2026-10-23 12:03:26 +02:00

61 lines
2.4 KiB
Go

package updates
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
// backendLinkMigrations create the verified frontend-to-backend account link
// (one frontend user to one administrator, both sides unique) and the
// one-shot link requests an administrator starts on My account. A request
// stores only the SHA-256 of its code. Deleting either account removes its
// link and requests; deleting the administrator who set a link keeps the
// link and clears linked_by.
//
// The foreign keys to backend_users are added when that table exists, which
// it always does when lagoon.Migrate runs the plugin (the admin's own
// migrations run first); a run of the plugin's migrations alone, as some
// tests do, creates the tables without them.
var backendLinkMigrations = []*gormigrate.Migration{
{
ID: "202610220001_create_golem15_user_backend_links",
Migrate: func(tx *gorm.DB) error {
return tx.Exec(`CREATE TABLE golem15_user_backend_links (
id BIGSERIAL PRIMARY KEY,
user_id INTEGER NOT NULL UNIQUE REFERENCES users(id) ON DELETE CASCADE,
backend_user_id INTEGER NOT NULL UNIQUE,
linked_by INTEGER NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE golem15_user_backend_link_requests (
id BIGSERIAL PRIMARY KEY,
code_hash CHAR(64) NOT NULL UNIQUE,
backend_user_id INTEGER NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
used_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX golem15_user_backend_link_requests_backend_user_id_index ON golem15_user_backend_link_requests (backend_user_id);
DO $$
BEGIN
IF to_regclass('backend_users') IS NOT NULL THEN
ALTER TABLE golem15_user_backend_links
ADD CONSTRAINT golem15_user_backend_links_backend_user_id_foreign
FOREIGN KEY (backend_user_id) REFERENCES backend_users(id) ON DELETE CASCADE,
ADD CONSTRAINT golem15_user_backend_links_linked_by_foreign
FOREIGN KEY (linked_by) REFERENCES backend_users(id) ON DELETE SET NULL;
ALTER TABLE golem15_user_backend_link_requests
ADD CONSTRAINT golem15_user_backend_link_requests_backend_user_id_foreign
FOREIGN KEY (backend_user_id) REFERENCES backend_users(id) ON DELETE CASCADE;
END IF;
END
$$`).Error
},
Rollback: func(tx *gorm.DB) error {
return tx.Exec("DROP TABLE IF EXISTS golem15_user_backend_link_requests; DROP TABLE IF EXISTS golem15_user_backend_links").Error
},
},
}
func init() { Register(backendLinkMigrations...) }