1use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Credentials {
13 pub api_key: String,
15
16 pub user_id: String,
18
19 pub email: String,
21
22 #[serde(default)]
24 pub name: Option<String>,
25
26 pub authenticated_at: DateTime<Utc>,
28
29 pub auth_url: String,
31}
32
33impl Credentials {
34 pub fn is_valid(&self) -> bool {
36 !self.api_key.is_empty() && self.api_key.starts_with("mecha_")
37 }
38
39 pub fn masked_api_key(&self) -> String {
41 if self.api_key.len() > 12 {
42 format!("{}...{}", &self.api_key[..12], &self.api_key[self.api_key.len() - 4..])
43 } else {
44 "***".to_string()
45 }
46 }
47
48 pub fn display_name(&self) -> &str {
50 self.name.as_deref().unwrap_or(&self.email)
51 }
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct DeviceCodeResponse {
59 pub device_code: String,
61
62 pub user_code: String,
64
65 pub verification_uri: String,
67
68 pub expires_in: u32,
70
71 pub interval: u32,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77#[serde(tag = "status", rename_all = "snake_case")]
78pub enum DeviceCodeStatus {
79 Pending,
81
82 Authorized {
84 api_key: String,
85 user_id: String,
86 email: String,
87 name: Option<String>,
88 },
89
90 Denied,
92
93 Expired,
95}
96
97impl DeviceCodeStatus {
98 pub fn is_terminal(&self) -> bool {
100 !matches!(self, DeviceCodeStatus::Pending)
101 }
102
103 pub fn is_authorized(&self) -> bool {
105 matches!(self, DeviceCodeStatus::Authorized { .. })
106 }
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
111#[serde(tag = "error", rename_all = "snake_case")]
112pub enum AuthError {
113 #[error("Network error: {message}")]
115 NetworkError { message: String },
116
117 #[error("Server error ({status_code:?}): {message}")]
119 ServerError { message: String, status_code: Option<u16> },
120
121 #[error("Device code expired")]
123 ExpiredCode,
124
125 #[error("Access denied by user")]
127 AccessDenied,
128
129 #[error("Invalid credentials: {message}")]
131 InvalidCredentials { message: String },
132
133 #[error("Rate limited, retry after {retry_after:?} seconds")]
135 RateLimited { retry_after: Option<u32> },
136}