Skip to content

@ursalock/server

Self-hostable backend with Hono and SQLite.

import { createServer } from "@ursalock/server";
const server = createServer({
jwtSecret: process.env.JWT_SECRET,
dbPath: "./data/vault.db",
});
server.listen(3000);
VariableRequiredDefaultDescription
JWT_SECRETYes-Secret for JWT signing
JWT_ISSUERNoursalockJWT issuer claim
JWT_ACCESS_EXPIRYNo15mAccess token expiry
JWT_REFRESH_EXPIRYNo7dRefresh token expiry
DB_PATHNo./data/vault.dbSQLite path
PORTNo3000HTTP port

Register a new user.

Terminal window
curl -X POST https://vault.example.com/auth/email/register \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "secret"}'

Response:

{
"accessToken": "eyJ...",
"refreshToken": "eyJ...",
"user": { "id": "...", "email": "user@example.com" }
}

Login with email/password.

Terminal window
curl -X POST https://vault.example.com/auth/email/login \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "secret"}'

Get current user.

Terminal window
curl https://vault.example.com/auth/me \
-H "Authorization: Bearer <token>"

Refresh access token.

Terminal window
curl -X POST https://vault.example.com/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refreshToken": "eyJ..."}'

Logout (invalidate refresh token).

Terminal window
curl -X POST https://vault.example.com/auth/logout \
-H "Authorization: Bearer <token>"

List user’s vaults.

Terminal window
curl https://vault.example.com/vault \
-H "Authorization: Bearer <token>"

Response:

{
"vaults": [
{
"uid": "abc123",
"name": "my-store",
"version": 1,
"updatedAt": 1234567890
}
]
}

Create a vault (container only, no data).

Terminal window
curl -X POST https://vault.example.com/vault \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"name": "my-store"}'

Get a specific vault.

Terminal window
curl https://vault.example.com/vault/abc123 \
-H "Authorization: Bearer <token>"

Response:

{
"uid": "abc123",
"name": "my-store",
"version": 1,
"updatedAt": 1234567890
}

Get vault by name.

Terminal window
curl https://vault.example.com/vault/by-name/my-store \
-H "Authorization: Bearer <token>"

Delete a vault and all its documents.

Terminal window
curl -X DELETE https://vault.example.com/vault/abc123 \
-H "Authorization: Bearer <token>"

Documents are encrypted data stored within vaults. The data field contains base64-encoded encrypted ciphertext. The server never sees plaintext.

Create a document in a vault.

Terminal window
curl -X POST https://vault.example.com/vaults/abc123/documents \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"collection": "notes",
"data": "base64-encrypted-data...",
"hmac": "optional-hmac-for-integrity"
}'

Response:

{
"uid": "doc-xyz",
"vaultUid": "abc123",
"collection": "notes",
"data": "base64-encrypted-data...",
"hmac": "optional-hmac...",
"version": 1,
"createdAt": 1234567890,
"updatedAt": 1234567890
}

List documents in a vault.

Query parameters:

  • collection — Filter by collection name
  • since — Only return documents updated after this timestamp (milliseconds)
  • limit — Maximum number of documents to return
Terminal window
curl "https://vault.example.com/vaults/abc123/documents?collection=notes&since=1234567890" \
-H "Authorization: Bearer <token>"

Response:

{
"documents": [
{
"uid": "doc-xyz",
"vaultUid": "abc123",
"collection": "notes",
"data": "base64-encrypted-data...",
"hmac": "optional-hmac...",
"version": 1,
"createdAt": 1234567890,
"updatedAt": 1234567890,
"deletedAt": null
}
]
}

Get a specific document.

Terminal window
curl https://vault.example.com/vaults/abc123/documents/doc-xyz \
-H "Authorization: Bearer <token>"

Update a document.

Terminal window
curl -X PUT https://vault.example.com/vaults/abc123/documents/doc-xyz \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"data": "new-encrypted-data...",
"hmac": "new-hmac...",
"version": 1
}'

Returns 409 Conflict if the version doesn’t match (optimistic locking for conflict resolution).

Soft delete a document (sets deletedAt timestamp).

Terminal window
curl -X DELETE https://vault.example.com/vaults/abc123/documents/doc-xyz \
-H "Authorization: Bearer <token>"

API keys provide programmatic access without user passwords.

Create an API key.

Terminal window
curl -X POST https://vault.example.com/auth/api-keys \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"name": "My App Key", "expiresAt": 1735689600000}'

Response:

{
"uid": "key-123",
"name": "My App Key",
"key": "urs_abc123...",
"expiresAt": 1735689600000,
"createdAt": 1234567890
}

Note: The key field is only returned once at creation. Store it securely.

List API keys for the current user.

Terminal window
curl https://vault.example.com/auth/api-keys \
-H "Authorization: Bearer <token>"

Response:

{
"keys": [
{
"uid": "key-123",
"name": "My App Key",
"expiresAt": 1735689600000,
"createdAt": 1234567890,
"lastUsedAt": 1234567890
}
]
}

Revoke an API key.

Terminal window
curl -X DELETE https://vault.example.com/auth/api-keys/key-123 \
-H "Authorization: Bearer <token>"
Terminal window
curl https://vault.example.com/health

Response:

{"status": "ok", "timestamp": 1234567890}
CREATE TABLE users (
id TEXT PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
password_hash TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE vaults (
uid TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES users(id),
name TEXT NOT NULL,
version INTEGER DEFAULT 1,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
UNIQUE(user_id, name)
);
CREATE TABLE documents (
uid TEXT PRIMARY KEY,
vault_uid TEXT NOT NULL REFERENCES vaults(uid) ON DELETE CASCADE,
collection TEXT NOT NULL,
data TEXT NOT NULL,
hmac TEXT,
version INTEGER DEFAULT 1,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
deleted_at INTEGER,
INDEX(vault_uid, collection),
INDEX(vault_uid, updated_at)
);
CREATE TABLE api_keys (
uid TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES users(id),
name TEXT NOT NULL,
key_hash TEXT NOT NULL,
expires_at INTEGER,
last_used_at INTEGER,
created_at INTEGER NOT NULL
);
CREATE TABLE refresh_tokens (
jti TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES users(id),
expires_at INTEGER NOT NULL,
created_at INTEGER NOT NULL
);