//! Per-tenant API tokens used by `compliance-mcp` to authenticate MCP //! HTTP requests on behalf of LLM clients (Claude Desktop, Cursor, //! ChatGPT, etc.) that can't run a Keycloak OIDC flow. //! //! Tokens are opaque strings of the form `mcpt_<44 url-safe random //! chars>`. The raw value is shown to the user exactly once at //! creation; the database only ever sees the SHA-256 hash. Lookups go //! through the cross-tenant `__admin.mcp_tokens` collection //! and return the `tenant_id` the MCP server should route to. use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; /// Persisted token metadata. `token_hash` is the SHA-256 hex of the /// raw token; the raw token itself is never stored. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct McpToken { #[serde(rename = "_id", skip_serializing_if = "Option::is_none")] pub id: Option, /// SHA-256 hex of the raw token. Unique index in the collection. pub token_hash: String, /// First 8 chars of the raw token — purely for UI display so users /// can identify which token is which without re-issuing. pub token_prefix: String, /// Routes to `_` on MCP requests. pub tenant_id: String, /// User-given label, e.g. "Claude Desktop" or "Sharang's laptop". pub name: String, /// Keycloak `sub` of the user who created this token, for audit. pub created_by: String, #[serde(with = "super::serde_helpers::bson_datetime")] pub created_at: DateTime, #[serde(default, with = "super::serde_helpers::opt_bson_datetime")] pub last_used_at: Option>, /// Soft-delete flag. A revoked token doc stays around for audit /// but never authenticates. #[serde(default)] pub revoked: bool, } /// Public projection of a token — never includes the hash. /// Returned by `GET /api/v1/mcp-tokens`. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct McpTokenView { pub id: String, pub name: String, /// `mcpt_xxxx…` so the user can identify which row is which. pub token_prefix: String, pub created_by: String, #[serde(with = "super::serde_helpers::bson_datetime")] pub created_at: DateTime, #[serde(default, with = "super::serde_helpers::opt_bson_datetime")] pub last_used_at: Option>, pub revoked: bool, } impl From<&McpToken> for McpTokenView { fn from(t: &McpToken) -> Self { Self { id: t.id.map(|o| o.to_hex()).unwrap_or_default(), name: t.name.clone(), token_prefix: t.token_prefix.clone(), created_by: t.created_by.clone(), created_at: t.created_at, last_used_at: t.last_used_at, revoked: t.revoked, } } }