Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ SURREAL_DATABASE=main
SURREAL_USERNAME=your_username
SURREAL_PASSWORD=your_password

# Database-scoped backend-service grant used by the proxy to fetch encrypted provider secrets.
# Generate it once after applying the schema:
# ACCESS backend_service ON DATABASE GRANT FOR USER;
# Use the returned `grant.key` value directly here.
VALYMUX_DB_SERVICE_KEY=replace_with_backend_service_key

# 32-byte (256-bit) encryption key, hex-encoded, for encrypting provider secrets
# at rest. Generate with:
# openssl rand -hex 32
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
/skills
.claude
documents
surrealdbdocs
.codex
318 changes: 287 additions & 31 deletions AGENTS.md

Large diffs are not rendered by default.

318 changes: 287 additions & 31 deletions CLAUDE.md

Large diffs are not rendered by default.

54 changes: 39 additions & 15 deletions crates/surrealdb/schema/auth/002_virtual_api_key.surql
Original file line number Diff line number Diff line change
@@ -1,20 +1,44 @@
DEFINE TABLE IF NOT EXISTS virtual_api_key SCHEMAFULL
DEFINE TABLE OVERWRITE virtual_api_key SCHEMAFULL
PERMISSIONS
FOR select, update, delete
WHERE user = $auth.id
FOR select
WHERE (user = $auth.id AND $access = "account")
OR (id = $auth AND $access = "virtual_key_access")
-- account: full update; virtual_key_access: only its own record (field-level perms restrict what)
FOR update
WHERE (user = $auth.id AND $access = "account")
OR (id = $auth AND $access = "virtual_key_access")
FOR delete
WHERE user = $auth.id AND $access = "account"
FOR create
WHERE user = $auth.id;
WHERE user = $auth.id AND $access = "account";

DEFINE FIELD IF NOT EXISTS user ON TABLE virtual_api_key TYPE record<user>;
DEFINE FIELD IF NOT EXISTS name ON TABLE virtual_api_key TYPE string;
DEFINE FIELD IF NOT EXISTS key_prefix ON TABLE virtual_api_key TYPE string;
DEFINE FIELD IF NOT EXISTS key_hash ON TABLE virtual_api_key TYPE string;
DEFINE FIELD IF NOT EXISTS allowed_models ON TABLE virtual_api_key TYPE array;
DEFINE FIELD IF NOT EXISTS tags ON TABLE virtual_api_key TYPE array;
DEFINE FIELD OVERWRITE enabled ON TABLE virtual_api_key TYPE bool DEFAULT true;
DEFINE FIELD IF NOT EXISTS expires_at ON TABLE virtual_api_key TYPE option<datetime>;
DEFINE FIELD IF NOT EXISTS last_used_at ON TABLE virtual_api_key TYPE option<datetime>;
DEFINE FIELD IF NOT EXISTS created_at ON TABLE virtual_api_key TYPE datetime VALUE time::now() READONLY;
DEFINE FIELD IF NOT EXISTS updated_at ON TABLE virtual_api_key TYPE datetime VALUE time::now();
-- Sensitive fields: only the owning account session may update these.
DEFINE FIELD OVERWRITE user ON TABLE virtual_api_key TYPE record<user>
PERMISSIONS FOR update WHERE user = $auth.id AND $access = "account";
DEFINE FIELD OVERWRITE name ON TABLE virtual_api_key TYPE string
ASSERT string::len($value) >= 1 AND string::len($value) <= 256
PERMISSIONS FOR update WHERE user = $auth.id AND $access = "account";
DEFINE FIELD OVERWRITE key_prefix ON TABLE virtual_api_key TYPE string
PERMISSIONS FOR update WHERE user = $auth.id AND $access = "account";
DEFINE FIELD OVERWRITE key_hash ON TABLE virtual_api_key TYPE string
PERMISSIONS FOR update WHERE user = $auth.id AND $access = "account";
DEFINE FIELD OVERWRITE allowed_models ON TABLE virtual_api_key TYPE array
PERMISSIONS FOR update WHERE user = $auth.id AND $access = "account";
DEFINE FIELD OVERWRITE tags ON TABLE virtual_api_key TYPE array
PERMISSIONS FOR update WHERE user = $auth.id AND $access = "account";
DEFINE FIELD OVERWRITE enabled ON TABLE virtual_api_key TYPE bool DEFAULT true
PERMISSIONS FOR update WHERE user = $auth.id AND $access = "account";
DEFINE FIELD OVERWRITE expires_at ON TABLE virtual_api_key TYPE option<datetime>
PERMISSIONS FOR update WHERE user = $auth.id AND $access = "account";
-- last_used_at: account can update it manually; virtual_key_access updates it on each proxy call.
DEFINE FIELD OVERWRITE last_used_at ON TABLE virtual_api_key TYPE option<datetime>
PERMISSIONS
FOR select WHERE (user = $auth.id AND $access = "account") OR (id = $auth AND $access = "virtual_key_access")
FOR update WHERE (user = $auth.id AND $access = "account") OR (id = $auth AND $access = "virtual_key_access");
DEFINE FIELD OVERWRITE created_at ON TABLE virtual_api_key TYPE datetime VALUE time::now() READONLY;
DEFINE FIELD OVERWRITE updated_at ON TABLE virtual_api_key TYPE datetime VALUE time::now();

