FID (Fediverse-ID) IdentitΓ Unificata e Passport dell'Istanza β
TuneCamp utilizza un modello di identitΓ decentralizzato e auto-sovrano basato su FID (Fediverse-ID) (fid): una coppia di chiavi Ed25519 derivata nel browser da alias e passphrase, richieste firmate su HTTPS semplice e nessun relay.
Questa architettura consente agli utenti di unificare i propri profili tra istanze TuneCamp indipendenti senza dipendere da un Single Sign-On (SSO) centralizzato o da un database condiviso.
π Portale Globale & Demo β
Il portale centralizzato SSO e d'identitΓ ufficiale Γ¨ distribuito su:
π https://fid-portal.vercel.app/ (o tunecamp.org)
ποΈ Panoramica dell'Architettura β
βββββββββββββββββββββββββββββ
β fid-portal.vercel.app β
β (Ed25519 identity, β
β keys stay in browser) β
βββββββββββββββ¬ββββββββββββββ
β HTTPS (signed requests)
βββββββββββββββββββββββ΄ββββββββββββββββββββββ
β β
ββββββββββββββΌβββββββββββββ ββββββββββββββΌβββββββββββββ
β TuneCamp Instance A β β TuneCamp Instance B β
β (sudorecords.scobru...) β β (tunecamp.subterra...) β
βββββββββββββββββββββββββββ βββββββββββββββββββββββββββπ Flusso di Vincolo a Due Passaggi (Handshake) β
Passo 1 (Istanza $\rightarrow$ fid-portal.vercel.app):
- Nelle impostazioni locali di TuneCamp, l'utente clicca "Genera Challenge di Vincolo" (
GET /api/auth/zen/challenge). - L'istanza genera un nonce monouso
{ instanceDomain, username, nonce, timestamp }. - L'utente copia il JSON del Challenge.
- Nelle impostazioni locali di TuneCamp, l'utente clicca "Genera Challenge di Vincolo" (
Passo 2 (fid-portal.vercel.app $\rightarrow$ Istanza):
- Su
fid-portal.vercel.app/profile.html, l'utente apre "Collega Istanza" $\rightarrow$ "Firma Challenge Istanza". - L'utente incolla il JSON del Challenge.
- Il portale firma il challenge con la chiave privata d'identitΓ dell'utente e genera un JSON del Passaporto.
- L'utente copia il JSON del Passaporto e lo incolla nuovamente nell'istanza locale TuneCamp per attivare il collegamento verificato.
- Su
π Endpoint β
π Dopo fid 5.0: ricollegare un account β
fid 5.0 ha sostituito le chiavi Zen SEA con chiavi Ed25519, quindi lo stesso alias e la stessa passphrase ora derivano una chiave diversa. Un account il cui zen_pub è la vecchia chiave non viene più raggiunto dall'accesso: l'SSO risponde Username already exists⦠(FID_KEY_CHANGED) oppure il collegamento FID identity not found.
Gli account creati tramite FID non hanno password, il server rifiuta di scriverne una e la vecchia chiave non si puΓ² piΓΉ verificare (Zen non c'Γ¨ piΓΉ): il proprietario non puΓ² dimostrare da solo che l'account Γ¨ suo. Serve l'approvazione di un amministratore.
Ancora connesso all'istanza (self-service). Apri Profilo e premi Collega la mia identità FID a questo account. L'istanza genera un codice monouso (5 minuti) e apre tunecamp.org/profile.html con il codice nel frammento dell'URL. Accedi lì con alias e passphrase e premi Collega questa identità : la pagina firma la challenge dell'istanza con la nuova chiave e la associa all'account, senza intervento dell'amministratore.
Dall'interfaccia web. Il proprietario accede dal portale FID con la nuova chiave. L'istanza risponde FID_KEY_CHANGED e registra una richiesta di ricollegamento sull'account (vince la prima richiesta: un'altra chiave non puΓ² sostituirla finchΓ© l'amministratore non la rifiuta). In Admin β Users l'account mostra FID relink requested con la chiave e i pulsanti Approve / Dismiss (solo l'amministratore principale). Prima di approvare, conferma con la persona che la chiave Γ¨ quella mostrata dal suo portale: chiunque conosca un nome utente puΓ² inviare una richiesta. Dopo l'approvazione il proprietario accede di nuovo con l'SSO.
Dall'host. Lo stesso spostamento senza interfaccia:
npm run fid:relink -- <username> <nuova-chiave-pubblica> # aggiungi --db percorso/del/db se non Γ¨ quello configuratoLa nuova chiave pubblica Γ¨ quella mostrata dalla pagina del profilo FID dopo aver rifatto l'accesso. Lo strumento rifiuta una chiave malformata o giΓ usata da un altro account, disconnette le sessioni dell'account, azzera i passaporti (erano emessi per la vecchia chiave) e rimuove i record della libreria della vecchia identitΓ , che nessuno puΓ² piΓΉ leggere. Gli account che hanno una password possono accedere normalmente e collegare la nuova chiave dal profilo.
1. Genera Challenge β
- Endpoint:
GET /api/auth/zen/challenge - Autenticazione Richiesta: Sì (
requireUser) - Risposta:
{
"success": true,
"challenge": {
"instanceDomain": "sudorecords.scobrudot.dev",
"username": "scobru",
"nonce": "a4f891b2c3d4e5f67890123456789abc",
"timestamp": 1721926658000
}
}2. Verifica Challenge & Emetti Badge Passaporto β
- Endpoint:
POST /api/auth/zen/link - Autenticazione Richiesta: Sì (
requireUser) - Corpo:
{
"zenPubKey": "bE9DAycqb9gbxMJHTxh5RRxVQRPpG-wrCojHVl0s9sM",
"challenge": { ... },
"seaSignature": "SEA.sign_signature_data"
}- Risposta:
{
"success": true,
"passport": {
"instanceDomain": "sudorecords.scobrudot.dev",
"localUsername": "scobru",
"zenPubKey": "bE9DAycqb9gbxMJHTxh5RRxVQRPpG-wrCojHVl0s9sM",
"issuedAt": 1721926658000,
"passportSignature": "HMAC_SHA256_SIGNATURE",
"publicDataEndpoint": "https://sudorecords.scobrudot.dev/api/auth/zen/user/scobru/public"
}
}3. Login con FID SSO β
- Endpoint:
POST /api/auth/zen/sso - Autenticazione Richiesta: No (Pubblico con Limitazione di Frequenza)
- Corpo:
{
"ssoToken": {
"clientId": "tunecamp-webapp",
"instanceDomain": "sudorecords.scobrudot.dev",
"username": "scobru",
"zenPubKey": "bE9DAycqb9gbxMJHTxh5RRxVQRPpG-wrCojHVl0s9sM",
"issuedAt": 1721926658000
},
"apSeed": "32_byte_hex_seed..."
}- Comportamento:
- Valida
ssoTokentramiteFidSsoHandler.validateSsoToken(). - Risolve l'account con la chiave d'identitΓ contro cui la firma Γ¨ stata effettivamente verificata β
masterKeySource.pubKeyse presente, altrimenti il campo piattozenPubKey. Un token che li porta entrambi e li lascia discordare viene rifiutato (400 SSO token identity mismatch): sono due campi indipendenti dello stesso payload, quindi fidarsi di una chiave diversa da quella verificata permetterebbe a qualunque coppia di chiavi di rivendicare qualunque account. Richiedefidβ₯ 4.0.1, che rifiuta giΓ da sΓ© un token simile; questa rotta ripete il controllo per non dipendere dalla libreria. - Deriva le chiavi Ed25519 ActivityPub in modo deterministico sul server da
apSeed. - Salva quelle chiavi sull'account (
admin.ap_public_key/ap_private_key) se non giΓ presenti, e sull'artista collegato quando esiste. Senza questo passaggio l'account non avrebbe alcun attore Fediverse: l'SSO non passa mai daPOST /api/auth/login, che Γ¨ il punto in cui la generazione delle chiavi avviene altrimenti. - I nuovi utenti SSO iniziano come Ascoltatori standard (
UserRole.NORMAL_USER) senza profili artista creati automaticamente. - Se promossi internamente dagli amministratori di istanza, il ruolo e il link artista assegnati vengono rispettati.
- Valida
4. Esportazione Profilo Utente Pubblico β
- Endpoint:
GET /api/auth/zen/user/:username/public - Autenticazione Richiesta: No (Pubblico)
- Risposta: Ritorna solo informazioni pubbliche del profilo, pubblicazioni e playlist pubbliche per l'aggregazione tra istanze su
fid-portal.vercel.app.
5. Scoperta Istanze per il Portale β
- Endpoint:
GET /api/auth/zen/instances - Autenticazione Richiesta: Sì (
requireUser) - Risposta: Ritorna le voci
fid_registrydell'utente (istanze collegate con info artista, firme passaporto, stato di verifica). - Scopo: Consente al portale globale di scoprire quali istanze un utente ha collegato senza dover interrogare ciascuna istanza.
6. Collegamento Artista Tra Istanze (Registro FID) β
- Tabella:
fid_registry(per-istanza, traccia le istanze collegate per utente) - Endpoint: Rimossa - collegamento tra istanze ora gestito esternamente a
tunecamp.org/profile.html - Flusso: Utente si autentica sull'Istanza A, ottiene il passaporto da
tunecamp.org/profile.htmltramite il portale FID, e collega tramite la pagina profilo esterna.
7. Autenticazione FID per MCP Server β
- Header di Autenticazione:
Authorization: FID <zen_pub_key> - Middleware:
requireFidAuthinauth.ts - Comportamento: Cerca l'utente tramite la chiave
zen_pub, deriva il contesto e concede l'accesso agli strumenti MCP (search_music, list_recent_albums, scan_library, get_system_stats) senza token JWT. - Caso d'Uso: Assistenti IA (Claude Desktop, ecc.) si autenticano tramite l'identitΓ FID dell'utente per ispezionare/gestire il catalogo tra istanze.
8. Aggregazione Profilo Unificato (tunecamp-website/profile.html) β
- Origine Dati: Aggrega
publicReleases,publicLikes,publicPlaylistsda tutte le istanze collegate tramite le loro rotte/api/auth/zen/user/:username/public. - Archiviazione: Salva i dati per-istanza in cache nel
localStorage(tunecamp_instance_data). - Tab: Pubblicazioni, Preferiti (stelle), Playlist β ognuna mostra il badge dell'istanza.
- Sincronizzazione Automatica: Al login,
loadLinkedInstances()recupera il registro e sincronizza automaticamente le istanze verificate. - Sincronizzazione Manuale: Pulsante "Sincronizza" per ogni istanza nell'elenco delle Istanze Collegate.
- Sync della libreria (player): quando un'identitΓ Γ¨ collegata a un'istanza, il player puΓ² replicare la libreria dell'ascoltatore (preferiti, artisti, playlist) su quell'istanza. I record sono cifrati nel browser con una chiave derivata dalla chiave d'identitΓ ; l'istanza conserva solo testo cifrato. Vedi sotto.
9. Sync della Libreria (player del sito) β
Sincronizzazione tra dispositivi della libreria di un ascoltatore, piΓΉ le playlist condivise pubbliche. HTTP semplice, nessun relay.
- Endpoint (tutti sotto
/api/auth/zen/library/:pub, CORS aperto, nessun cookie):GET /:pub?since=<ms>β i record cambiati doposince, tombstone incluse. Firmato.PUT /:pubβ inserisce o aggiorna{ records: [{ bucket, id, d, at, del }] }. Un record viene accettato solo seatΓ¨ piΓΉ recente di quello salvato (vince l'ultima scrittura). Firmato.GET /:pub/shared/:idβ una playlist condivisa pubblica{ name, items, at }. Anonimo.GET /:pub/accountβ{ username }se l'istanza ha un account attivo collegato alla chiave, altrimenti404. Anonimo; un nuovo dispositivo lo usa per trovare dove sta la sua libreria. Espone solo il legame chiave β username che gli endpoint dei passaporti giΓ pubblicano.
- Autenticazione:
X-Fid-Auth: <ts>.<sig>, dovesigè la firma della chiave d'identità sufid-library:<METODO>:<percorso>:<ts>:<sha256 esadecimale del corpo>. Timestamp con più di 5 minuti di scarto vengono rifiutati. La chiave deve appartenere a un account attivo su quell'istanza (admin.zen_pub), così un'istanza non è mai spazio gratuito per sconosciuti. - Bucket:
favorites,artists,playlists(testo cifrato ind) eshared(JSON in chiaro, fino a 200 brani). - Limiti: 200 record per richiesta, 64 KB per record, 5000 record e 8 MB per identitΓ , 120 richieste al minuto per IP. Oltre il limite:
413. - Archiviazione: tabella
library_sync (pub, bucket, id, d, at, del). Un record eliminato mantiene la riga come tombstone con payload vuoto.