1mod login;
2mod oauth;
3mod ui;
4
5pub use login::login_flow;
6
7use anyhow::{Context, Result};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::path::PathBuf;
11
12#[derive(Debug, Clone, Serialize, Deserialize, Default)]
13pub struct Credentials {
14 pub providers: HashMap<String, ProviderCredential>,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18#[serde(tag = "type", rename_all = "snake_case")]
19pub enum ProviderCredential {
20 ApiKey {
21 key: String,
22 },
23 OAuth {
24 access_token: String,
25 refresh_token: Option<String>,
26 expires_at: Option<i64>,
27 api_key: Option<String>,
28 },
29}
30
31impl ProviderCredential {
32 pub fn api_key(&self) -> Option<&str> {
33 match self {
34 ProviderCredential::ApiKey { key } => Some(key.as_str()),
35 ProviderCredential::OAuth {
36 api_key: Some(k), ..
37 } => Some(k.as_str()),
38 ProviderCredential::OAuth { access_token, .. } => Some(access_token.as_str()),
39 }
40 }
41}
42
43impl Credentials {
44 fn path() -> PathBuf {
45 crate::config::Config::config_dir().join("credentials.json")
46 }
47
48 pub fn load() -> Result<Self> {
49 let path = Self::path();
50 if path.exists() {
51 let content = std::fs::read_to_string(&path).context("reading credentials file")?;
52 serde_json::from_str(&content).context("parsing credentials file")
53 } else {
54 Ok(Self::default())
55 }
56 }
57
58 pub fn save(&self) -> Result<()> {
59 let path = Self::path();
60 if let Some(parent) = path.parent() {
61 std::fs::create_dir_all(parent)?;
62 }
63 std::fs::write(&path, serde_json::to_string_pretty(self)?)
64 .context("writing credentials file")
65 }
66
67 pub fn get(&self, provider: &str) -> Option<&ProviderCredential> {
68 self.providers.get(provider)
69 }
70
71 pub fn set(&mut self, provider: &str, cred: ProviderCredential) {
72 self.providers.insert(provider.to_string(), cred);
73 }
74}