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 owner: 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            owner: stdin_info
46                .get("path")
47                .ok_or(GitError::MissingInfo(String::from("path")))?
48                .split_once("/")
49                .map(|s| s.0)
50                .ok_or(GitError::InvalidInfo(String::from(
51                    "Provided path not complete",
52                )))?
53                .to_string(),
54        })
55    }
56}
57
58#[derive(Clone)]
59pub struct Login {
60    pub username: String,
61    pub host: String,
62    pub email: Option<String>,
63}
64
65impl Login {
66    pub fn new(username: String, host: String, email: Option<String>) -> Self {
67        Self {
68            username,
69            host,
70            email,
71        }
72    }
73
74    fn entry(&self) -> Entry {
75        let identifier = format!("{}@{}", self.username, self.host);
76        match Entry::new("git-auth", &identifier) {
77            Ok(entry) => entry,
78            Err(keyring::error::Error::TooLong(_, max)) => {
79                Entry::new("git-auth", identifier.split_at(max as usize).0)
80                    .expect("Expected entry creation success after handling length")
81            }
82            Err(err) => panic!("Unrecoverable error in login creation:\n{err}"),
83        }
84    }
85
86    pub fn get_password(&self) -> keyring::Result<String> {
87        self.entry().get_password()
88    }
89    pub fn set_password(&self, password: &str) -> keyring::Result<()> {
90        self.entry().set_password(password)
91    }
92    pub fn delete_password(&self) -> keyring::Result<()> {
93        self.entry().delete_credential()
94    }
95}
96
97impl Display for Login {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        match &self.email {
100            Some(email) => write!(f, "{}, ({})", self.username, email),
101            None => write!(f, "{}", self.username),
102        }
103    }
104}
105
106pub fn send_creds(creds: &Login) -> keyring::Result<()> {
107    println!("username={}", creds.username);
108    println!("password={}", creds.get_password()?);
109    println!("quit=1");
110    Ok(())
111}