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 }