DEFINE INDEX IF NOT EXISTS virtual_api_key_hash_unique ON TABLE virtual_api_key FIELDS key_hash UNIQUE;

-- Performance index: key lookups by owner (list, cascade delete, usage queries)
DEFINE INDEX IF NOT EXISTS idx_virtual_api_key_user ON TABLE virtual_api_key FIELDS user;
9 changes: 9 additions & 0 deletions crates/surrealdb/schema/auth/003_virtual_key_access.surql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
DEFINE ACCESS OVERWRITE virtual_key_access ON DATABASE TYPE RECORD
SIGNIN (
SELECT * FROM virtual_api_key
WHERE key_hash = crypto::sha256($virtual_key)
AND enabled = true
AND (expires_at = NONE OR expires_at > time::now())
LIMIT 1
)
DURATION FOR TOKEN 15m, FOR SESSION NONE;
10 changes: 10 additions & 0 deletions crates/surrealdb/schema/auth/004_backend_service.surql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- backend_service is used by the ValyMux proxy to authenticate directly to SurrealDB.
-- This access type only allows reading encrypted provider credentials via fn::backend_fetch_secret.
-- The encrypted values still require SURREAL_ENCRYPTION_KEY to decrypt (held in the Rust process).
--
-- Grant duration: 30 days. Rotate before expiry using scripts/generate_backend_grant.sh:
-- 1. Run ./scripts/generate_backend_grant.sh → new VALYMUX_DB_SERVICE_KEY
-- 2. Update .env and restart the server
-- 3. Revoke the old grant: ACCESS backend_service ON DATABASE REVOKE GRANT <old-id>;
DEFINE ACCESS OVERWRITE backend_service ON DATABASE TYPE BEARER FOR USER
DURATION FOR GRANT 30d, FOR TOKEN 1h, FOR SESSION 24h;
21 changes: 21 additions & 0 deletions crates/surrealdb/schema/functions/001_user_functions.surql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-- User Functions
-- PERMISSIONS WHERE $auth != NONE: $auth.id IS available inside the function body

DEFINE FUNCTION OVERWRITE fn::get_current_user() {
-- Explicit column list intentionally excludes the password hash.
LET $user = (
SELECT id, name, email, enabled, created_at, updated_at
FROM $auth.id
)[0];
IF $user = NONE {
THROW "authenticated user not found";
};
IF $user.enabled = false {
THROW "account is disabled";
};
RETURN $user;
} PERMISSIONS WHERE $auth != NONE AND $access = "account";

