Files
fn_registry/functions/infra/wellknown_oidc_patch.go
egutierrez daef7ea190 feat(matrix): MAS migration helpers + 2 flows + 15 issues + capability group
Helper functions (matrix-mas capability group):
- mas_client_register_bash_infra: register/sync OAuth clients via mas-cli
- mas_syn2mas_migration_bash_infra: dry-run + apply user migration to MAS
- synapse_msc3861_enable_go_infra: edit homeserver.yaml MSC3861 block (with diff)
- wellknown_oidc_patch_go_infra: patch well-known JSON with msc2965.authentication
- synapse_login_flows_check_go_infra: health-check post-migration login flows

Flows + issues for custom Matrix clients (PC + Android):
- 0010 matrix-client-pc: Wails + React+Mantine (issues 0147-0153)
- 0011 matrix-client-android: Kotlin + Compose (issues 0154-0161)
- 0162 enable MAS as auth provider (Synapse delegate) — EXECUTED on VPS
- 0163 custom admin panel propio (sustituye synapse-admin)

Production state (organic-machine.com):
- Synapse migrated SQLite -> Postgres
- MSC3861 active, password_config disabled
- 21 users + 41 access_tokens migrated via syn2mas
- 4 MAS clients registered (element, matrix_pc, matrix_android, admin_panel)
- synapse-admin container removed + Coolify route deleted
- well-known patched with org.matrix.msc2965.authentication

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 22:53:33 +02:00

123 lines
4.0 KiB
Go

package infra
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
)
// WellknownOidcPatchConfig holds the parameters for WellknownOidcPatch.
type WellknownOidcPatchConfig struct {
WellknownJsonPath string // absolute path to the .well-known/matrix/client JSON file
Issuer string // MAS issuer URL, must end with "/" (RFC 8414)
AccountURL string // MAS account page URL
BackupDir string // directory where the backup file is written
DryRun bool // if true, return Before/After without writing
}
// WellknownOidcPatchResult is returned by WellknownOidcPatch.
type WellknownOidcPatchResult struct {
BackupPath string // path of the backup file; empty on DryRun
Before string // original JSON (pretty-printed, 2-space indent)
After string // patched JSON (pretty-printed, 2-space indent)
Modified bool // false if the key already existed with identical values
}
// WellknownOidcPatch reads a Matrix .well-known/matrix/client JSON file,
// adds (or updates) the org.matrix.msc2965.authentication key with the
// supplied MAS issuer and account URL, and writes the result back to the
// same path. All existing keys (m.homeserver, org.matrix.msc4143.rtc_foci,
// etc.) are preserved. A timestamped backup is created in BackupDir before
// any write. Set DryRun to true to preview the change without touching the
// filesystem.
func WellknownOidcPatch(cfg WellknownOidcPatchConfig) (WellknownOidcPatchResult, error) {
const oidcKey = "org.matrix.msc2965.authentication"
// 1. Read existing file.
raw, err := os.ReadFile(cfg.WellknownJsonPath)
if err != nil {
return WellknownOidcPatchResult{}, fmt.Errorf("wellknown_oidc_patch: read %s: %w", cfg.WellknownJsonPath, err)
}
// 2. Parse into a generic map to preserve unknown keys.
var doc map[string]any
if err := json.Unmarshal(raw, &doc); err != nil {
return WellknownOidcPatchResult{}, fmt.Errorf("wellknown_oidc_patch: invalid JSON in %s: %w", cfg.WellknownJsonPath, err)
}
// 3. Pretty-print Before.
beforeBytes, err := json.MarshalIndent(doc, "", " ")
if err != nil {
return WellknownOidcPatchResult{}, fmt.Errorf("wellknown_oidc_patch: marshal before: %w", err)
}
before := string(beforeBytes)
// 4. Build the new authentication block.
newAuth := map[string]any{
"issuer": cfg.Issuer,
"account": cfg.AccountURL,
}
// 5. Check if the key already exists with identical values.
modified := true
if existing, ok := doc[oidcKey]; ok {
existingBytes, _ := json.Marshal(existing)
newBytes, _ := json.Marshal(newAuth)
if string(existingBytes) == string(newBytes) {
modified = false
}
}
if !modified {
return WellknownOidcPatchResult{
BackupPath: "",
Before: before,
After: before,
Modified: false,
}, nil
}
// 6. Apply the patch.
doc[oidcKey] = newAuth
afterBytes, err := json.MarshalIndent(doc, "", " ")
if err != nil {
return WellknownOidcPatchResult{}, fmt.Errorf("wellknown_oidc_patch: marshal after: %w", err)
}
after := string(afterBytes)
// 7. DryRun: return without writing anything.
if cfg.DryRun {
return WellknownOidcPatchResult{
BackupPath: "",
Before: before,
After: after,
Modified: true,
}, nil
}
// 8. Create backup.
if err := os.MkdirAll(cfg.BackupDir, 0o755); err != nil {
return WellknownOidcPatchResult{}, fmt.Errorf("wellknown_oidc_patch: mkdir backup dir: %w", err)
}
backupName := fmt.Sprintf("wellknown_%d.json", time.Now().Unix())
backupPath := filepath.Join(cfg.BackupDir, backupName)
if err := os.WriteFile(backupPath, raw, 0o644); err != nil {
return WellknownOidcPatchResult{}, fmt.Errorf("wellknown_oidc_patch: write backup: %w", err)
}
// 9. Write patched file.
if err := os.WriteFile(cfg.WellknownJsonPath, afterBytes, 0o644); err != nil {
return WellknownOidcPatchResult{}, fmt.Errorf("wellknown_oidc_patch: write %s: %w", cfg.WellknownJsonPath, err)
}
return WellknownOidcPatchResult{
BackupPath: backupPath,
Before: before,
After: after,
Modified: true,
}, nil
}