git_auth/
db.rs

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}