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}