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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
use crate::cache;
use crate::db::DbExt;
use crate::models::UserFlags;
#[async_trait::async_trait]
pub trait AuthDbExt<'t>: DbExt<'t> {
/// Fetches the password hash for the given user ID and verifies it against the given password.
///
/// # Note
/// This assumes that the user is not a bot account and has a password that is not NULL.
///
/// # Errors
/// * If an error occurs with fetching the user.
/// * If the user is not found.
#[cfg(feature = "auth")]
async fn verify_password(&self, user_id: u64, password: String) -> crate::Result<bool> {
let hashed: String = sqlx::query!(
r#"SELECT password AS "password!" FROM users WHERE id = $1"#,
user_id as i64,
)
.fetch_one(self.executor())
.await?
.password;
Ok(crate::auth::verify_password(password, hashed).await?)
}
/// Fetches a user token from the database with the given user ID.
///
/// # Errors
/// * If an error occurs with fetching the user token. If the user token is not found,
/// `Ok(None)` is returned.
async fn fetch_token(&self, user_id: u64) -> sqlx::Result<Option<String>> {
sqlx::query!(
"SELECT token FROM tokens WHERE user_id = $1",
user_id as i64
)
.fetch_optional(self.executor())
.await
.map(|r| r.map(|r| r.token))
}
/// Resolves a user ID and their user flags from a token. Returns `(user_id, flags)`.
///
/// # Errors
/// * If an error occurs with fetching the user token. If the user token is not found,
/// `Ok(None)` is returned.
async fn fetch_user_info_by_token(
&self,
token: impl AsRef<str> + Send + Sync,
) -> crate::Result<Option<(u64, UserFlags)>> {
if let Some(cached) = cache::user_info_for_token(token.as_ref()).await? {
return Ok(Some(cached));
}
if let Some(out @ (user_id, flags)) = sqlx::query!(
"SELECT id, flags FROM users WHERE id = (SELECT user_id FROM tokens WHERE token = $1)",
token.as_ref(),
)
.fetch_optional(self.executor())
.await?
.map(|r| (r.id as u64, UserFlags::from_bits_truncate(r.flags as u32)))
{
let token = token.as_ref();
cache::cache_token(token, user_id, flags).await?;
Ok(Some(out))
} else {
Ok(None)
}
}
/// Registers a new token for the given user ID.
///
/// # Note
/// This method uses transactions, on the event of an ``Err`` the transaction must be properly
/// rolled back, and the transaction must be committed to save the changes.
///
/// # Errors
/// * If an error occurs with creating the token.
async fn register_token(
&mut self,
user_id: u64,
token: impl AsRef<str> + Send,
) -> sqlx::Result<()> {
sqlx::query!(
"INSERT INTO tokens (user_id, token) VALUES ($1, $2)",
user_id as i64,
token.as_ref(),
)
.execute(self.transaction())
.await
.map(|_| ())
}
/// Deletes all stale push notification registration keys using a SQL function.
///
/// # Note
/// This method uses transactions, on the event of an ``Err`` the transaction must be properly
/// rolled back, and the transaction must be committed to save the changes.
///
/// # Errors
/// * If an error occurs with deleting the keys.
async fn delete_stale_push_keys(&mut self) -> sqlx::Result<()> {
sqlx::query!("SELECT delete_stale_keys()::TEXT")
.fetch_one(self.transaction())
.await
.map(|_| ())
}
/// Deletes all tokens associated with the given user ID.
///
/// # Note
/// This method uses transactions, on the event of an ``Err`` the transaction must be properly
/// rolled back, and the transaction must be committed to save the changes.
///
/// # Errors
/// * If an error occurs with deleting the tokens.
async fn delete_all_tokens(&mut self, user_id: u64) -> crate::Result<()> {
sqlx::query!("DELETE FROM tokens WHERE user_id = $1", user_id as i64)
.execute(self.transaction())
.await?;
cache::invalidate_tokens_for(user_id).await?;
Ok(())
}
/// Fetches all push notification registration keys associated with the given user ID.
///
/// # Errors
/// * If an error occurs with fetching the keys.
/// * If the user is not found.
/// * If the user is a bot account.
async fn fetch_push_keys(&self, user_id: u64) -> crate::Result<Vec<String>> {
let rows = sqlx::query!(
"SELECT registration_key AS key FROM push_registration_keys WHERE user_id = $1",
user_id as i64,
)
.fetch_all(self.executor())
.await?;
Ok(rows.into_iter().map(|r| r.key).collect())
}
/// Fetches the ID of the user associated with the push notification given registration key.
///
/// # Errors
/// * If an error occurs with fetching the user. If the user is not found, `Ok(None)` is
/// returned.
async fn fetch_user_id_by_push_key(
&self,
key: impl AsRef<str> + Send,
) -> crate::Result<Option<u64>> {
let user_id = sqlx::query!(
"SELECT user_id FROM push_registration_keys WHERE registration_key = $1",
key.as_ref(),
)
.fetch_optional(self.executor())
.await?
.map(|r| r.user_id as u64);
Ok(user_id)
}
/// Inserts a new push notification registration key for the given user ID.
///
/// # Note
/// This method uses transactions, on the event of an ``Err`` the transaction must be properly
/// rolled back, and the transaction must be committed to save the changes.
///
/// # Errors
/// * If an error occurs with inserting the key.
/// * If the user is a bot account.
async fn insert_push_key(
&mut self,
user_id: u64,
key: impl AsRef<str> + Send,
) -> crate::Result<()> {
sqlx::query!(
r"INSERT INTO push_registration_keys (user_id, registration_key) VALUES ($1, $2)
ON CONFLICT (registration_key) DO UPDATE SET created_at = CURRENT_TIMESTAMP",
user_id as i64,
key.as_ref(),
)
.execute(self.transaction())
.await?;
Ok(())
}
/// Deletes all push notification registration keys associated with the given user ID.
///
/// # Note
/// This method uses transactions, on the event of an ``Err`` the transaction must be properly
/// rolled back, and the transaction must be committed to save the changes.
///
/// # Errors
/// * If an error occurs with deleting the keys.
/// * If the user is a bot account.
async fn delete_push_keys(&mut self, user_id: u64) -> crate::Result<()> {
sqlx::query!(
"DELETE FROM push_registration_keys WHERE user_id = $1",
user_id as i64,
)
.execute(self.transaction())
.await?;
Ok(())
}
/// Deletes a single push notification registration key.
///
/// # Note
/// This method uses transactions, on the event of an ``Err`` the transaction must be properly
/// rolled back, and the transaction must be committed to save the changes.
///
/// # Errors
/// * If an error occurs with deleting the key.
async fn delete_push_key(&mut self, key: impl AsRef<str> + Send) -> crate::Result<()> {
sqlx::query!(
"DELETE FROM push_registration_keys WHERE registration_key = $1",
key.as_ref(),
)
.execute(self.transaction())
.await?;
Ok(())
}
}
impl<'t, T> AuthDbExt<'t> for T where T: DbExt<'t> {}