c441366f89
- 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
131 lines
5.6 KiB
Markdown
131 lines
5.6 KiB
Markdown
---
|
|
name: mas_oidc_loopback
|
|
kind: function
|
|
lang: go
|
|
domain: infra
|
|
version: "0.1.0"
|
|
purity: impure
|
|
signature: "func MasOidcLoopback(cfg MasOidcLoopbackConfig) (*MasOidcLoopbackResult, error)"
|
|
description: "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."
|
|
tags: ["matrix", "mas", "oidc", "oauth2", "pkce", "loopback", "client", "infra", "matrix-mas", "auth"]
|
|
uses_functions: []
|
|
uses_types: []
|
|
returns: []
|
|
returns_optional: false
|
|
error_type: "error_go_core"
|
|
imports:
|
|
- "context"
|
|
- "crypto/rand"
|
|
- "crypto/sha256"
|
|
- "encoding/base64"
|
|
- "encoding/json"
|
|
- "fmt"
|
|
- "io"
|
|
- "net"
|
|
- "net/http"
|
|
- "net/url"
|
|
- "os/exec"
|
|
- "runtime"
|
|
- "strings"
|
|
- "time"
|
|
tested: true
|
|
tests:
|
|
- "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"
|
|
test_file_path: "functions/infra/mas_oidc_loopback_test.go"
|
|
file_path: "functions/infra/mas_oidc_loopback.go"
|
|
params:
|
|
- name: Issuer
|
|
desc: "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: ClientID
|
|
desc: "ULID del client registrado en MAS. Para clients publicos (client_auth_method: none) no se necesita client_secret."
|
|
- name: Scopes
|
|
desc: "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: LoopbackPort
|
|
desc: "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: OpenBrowser
|
|
desc: "Si true, abre el browser del SO automaticamente (xdg-open/open/rundll32). Si false, imprime la URL a stdout para apertura manual."
|
|
- name: TimeoutSeconds
|
|
desc: "Tiempo maximo en segundos esperando el callback del browser. Default 300s si <= 0."
|
|
output: "MasOidcLoopbackResult con AccessToken (Bearer para Synapse), RefreshToken, ExpiresIn, TokenType, Scope e IDToken."
|
|
---
|
|
|
|
## Ejemplo
|
|
|
|
```go
|
|
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):
|
|
|
|
```go
|
|
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).
|