How It Works

From Launch to Orbit

A step-by-step walkthrough of how two users set up a MindooDB tenant, exchange encrypted data, and collaborate — all without the server ever seeing plaintext.

01

Launch

Admin bootstraps the tenant

1
User 1

Create the Tenant

The admin creates a new tenant with a single call. This generates all keys, opens the tenant, and registers the first user in the directory.

const { tenant, adminUser, appUser, keyBag }
  = await factory.createTenant({
    tenantId: "acme",
    adminName: "cn=admin/o=acme",
    adminPassword: "••••••••",
    userName: "cn=alice/o=acme",
    userPassword: "••••••••",
  });
2
User 1

Publish to Server

The admin registers the tenant on a MindooDB server. The server only stores encrypted data and public keys.

await tenant.publishToServer(
  "https://sync.acme.com",
  { registerUsers: [appUser] }
);
🔒 register-tenant
Server stores public keys only
3
User 1

Create & Sync a Document

Alice creates a todo document and pushes the encrypted changes to the server.

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);
02

Signal

A new user requests to join

4
User 2

Create Join Request

Bob creates his identity locally — his private keys never leave his device. He generates a join request containing only public keys.

const bob = await factory.createUserId(
  "cn=bob/o=acme", "••••••••"
);

const joinRequestURI = factory.createJoinRequest(
  bob, { format: "uri" }
);
// → mdb://join-request/eyJ2IjoxLCJ1c2Vy...
mdb://join-request/...
Public keys only — safe to share anywhere
03

Docking

Admin approves the join request

6
User 1

Approve Join Request

The admin approves Bob's request. This registers Bob in the directory and encrypts the symmetric keys with a shared password.

const joinResponseURI =
  await tenant.approveJoinRequest(
    joinRequestURI,
    {
      adminSigningKey: adminUser
        .userSigningKeyPair.privateKey,
      adminPassword: "••••••••",
      sharePassword: "one-time-secret",
      format: "uri",
    }
  );
// → mdb://join-response/eyJ2IjoxLCJ0ZW5h...
🔒 mdb://join-response/...
Share the password by phone or in person
04

Orbit

Real-time collaboration begins

8
User 2

Join, Pull, Modify, Push

Bob joins the tenant, pulls the latest data, marks the todo as done, and pushes back.

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);
9
User 1

Fetch Changes

Alice pulls the latest changes and sees the todo is done.

await db.pullChangesFrom(remote);
await db.syncStoreChanges();

const updated = await db.getDocument(todoDocId);
console.log(updated.getData().done);
// → true ✓
Todo is done ✓

What the Server Sees vs. Doesn't See

Server sees

  • Public signing keys (Ed25519)
  • Public encryption keys (RSA-OAEP)
  • Encrypted blobs (AES-256-GCM ciphertext)
  • Entry metadata (timestamps, content hashes)

Server never sees

  • Usernames (encrypted with admin RSA key)
  • Document content (end-to-end encrypted)
  • Private keys (stay on device)
  • Shared passwords (out-of-band only)
  • Symmetric encryption keys
Get Started Architecture Deep Dive