Files
matrix_client_pc/internal/infra/matrix_crypto_init.go
T
Egutierrez 36a485ea26 feat: chat E2EE MVP - rooms list + timeline + composer + sync (issues 0148+0149+0150)
Backend extends MatrixService with Start()/Stop()/ListRooms()/LoadTimeline()/
SendText()/SendMarkdown(). On login the service initialises the crypto store
(cryptohelper, Olm/Megolm via goolm build tag) and a sync loop that fans
events out through Wails events ("matrix:event", "matrix:error"). Pickle
key is 32 random bytes hex-encoded in the OS keyring alongside the access
token, so the crypto SQLite store survives restarts.

Vendors 4 fresh helpers from fn_registry/functions/infra/:
  matrix_crypto_init.go (//go:build goolm || libolm)
  matrix_sync_service.go
  matrix_message_send.go
  matrix_room_list.go
Plus the existing 3 (mas_oidc_loopback, keyring_token_store, matrix_client_init).
go-sqlite3 driver pulled explicitly via sqlite_driver.go.

Frontend rewires HomeScreen as a 3-zone AppShell (sidebar / timeline /
composer). useMatrixRooms polls + reacts to the sync stream; useMatrixTimeline
loads the last 50 events of the selected room and appends live ones. New
components: RoomList, Timeline, EventBubble, Composer. Composer supports
plain text (default) and a markdown toggle; Enter sends, Shift+Enter newline.

wails.json now passes "build:tags": "goolm" by default. Tested with
wails build -tags goolm on linux/amd64 and windows/amd64.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 01:03:31 +02:00

108 lines
4.2 KiB
Go

//go:build goolm || libolm
package infra
import (
"context"
"fmt"
"os"
"path/filepath"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/crypto/cryptohelper"
)
// MatrixCryptoInitConfig parametriza la inicializacion del crypto store Olm/Megolm.
type MatrixCryptoInitConfig struct {
// Client es el *mautrix.Client ya inicializado via MatrixClientInit.
// Debe tener AccessToken, UserID y DeviceID poblados.
Client *mautrix.Client
// StorePath es la ruta absoluta al archivo SQLite del crypto store.
// Debe ser separado del state store. El SDK gestiona el schema internamente.
// Si el directorio padre no existe, se crea con permisos 0700.
// Ejemplo: "/home/lucas/.config/matrix_client_pc/egutierrez/crypto.db"
StorePath string
// PickleKey son exactamente 32 bytes usados por cryptohelper para cifrar las
// sesiones Olm en disco at-rest. DEBE persistir entre arranques (guardar en keyring).
// Si se pierde, el store SQLite se vuelve inutilizable y hay que crear nuevo dispositivo.
PickleKey []byte
}
// MatrixCryptoInitResult contiene el helper listo para usar.
type MatrixCryptoInitResult struct {
// Helper es el *cryptohelper.CryptoHelper inicializado.
// Ya esta asignado a client.Crypto — el Sync loop cifra/descifra automaticamente.
Helper *cryptohelper.CryptoHelper
// StorePath es la ruta al archivo SQLite del crypto store (igual que cfg.StorePath).
StorePath string
}
// MatrixCryptoInit inicializa el crypto store Olm/Megolm para un cliente mautrix
// usando cryptohelper — el wrapper oficial que abstrae SQLite + Olm identity keys +
// one-time key upload + decrypt automatico via el Syncer.
//
// Pasos:
// 1. Valida inputs (Client no nil con AccessToken/UserID/DeviceID, StorePath
// absoluto, PickleKey exactamente 32 bytes).
// 2. Crea el directorio padre de StorePath con permisos 0700 si no existe.
// 3. Construye el helper via cryptohelper.NewCryptoHelper(client, pickleKey, storePath).
// 4. Llama helper.Init(ctx) — crea tablas SQLite, carga cuenta Olm, sube one-time keys.
// 5. Asigna client.Crypto = helper para que SendMessageEvent cifre automaticamente.
// 6. Devuelve MatrixCryptoInitResult con el helper listo.
func MatrixCryptoInit(ctx context.Context, cfg MatrixCryptoInitConfig) (*MatrixCryptoInitResult, error) {
// 1. Validar Client
if cfg.Client == nil {
return nil, fmt.Errorf("matrix_crypto_init: Client no puede ser nil")
}
if cfg.Client.AccessToken == "" {
return nil, fmt.Errorf("matrix_crypto_init: Client.AccessToken no puede estar vacio")
}
if cfg.Client.UserID == "" {
return nil, fmt.Errorf("matrix_crypto_init: Client.UserID no puede estar vacio")
}
if cfg.Client.DeviceID == "" {
return nil, fmt.Errorf("matrix_crypto_init: Client.DeviceID no puede estar vacio — descubrirlo via MatrixClientInit o Whoami antes de llamar MatrixCryptoInit")
}
// Validar StorePath
if cfg.StorePath == "" {
return nil, fmt.Errorf("matrix_crypto_init: StorePath no puede estar vacio")
}
if !filepath.IsAbs(cfg.StorePath) {
return nil, fmt.Errorf("matrix_crypto_init: StorePath debe ser una ruta absoluta (got %q)", cfg.StorePath)
}
// Validar PickleKey: exactamente 32 bytes
if len(cfg.PickleKey) != 32 {
return nil, fmt.Errorf("matrix_crypto_init: PickleKey debe tener exactamente 32 bytes (got %d)", len(cfg.PickleKey))
}
// 2. Crear directorio padre con permisos 0700 (datos sensibles)
storeDir := filepath.Dir(cfg.StorePath)
if err := os.MkdirAll(storeDir, 0700); err != nil {
return nil, fmt.Errorf("matrix_crypto_init: no se pudo crear directorio del store %q: %w", storeDir, err)
}
// 3. Construir CryptoHelper — acepta string como path SQLite directamente (v0.28 API)
helper, err := cryptohelper.NewCryptoHelper(cfg.Client, cfg.PickleKey, cfg.StorePath)
if err != nil {
return nil, fmt.Errorf("matrix_crypto_init: NewCryptoHelper failed: %w", err)
}
// 4. Init: crea tablas SQLite, carga cuenta Olm, sube one-time keys al servidor
if err := helper.Init(ctx); err != nil {
return nil, fmt.Errorf("matrix_crypto_init: helper.Init failed (comprueba conectividad con Synapse y validez del token): %w", err)
}
// 5. Asignar client.Crypto para que SendMessageEvent cifre automaticamente
cfg.Client.Crypto = helper
return &MatrixCryptoInitResult{
Helper: helper,
StorePath: cfg.StorePath,
}, nil
}