git_auth/
lib.rs

1use keyring::Entry;
2use std::{
3    collections::HashMap,
4    fmt::Display,
5    io::{self, Read},
6};
7
8pub mod db;
9pub mod error;
10pub mod github;
11
12use error::GitError;
13
14// TODO: Some of the contexts could be simplified to expects
15
16#[allow(dead_code)] // Complains because it isn't used anywhere in lib.rs
17#[derive(Debug)]
18pub struct Request {
19    pub host: String,
20    protocol: String,
21    pub path: Option<String>,
22}
23
24impl Request {
25    pub fn from_stdin() -> Result<Self, GitError> {
26        let mut buf = String::new();
27        let _ = io::stdin().read_to_string(&mut buf)?;
28        let stdin_info: HashMap<String, String> = buf
29            .lines()
30            .filter_map(|l| {
31                l.split_once("=")
32                    .map(|(k, v)| (k.trim().to_string(), v.trim().to_string()))
33            })
34            .collect();
35
36        Ok(Request {
37            protocol: stdin_info
38                .get("protocol")
39                .ok_or(GitError::MissingInfo(String::from("protocol")))?
40                .to_string(),
41            host: stdin_info
42                .get("host")
43                .ok_or(GitError::MissingInfo(String::from("host")))?
44                .to_string(),
45            path: stdin_info.get("path").cloned(),
46        })
47    }
48}
49
50#[derive(Clone)]
51pub struct Login {
52    pub username: String,
53    pub host: String,
54    pub email: Option<String>,
55}
56
57impl Login {
58    pub fn new(username: String, host: String, email: Option<String>) -> Self {
59        Self {
60            username,
61            host,
62            email,
63        }
64    }
65
66    fn entry(&self) -> Entry {
67        let identifier = format!("{}@{}", self.username, self.host);
68        match Entry::new("git-auth", &identifier) {
69            Ok(entry) => entry,
70            Err(keyring::error::Error::TooLong(_, max)) => {
71                Entry::new("git-auth", identifier.split_at(max as usize).0)
72                    .expect("Expected entry creation success after handling length")
73            }
74            Err(err) => panic!("Unrecoverable error in login creation:\n{err}"),
75        }
76    }
77
78    pub fn get_password(&self) -> keyring::Result<String> {
79        self.entry().get_password()
80    }
81    pub fn set_password(&self, password: &str) -> keyring::Result<()> {
82        self.entry().set_password(password)
83    }
84}
85
86impl Display for Login {
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88        match &self.email {
89            Some(email) => write!(f, "{}, ({})", self.username, email),
90            None => write!(f, "{}", self.username),
91        }
92    }
93}
94
95pub fn send_creds(creds: &Login) -> keyring::Result<()> {
96    println!("username={}", creds.username);
97    println!("password={}", creds.get_password()?);
98    println!("quit=1");
99    Ok(())
100}