Files
fn_registry/functions/infra/mas_oidc_loopback.md
T
egutierrez c441366f89 feat(matrix-mas): 3 helpers for matrix_client_pc (issue 0147)
- mas_oidc_loopback_go_infra: OAuth2 PKCE + loopback HTTP for desktop login
- keyring_token_store_go_infra: persist OAuth tokens in SO keychain
- matrix_client_init_go_infra: init mautrix.Client from access_token + whoami

Plus go.work workspace including matrix_client_pc sub-repo for shared
import path during dev. All 3 fns tagged matrix-mas capability group.

Tests: TestMasOidcLoopback (15 cases), TestKeyringTokenStore (5 cases,
SKIP on headless), TestMatrixClientInit (6 cases) — all green/skip.

Refs: dev/issues/0147-matrix-client-pc-scaffold.md
Refs: dataforge/matrix_client_pc commit f28c2b1
2026-05-24 23:23:49 +02:00

5.6 KiB

name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, tested, tests, test_file_path, file_path, params, output
name kind lang domain version purity signature description tags uses_functions uses_types returns returns_optional error_type imports tested tests test_file_path file_path params output
mas_oidc_loopback function go infra 0.1.0 impure func MasOidcLoopback(cfg MasOidcLoopbackConfig) (*MasOidcLoopbackResult, error) Ejecuta el flujo OAuth2 Authorization Code + PKCE contra Matrix Authentication Service (MAS) usando un servidor HTTP loopback en localhost para recibir el callback. Abre el browser del SO (o imprime la URL si OpenBrowser=false), espera el codigo de autorizacion, lo intercambia por tokens y devuelve AccessToken listo para usar como Bearer contra Synapse.
matrix
mas
oidc
oauth2
pkce
loopback
client
infra
matrix-mas
auth
false error_go_core
context
crypto/rand
crypto/sha256
encoding/base64
encoding/json
fmt
io
net
net/http
net/url
os/exec
runtime
strings
time
true
state mismatch devuelve error
token endpoint 400 devuelve error con body
timeout sin callback devuelve error
validacion - Issuer vacio
validacion - Issuer sin slash final
validacion - ClientID vacio
validacion - LoopbackPort negativo
scopes nil usa defaults - error es de discovery no de scopes
functions/infra/mas_oidc_loopback_test.go functions/infra/mas_oidc_loopback.go
name desc
Issuer URL base del MAS. Debe terminar en '/'. La funcion hace GET a {Issuer}.well-known/openid-configuration para descubrir authorization_endpoint y token_endpoint.
name desc
ClientID ULID del client registrado en MAS. Para clients publicos (client_auth_method: none) no se necesita client_secret.
name desc
Scopes Lista de scopes OAuth2 a solicitar. Si nil o vacio, usa defaults: [openid, urn:matrix:org.matrix.msc2967.client:api:*]. Para acceso completo Matrix.
name desc
LoopbackPort Puerto local para el servidor de callback (debe coincidir con redirect_uri registrado en MAS: http://127.0.0.1:{port}/callback). Si 0, elige un puerto libre dinamicamente.
name desc
OpenBrowser Si true, abre el browser del SO automaticamente (xdg-open/open/rundll32). Si false, imprime la URL a stdout para apertura manual.
name desc
TimeoutSeconds Tiempo maximo en segundos esperando el callback del browser. Default 300s si <= 0.
MasOidcLoopbackResult con AccessToken (Bearer para Synapse), RefreshToken, ExpiresIn, TokenType, Scope e IDToken.

Ejemplo

import "fn-registry/functions/infra"

cfg := infra.MasOidcLoopbackConfig{
    Issuer:         "https://auth-af2f3d.organic-machine.com/",
    ClientID:       "VDC4XQ2ZKN2TJ0BYVJ54FK7M6Y",  // matrix_client_pc client en MAS
    Scopes:         []string{"openid", "urn:matrix:org.matrix.msc2967.client:api:*"},
    LoopbackPort:   8765,
    OpenBrowser:    true,
    TimeoutSeconds: 300,
}
res, err := infra.MasOidcLoopback(cfg)
if err != nil {
    log.Fatalf("login failed: %v", err)
}
// res.AccessToken -> Bearer token para requests Synapse
// res.RefreshToken -> guardar para renovacion posterior
fmt.Printf("Logged in. Token expires in %d seconds.\n", res.ExpiresIn)

Con OpenBrowser=false (servidor headless o CLI):

cfg := infra.MasOidcLoopbackConfig{
    Issuer:         "https://auth-af2f3d.organic-machine.com/",
    ClientID:       "VDC4XQ2ZKN2TJ0BYVJ54FK7M6Y",
    LoopbackPort:   8765,
    OpenBrowser:    false,  // imprime la URL a stdout
    TimeoutSeconds: 120,
}
res, err := infra.MasOidcLoopback(cfg)
// El usuario copia la URL del stdout y la abre en su browser

Cuando usarla

Cuando una app desktop (Wails, Tauri, CLI Go) necesite autenticar al usuario contra MAS sin un browser embebido: la funcion gestiona todo el flujo PKCE, arranca el servidor loopback, espera el callback y devuelve los tokens listos para usar. Usar antes de cualquier llamada autenticada a la Matrix Client-Server API via Synapse.

Gotchas

  • LoopbackPort debe coincidir con el redirect_uri registrado en MAS. Si el client en MAS tiene redirect_uris: [http://127.0.0.1:8765/callback], el LoopbackPort debe ser 8765. MAS rechaza con 400 si el redirect_uri no coincide. Con LoopbackPort: 0 la funcion elige puerto libre, pero el client en MAS necesitaria soportar wildcard http://127.0.0.1:*/callback (verificar la config del client en MAS).

  • El client en MAS debe ser publico (client_auth_method: none). Esta funcion implementa PKCE sin client_secret (RFC 7636). Si el client tiene client_secret_basic o client_secret_post, el token endpoint rechazara el intercambio porque falta el secret. Para clients confidenciales, usar otra funcion con autenticacion del client.

  • OpenBrowser en servidores headless: xdg-open en Linux requiere entorno de escritorio. En servidores SSH sin DISPLAY, usar OpenBrowser: false e imprimir la URL para que el operador la abra en su PC.

  • El loopback server muere tras recibir el primer callback. No es apto para flujos multi-sesion ni refresh. Para renovar tokens usar el RefreshToken con un helper de token refresh (oauth2_refresh_go_infra).

  • State mismatch indica ataque CSRF o multi-tab. Si el callback llega con un state distinto al generado, la funcion aborta con error. El browser puede mostrar un error si el usuario abre varias pestanas del authorize.

  • Timeout: si el usuario no completa el login antes de TimeoutSeconds, la funcion devuelve error y el loopback server se cierra. El proceso del browser queda abierto (el OS no lo mata automaticamente).