Files
egutierrez 4bce095964 refactor(infra): split de drivers pesados a subpaquetes + fix TestSSEHandler
Mueve duckdb_open, clickhouse_open, postgres_open, matrix_* y keyring_token_store
del paquete monolitico functions/infra a subpaquetes propios
(functions/infra/{duckdb,clickhouse,postgres,matrix,keyring}). El paquete infra ya
no importa los drivers (go-duckdb, clickhouse-go, pgx, mautrix, go-keyring), por lo
que las apps que solo usan funciones ligeras (process, cron, http, sqlite) dejan de
arrastrarlos. Reduccion de binarios: dag_engine 72->10MB, registry_api 70->8.7MB,
services_api 70->9MB, call_monitor 68->6.6MB, sqlite_api 70->8.9MB.

Los IDs del registry se mantienen estables (domain: infra en frontmatter). Se
preservan los build tags goolm/libolm de matrix_crypto_init.

Tambien corrige TestSSEHandler: el test leia el body con un unico Read() que con
HTTP chunked solo capturaba el primer evento; ahora usa io.ReadAll hasta EOF.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 23:48:59 +02:00

5.4 KiB

name, kind, lang, domain, version, purity, signature, description, tags, params, output, uses_functions, uses_types, returns, returns_optional, error_type, imports, tested, tests, test_file_path, file_path
name kind lang domain version purity signature description tags params output uses_functions uses_types returns returns_optional error_type imports tested tests test_file_path file_path
keyring_token_store function go infra 0.1.0 impure type KeyringTokenStore struct { Service string } func NewKeyringTokenStore(service string) *KeyringTokenStore func (s *KeyringTokenStore) Save(account string, t Token) error func (s *KeyringTokenStore) Load(account string) (*Token, error) func (s *KeyringTokenStore) Delete(account string) error var ErrNotFound = errors.New("token not found in keyring") Persiste tokens OAuth/OIDC entre arranques usando el keyring del SO (Secret Service en Linux, Keychain en macOS, Credential Manager en Windows). Serializa Token a JSON cifrado at-rest por el OS.
security
keyring
tokens
oauth
persistence
infra
matrix-mas
name desc
service Namespace estable del keyring para esta app. Ej: 'fn_registry.matrix_client_pc'. No compartir entre apps.
name desc
account Identificador unico del token. Ej: user_id '@egutierrez:matrix-af2f3d.organic-machine.com'.
name desc
Token.AccessToken Access token OAuth/OIDC. Campo obligatorio.
name desc
Token.RefreshToken Refresh token. Omitido en JSON si vacio.
name desc
Token.ExpiresAt Momento de expiracion del access token. Zero = nunca expira.
name desc
Token.UserID Identificador del usuario, ej. '@user:homeserver'. Obligatorio.
name desc
Token.DeviceID Device ID asignado por el homeserver Matrix.
name desc
Token.HomeserverURL URL base del homeserver Matrix. Ej: 'https://matrix-af2f3d.organic-machine.com'.
name desc
Token.Issuer URL del emisor OIDC/MAS. Requerido para flows OIDC.
name desc
Token.ClientID Client ID usado en el flow MAS/OIDC.
Save/Delete retornan error envuelto con contexto. Load retorna *Token o ErrNotFound (chequear con errors.Is).
true error_go_core
encoding/json
errors
fmt
time
github.com/zalando/go-keyring
true
Save then Load returns matching token
Load nonexistent returns ErrNotFound
Save then Delete then Load returns ErrNotFound
Delete nonexistent is idempotent
Save twice overwrites with second token
functions/infra/keyring/keyring_token_store_test.go functions/infra/keyring/keyring_token_store.go

Ejemplo

store := NewKeyringTokenStore("fn_registry.matrix_client_pc")

tok := Token{
    AccessToken:   "mxat_abc123...",
    RefreshToken:  "mxrt_xyz789...",
    ExpiresAt:     time.Now().Add(time.Hour),
    UserID:        "@egutierrez:matrix-af2f3d.organic-machine.com",
    DeviceID:      "ABCDEF1234",
    HomeserverURL: "https://matrix-af2f3d.organic-machine.com",
    Issuer:        "https://auth-af2f3d.organic-machine.com/",
    ClientID:      "VDC4XQ2ZKN2TJ0BYVJ54FK7M6Y",
}

if err := store.Save(tok.UserID, tok); err != nil {
    log.Fatalf("save token: %v", err)
}

// En otro arranque de la app:
got, err := store.Load("@egutierrez:matrix-af2f3d.organic-machine.com")
if errors.Is(err, ErrNotFound) {
    // primer login — arrancar flujo OAuth
}

// Logout o revocacion:
_ = store.Delete(tok.UserID)

Cuando usarla

Usar cuando una app desktop Go necesite persistir tokens OAuth/OIDC entre arranques sin escribirlos en disco en texto plano. Ideal para matrix_client_pc (tokens MAS + Matrix), admin_panel, cualquier CLI Go con login interactivo. Usar en el path de callback OAuth justo despues de recibir el token, y en el arranque para recuperarlo antes de pedir credenciales.

Gotchas

  • Linux requiere D-Bus session activa. En servidores headless sin GUI, keyring.Set falla. En contenedores Docker sin Secret Service (GNOME Keyring / KWallet): NO funciona salvo montaje de socket D-Bus. Para apps que corren en servidor headless, usar un fallback de archivo cifrado (fuera del scope de esta funcion).
  • Windows Credential Manager tiene limite de ~2500 chars por entrada. Tokens JWT largos (base64 > 2KB) pueden cortarse. Mantener tokens < 2KB o comprimir/dividir.
  • macOS Keychain vincula el entry al binario que lo creo. Si el ejecutable cambia de firma (recompilacion con nuevo cert), el sistema pide permiso al usuario otra vez. Es comportamiento esperado de macOS, no un bug.
  • Snap / Flatpak en Linux pueden bloquear el acceso a Secret Service por sandboxing. Documentar en el README de apps distribuidas via estos formatos.
  • El JSON serializado almacena el AccessToken en texto plano dentro del entry del keyring. El keyring del SO cifra at-rest, pero no usar esto para secretos de alta seguridad (claves privadas, PINs bancarios, etc.).
  • Nunca loggear el valor de AccessToken/RefreshToken. Los errores solo incluyen la descripcion del fallo, nunca el valor.
  • List() no esta implementada en v0.1.0 porque go-keyring no expone listado de accounts. TODO: implementar via index local en os.UserConfigDir()/service/accounts.json si se necesita en el futuro.

Notas

Depende de github.com/zalando/go-keyring (v0.2.x). En Linux usa github.com/godbus/dbus/v5 para comunicarse con Secret Service. En Windows usa github.com/danieljoos/wincred. Ambas dependencias se agregan transitivamente.

El campo Token.ExpiresAt se redondea a segundos al serializar/deserializar con time.Time en JSON. El caller debe comparar con time.Now().Before(t.ExpiresAt) para saber si el token ha expirado.