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            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}