Cómo funciona
Del despegue a la órbita
Paso a paso: un administrador crea un tenant de MindooDB y dos usuarios intercambian datos cifrados y colaboran — sin que el servidor llegue a ver texto en claro.
Despegue
El administrador pone en marcha el tenant
Crear el tenant
El administrador crea un tenant nuevo con una sola llamada. Así se generan todas las claves, se abre el tenant y se registra en el directorio a la primera usuaria, Alice.
const { tenant, adminUser, appUser, keyBag }
= await factory.createTenant({
tenantId: "acme",
adminName: "cn=admin/o=acme",
adminPassword: "••••••••",
userName: "cn=alice/o=acme",
userPassword: "••••••••",
}); Publicar en el servidor
El administrador registra el tenant en un servidor MindooDB. El servidor solo guarda datos cifrados y claves públicas.
await tenant.publishToServer(
"https://sync.acme.com",
{ registerUsers: [appUser] }
); Crear y sincronizar un documento
Alice crea un documento de tareas y envía los cambios cifrados al servidor.
const db = await tenant.openDB("todos");
const doc = await db.createDocument();
await db.changeDoc(doc, (d) => {
const data = d.getData();
data.title = "Buy groceries";
data.done = false;
});
const remote = await tenant.connectToServer(
"https://sync.acme.com", "todos"
);
await db.pushChangesTo(remote); Señal
Un usuario nuevo pide unirse
Crear la solicitud de unión
Bob genera su identidad en local — sus claves privadas no salen nunca de su dispositivo. Crea una solicitud de unión que solo contiene claves públicas.
const bob = await factory.createUserId(
"cn=bob/o=acme", "••••••••"
);
const joinRequestURI = factory.createJoinRequest(
bob, { format: "uri" }
);
// → mdb://join-request/eyJ2IjoxLCJ1c2Vy... Acoplamiento
El administrador aprueba la solicitud de unión
Aprobar la solicitud de unión
El administrador aprueba la solicitud de Bob. Con ello queda registrado en el directorio y las claves simétricas se cifran con una contraseña compartida.
const joinResponseURI =
await tenant.approveJoinRequest(
joinRequestURI,
{
adminSigningKey: adminUser
.userSigningKeyPair.privateKey,
adminPassword: "••••••••",
sharePassword: "one-time-secret",
format: "uri",
}
);
// → mdb://join-response/eyJ2IjoxLCJ0ZW5h... Órbita
Empieza la colaboración en tiempo real
Unirse, recibir, modificar, enviar
Bob se une al tenant, recibe los datos más recientes, marca la tarea como hecha y envía el cambio de vuelta.
const { tenant: bobTenant } =
await factory.joinTenant(
joinResponseURI,
{
user: bob,
password: "••••••••",
sharePassword: "one-time-secret",
}
);
const remote = await bobTenant.connectToServer(
"https://sync.acme.com", "todos"
);
const db = await bobTenant.openDB("todos");
await db.pullChangesFrom(remote);
await db.syncStoreChanges();
const todo = await db.getDocument(todoDocId);
await db.changeDoc(todo, (d) => {
d.getData().done = true;
});
await db.pushChangesTo(remote); Recibir los cambios
Alice recibe los últimos cambios y ve que la tarea está hecha.
await db.pullChangesFrom(remote);
await db.syncStoreChanges();
const updated = await db.getDocument(todoDocId);
console.log(updated.getData().done);
// → true ✓ Tarea hecha ✓ Lo que el servidor ve y lo que no
El servidor ve
- Claves públicas de firma (Ed25519)
- Claves públicas de cifrado (RSA-OAEP)
- Blobs cifrados (texto cifrado AES-256-GCM)
- Metadatos de las entradas (marcas de tiempo, hashes de contenido)
- Hashes de nombres de usuario (SHA-256, no los nombres reales)
El servidor nunca ve
- Los nombres de usuario (cifrados con la clave RSA del administrador)
- El contenido de los documentos (cifrado de extremo a extremo)
- Las claves privadas (se quedan en el dispositivo)
- Las contraseñas compartidas (solo por canal aparte)
- Las claves de cifrado simétricas