Skip to main content

quorum_public/db/queries/
tokens.rs

1//! Functions for managing refresh tokens in the database.
2//! This includes storing, validating, revoking, and deleting refresh tokens.
3
4use quorum_core::db::DB;
5use sha2::{Digest, Sha256};
6use std::error::Error;
7
8/// Stores a new refresh token for a user in the database.
9/// The token is hashed before storage for security purpsoes.
10///
11/// # Arguments
12/// * `db` - A reference to the database connection.
13/// * `user_id` - The ID of the user the token belongs to.
14/// * `refresh_token` - The refresh token to be stored.
15/// * `expires_at` - The expiration time of the refresh token (Unix timestamp).
16///
17/// # Returns
18/// * `Ok(())` if the token was successfully stored.
19/// * `Err` if there was an error during the database operation.
20///
21/// # Exmaple
22/// ```rust
23/// use crate::db::DB;
24/// use crate::db::queries::tokens::store_refresh_token;
25/// let db = DB::new("localhost:8529", "my_database").await.unwrap();
26/// store_refresh_token(&db, "user123", "some_refresh_token", 1700000000).await.unwrap();
27/// ```
28pub async fn store_refresh_token(
29    db: &DB,
30    user_id: &str,
31    refresh_token: &str,
32    expires_at: i64,
33) -> Result<(), Box<dyn Error + Send + Sync>> {
34    let token_hash = hash_token(refresh_token);
35
36    let query = "CREATE refresh_token
37         SET user_id = $user_id,
38             token_hash = $token_hash,
39             expires_at = $expires_at,
40             is_revoked = false";
41
42    db.query(query)
43        .bind(("user_id", format!("users:{}", user_id)))
44        .bind(("token_hash", token_hash))
45        .bind(("expires_at", expires_at))
46        .await?;
47
48    Ok(())
49}
50
51/// Revokes a refresh token by setting its `is_revoked` flag to true in the database.
52///
53/// # Arguments
54/// * `db` - A reference to the database connection.
55/// * `refresh_token` - The refresh token to be revoked.
56///
57/// # Returns
58/// * `Ok(())` if the token was successfully revoked.
59/// * `Err` if there was an error during the database operation.
60///
61/// # Example
62/// ```rust
63/// use crate::db::DB;
64/// use crate::db::queries::tokens::revoke_refresh_token;
65/// let db = DB::new("localhost:8529", "my_database").await.unwrap();
66/// revoke_refresh_token(&db, "some_refresh_token").await.unwrap();
67/// ```
68pub async fn revoke_refresh_token(
69    db: &DB,
70    refresh_token: &str,
71) -> Result<(), Box<dyn Error + Send + Sync>> {
72    let token_hash = hash_token(refresh_token);
73    let query = "UPDATE refresh_token SET is_revoked = true WHERE token_hash = $token_hash";
74
75    db.query(query).bind(("token_hash", token_hash)).await?;
76
77    Ok(())
78}
79
80/// Deletes all refresh tokens associated with a user from the database.
81///
82/// # Arguments
83/// * `db` - A reference to the database connection.
84/// * `user_id` - The ID of the user whose tokens should be deleted.
85///
86/// # Returns
87/// * `Ok(())` if the tokens were successfully deleted.
88/// * `Err` if there was an error during the database operation.
89///
90/// # Example
91/// ```rust
92/// use crate::db::DB;
93/// use crate::db::queries::tokens::delete_all_user_tokens;
94/// let db = DB::new("localhost:8529", "my_database").await.unwrap();
95/// delete_all_user_tokens(&db, "user123").await.unwrap();
96/// ```
97pub async fn delete_all_user_tokens(
98    db: &DB,
99    user_id: &str,
100) -> Result<(), Box<dyn Error + Send + Sync>> {
101    let query = "DELETE FROM refresh_token WHERE user_id = $user_id";
102
103    db.query(query)
104        .bind(("user_id", format!("users:{}", user_id)))
105        .await?;
106
107    Ok(())
108}
109
110/// Hashes a refresh token using SHA-256 for secure storage in the database.
111///
112/// # Arguments
113/// * `token` - The refresh token to be hashed.
114///
115/// # Returns
116/// * A `String` representing the SHA-256 hash of the token.
117///
118/// # Example
119/// ```rust
120/// let token = "some_refresh_token";
121/// let hashed_token = hash_token(token);
122/// ```
123fn hash_token(token: &str) -> String {
124    let mut hasher = Sha256::new();
125    hasher.update(token.as_bytes());
126    hex::encode(hasher.finalize())
127}
128
129pub async fn validate_refresh_token(
130    db: &DB,
131    user_id: &str,
132    refresh_token: &str,
133) -> Result<(), Box<dyn Error + Send + Sync>> {
134    let token_hash = hash_token(refresh_token);
135
136    let query = "SELECT VALUE count() > 0 FROM refresh_token
137         WHERE user_id = $user_id
138         AND token_hash = $token_hash
139         AND is_revoked = false LIMIT 1";
140
141    let mut response = db
142        .query(query)
143        .bind(("user_id", format!("users:{}", user_id)))
144        .bind(("token_hash", token_hash))
145        .await?;
146
147    let is_valid = response.take::<Option<bool>>(0)?.unwrap_or(false);
148
149    if is_valid {
150        Ok(())
151    } else {
152        Err("Refresh token not found or revoked".into())
153    }
154}