@dotrino/identity 0.32.0 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotrino/identity",
3
- "version": "0.32.0",
3
+ "version": "0.33.0",
4
4
  "description": "Identidad y rating de usuarios compartidos entre apps de Dotrino (vault iframe + postMessage)",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -27,6 +27,9 @@
27
27
  },
28
28
  "./content": {
29
29
  "import": "./vault/content.js"
30
+ },
31
+ "./keyid": {
32
+ "import": "./vault/keyid.js"
30
33
  }
31
34
  },
32
35
  "files": [
@@ -21,6 +21,7 @@
21
21
  * servidor de geo sin cargar el iframe.
22
22
  */
23
23
  import { canonicalStringify, bufToBase64, base64ToBuf } from './core.js'
24
+ import { pubkeyId, keyLabel } from './keyid.js'
24
25
 
25
26
  const ECDSA = { name: 'ECDSA', namedCurve: 'P-256' }
26
27
  const SIGN = { name: 'ECDSA', hash: { name: 'SHA-256' } }
@@ -46,12 +47,9 @@ async function rawVerify (publicJwkStr, bytes, sigB64) {
46
47
  const publicOf = (privateJwk) => ({ kty: privateJwk.kty, crv: privateJwk.crv, x: privateJwk.x, y: privateJwk.y })
47
48
  const scopeAllows = (scope, expected) => Array.isArray(scope) ? scope.includes(expected) : scope === expected
48
49
 
49
- /** id corto y estable de un pubkey (sha-256 hex de los campos canónicos del JWK). */
50
- export async function pubkeyId (publicJwkStr) {
51
- const jwk = typeof publicJwkStr === 'string' ? JSON.parse(publicJwkStr) : publicJwkStr
52
- const h = await crypto.subtle.digest('SHA-256', enc(canonicalStringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y })))
53
- return [...new Uint8Array(h)].map(b => b.toString(16).padStart(2, '0')).join('')
54
- }
50
+ // El id de una llave y su huella legible viven en `./keyid.js`, que no tiene dependencias:
51
+ // así una interfaz puede mostrar `AB12-CD34` sin arrastrar todo esto. Una sola implementación.
52
+ export { pubkeyId, keyLabel }
55
53
 
56
54
  /**
57
55
  * Verifica que `signature` (base64) sobre `data` fue hecha por la privada de
package/vault/core.js CHANGED
@@ -1005,8 +1005,21 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null, k
1005
1005
  // ----- perfiles (multi-perfil por dispositivo) -----
1006
1006
  // Cambiar/crear setea el perfil activo; la app RECARGA la página y re-inicializa con él
1007
1007
  // (no reactivo, por diseño). Las apps abiertas conservan el perfil con el que cargaron.
1008
+ /**
1009
+ * Los perfiles de este dispositivo, para el conmutador. Incluye el AVATAR de cada uno:
1010
+ * sin él, la lista caía siempre al identicon automático y tu foto no aparecía —aunque la
1011
+ * hubieras subido— porque el avatar vive en el `me` de cada perfil, no en el registro.
1012
+ */
1008
1013
  async listProfiles () {
1009
- return loadProfiles().map((p) => ({ id: p.id, name: p.name || '', pubkey: p.pubkey || null, current: p.id === currentPid }))
1014
+ return loadProfiles().map((p) => {
1015
+ let avatar = null
1016
+ try {
1017
+ const raw = p.id === currentPid ? JSON.stringify(me || null) : rawKv.getItem(`dotrino.identity.p.${p.id}.me`)
1018
+ const m = raw ? JSON.parse(raw) : null
1019
+ if (m && typeof m.avatar === 'string') avatar = m.avatar
1020
+ } catch (_) {}
1021
+ return { id: p.id, name: p.name || '', pubkey: p.pubkey || null, avatar, current: p.id === currentPid }
1022
+ })
1010
1023
  },
1011
1024
  async currentProfile () {
1012
1025
  const e = loadProfiles().find((p) => p.id === currentPid) || {}
package/vault/keyid.js ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * keyid.js — el identificador LEGIBLE de una llave.
3
+ *
4
+ * Una pubkey es un JWK: `{"crv":"P-256","kty":"EC","x":"…","y":"…"}`. Eso no se le enseña a
5
+ * nadie — ni entero ni recortado, que es peor: parece un error. Para que una persona pueda
6
+ * reconocer y comparar una llave se usa su huella corta, `AB12-CD34`, que es la misma que
7
+ * ya se muestra al emparejar («aprueba el dispositivo AB12-CD34») y en el acta del perfil.
8
+ *
9
+ * Módulo aparte y sin dependencias a propósito: cualquier interfaz que necesite mostrar una
10
+ * llave puede importarlo sin arrastrar el resto de la identidad. `capabilities.js` reexporta
11
+ * `pubkeyId` desde aquí para que haya UNA sola implementación.
12
+ */
13
+
14
+ const enc = (s) => new TextEncoder().encode(s)
15
+
16
+ /** Serialización canónica de los campos que identifican la llave (mismo orden siempre). */
17
+ function canonicalJwk (jwk) {
18
+ return JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y })
19
+ .replace(/^{/, '{').replace(/}$/, '}')
20
+ }
21
+
22
+ /** id corto y estable de un pubkey (sha-256 hex de los campos canónicos del JWK). */
23
+ export async function pubkeyId (publicJwkStr) {
24
+ const jwk = typeof publicJwkStr === 'string' ? JSON.parse(publicJwkStr) : publicJwkStr
25
+ const h = await crypto.subtle.digest('SHA-256', enc(canonicalJwk(jwk)))
26
+ return [...new Uint8Array(h)].map((b) => b.toString(16).padStart(2, '0')).join('')
27
+ }
28
+
29
+ /**
30
+ * La huella que SÍ se le enseña a una persona: `AB12-CD34`. Corta, comparable de un vistazo
31
+ * y la misma en todo el ecosistema (emparejamiento, acta, lista de dispositivos).
32
+ */
33
+ export async function keyLabel (publicJwkStr) {
34
+ if (!publicJwkStr) return ''
35
+ try {
36
+ const id = (await pubkeyId(publicJwkStr)).slice(0, 8).toUpperCase()
37
+ return id.slice(0, 4) + '-' + id.slice(4, 8)
38
+ } catch (_) { return '' }
39
+ }
40
+
41
+ export default { pubkeyId, keyLabel }