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 CONSTRAINT username_unique UNIQUE (host, username),
24 CONSTRAINT email_unique UNIQUE (host, email)
25 )
26 ",
27 (),
28 )?;
29
30 conn.execute(
31 "CREATE TABLE IF NOT EXISTS requests (
32 id INTEGER PRIMARY KEY AUTOINCREMENT,
33 protocol TEXT NOT NULL,
34 host TEXT NOT NULL,
35 owner TEXT,
36 valid BOOLEAN NOT NULL DEFAULT 0,
37 user_id INTEGER,
38 FOREIGN KEY (user_id) REFERENCES logins (id)
39 )
40 ",
41 (),
42 )?;
43
44 Ok(conn)
45}
46
47pub fn add_login(conn: &Connection, login: &Login) -> rusqlite::Result<i64> {
48 conn.execute(
49 "INSERT INTO logins (username, email, host) VALUES (?1, ?2, ?3)",
50 params![login.username, login.email, login.host],
51 )?;
52 Ok(conn.last_insert_rowid())
53}
54
55pub fn validate_request(conn: &Connection, request: &Request, valid: bool) -> rusqlite::Result<()> {
56 conn.execute(
57 "
58 UPDATE requests
59 SET valid = ?1
60 WHERE host = ?2
61 AND owner = ?3
62 AND protocol = ?4
63 ",
64 params![valid, request.host, request.owner, request.protocol],
65 )?;
66 Ok(())
67}
68
69pub fn add_request(conn: &Connection, request: &Request, user_id: &i64) -> rusqlite::Result<i64> {
70 conn.execute(
71 "INSERT INTO requests (protocol, owner, host, user_id) VALUES (?1, ?2, ?3, ?4)",
72 params![request.protocol, request.owner, request.host, user_id],
73 )?;
74 Ok(conn.last_insert_rowid())
75}
76
77pub fn fetch_login(conn: &Connection, request: &Request) -> rusqlite::Result<(Login, bool)> {
78 conn.query_row(
79 "
80 SELECT l.username, l.email, r.valid
81 FROM requests r
82 JOIN logins l ON r.user_id = l.id
83 WHERE r.host = ?1
84 AND r.owner = ?2
85 AND r.protocol = ?3
86 ",
87 params![request.host, request.owner, request.protocol],
88 |row| {
89 Ok((
90 Login::new(
91 row.get("username")?,
92 request.host.clone(),
93 row.get("email")?,
94 ),
95 row.get("valid")?,
96 ))
97 },
98 )
99}
100
101pub fn fetch_available_logins(
102 conn: &Connection,
103 request: &Request,
104) -> rusqlite::Result<Vec<Login>> {
105 let mut stmt = conn.prepare("SELECT username, email FROM logins WHERE host = ?1")?;
106 stmt.query_map(params![request.host], |row| {
107 Ok(Login::new(
108 row.get("username")?,
109 request.host.clone(),
110 row.get("email")?,
111 ))
112 })?
113 .collect()
114}
115
116pub fn fetch_all_logins(conn: &Connection) -> rusqlite::Result<Vec<Login>> {
117 let mut stmt = conn.prepare("SELECT username, email, host FROM logins")?;
118 stmt.query_map((), |row| {
119 Ok(Login::new(
120 row.get("username")?,
121 row.get("host")?,
122 row.get("email")?,
123 ))
124 })?
125 .collect()
126}