tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1435Shell 145Markdown 88Rust 852987 Sutichaykuna

⎇ 84e3737e10e17927380ed3e232e50cebfdd39123 recipes/atuq/extensions/boveda/fondo.js JavaScript · 157 siq'ikuna · 5.7 KB Ñawpa willay Pi imata llamkarqan ch'uya

feat(atuq): la boveda de la suite guarda las contrasenas, y el gestor de Gecko se aparta 6f0ba974c · sergio ·

1"use strict";
2// bóveda — las contraseñas las guarda la suite, no el navegador (SDD-BOVEDA §8), v1.
3//
4// Esta extensión **no sabe nada**. Pregunta y muestra; quien decide qué credencial
5// corresponde a qué página es la función pura de `pacha-boveda-core`, del otro lado
6// del cable. Reimplementar acá la regla de coincidencia sería fabricar un sustituto
7// paralelo del original —lo que la regla 10 de tawasuyu prohíbe— y, peor, sería
8// poner en JS la decisión de a quién se le entrega una contraseña.
9//
10// ── LO QUE ESTA EXTENSIÓN NO PUEDE HACER, Y ES A PROPÓSITO ───────────────────
11// No puede sacar una contraseña de la bóveda. Pide `vault.match`, que contesta
12// títulos y usuarios; la contraseña sale por `vault.fill`, y ese verbo **pregunta
13// en el escritorio** antes de contestar. Si esta extensión queda comprometida, o si
14// una página consigue hablarle, lo que obtiene es la lista de títulos de los sitios
15// que coinciden con su propia dirección.
16//
17// ── LA URL LA PONE EL CHROME, NUNCA LA PÁGINA ────────────────────────────────
18// Todo lo que se le manda al host lleva `tab.url`, que lo dice el navegador. Si la
19// dirección viniera del content script, una página podría decir que es el banco y
20// pedir lo que no es suyo. El content script sólo avisa que **hay** un formulario;
21// nunca dice de quién.
22
23const HOST = "puriy_costura";
24
25let puerto = null;
26let proximoId = 1;
27// id de petición → qué hacer con la respuesta.
28const esperando = new Map();
29
30function di(s) {
31  try { dump("BOVEDA " + s + "\n"); } catch (e) {}
32}
33
34function conectar() {
35  try {
36    puerto = browser.runtime.connectNative(HOST);
37    puerto.onMessage.addListener(alRecibir);
38    puerto.onDisconnect.addListener(p => {
39      di("DESCONECTADO " + (p.error ? p.error.message : "sin error"));
40      puerto = null;
41      // Lo que quedó esperando no va a llegar nunca: se le contesta que no, en
42      // vez de dejar a la página esperando para siempre.
43      for (const [, f] of esperando) { try { f(null); } catch (e) {} }
44      esperando.clear();
45    });
46    di("CONECTADO " + HOST);
47  } catch (e) {
48    di("SIN HOST " + e.message);
49    puerto = null;
50  }
51}
52
53function pedir(mensaje) {
54  return new Promise(resolver => {
55    if (!puerto) { conectar(); }
56    if (!puerto) { resolver(null); return; }
57    const id = proximoId++;
58    esperando.set(id, resolver);
59    try {
60      puerto.postMessage(Object.assign({ id }, mensaje));
61    } catch (e) {
62      esperando.delete(id);
63      di("NO PUDE PEDIR " + e.message);
64      resolver(null);
65    }
66  });
67}
68
69function alRecibir(r) {
70  if (!r || typeof r.id !== "number") { return; }
71  const f = esperando.get(r.id);
72  if (!f) { return; }
73  esperando.delete(r.id);
74  f(r);
75}
76
77// ── La insignia: cuántas credenciales hay para lo que estás mirando ──────────
78
79async function mirar(tabId, url) {
80  if (!url || !/^https?:/.test(url)) {
81    browser.browserAction.setBadgeText({ tabId, text: "" });
82    return 0;
83  }
84  const r = await pedir({ verb: "vault.match", url });
85  if (!r || r.ok !== true) {
86    browser.browserAction.setBadgeText({ tabId, text: "" });
87    return 0;
88  }
89  if (r.locked === true) {
90    // Cerrada no es lo mismo que vacía, y se dice: si no, el usuario cree que
91    // no tiene guardada la contraseña que sí tiene.
92    browser.browserAction.setBadgeText({ tabId, text: "🔒" });
93    return 0;
94  }
95  const n = (r.items || []).length;
96  browser.browserAction.setBadgeText({ tabId, text: n ? String(n) : "" });
97  return n;
98}
99
100browser.tabs.onUpdated.addListener((tabId, cambio, tab) => {
101  if (cambio.status === "complete") { mirar(tabId, tab.url); }
102});
103
104// ── Rellenar: el usuario lo pide, el escritorio lo autoriza ──────────────────
105
106browser.browserAction.onClicked.addListener(async tab => {
107  const r = await pedir({ verb: "vault.match", url: tab.url });
108  if (!r || r.ok !== true || r.locked === true) {
109    di("no hay bóveda abierta");
110    return;
111  }
112  const items = r.items || [];
113  if (items.length === 0) { di("nada para " + tab.url); return; }
114
115  // v1: si hay varias, se usa la primera —que viene de la más específica a la
116  // menos, decidido del otro lado—. Elegir entre varias es una pantalla, y la
117  // pantalla es F2c.
118  const elegida = items[0];
119  const f = await pedir({ verb: "vault.fill", url: tab.url, item: elegida.id });
120  if (!f || f.ok !== true) { di("fill falló"); return; }
121  if (f.denied === true) { di("el usuario dijo que no"); return; }
122  if (!f.password) { di("sin contraseña en la respuesta"); return; }
123
124  browser.tabs.sendMessage(tab.id, {
125    tipo: "rellenar",
126    username: f.username || "",
127    password: f.password,
128    totp: f.totp || null,
129  });
130});
131
132// ── Guardar lo que el usuario acaba de tipear ────────────────────────────────
133
134browser.runtime.onMessage.addListener(async (msg, sender) => {
135  if (!msg || !sender || !sender.tab) { return; }
136  // ⚠ La URL sale de `sender.tab.url` —lo que dice el navegador— y NO del
137  // mensaje. Lo único que la página aporta es lo que el usuario tipeó en ella.
138  const url = sender.tab.url;
139
140  if (msg.tipo === "hay-formulario") {
141    mirar(sender.tab.id, url);
142    return;
143  }
144  if (msg.tipo === "se-envio" && msg.password) {
145    const r = await pedir({
146      verb: "vault.save",
147      url,
148      username: msg.username || "",
149      password: msg.password,
150      title: sender.tab.title || "",
151    });
152    if (r && r.denied === true) { di("no se guardó: el usuario dijo que no"); }
153    else if (r && r.ok === true) { di("guardada para " + url); mirar(sender.tab.id, url); }
154  }
155});
156
157conectar();