Skip to main content

zlayer_types/secrets/
client_keys.rs

1//! Client public key data shapes for sealed-box recipient registration.
2//!
3//! Lifted into `zlayer-types` so cross-crate consumers can name these
4//! without depending on `zlayer-secrets`. The persistent store and the
5//! `ClientKeyStore` trait stay in `zlayer-secrets` and consume these from
6//! here.
7
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10
11use crate::secrets::error::{Result, SecretsError};
12
13/// Required length, in bytes, of an X25519 / Curve25519 public key.
14pub const PUBLIC_KEY_LEN: usize = 32;
15
16/// The kind of actor a registered client key belongs to.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(rename_all = "snake_case")]
19pub enum ActorKind {
20    /// A human user (matches the `users` table in the auth backend).
21    User,
22    /// A programmatic API key (matches the `api_keys` table).
23    ApiKey,
24}
25
26impl ActorKind {
27    /// Database / wire representation of this actor kind.
28    #[must_use]
29    pub fn as_str(&self) -> &'static str {
30        match self {
31            Self::User => "user",
32            Self::ApiKey => "api_key",
33        }
34    }
35
36    /// Parses an [`ActorKind`] from its database / wire string form.
37    ///
38    /// # Errors
39    ///
40    /// Returns [`SecretsError::Storage`] if `s` is not one of the
41    /// recognized values (`"user"` or `"api_key"`).
42    #[allow(clippy::should_implement_trait)]
43    pub fn from_str(s: &str) -> Result<Self> {
44        match s {
45            "user" => Ok(Self::User),
46            "api_key" => Ok(Self::ApiKey),
47            other => Err(SecretsError::Storage(format!(
48                "invalid actor_kind in client_public_keys row: {other:?}"
49            ))),
50        }
51    }
52}
53
54/// A registered client public key bound to an actor.
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct ClientPublicKey {
57    /// Opaque, unique identifier (`ck_<32-hex>`). Surfaced to clients so
58    /// they can reference the key on subsequent requests.
59    pub key_id: String,
60    /// Whether the owning actor is a `User` or an `ApiKey`.
61    pub actor_kind: ActorKind,
62    /// Identifier of the owning actor (UUID/text, opaque to this crate).
63    pub actor_id: String,
64    /// The 32-byte X25519 public key bytes.
65    pub public_key: Vec<u8>,
66    /// Optional human-readable label (e.g. browser/device name).
67    pub label: Option<String>,
68    /// When the key was registered.
69    pub created_at: DateTime<Utc>,
70    /// Most recent successful use (sealed-box decrypt), if any.
71    pub last_used_at: Option<DateTime<Utc>>,
72    /// Soft-delete timestamp; `None` means the key is still active.
73    pub revoked_at: Option<DateTime<Utc>>,
74}