You've already forked AstralRinth
forked from didirus/AstralRinth
* tweak(frontend): do not sign up for the newsletter by default * fix(labrinth): retire Sendy for new email newsletter subscriptions
247 lines
6.9 KiB
Rust
247 lines
6.9 KiB
Rust
use super::AuthProvider;
|
|
use crate::auth::AuthenticationError;
|
|
use crate::database::models::{DBUser, user_item};
|
|
use crate::database::redis::RedisPool;
|
|
use crate::models::pats::Scopes;
|
|
use crate::models::users::User;
|
|
use crate::queue::session::AuthQueue;
|
|
use crate::routes::internal::session::get_session_metadata;
|
|
use actix_web::HttpRequest;
|
|
use actix_web::http::header::{AUTHORIZATION, HeaderValue};
|
|
use chrono::Utc;
|
|
|
|
pub async fn get_maybe_user_from_headers<'a, E>(
|
|
req: &HttpRequest,
|
|
executor: E,
|
|
redis: &RedisPool,
|
|
session_queue: &AuthQueue,
|
|
required_scopes: Scopes,
|
|
) -> Result<Option<(Scopes, User)>, AuthenticationError>
|
|
where
|
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
|
{
|
|
if !req.headers().contains_key(AUTHORIZATION) {
|
|
return Ok(None);
|
|
}
|
|
|
|
// Fetch DB user record and minos user from headers
|
|
let Some((scopes, db_user)) = get_user_record_from_bearer_token(
|
|
req,
|
|
None,
|
|
executor,
|
|
redis,
|
|
session_queue,
|
|
)
|
|
.await?
|
|
else {
|
|
return Ok(None);
|
|
};
|
|
|
|
if !scopes.contains(required_scopes) {
|
|
return Ok(None);
|
|
}
|
|
|
|
Ok(Some((scopes, User::from_full(db_user))))
|
|
}
|
|
|
|
pub async fn get_full_user_from_headers<'a, E>(
|
|
req: &HttpRequest,
|
|
executor: E,
|
|
redis: &RedisPool,
|
|
session_queue: &AuthQueue,
|
|
required_scopes: Scopes,
|
|
) -> Result<(Scopes, DBUser), AuthenticationError>
|
|
where
|
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
|
{
|
|
let (scopes, db_user) = get_user_record_from_bearer_token(
|
|
req,
|
|
None,
|
|
executor,
|
|
redis,
|
|
session_queue,
|
|
)
|
|
.await?
|
|
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
|
|
|
if !scopes.contains(required_scopes) {
|
|
return Err(AuthenticationError::InvalidCredentials);
|
|
}
|
|
|
|
Ok((scopes, db_user))
|
|
}
|
|
|
|
pub async fn get_user_from_headers<'a, E>(
|
|
req: &HttpRequest,
|
|
executor: E,
|
|
redis: &RedisPool,
|
|
session_queue: &AuthQueue,
|
|
required_scopes: Scopes,
|
|
) -> Result<(Scopes, User), AuthenticationError>
|
|
where
|
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
|
{
|
|
let (scopes, db_user) = get_full_user_from_headers(
|
|
req,
|
|
executor,
|
|
redis,
|
|
session_queue,
|
|
required_scopes,
|
|
)
|
|
.await?;
|
|
|
|
Ok((scopes, User::from_full(db_user)))
|
|
}
|
|
|
|
pub async fn get_user_record_from_bearer_token<'a, 'b, E>(
|
|
req: &HttpRequest,
|
|
token: Option<&str>,
|
|
executor: E,
|
|
redis: &RedisPool,
|
|
session_queue: &AuthQueue,
|
|
) -> Result<Option<(Scopes, user_item::DBUser)>, AuthenticationError>
|
|
where
|
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
|
{
|
|
let token = if let Some(token) = token {
|
|
token
|
|
} else {
|
|
extract_authorization_header(req)?
|
|
};
|
|
|
|
let possible_user = match token.split_once('_') {
|
|
Some(("mrp", _)) => {
|
|
let pat =
|
|
crate::database::models::pat_item::DBPersonalAccessToken::get(
|
|
token, executor, redis,
|
|
)
|
|
.await?
|
|
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
|
|
|
if pat.expires < Utc::now() {
|
|
return Err(AuthenticationError::InvalidCredentials);
|
|
}
|
|
|
|
let user =
|
|
user_item::DBUser::get_id(pat.user_id, executor, redis).await?;
|
|
|
|
session_queue.add_pat(pat.id).await;
|
|
|
|
user.map(|x| (pat.scopes, x))
|
|
}
|
|
Some(("mra", _)) => {
|
|
let session =
|
|
crate::database::models::session_item::DBSession::get(
|
|
token, executor, redis,
|
|
)
|
|
.await?
|
|
.ok_or_else(|| AuthenticationError::InvalidCredentials)?;
|
|
|
|
if session.expires < Utc::now() {
|
|
return Err(AuthenticationError::InvalidCredentials);
|
|
}
|
|
|
|
let user =
|
|
user_item::DBUser::get_id(session.user_id, executor, redis)
|
|
.await?;
|
|
|
|
let rate_limit_ignore = dotenvy::var("RATE_LIMIT_IGNORE_KEY")?;
|
|
if req
|
|
.headers()
|
|
.get("x-ratelimit-key")
|
|
.and_then(|x| x.to_str().ok())
|
|
.is_none_or(|x| x != rate_limit_ignore)
|
|
{
|
|
let metadata = get_session_metadata(req).await?;
|
|
session_queue.add_session(session.id, metadata).await;
|
|
}
|
|
|
|
user.map(|x| (Scopes::all(), x))
|
|
}
|
|
Some(("mro", _)) => {
|
|
use crate::database::models::oauth_token_item::DBOAuthAccessToken;
|
|
|
|
let hash = DBOAuthAccessToken::hash_token(token);
|
|
let access_token =
|
|
crate::database::models::oauth_token_item::DBOAuthAccessToken::get(hash, executor)
|
|
.await?
|
|
.ok_or(AuthenticationError::InvalidCredentials)?;
|
|
|
|
if access_token.expires < Utc::now() {
|
|
return Err(AuthenticationError::InvalidCredentials);
|
|
}
|
|
|
|
let user = user_item::DBUser::get_id(
|
|
access_token.user_id,
|
|
executor,
|
|
redis,
|
|
)
|
|
.await?;
|
|
|
|
session_queue.add_oauth_access_token(access_token.id).await;
|
|
|
|
user.map(|u| (access_token.scopes, u))
|
|
}
|
|
Some(("github" | "gho" | "ghp", _)) => {
|
|
let user = AuthProvider::GitHub.get_user(token).await?;
|
|
let id =
|
|
AuthProvider::GitHub.get_user_id(&user.id, executor).await?;
|
|
|
|
let user = user_item::DBUser::get_id(
|
|
id.ok_or_else(|| AuthenticationError::InvalidCredentials)?,
|
|
executor,
|
|
redis,
|
|
)
|
|
.await?;
|
|
|
|
user.map(|x| ((Scopes::all() ^ Scopes::restricted()), x))
|
|
}
|
|
_ => return Err(AuthenticationError::InvalidAuthMethod),
|
|
};
|
|
|
|
Ok(possible_user)
|
|
}
|
|
|
|
pub fn extract_authorization_header(
|
|
req: &HttpRequest,
|
|
) -> Result<&str, AuthenticationError> {
|
|
let headers = req.headers();
|
|
let token_val: Option<&HeaderValue> = headers.get(AUTHORIZATION);
|
|
let token_val = token_val
|
|
.ok_or_else(|| AuthenticationError::InvalidAuthMethod)?
|
|
.to_str()
|
|
.map_err(|_| AuthenticationError::InvalidCredentials)?;
|
|
Ok(if let Some(token) = token_val.strip_prefix("Bearer ") {
|
|
token
|
|
} else {
|
|
token_val
|
|
})
|
|
}
|
|
|
|
pub async fn check_is_moderator_from_headers<'a, 'b, E>(
|
|
req: &HttpRequest,
|
|
executor: E,
|
|
redis: &RedisPool,
|
|
session_queue: &AuthQueue,
|
|
required_scopes: Scopes,
|
|
) -> Result<User, AuthenticationError>
|
|
where
|
|
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
|
|
{
|
|
let user = get_user_from_headers(
|
|
req,
|
|
executor,
|
|
redis,
|
|
session_queue,
|
|
required_scopes,
|
|
)
|
|
.await?
|
|
.1;
|
|
|
|
if user.role.is_mod() {
|
|
Ok(user)
|
|
} else {
|
|
Err(AuthenticationError::InvalidCredentials)
|
|
}
|
|
}
|