rust_mcp_sdk/auth/client_auth/
token.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct TokenResponse {
6 pub access_token: String,
7 pub token_type: String,
8 #[serde(default)]
9 pub expires_in: Option<u64>,
10 #[serde(default)]
11 pub refresh_token: Option<String>,
12 #[serde(default)]
13 pub scope: Option<String>,
14}
15
16impl TokenResponse {
17 fn now_secs() -> u64 {
18 std::time::SystemTime::now()
19 .duration_since(std::time::UNIX_EPOCH)
20 .unwrap_or_default()
21 .as_secs()
22 }
23
24 pub fn issued_at_secs(&self) -> u64 {
25 Self::now_secs()
26 }
27
28 pub fn expires_at_secs(&self) -> Option<u64> {
29 self.expires_in
30 .map(|secs| self.issued_at_secs().saturating_add(secs))
31 }
32
33 pub fn is_expired(&self) -> bool {
34 self.expires_at_secs()
35 .map(|exp| exp <= Self::now_secs().saturating_add(30))
36 .unwrap_or(false)
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
45pub enum GrantType {
46 ClientCredentials,
48 AuthorizationCode { code: String, redirect_uri: String },
50 AuthorizationCodePkce {
52 code: String,
53 redirect_uri: String,
54 code_verifier: String,
56 },
57 RefreshToken { refresh_token: String },
59}
60
61impl GrantType {
62 pub fn as_str(&self) -> &'static str {
63 match self {
64 GrantType::ClientCredentials => "client_credentials",
65 GrantType::AuthorizationCode { .. } => "authorization_code",
66 GrantType::AuthorizationCodePkce { .. } => "authorization_code",
67 GrantType::RefreshToken { .. } => "refresh_token",
68 }
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn token_response_deserialize_full() {
78 let json = r#"{
79 "access_token": "abc123",
80 "token_type": "bearer",
81 "expires_in": 3600,
82 "refresh_token": "ref456",
83 "scope": "mcp read"
84 }"#;
85 let token: TokenResponse = serde_json::from_str(json).unwrap();
86 assert_eq!(token.access_token, "abc123");
87 assert_eq!(token.token_type, "bearer");
88 assert_eq!(token.expires_in, Some(3600));
89 assert_eq!(token.refresh_token.as_deref(), Some("ref456"));
90 assert_eq!(token.scope.as_deref(), Some("mcp read"));
91 assert!(!token.is_expired());
92 }
93
94 #[test]
95 fn token_response_deserialize_minimal() {
96 let json = r#"{"access_token": "abc", "token_type": "bearer"}"#;
97 let token: TokenResponse = serde_json::from_str(json).unwrap();
98 assert_eq!(token.access_token, "abc");
99 assert_eq!(token.expires_in, None);
100 assert_eq!(token.refresh_token, None);
101 }
102
103 #[test]
104 fn token_with_very_short_expiry_is_expired() {
105 let json = r#"{"access_token": "x", "token_type": "bearer", "expires_in": 0}"#;
106 let token: TokenResponse = serde_json::from_str(json).unwrap();
107 assert!(token.is_expired());
108 }
109
110 #[test]
111 fn grant_type_as_str() {
112 assert_eq!(GrantType::ClientCredentials.as_str(), "client_credentials");
113 assert_eq!(
114 GrantType::AuthorizationCode {
115 code: "x".into(),
116 redirect_uri: "https://cb".into()
117 }
118 .as_str(),
119 "authorization_code"
120 );
121 assert_eq!(
122 GrantType::AuthorizationCodePkce {
123 code: "x".into(),
124 redirect_uri: "https://cb".into(),
125 code_verifier: "v".into(),
126 }
127 .as_str(),
128 "authorization_code"
129 );
130 assert_eq!(
131 GrantType::RefreshToken {
132 refresh_token: "rt".into()
133 }
134 .as_str(),
135 "refresh_token"
136 );
137 }
138}