feat: add meta and settings endpoints for improved API functionality

- Introduced new meta endpoints: `/api/alive`, `/api/now`, `/api/version`, and `/api/hibp/breach` for health checks and versioning.
- Added settings endpoints: `/api/settings/domains` for retrieving and managing equivalent domains, with stub implementations for future persistence.
- Updated router to include new handlers and ensure proper routing for the added endpoints.
This commit is contained in:
qaz741wsd856 2025-12-28 18:35:19 +00:00
parent 5694d87a45
commit dfa82a0918
5 changed files with 132 additions and 2 deletions

58
src/handlers/meta.rs Normal file
View file

@ -0,0 +1,58 @@
use axum::{extract::State, Json};
use chrono::{SecondsFormat, Utc};
use serde::Deserialize;
use serde_json::{json, Value};
use std::sync::Arc;
use worker::Env;
use crate::{db, error::AppError};
/// GET /api/now
///
/// Mirrors vaultwarden's `/api/now`: returns current UTC timestamp as an RFC3339 string.
#[worker::send]
pub async fn now() -> Json<String> {
Json(Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true))
}
/// GET /api/alive
///
/// Simple healthcheck. Vaultwarden uses this to also verify DB connectivity.
#[worker::send]
pub async fn alive(
State(env): State<Arc<Env>>,
) -> Result<Json<String>, AppError> {
// Verify D1 binding is present + basic query works.
let db = db::get_db(&env)?;
db.prepare("SELECT 1 as ok")
.first::<i32>(Some("ok"))
.await
.map_err(|_| AppError::Database)?;
Ok(now().await)
}
/// GET /api/version
///
/// Returns a Bitwarden-server-like version string. Clients sometimes call this endpoint.
#[worker::send]
pub async fn version() -> Json<&'static str> {
// Keep this in sync with `src/handlers/config.rs`'s `version`.
Json("2025.12.0")
}
#[derive(Debug, Deserialize)]
pub struct HibpBreachQuery {
#[allow(dead_code)] // stub endpoint doesn't use it yet
pub username: String,
}
/// GET /api/hibp/breach?username=...
///
/// Vaultwarden can proxy HaveIBeenPwned if configured. This minimal server doesn't.
/// We return an empty array to indicate "no breach data" without surfacing an error in clients.
#[worker::send]
pub async fn hibp_breach(_query: axum::extract::Query<HibpBreachQuery>) -> Json<Value> {
Json(json!([]))
}

View file

@ -7,7 +7,9 @@ pub mod emergency_access;
pub mod folders;
pub mod identity;
pub mod import;
pub mod meta;
pub mod purge;
pub mod settings;
pub mod sync;
pub mod twofactor;
pub mod webauth;

61
src/handlers/settings.rs Normal file
View file

@ -0,0 +1,61 @@
use axum::Json;
use serde::Deserialize;
use serde_json::{json, Value};
use crate::{auth::Claims, error::AppError};
/// GET /api/settings/domains
///
/// Equivalent domains (eq_domains) are used by clients to treat some domains as interchangeable
/// for URI matching (e.g. `google.com` vs `youtube.com` in predefined "global" groups).
///
/// Vaultwarden persists per-user:
/// - `equivalentDomains`: custom groups set by the user
/// - `excludedGlobalEquivalentDomains`: which predefined groups are disabled
///
/// This minimal server currently does not persist these settings. We return empty data to
/// prevent 404s. In the future, we can implement storage by adding two `users` columns:
/// - `equivalent_domains` (TEXT JSON, default "[]")
/// - `excluded_globals` (TEXT JSON, default "[]")
/// and (optionally) embedding/updating the global domains dataset.
#[worker::send]
pub async fn get_domains(_claims: Claims) -> Result<Json<Value>, AppError> {
Ok(Json(json!({
"equivalentDomains": [],
"globalEquivalentDomains": [],
"object": "domains"
})))
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EquivDomainData {
#[allow(dead_code)] // stub endpoint doesn't persist these yet
pub excluded_global_equivalent_domains: Option<Vec<i32>>,
#[allow(dead_code)] // stub endpoint doesn't persist these yet
pub equivalent_domains: Option<Vec<Vec<String>>>,
}
/// POST /api/settings/domains
///
/// Stub: accept payload but do not persist yet.
#[worker::send]
pub async fn post_domains(
_claims: Claims,
_payload: Json<EquivDomainData>,
) -> Result<Json<Value>, AppError> {
Ok(Json(json!({})))
}
/// PUT /api/settings/domains
///
/// Stub: behaves like POST.
#[worker::send]
pub async fn put_domains(
claims: Claims,
payload: Json<EquivDomainData>,
) -> Result<Json<Value>, AppError> {
post_domains(claims, payload).await
}

View file

@ -92,7 +92,7 @@ pub async fn get_sync_data(
.map_err(|_| AppError::Internal)?;
let response = format!(
r#"{{"profile":{},"folders":{},"collections":[],"policies":[],"ciphers":{},"domains":null,"sends":[],"userDecryption":{},"object":"sync"}}"#,
r#"{{"profile":{},"folders":{},"collections":[],"policies":[],"ciphers":{},"domains":{{"equivalentDomains":[],"globalEquivalentDomains":[],"object":"domains"}},"sends":[],"userDecryption":{},"object":"sync"}}"#,
profile_json, folders_json, ciphers_json, user_decryption_json
);

View file

@ -7,7 +7,7 @@ use worker::Env;
use crate::handlers::{
accounts, attachments, ciphers, config, devices, emergency_access, folders, identity, import,
sync, twofactor, webauth,
meta, settings, sync, twofactor, webauth,
};
pub fn api_router(env: Env) -> Router {
@ -138,6 +138,15 @@ pub fn api_router(env: Env) -> Router {
.route("/api/folders/{id}", delete(folders::delete_folder))
.route("/api/folders/{id}/delete", post(folders::delete_folder))
.route("/api/config", get(config::config))
// Meta endpoints (mirrors a subset of vaultwarden core/mod.rs)
.route("/api/alive", get(meta::alive))
.route("/api/now", get(meta::now))
.route("/api/version", get(meta::version))
.route("/api/hibp/breach", get(meta::hibp_breach))
// Settings (stubbed)
.route("/api/settings/domains", get(settings::get_domains))
.route("/api/settings/domains", post(settings::post_domains))
.route("/api/settings/domains", put(settings::put_domains))
// Emergency access (stub - returns empty lists, feature not supported)
.route(
"/api/emergency-access/trusted",