1use crate::{Login, Request, error::DatabaseError};
2use rusqlite::{Connection, params};
3use std::{env, fs};
4
5pub fn open() -> Result<Connection, DatabaseError> {
6 let path = env::home_dir()
7 .ok_or(DatabaseError::Path)?
8 .join(".local/share/git-auth");
9
10 if !path.exists() {
11 fs::create_dir_all(&path)?;
12 }
13
14 let conn = Connection::open(path.join("creds.db"))?;
15
16 conn.execute("PRAGMA foreign_keys = ON", ())?;
17 conn.execute(
18 "CREATE TABLE IF NOT EXISTS logins (
19 id INTEGER PRIMARY KEY AUTOINCREMENT,
20 username TEXT NOT NULL,
21 email TEXT,
22 host TEXT NOT NULL
23 )
24 ",
25 (),
26 )?;
27
28 conn.execute(
29 "CREATE TABLE IF NOT EXISTS requests (
30 id INTEGER PRIMARY KEY AUTOINCREMENT,
31 protocol TEXT NOT NULL,
32 host TEXT NOT NULL,
33 path TEXT,
34 valid BOOLEAN NOT NULL DEFAULT 0,
35 user_id INTEGER,
36 FOREIGN KEY (user_id) REFERENCES logins (id)
37 )
38 ",
39 (),
40 )?;
41
42 Ok(conn)
43}
44
45pub fn add_login(conn: &Connection, login: &Login) -> rusqlite::Result<i64> {
46 conn.execute(
47 "INSERT INTO logins (username, email, host) VALUES (?1, ?2, ?3)",
48 params![login.username, login.email, login.host],
49 )?;
50 Ok(conn.last_insert_rowid())
51}
52
53pub fn validate_request(conn: &Connection, request: &Request, valid: bool) -> rusqlite::Result<()> {
54 conn.execute(
55 "
56 UPDATE requests
57 SET valid = ?1
58 WHERE host = ?2
59 AND path = ?3
60 AND protocol = ?4
61 ",
62 params![valid, request.host, request.path, request.protocol],
63 )?;
64 Ok(())
65}
66pub fn add_request(conn: &Connection, request: &Request, user_id: &i64) -> rusqlite::Result<i64> {
67 conn.execute(
68 "INSERT INTO requests (protocol, path, host, user_id) VALUES (?1, ?2, ?3, ?4)",
69 params![request.protocol, request.path, request.host, user_id],
70 )?;
71 Ok(conn.last_insert_rowid())
72}
73
74pub fn fetch_login(conn: &Connection, request: &Request) -> rusqlite::Result<(Login, bool)> {
75 conn.query_row(
76 "
77 SELECT l.username, l.email, r.valid
78 FROM requests r
79 JOIN logins l ON r.user_id = l.id
80 WHERE r.host = ?1
81 AND r.path = ?2
82 AND r.protocol = ?3
83 ",
84 params![request.host, request.path, request.protocol],
85 |row| {
86 Ok((
87 Login::new(
88 row.get("username")?,
89 request.host.clone(),
90 row.get("email")?,
91 ),
92 row.get("valid")?,
93 ))
94 },
95 )
96}
97
98pub fn fetch_available_logins(
99 conn: &Connection,
100 request: &Request,
101) -> rusqlite::Result<Vec<Login>> {
102 let mut stmt = conn.prepare("SELECT username, email FROM logins WHERE host = ?1")?;
103 stmt.query_map(params![request.host], |row| {
104 Ok(Login::new(
105 row.get("username")?,
106 request.host.clone(),
107 row.get("email")?,
108 ))
109 })?
110 .collect()
111}