@dotrino/vault 0.22.0 → 0.23.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 +9 -2
- package/src/admin.js +59 -3
- package/src/types.d.ts +7 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotrino/vault",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Usa ESTE dispositivo (navegador) como
|
|
3
|
+
"version": "0.23.0",
|
|
4
|
+
"description": "Usa ESTE dispositivo (navegador) como b\u00f3veda/CA del ecosistema Dotrino: atiende enrolamientos por el proxy y firma certificados de delegaci\u00f3n a tus m\u00e1quinas. Incluye el cliente de SERVICIO (Node): un proyecto se enrola una vez y jala sus credenciales del vault en vez del .env (`import '@dotrino/vault/config'`).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"module": "src/index.js",
|
|
@@ -50,5 +50,12 @@
|
|
|
50
50
|
"type": "git",
|
|
51
51
|
"url": "git+https://github.com/imdotrino/dotrino-vault.git",
|
|
52
52
|
"directory": "lib"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"typescript": "^5.7.3",
|
|
56
|
+
"@types/node": "^22.0.0"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"type-check": "tsc --noEmit"
|
|
53
60
|
}
|
|
54
61
|
}
|
package/src/admin.js
CHANGED
|
@@ -9,8 +9,16 @@
|
|
|
9
9
|
*
|
|
10
10
|
* sí · ver el acta y la bitácora · iniciar un emparejamiento (mostrar el QR)
|
|
11
11
|
* · APROBAR o rechazar a quien entra · REVOCAR a un miembro
|
|
12
|
+
* · VARIABLES DE ENTORNO: crearlas y darles valor (de un scope o de un aparato),
|
|
13
|
+
* y ver el valor de las marcadas PÚBLICAS
|
|
12
14
|
* no · cambiar permisos · traspasar el mando · conceder `admin`
|
|
13
|
-
* ·
|
|
15
|
+
* · ver el valor de una variable PRIVADA · borrar variables
|
|
16
|
+
*
|
|
17
|
+
* SOBRE LAS VARIABLES, que es la rendija más nueva: lo que cruza la frontera no es «los
|
|
18
|
+
* secretos» sino los que su dueño MARCÓ como mostrables. Una privada se puede reescribir
|
|
19
|
+
* a ciegas desde la consola, pero su valor no sale de la máquina de la bóveda ni para un
|
|
20
|
+
* aparato tuyo con `admin`. Y los valores que sí salen viajan CIFRADOS con la clave de
|
|
21
|
+
* contenido del perfil (quien llama sella y abre): el proxy transporta y no ve nada.
|
|
14
22
|
*
|
|
15
23
|
* La frontera no es un capricho: un admin puede **admitir y expulsar**, pero no
|
|
16
24
|
* reescribir quién manda. Así un aparato con `admin` robado hace daño **acotado y
|
|
@@ -26,7 +34,13 @@
|
|
|
26
34
|
*/
|
|
27
35
|
|
|
28
36
|
/** Las únicas operaciones que existen a distancia. Lista cerrada, como las capacidades. */
|
|
29
|
-
export const ADMIN_OPS = Object.freeze([
|
|
37
|
+
export const ADMIN_OPS = Object.freeze([
|
|
38
|
+
'pending', 'pair', 'approve', 'reject', 'revoke', 'audit',
|
|
39
|
+
// Variables de entorno: verlas (nombres siempre; valor solo de las públicas) y
|
|
40
|
+
// ponerles valor (de las dos). Borrar NO está, y es a propósito: un aparato robado
|
|
41
|
+
// no debe poder dejar sin configuración a los servicios.
|
|
42
|
+
'vars', 'var.set'
|
|
43
|
+
])
|
|
30
44
|
|
|
31
45
|
/** Cuánto se recuerda un nonce ya usado (el doble de la ventana de frescura). */
|
|
32
46
|
export const ADMIN_NONCE_TTL_MS = 10 * 60 * 1000
|
|
@@ -40,13 +54,18 @@ export const AUDIT_MAX = 500
|
|
|
40
54
|
* @param {(scope:string[])=>Promise<any>} o.verify verifica cadena+cert; devuelve `{ok, device, reason}`.
|
|
41
55
|
* @param {(limit:number)=>any[]} o.readActivity últimas entradas de la bitácora.
|
|
42
56
|
* @param {(pub:string)=>Promise<string>} o.deviceIdOf
|
|
57
|
+
* @param {{list:(a:object)=>Promise<any>, set:(a:object)=>Promise<any>}} [o.vars]
|
|
58
|
+
* mostrador de VARIABLES DE ENTORNO. Va inyectado porque aquí no hay cripto ni disco:
|
|
59
|
+
* quien lo implementa (la bóveda) es quien sella con la clave de contenido del perfil y
|
|
60
|
+
* quien decide qué valor puede salir. Sin él, las ops de variables responden que esta
|
|
61
|
+
* bóveda no las atiende, en vez de fingir que se aplicaron.
|
|
43
62
|
* @param {(ev:string, info?:object)=>Promise<void>} [o.notify] aviso a todos los miembros.
|
|
44
63
|
* @param {(op:string, info?:object)=>void} [o.audit]
|
|
45
64
|
* @param {string[]} [o.defaultScope] lo que recibe un dispositivo emparejado a distancia.
|
|
46
65
|
* @param {number} [o.ttlMs] vida del cert que se emita.
|
|
47
66
|
*/
|
|
48
67
|
export function createAdminDesk ({
|
|
49
|
-
desk, verify, readActivity = () => [], deviceIdOf,
|
|
68
|
+
desk, verify, readActivity = () => [], deviceIdOf, vars = null,
|
|
50
69
|
notify = async () => {}, audit = () => {},
|
|
51
70
|
defaultScope = ['vault:sign', 'vault:read', 'vault:store'],
|
|
52
71
|
ttlMs, now = () => Date.now()
|
|
@@ -127,6 +146,43 @@ export function createAdminDesk ({
|
|
|
127
146
|
return { ok: true, result: { ok: true } }
|
|
128
147
|
}
|
|
129
148
|
|
|
149
|
+
// VARIABLES DE ENTORNO. El módulo solo enruta y audita: qué valor puede salir y con
|
|
150
|
+
// qué se cifra lo decide la bóveda (`vars`), que es la que tiene la clave y el disco.
|
|
151
|
+
if (data.op === 'vars' || data.op === 'var.set') {
|
|
152
|
+
if (!vars) return { ok: false, error: 'admin: this vault does not serve environment variables' }
|
|
153
|
+
// Un destino y solo uno: o un scope, o un aparato. Sin esto, mandar los dos dejaría
|
|
154
|
+
// que quien llama adivine dónde acabó su variable.
|
|
155
|
+
// Booleanos a propósito: comparar los VALORES («proxy» vs una pubkey) nunca da
|
|
156
|
+
// igual, así que mandar los dos destinos se colaba por el hueco.
|
|
157
|
+
const toScope = typeof data.ns === 'string' && !!data.ns
|
|
158
|
+
const toDevice = typeof data.pub === 'string' && !!data.pub
|
|
159
|
+
if (data.op === 'vars') {
|
|
160
|
+
const result = await vars.list({ by })
|
|
161
|
+
audit('admin.vars', { by })
|
|
162
|
+
return { ok: true, result }
|
|
163
|
+
}
|
|
164
|
+
if (toScope === toDevice) return { ok: false, error: 'admin: var.set needs exactly one target (ns or pub)' }
|
|
165
|
+
if (typeof data.key !== 'string' || !data.key) return { ok: false, error: 'admin: var.set needs a key' }
|
|
166
|
+
if (!data.enc || typeof data.enc !== 'object') {
|
|
167
|
+
// El valor NUNCA viaja en claro: si llega sin sobre, es un error de quien llama,
|
|
168
|
+
// no algo que se pueda «arreglar» aceptándolo.
|
|
169
|
+
return { ok: false, error: 'admin: var.set needs the value sealed with the profile content key' }
|
|
170
|
+
}
|
|
171
|
+
const result = await vars.set({
|
|
172
|
+
ns: toScope ? data.ns : null,
|
|
173
|
+
pub: toDevice ? data.pub : null,
|
|
174
|
+
key: data.key,
|
|
175
|
+
enc: data.enc,
|
|
176
|
+
public: typeof data.public === 'boolean' ? data.public : undefined,
|
|
177
|
+
by
|
|
178
|
+
})
|
|
179
|
+
audit('admin.var.set', { by, ns: toScope ? data.ns : null, device: toDevice ? await deviceIdOf(data.pub).catch(() => null) : null, key: data.key })
|
|
180
|
+
// Cambiar la configuración de un servicio a distancia no puede ser invisible: es
|
|
181
|
+
// la contrapartida de delegar (F3 de docs/consola-remota.md).
|
|
182
|
+
await notify('vars', { by, key: data.key, ns: toScope ? data.ns : null })
|
|
183
|
+
return { ok: true, result: result || { ok: true } }
|
|
184
|
+
}
|
|
185
|
+
|
|
130
186
|
// QUITAR UN DISPOSITIVO se hace por `sub` (su llave): sale del acta Y se le retiran
|
|
131
187
|
// todos los certificados. Las dos cosas o ninguna.
|
|
132
188
|
//
|
package/src/types.d.ts
ADDED