1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
use tokio;
use tokio::sync::Mutex;
use std::{
sync::Arc,
ops::DerefMut,
time::SystemTime,
fs,
process,
};
use sqlx::{
ConnectOptions,
SqliteConnection,
Row,
};
use sqlx::sqlite::{
SqliteConnectOptions,
SqliteJournalMode,
};
use rand::RngCore;
use argon2::password_hash::rand_core::OsRng;
use crate::constants::*;
use crate::util::base64_encode;
async fn initialise_schema(conn: &mut SqliteConnection) {
match sqlx::query(&fs::read_to_string("schema.sql").unwrap()).execute(conn).await {
Ok(_) => {},
Err(err) => {
eprintln!("Error: Could not initialise database schema: {err}");
process::exit(1);
},
}
}
pub async fn open() -> SqliteConnection {
let opts = SqliteConnectOptions::new()
.filename(DB_FILE_NAME)
.create_if_missing(true)
.journal_mode(SqliteJournalMode::Wal)
.pragma("foreign_keys", "on");
let mut conn = match opts.connect().await {
Ok(conn) => conn,
Err(err) => {
eprintln!("Error: Could not open database file '{DB_FILE_NAME}': {err}");
process::exit(1);
}
};
match sqlx::query("select version from meta").fetch_one(&mut conn).await {
Ok(row) => {
let ver: i64 = row.get(0);
if ver == DB_SCHEMA_VERSION {
conn
} else {
eprintln!("Error: Database schema version {ver} but application schema version {DB_SCHEMA_VERSION}");
process::exit(1);
}
},
Err(err) => {
eprintln!("Error: Failed querying database meta version: {err}");
eprintln!("Initialising schema.");
initialise_schema(&mut conn).await;
conn
},
}
}
pub type DB = Arc<Mutex<SqliteConnection>>;
pub async fn register_account(db: DB, username: &str, passhash: &str) -> Result<(), String> {
let mut conn = db.lock().await;
match sqlx::query("insert into Users (username, passhash) values ($1, $2)")
.bind(username)
.bind(passhash)
.execute(conn.deref_mut())
.await {
Ok(_) => Ok(()),
Err(_) => Err("User already exists".to_string()),
}
}
pub async fn get_passhash(db: DB, username: &str) -> Result<String, ()> {
let mut conn = db.lock().await;
match sqlx::query("select passhash from Users where username = $1")
.bind(username)
.fetch_optional(conn.deref_mut())
.await {
Ok(Some(row)) => Ok(row.get(0)),
Ok(None) => Err(()),
Err(err) => {
eprintln!("db_get_passhash: sqlx error: {err}");
Err(())
}
}
}
fn generate_login_token() -> String {
let mut bytes = [0u8; 32];
OsRng.fill_bytes(&mut bytes);
base64_encode(&bytes)
}
pub async fn create_login_token(db: DB, username: &str) -> Result<String, ()> {
let mut conn = db.lock().await;
let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
let token = generate_login_token();
match sqlx::query("insert into Logins (user, token, expire) values ($1, $2, $3)")
.bind(username)
.bind(&token)
.bind(now as i64 + LOGIN_TOKEN_EXPIRE_SECS)
.execute(conn.deref_mut()).await {
Ok(_) => Ok(token),
Err(_) => Err(()),
}
}
|