DEFINE FUNCTION OVERWRITE fn::update_profile($changed_name: string) {
RETURN (UPDATE user SET name = $changed_name WHERE id = $auth.id RETURN AFTER)[0];
} PERMISSIONS WHERE $auth != NONE AND $access = "account";
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
-- Provider Credential Functions
-- PERMISSIONS WHERE $auth != NONE: $auth.id IS available inside the function body

DEFINE FUNCTION OVERWRITE fn::list_provider_credentials() {
RETURN SELECT * OMIT encrypted_api_key FROM provider_credential
WHERE user = $auth.id
ORDER BY created_at ASC;
} PERMISSIONS WHERE $auth != NONE AND $access = "account";

DEFINE FUNCTION OVERWRITE fn::get_provider_credential($id: record<provider_credential>) {
RETURN (SELECT * OMIT encrypted_api_key FROM $id WHERE user = $auth.id)[0];
} PERMISSIONS WHERE $auth != NONE AND $access = "account";

-- encrypted_api_key must be encrypted by Rust before calling this
DEFINE FUNCTION OVERWRITE fn::create_provider_credential(
$provider: string,
$label: string,
$encrypted_api_key: string,
$tags: array
) {
RETURN CREATE provider_credential CONTENT {
user: $auth.id,
provider: $provider,
label: $label,
encrypted_api_key: $encrypted_api_key,
tags: $tags,
enabled: true,
sync_status: "pending",
model_count: 0
};
} PERMISSIONS WHERE $auth != NONE AND $access = "account";

DEFINE FUNCTION OVERWRITE fn::update_provider_credential(
$id: record<provider_credential>,
$label: string,
$tags: array,
$enabled: bool,
$encrypted_api_key: option<string>
) {
LET $existing = (SELECT * FROM $id WHERE user = $auth.id)[0];
IF $existing = NONE {
THROW "provider credential not found or access denied";
};

IF $encrypted_api_key != NONE {
LET $updated = (UPDATE $id MERGE {
label: $label,
tags: $tags,
enabled: $enabled,
encrypted_api_key: $encrypted_api_key,
sync_status: "pending",
sync_error: NONE
} WHERE user = $auth.id RETURN AFTER)[0];
Comment thread
coderabbitai[bot] marked this conversation as resolved.
RETURN $updated;
};

RETURN (UPDATE $id MERGE {
label: $label,
tags: $tags,
enabled: $enabled
} WHERE user = $auth.id RETURN AFTER)[0];
} PERMISSIONS WHERE $auth != NONE AND $access = "account";

-- Atomically transitions a credential to "syncing" only if it is not already syncing,
-- or if the existing sync lock is stale (started more than 10 minutes ago).
-- Returns true if the caller acquired the sync lock, false if already syncing.
DEFINE FUNCTION OVERWRITE fn::start_provider_sync($id: record<provider_credential>) {
LET $updated = (
UPDATE $id SET sync_status = "syncing", sync_error = NONE, sync_started_at = time::now()
WHERE user = $auth.id
AND sync_status != "deleting"
AND (sync_status != "syncing"
OR sync_started_at = NONE
OR sync_started_at < time::now() - 10m)
RETURN AFTER
)[0];
RETURN $updated != NONE;
} PERMISSIONS WHERE $auth != NONE AND $access = "account";
Comment thread
coderabbitai[bot] marked this conversation as resolved.

DEFINE FUNCTION OVERWRITE fn::delete_provider_credential($id: record<provider_credential>) {
LET $before = (SELECT * FROM $id WHERE user = $auth.id)[0];
IF $before = NONE {
RETURN NONE;
};

UPDATE $id MERGE {
sync_status: "deleting",
sync_error: NONE
} WHERE user = $auth.id;

DELETE supports WHERE in = $id;
DELETE virtual_key_route WHERE out = $id;
DELETE $id WHERE user = $auth.id;

RETURN $before;
} PERMISSIONS WHERE $auth != NONE AND $access = "account";
Loading
Loading