Administrator privileges to get and delete subscribers
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
use crate::{
|
||||
routes::AdminError, session_state::TypedSession, telemetry::spawn_blocking_with_tracing,
|
||||
};
|
||||
use crate::telemetry::spawn_blocking_with_tracing;
|
||||
use anyhow::Context;
|
||||
use argon2::{
|
||||
Algorithm, Argon2, Params, PasswordHash, PasswordHasher, PasswordVerifier, Version,
|
||||
password_hash::{SaltString, rand_core::OsRng},
|
||||
};
|
||||
use axum::{extract::Request, middleware::Next, response::Response};
|
||||
use secrecy::{ExposeSecret, SecretString};
|
||||
use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
@@ -60,8 +57,9 @@ fn compute_pasword_hash(password: SecretString) -> Result<SecretString, anyhow::
|
||||
pub async fn validate_credentials(
|
||||
Credentials { username, password }: Credentials,
|
||||
connection_pool: &PgPool,
|
||||
) -> Result<Uuid, AuthError> {
|
||||
) -> Result<(Uuid, Role), AuthError> {
|
||||
let mut user_id = None;
|
||||
let mut role = None;
|
||||
let mut expected_password_hash = SecretString::from(
|
||||
"$argon2id$v=19$m=15000,t=2,p=1$\
|
||||
gZiV/M1gPc22ElAH/Jh1Hw$\
|
||||
@@ -69,13 +67,14 @@ CWOrkoo7oJBQ/iyh7uJ0LO2aLEfrHwTWllSAxT0zRno"
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
if let Some((stored_user_id, stored_expected_password_hash)) =
|
||||
if let Some((stored_user_id, stored_expected_password_hash, stored_role)) =
|
||||
get_stored_credentials(&username, connection_pool)
|
||||
.await
|
||||
.context("Failed to retrieve credentials from database.")
|
||||
.map_err(AuthError::UnexpectedError)?
|
||||
{
|
||||
user_id = Some(stored_user_id);
|
||||
role = Some(stored_role);
|
||||
expected_password_hash = stored_expected_password_hash;
|
||||
}
|
||||
|
||||
@@ -86,12 +85,16 @@ CWOrkoo7oJBQ/iyh7uJ0LO2aLEfrHwTWllSAxT0zRno"
|
||||
.ok_or_else(|| anyhow::anyhow!("Unknown username."))
|
||||
.map_err(AuthError::InvalidCredentials)?;
|
||||
|
||||
let role = role
|
||||
.ok_or_else(|| anyhow::anyhow!("Unknown role."))
|
||||
.map_err(AuthError::UnexpectedError)?;
|
||||
|
||||
handle
|
||||
.await
|
||||
.context("Failed to spawn blocking task.")
|
||||
.map_err(AuthError::UnexpectedError)?
|
||||
.map_err(AuthError::InvalidCredentials)
|
||||
.map(|_| uuid)
|
||||
.map(|_| (uuid, role))
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "Verify password", skip_all)]
|
||||
@@ -113,10 +116,10 @@ fn verify_password_hash(
|
||||
async fn get_stored_credentials(
|
||||
username: &str,
|
||||
connection_pool: &PgPool,
|
||||
) -> Result<Option<(Uuid, SecretString)>, sqlx::Error> {
|
||||
) -> Result<Option<(Uuid, SecretString, Role)>, sqlx::Error> {
|
||||
let row = sqlx::query!(
|
||||
r#"
|
||||
SELECT user_id, password_hash
|
||||
SELECT user_id, password_hash, role as "role: Role"
|
||||
FROM users
|
||||
WHERE username = $1
|
||||
"#,
|
||||
@@ -124,37 +127,26 @@ async fn get_stored_credentials(
|
||||
)
|
||||
.fetch_optional(connection_pool)
|
||||
.await?
|
||||
.map(|row| (row.user_id, SecretString::from(row.password_hash)));
|
||||
.map(|row| (row.user_id, SecretString::from(row.password_hash), row.role));
|
||||
Ok(row)
|
||||
}
|
||||
|
||||
pub async fn require_auth(
|
||||
session: TypedSession,
|
||||
mut request: Request,
|
||||
next: Next,
|
||||
) -> Result<Response, AdminError> {
|
||||
let user_id = session
|
||||
.get_user_id()
|
||||
.await
|
||||
.map_err(|e| AdminError::UnexpectedError(e.into()))?
|
||||
.ok_or(AdminError::NotAuthenticated)?;
|
||||
let username = session
|
||||
.get_username()
|
||||
.await
|
||||
.map_err(|e| AdminError::UnexpectedError(e.into()))?
|
||||
.ok_or(AdminError::UnexpectedError(anyhow::anyhow!(
|
||||
"Could not find username in session."
|
||||
)))?;
|
||||
|
||||
request
|
||||
.extensions_mut()
|
||||
.insert(AuthenticatedUser { user_id, username });
|
||||
|
||||
Ok(next.run(request).await)
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq, sqlx::Type)]
|
||||
#[sqlx(type_name = "user_role", rename_all = "lowercase")]
|
||||
pub enum Role {
|
||||
Admin,
|
||||
Writer,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AuthenticatedUser {
|
||||
pub user_id: Uuid,
|
||||
pub username: String,
|
||||
pub role: Role,
|
||||
}
|
||||
|
||||
impl AuthenticatedUser {
|
||||
pub fn is_admin(&self) -> bool {
|
||||
matches!(self.role, Role::Admin)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user