bitrouter_core/auth/claims.rs
1//! JWT claims types for the BitRouter authentication protocol.
2//!
3//! These types define the payload of a BitRouter JWT. The `iss` claim carries
4//! the signer's CAIP-10 account identifier (e.g.
5//! `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:<base58_pubkey>`), which the
6//! server uses for both signature verification and account resolution.
7
8use serde::{Deserialize, Serialize};
9
10/// JWT claims for BitRouter authentication tokens.
11///
12/// Tokens are self-signed by the account holder's web3 wallet key. The
13/// CAIP-10 address in `iss` is the sole identity — the token has zero
14/// knowledge of the underlying account ID or server-side state.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct BitrouterClaims {
17 /// CAIP-10 account identifier of the signer.
18 ///
19 /// Examples:
20 /// - `"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:DRpb..."`
21 /// - `"eip155:8453:0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"`
22 pub iss: String,
23
24 /// CAIP-2 chain identifier indicating which chain was used to sign.
25 ///
26 /// Examples: `"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"`, `"eip155:8453"`.
27 pub chain: String,
28
29 /// Issued-at UNIX timestamp (seconds since epoch).
30 #[serde(skip_serializing_if = "Option::is_none")]
31 pub iat: Option<u64>,
32
33 /// Expiration UNIX timestamp. Required for admin-scope tokens.
34 /// Long-lived API tokens may omit this, relying on budget exhaustion
35 /// or key rotation for invalidation.
36 #[serde(skip_serializing_if = "Option::is_none")]
37 pub exp: Option<u64>,
38
39 /// Authorization scope granted by this token.
40 pub scope: TokenScope,
41
42 /// Optional allowlist of model name patterns this token may access.
43 /// When `None`, all models configured on the server are accessible.
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub models: Option<Vec<String>>,
46
47 /// Budget limit in micro USD (1 USD = 1,000,000 μUSD).
48 /// Matches on-chain stablecoin precision (6 decimals).
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub budget: Option<u64>,
51
52 /// Whether the budget applies per-session or per-account.
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub budget_scope: Option<BudgetScope>,
55
56 /// The range over which the budget is measured.
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub budget_range: Option<BudgetRange>,
59}
60
61/// Token authorization scope.
62///
63/// - `Admin`: Account management operations (rotate key, manage sessions).
64/// Scoped to the caller's own account — NOT global server admin.
65/// - `Api`: LLM inference endpoints only.
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
67#[serde(rename_all = "lowercase")]
68pub enum TokenScope {
69 Admin,
70 Api,
71}
72
73/// Budget scope — determines what the budget limit applies to.
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
75#[serde(rename_all = "lowercase")]
76pub enum BudgetScope {
77 /// Budget applies independently to each chat session.
78 Session,
79 /// Budget applies to the entire account across all sessions.
80 Account,
81}
82
83/// Budget range — the window over which the budget is measured.
84#[derive(Debug, Clone, Serialize, Deserialize)]
85#[serde(tag = "type", rename_all = "lowercase")]
86pub enum BudgetRange {
87 /// Budget covers the next N conversation rounds.
88 Rounds { count: u32 },
89 /// Budget covers a time period (in seconds).
90 Duration { seconds: u64 },
91}