Skip to main content

sbol_db_sqlite/repo/
tokens.rs

1//! The API-token store over SQLite.
2//!
3//! Tokens are persisted only as their hash (the application layer hashes the
4//! plaintext), so a leaked row cannot be replayed.
5
6use async_trait::async_trait;
7use chrono::Utc;
8use sbol_db_core::{DomainError, UserId};
9use sbol_db_storage::TokenStore;
10use sqlx::Row;
11
12use crate::pool::db_err;
13use crate::SqlitePool;
14
15#[derive(Clone)]
16pub struct SqliteTokenStore {
17    pool: SqlitePool,
18}
19
20impl SqliteTokenStore {
21    pub fn new(pool: SqlitePool) -> Self {
22        Self { pool }
23    }
24}
25
26#[async_trait]
27impl TokenStore for SqliteTokenStore {
28    async fn issue(&self, token_hash: &str, user_id: UserId) -> Result<(), DomainError> {
29        sqlx::query("INSERT INTO sbh_api_token (token_hash, user_id, created_at) VALUES (?, ?, ?)")
30            .bind(token_hash)
31            .bind(user_id.as_uuid().to_string())
32            .bind(Utc::now())
33            .execute(&self.pool)
34            .await
35            .map_err(db_err)?;
36        Ok(())
37    }
38
39    async fn resolve(&self, token_hash: &str) -> Result<Option<UserId>, DomainError> {
40        let row = sqlx::query("SELECT user_id FROM sbh_api_token WHERE token_hash = ?")
41            .bind(token_hash)
42            .fetch_optional(&self.pool)
43            .await
44            .map_err(db_err)?;
45        match row {
46            Some(r) => {
47                let user_id: String = r.try_get("user_id").map_err(db_err)?;
48                Ok(Some(UserId(
49                    uuid::Uuid::parse_str(&user_id).map_err(db_err)?,
50                )))
51            }
52            None => Ok(None),
53        }
54    }
55
56    async fn revoke(&self, token_hash: &str) -> Result<bool, DomainError> {
57        let res = sqlx::query("DELETE FROM sbh_api_token WHERE token_hash = ?")
58            .bind(token_hash)
59            .execute(&self.pool)
60            .await
61            .map_err(db_err)?;
62        Ok(res.rows_affected() == 1)
63    }
64}