2024-02-11 16:48:03 +01:00
|
|
|
use std::{fmt::Debug, str::FromStr};
|
2024-02-10 13:31:23 +01:00
|
|
|
|
2024-02-11 16:48:03 +01:00
|
|
|
use axum::async_trait;
|
2024-02-10 13:07:36 +01:00
|
|
|
use secrecy::ExposeSecret;
|
2024-02-10 13:31:23 +01:00
|
|
|
use sqlx::{query, query_as, sqlite::SqliteConnectOptions, Connection, SqliteConnection};
|
2024-02-10 13:51:53 +01:00
|
|
|
use tracing::instrument;
|
2024-02-10 12:06:53 +01:00
|
|
|
|
2024-02-10 12:32:01 +01:00
|
|
|
use crate::secrets::UserToken;
|
2024-02-10 12:06:53 +01:00
|
|
|
|
2024-02-10 14:31:07 +01:00
|
|
|
// TODO: check again if it's possible to pass this as a parameter to query!
|
|
|
|
// const TABLE_USER_TOKENS: &str = "user_tokens";
|
2024-02-10 13:07:36 +01:00
|
|
|
|
2024-02-10 14:02:41 +01:00
|
|
|
pub enum UserTokenEntry {
|
2024-02-10 14:31:07 +01:00
|
|
|
Valid(
|
|
|
|
UserToken,
|
|
|
|
time::OffsetDateTime,
|
|
|
|
Option<time::OffsetDateTime>,
|
|
|
|
),
|
|
|
|
Invalid(
|
|
|
|
UserToken,
|
|
|
|
time::OffsetDateTime,
|
|
|
|
Option<time::OffsetDateTime>,
|
|
|
|
),
|
2024-02-10 14:02:41 +01:00
|
|
|
}
|
|
|
|
|
2024-02-11 16:48:03 +01:00
|
|
|
#[async_trait]
|
|
|
|
pub trait Database: Debug {
|
2024-02-10 14:02:41 +01:00
|
|
|
async fn get_token(&mut self, username: &str) -> Result<Option<UserTokenEntry>, sqlx::Error>;
|
2024-02-10 13:07:36 +01:00
|
|
|
|
|
|
|
async fn save_token(&mut self, username: &str, token: &UserToken) -> Result<(), sqlx::Error>;
|
2024-02-11 20:00:05 +01:00
|
|
|
|
|
|
|
async fn update_token_last_used(
|
|
|
|
&mut self,
|
|
|
|
username: &str,
|
|
|
|
token: &UserToken,
|
|
|
|
) -> Result<(), sqlx::Error>;
|
2024-02-10 12:06:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct SqliteDatabase {
|
|
|
|
conn: SqliteConnection,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SqliteDatabase {
|
2024-02-10 13:51:53 +01:00
|
|
|
#[instrument]
|
2024-02-10 14:35:39 +01:00
|
|
|
pub async fn open(connection_string: &str) -> Self {
|
|
|
|
let options = SqliteConnectOptions::from_str(connection_string)
|
2024-02-10 13:31:23 +01:00
|
|
|
.expect("Invalid database URI")
|
|
|
|
.create_if_missing(true);
|
|
|
|
|
2024-02-10 12:06:53 +01:00
|
|
|
let mut db = Self {
|
2024-02-10 13:31:23 +01:00
|
|
|
conn: SqliteConnection::connect_with(&options)
|
2024-02-10 12:06:53 +01:00
|
|
|
.await
|
|
|
|
.expect("Failed to open SQLite database"),
|
|
|
|
};
|
|
|
|
|
|
|
|
db.init().await;
|
|
|
|
|
|
|
|
db
|
|
|
|
}
|
|
|
|
|
2024-02-10 13:51:53 +01:00
|
|
|
#[instrument]
|
2024-02-10 12:06:53 +01:00
|
|
|
pub async fn init(&mut self) {
|
2024-02-10 14:31:07 +01:00
|
|
|
query!(
|
|
|
|
"CREATE TABLE IF NOT EXISTS user_tokens (
|
2024-02-10 13:07:36 +01:00
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
2024-02-10 13:18:08 +01:00
|
|
|
username VARCHAR(255) NOT NULL,
|
|
|
|
token NCHAR(30) NOT NULL,
|
|
|
|
valid BOOLEAN NOT NULL,
|
|
|
|
created DATETIME NOT NULL,
|
|
|
|
last_used DATETIME
|
2024-02-10 13:31:23 +01:00
|
|
|
)"
|
2024-02-10 14:31:07 +01:00
|
|
|
)
|
2024-02-10 12:06:53 +01:00
|
|
|
.execute(&mut self.conn)
|
|
|
|
.await
|
|
|
|
.expect("Failed to initialize table user_tokens");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-11 16:48:03 +01:00
|
|
|
#[async_trait]
|
2024-02-10 12:06:53 +01:00
|
|
|
impl Database for SqliteDatabase {
|
2024-02-10 13:51:53 +01:00
|
|
|
#[instrument]
|
2024-02-10 14:02:41 +01:00
|
|
|
async fn get_token(&mut self, username: &str) -> Result<Option<UserTokenEntry>, sqlx::Error> {
|
2024-02-10 14:31:07 +01:00
|
|
|
struct TokenRow {
|
|
|
|
token: String,
|
|
|
|
valid: bool,
|
|
|
|
created: time::OffsetDateTime,
|
|
|
|
last_used: Option<time::OffsetDateTime>,
|
|
|
|
}
|
|
|
|
let row = query_as!(
|
|
|
|
TokenRow,
|
2024-02-10 14:02:41 +01:00
|
|
|
"SELECT token, valid, created, last_used
|
2024-02-10 12:25:46 +01:00
|
|
|
FROM user_tokens
|
2024-02-10 14:31:07 +01:00
|
|
|
WHERE username = $1
|
2024-02-10 14:02:41 +01:00
|
|
|
ORDER BY created DESC",
|
2024-02-10 14:31:07 +01:00
|
|
|
username
|
2024-02-10 12:25:46 +01:00
|
|
|
)
|
2024-02-10 13:39:10 +01:00
|
|
|
.fetch_optional(&mut self.conn)
|
2024-02-10 12:25:46 +01:00
|
|
|
.await?;
|
|
|
|
|
2024-02-10 14:31:07 +01:00
|
|
|
Ok(row.map(
|
|
|
|
|TokenRow {
|
|
|
|
token,
|
|
|
|
valid,
|
|
|
|
created,
|
|
|
|
last_used,
|
2024-02-11 19:04:53 +01:00
|
|
|
}| match valid {
|
|
|
|
true => UserTokenEntry::Valid(UserToken::from(token), created, last_used),
|
|
|
|
false => UserTokenEntry::Invalid(UserToken::from(token), created, last_used),
|
2024-02-10 14:31:07 +01:00
|
|
|
},
|
|
|
|
))
|
2024-02-10 12:06:53 +01:00
|
|
|
}
|
2024-02-10 13:07:36 +01:00
|
|
|
|
2024-02-10 13:51:53 +01:00
|
|
|
#[instrument]
|
2024-02-10 13:07:36 +01:00
|
|
|
async fn save_token(&mut self, username: &str, token: &UserToken) -> Result<(), sqlx::Error> {
|
2024-02-10 14:31:07 +01:00
|
|
|
let token_inner = token.0.expose_secret();
|
|
|
|
query!(
|
|
|
|
"INSERT INTO user_tokens
|
2024-02-10 13:07:36 +01:00
|
|
|
(username, token, created, valid)
|
|
|
|
VALUES
|
2024-02-10 14:31:07 +01:00
|
|
|
($1, $2, DATETIME('NOW'), TRUE)
|
2024-02-10 13:07:36 +01:00
|
|
|
",
|
2024-02-10 14:31:07 +01:00
|
|
|
username,
|
|
|
|
token_inner
|
|
|
|
)
|
2024-02-10 13:07:36 +01:00
|
|
|
.execute(&mut self.conn)
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2024-02-11 20:00:05 +01:00
|
|
|
|
|
|
|
#[instrument]
|
|
|
|
async fn update_token_last_used(
|
|
|
|
&mut self,
|
|
|
|
username: &str,
|
|
|
|
token: &UserToken,
|
|
|
|
) -> Result<(), sqlx::Error> {
|
|
|
|
let token_inner = token.0.expose_secret();
|
|
|
|
query!(
|
|
|
|
"UPDATE user_tokens
|
|
|
|
SET last_used = DATETIME('NOW')
|
|
|
|
WHERE username = $1 AND token = $2",
|
|
|
|
username,
|
|
|
|
token_inner
|
|
|
|
)
|
|
|
|
.execute(&mut self.conn)
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2024-02-10 12:06:53 +01:00
|
|
|
}
|