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}