refactor: rewrite entire codebase in axum instead of actix
Replaces actix with axum for web, allows us to use socket.io and gives us access to the tower ecosystem of middleware breaks compatibility with our current websocket implementation, needs to be reimplemented for socket.io
This commit is contained in:
parent
3647086adb
commit
324137ce8b
47 changed files with 1381 additions and 1129 deletions
|
@ -1,16 +1,21 @@
|
|||
//! `/api/v1/auth/devices` Returns list of logged in devices
|
||||
|
||||
use actix_web::{HttpRequest, HttpResponse, get, web};
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{Json, extract::State, http::StatusCode, response::IntoResponse};
|
||||
use axum_extra::{
|
||||
TypedHeader,
|
||||
headers::{Authorization, authorization::Bearer},
|
||||
};
|
||||
use diesel::{ExpressionMethods, QueryDsl, Queryable, Selectable, SelectableHelper};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
Data,
|
||||
AppState,
|
||||
api::v1::auth::check_access_token,
|
||||
error::Error,
|
||||
schema::refresh_tokens::{self, dsl},
|
||||
utils::get_auth_header,
|
||||
};
|
||||
|
||||
#[derive(Serialize, Selectable, Queryable)]
|
||||
|
@ -18,7 +23,7 @@ use crate::{
|
|||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||
struct Device {
|
||||
device_name: String,
|
||||
created_at: i64
|
||||
created_at: i64,
|
||||
}
|
||||
|
||||
/// `GET /api/v1/auth/devices` Returns list of logged in devices
|
||||
|
@ -35,18 +40,13 @@ struct Device {
|
|||
///
|
||||
/// ]);
|
||||
/// ```
|
||||
#[get("/devices")]
|
||||
pub async fn get(
|
||||
req: HttpRequest,
|
||||
data: web::Data<Data>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let headers = req.headers();
|
||||
State(app_state): State<Arc<AppState>>,
|
||||
TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
let mut conn = app_state.pool.get().await?;
|
||||
|
||||
let auth_header = get_auth_header(headers)?;
|
||||
|
||||
let mut conn = data.pool.get().await?;
|
||||
|
||||
let uuid = check_access_token(auth_header, &mut conn).await?;
|
||||
let uuid = check_access_token(auth.token(), &mut conn).await?;
|
||||
|
||||
let devices: Vec<Device> = dsl::refresh_tokens
|
||||
.filter(dsl::uuid.eq(uuid))
|
||||
|
@ -54,5 +54,5 @@ pub async fn get(
|
|||
.get_results(&mut conn)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(devices))
|
||||
Ok((StatusCode::OK, Json(devices)))
|
||||
}
|
||||
|
|
|
@ -1,39 +1,47 @@
|
|||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use actix_web::{HttpResponse, post, web};
|
||||
use argon2::{PasswordHash, PasswordVerifier};
|
||||
use axum::{
|
||||
Json,
|
||||
extract::State,
|
||||
http::{HeaderValue, StatusCode},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use diesel::{ExpressionMethods, QueryDsl, dsl::insert_into};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
Data,
|
||||
AppState,
|
||||
error::Error,
|
||||
schema::*,
|
||||
utils::{PASSWORD_REGEX, generate_token, new_refresh_token_cookie, user_uuid_from_identifier},
|
||||
utils::{
|
||||
PASSWORD_REGEX, generate_token, new_access_token_cookie, new_refresh_token_cookie,
|
||||
user_uuid_from_identifier,
|
||||
},
|
||||
};
|
||||
|
||||
use super::Response;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct LoginInformation {
|
||||
pub struct LoginInformation {
|
||||
username: String,
|
||||
password: String,
|
||||
device_name: String,
|
||||
}
|
||||
|
||||
#[post("/login")]
|
||||
pub async fn response(
|
||||
login_information: web::Json<LoginInformation>,
|
||||
data: web::Data<Data>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
State(app_state): State<Arc<AppState>>,
|
||||
Json(login_information): Json<LoginInformation>,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
if !PASSWORD_REGEX.is_match(&login_information.password) {
|
||||
return Ok(HttpResponse::Forbidden().json(r#"{ "password_hashed": false }"#));
|
||||
return Err(Error::BadRequest("Bad password".to_string()));
|
||||
}
|
||||
|
||||
use users::dsl;
|
||||
|
||||
let mut conn = data.pool.get().await?;
|
||||
let mut conn = app_state.pool.get().await?;
|
||||
|
||||
let uuid = user_uuid_from_identifier(&mut conn, &login_information.username).await?;
|
||||
|
||||
|
@ -46,7 +54,7 @@ pub async fn response(
|
|||
let parsed_hash = PasswordHash::new(&database_password)
|
||||
.map_err(|e| Error::PasswordHashError(e.to_string()))?;
|
||||
|
||||
if data
|
||||
if app_state
|
||||
.argon2
|
||||
.verify_password(login_information.password.as_bytes(), &parsed_hash)
|
||||
.is_err()
|
||||
|
@ -85,7 +93,21 @@ pub async fn response(
|
|||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.cookie(new_refresh_token_cookie(&data.config, refresh_token))
|
||||
.json(Response { access_token }))
|
||||
let mut response = StatusCode::OK.into_response();
|
||||
|
||||
response.headers_mut().insert(
|
||||
"Set-Cookie",
|
||||
HeaderValue::from_str(
|
||||
&new_refresh_token_cookie(&app_state.config, refresh_token).to_string(),
|
||||
)?,
|
||||
);
|
||||
|
||||
response.headers_mut().insert(
|
||||
"Set-Cookie2",
|
||||
HeaderValue::from_str(
|
||||
&new_access_token_cookie(&app_state.config, access_token).to_string(),
|
||||
)?,
|
||||
);
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
use actix_web::{HttpRequest, HttpResponse, get, web};
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{
|
||||
extract::State,
|
||||
http::{HeaderValue, StatusCode},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use axum_extra::extract::CookieJar;
|
||||
use diesel::{ExpressionMethods, delete};
|
||||
use diesel_async::RunQueryDsl;
|
||||
|
||||
use crate::{
|
||||
Data,
|
||||
AppState,
|
||||
error::Error,
|
||||
schema::refresh_tokens::{self, dsl},
|
||||
};
|
||||
|
@ -20,28 +27,49 @@ use crate::{
|
|||
///
|
||||
/// 401 Unauthorized (no refresh token found)
|
||||
///
|
||||
#[get("/logout")]
|
||||
pub async fn res(req: HttpRequest, data: web::Data<Data>) -> Result<HttpResponse, Error> {
|
||||
let mut refresh_token_cookie = req.cookie("refresh_token").ok_or(Error::Unauthorized(
|
||||
"request has no refresh token".to_string(),
|
||||
))?;
|
||||
pub async fn res(
|
||||
State(app_state): State<Arc<AppState>>,
|
||||
jar: CookieJar,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
let mut refresh_token_cookie = jar
|
||||
.get("refresh_token")
|
||||
.ok_or(Error::Unauthorized(
|
||||
"request has no refresh token".to_string(),
|
||||
))?
|
||||
.to_owned();
|
||||
|
||||
let refresh_token = String::from(refresh_token_cookie.value());
|
||||
let access_token_cookie = jar.get("access_token");
|
||||
|
||||
let mut conn = data.pool.get().await?;
|
||||
let refresh_token = String::from(refresh_token_cookie.value_trimmed());
|
||||
|
||||
let mut conn = app_state.pool.get().await?;
|
||||
|
||||
let deleted = delete(refresh_tokens::table)
|
||||
.filter(dsl::token.eq(refresh_token))
|
||||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
refresh_token_cookie.make_removal();
|
||||
let mut response;
|
||||
|
||||
if deleted == 0 {
|
||||
return Ok(HttpResponse::NotFound()
|
||||
.cookie(refresh_token_cookie)
|
||||
.finish());
|
||||
response = StatusCode::NOT_FOUND.into_response();
|
||||
} else {
|
||||
response = StatusCode::OK.into_response();
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok().cookie(refresh_token_cookie).finish())
|
||||
refresh_token_cookie.make_removal();
|
||||
response.headers_mut().append(
|
||||
"Set-Cookie",
|
||||
HeaderValue::from_str(&refresh_token_cookie.to_string())?,
|
||||
);
|
||||
|
||||
if let Some(cookie) = access_token_cookie {
|
||||
let mut cookie = cookie.clone();
|
||||
cookie.make_removal();
|
||||
response
|
||||
.headers_mut()
|
||||
.append("Set-Cookie2", HeaderValue::from_str(&cookie.to_string())?);
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use actix_web::{Scope, web};
|
||||
use axum::{
|
||||
Router,
|
||||
routing::{delete, get, post},
|
||||
};
|
||||
use diesel::{ExpressionMethods, QueryDsl};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{Conn, error::Error, schema::access_tokens::dsl};
|
||||
use crate::{AppState, Conn, error::Error, schema::access_tokens::dsl};
|
||||
|
||||
mod devices;
|
||||
mod login;
|
||||
|
@ -17,23 +22,18 @@ mod reset_password;
|
|||
mod revoke;
|
||||
mod verify_email;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Response {
|
||||
access_token: String,
|
||||
}
|
||||
|
||||
pub fn web() -> Scope {
|
||||
web::scope("/auth")
|
||||
.service(register::res)
|
||||
.service(login::response)
|
||||
.service(logout::res)
|
||||
.service(refresh::res)
|
||||
.service(revoke::res)
|
||||
.service(verify_email::get)
|
||||
.service(verify_email::post)
|
||||
.service(reset_password::get)
|
||||
.service(reset_password::post)
|
||||
.service(devices::get)
|
||||
pub fn router() -> Router<Arc<AppState>> {
|
||||
Router::new()
|
||||
.route("/register", post(register::post))
|
||||
.route("/login", post(login::response))
|
||||
.route("/logout", delete(logout::res))
|
||||
.route("/refresh", post(refresh::post))
|
||||
.route("/revoke", post(revoke::post))
|
||||
.route("/verify-email", get(verify_email::get))
|
||||
.route("/verify-email", post(verify_email::post))
|
||||
.route("/reset-password", get(reset_password::get))
|
||||
.route("/reset-password", post(reset_password::post))
|
||||
.route("/devices", get(devices::get))
|
||||
}
|
||||
|
||||
pub async fn check_access_token(access_token: &str, conn: &mut Conn) -> Result<Uuid, Error> {
|
||||
|
|
|
@ -1,32 +1,45 @@
|
|||
use actix_web::{HttpRequest, HttpResponse, post, web};
|
||||
use axum::{
|
||||
extract::State,
|
||||
http::{HeaderValue, StatusCode},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use axum_extra::extract::CookieJar;
|
||||
use diesel::{ExpressionMethods, QueryDsl, delete, update};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use log::error;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
Data,
|
||||
AppState,
|
||||
error::Error,
|
||||
schema::{
|
||||
access_tokens::{self, dsl},
|
||||
refresh_tokens::{self, dsl as rdsl},
|
||||
},
|
||||
utils::{generate_token, new_refresh_token_cookie},
|
||||
utils::{generate_token, new_access_token_cookie, new_refresh_token_cookie},
|
||||
};
|
||||
|
||||
use super::Response;
|
||||
pub async fn post(
|
||||
State(app_state): State<Arc<AppState>>,
|
||||
jar: CookieJar,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
let mut refresh_token_cookie = jar
|
||||
.get("refresh_token")
|
||||
.ok_or(Error::Unauthorized(
|
||||
"request has no refresh token".to_string(),
|
||||
))?
|
||||
.to_owned();
|
||||
|
||||
#[post("/refresh")]
|
||||
pub async fn res(req: HttpRequest, data: web::Data<Data>) -> Result<HttpResponse, Error> {
|
||||
let mut refresh_token_cookie = req.cookie("refresh_token").ok_or(Error::Unauthorized(
|
||||
"request has no refresh token".to_string(),
|
||||
))?;
|
||||
let access_token_cookie = jar.get("access_token");
|
||||
|
||||
let mut refresh_token = String::from(refresh_token_cookie.value());
|
||||
let refresh_token = String::from(refresh_token_cookie.value_trimmed());
|
||||
|
||||
let current_time = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64;
|
||||
|
||||
let mut conn = data.pool.get().await?;
|
||||
let mut conn = app_state.pool.get().await?;
|
||||
|
||||
if let Ok(created_at) = rdsl::refresh_tokens
|
||||
.filter(rdsl::token.eq(&refresh_token))
|
||||
|
@ -45,15 +58,29 @@ pub async fn res(req: HttpRequest, data: web::Data<Data>) -> Result<HttpResponse
|
|||
error!("{error}");
|
||||
}
|
||||
|
||||
refresh_token_cookie.make_removal();
|
||||
let mut response = StatusCode::UNAUTHORIZED.into_response();
|
||||
|
||||
return Ok(HttpResponse::Unauthorized()
|
||||
.cookie(refresh_token_cookie)
|
||||
.finish());
|
||||
refresh_token_cookie.make_removal();
|
||||
response.headers_mut().append(
|
||||
"Set-Cookie",
|
||||
HeaderValue::from_str(&refresh_token_cookie.to_string())?,
|
||||
);
|
||||
|
||||
if let Some(cookie) = access_token_cookie {
|
||||
let mut cookie = cookie.clone();
|
||||
cookie.make_removal();
|
||||
response
|
||||
.headers_mut()
|
||||
.append("Set-Cookie2", HeaderValue::from_str(&cookie.to_string())?);
|
||||
}
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
let current_time = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() as i64;
|
||||
|
||||
let mut response = StatusCode::OK.into_response();
|
||||
|
||||
if lifetime > 1987200 {
|
||||
let new_refresh_token = generate_token::<32>()?;
|
||||
|
||||
|
@ -67,7 +94,13 @@ pub async fn res(req: HttpRequest, data: web::Data<Data>) -> Result<HttpResponse
|
|||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
refresh_token = new_refresh_token;
|
||||
response.headers_mut().append(
|
||||
"Set-Cookie",
|
||||
HeaderValue::from_str(
|
||||
&new_refresh_token_cookie(&app_state.config, new_refresh_token)
|
||||
.to_string(),
|
||||
)?,
|
||||
);
|
||||
}
|
||||
Err(error) => {
|
||||
error!("{error}");
|
||||
|
@ -86,14 +119,40 @@ pub async fn res(req: HttpRequest, data: web::Data<Data>) -> Result<HttpResponse
|
|||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
return Ok(HttpResponse::Ok()
|
||||
.cookie(new_refresh_token_cookie(&data.config, refresh_token))
|
||||
.json(Response { access_token }));
|
||||
if response.headers().get("Set-Cookie").is_some() {
|
||||
response.headers_mut().append(
|
||||
"Set-Cookie2",
|
||||
HeaderValue::from_str(
|
||||
&new_access_token_cookie(&app_state.config, access_token).to_string(),
|
||||
)?,
|
||||
);
|
||||
} else {
|
||||
response.headers_mut().append(
|
||||
"Set-Cookie",
|
||||
HeaderValue::from_str(
|
||||
&new_access_token_cookie(&app_state.config, access_token).to_string(),
|
||||
)?,
|
||||
);
|
||||
}
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
refresh_token_cookie.make_removal();
|
||||
let mut response = StatusCode::UNAUTHORIZED.into_response();
|
||||
|
||||
Ok(HttpResponse::Unauthorized()
|
||||
.cookie(refresh_token_cookie)
|
||||
.finish())
|
||||
refresh_token_cookie.make_removal();
|
||||
response.headers_mut().append(
|
||||
"Set-Cookie",
|
||||
HeaderValue::from_str(&refresh_token_cookie.to_string())?,
|
||||
);
|
||||
|
||||
if let Some(cookie) = access_token_cookie {
|
||||
let mut cookie = cookie.clone();
|
||||
cookie.make_removal();
|
||||
response
|
||||
.headers_mut()
|
||||
.append("Set-Cookie2", HeaderValue::from_str(&cookie.to_string())?);
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
|
|
@ -1,18 +1,25 @@
|
|||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use actix_web::{HttpResponse, post, web};
|
||||
use argon2::{
|
||||
PasswordHasher,
|
||||
password_hash::{SaltString, rand_core::OsRng},
|
||||
};
|
||||
use axum::{
|
||||
Json,
|
||||
extract::State,
|
||||
http::{HeaderValue, StatusCode},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use diesel::{ExpressionMethods, dsl::insert_into};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::Response;
|
||||
use crate::{
|
||||
Data,
|
||||
AppState,
|
||||
error::Error,
|
||||
objects::Member,
|
||||
schema::{
|
||||
|
@ -21,12 +28,13 @@ use crate::{
|
|||
users::{self, dsl as udsl},
|
||||
},
|
||||
utils::{
|
||||
EMAIL_REGEX, PASSWORD_REGEX, USERNAME_REGEX, generate_token, new_refresh_token_cookie,
|
||||
EMAIL_REGEX, PASSWORD_REGEX, USERNAME_REGEX, generate_token, new_access_token_cookie,
|
||||
new_refresh_token_cookie,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AccountInformation {
|
||||
pub struct AccountInformation {
|
||||
identifier: String,
|
||||
email: String,
|
||||
password: String,
|
||||
|
@ -34,17 +42,13 @@ struct AccountInformation {
|
|||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ResponseError {
|
||||
pub struct ResponseError {
|
||||
signups_enabled: bool,
|
||||
gorb_id_valid: bool,
|
||||
gorb_id_available: bool,
|
||||
email_valid: bool,
|
||||
email_available: bool,
|
||||
password_hashed: bool,
|
||||
password_minimum_length: bool,
|
||||
password_special_characters: bool,
|
||||
password_letters: bool,
|
||||
password_numbers: bool,
|
||||
password_strength: bool,
|
||||
}
|
||||
|
||||
impl Default for ResponseError {
|
||||
|
@ -55,21 +59,16 @@ impl Default for ResponseError {
|
|||
gorb_id_available: true,
|
||||
email_valid: true,
|
||||
email_available: true,
|
||||
password_hashed: true,
|
||||
password_minimum_length: true,
|
||||
password_special_characters: true,
|
||||
password_letters: true,
|
||||
password_numbers: true,
|
||||
password_strength: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/register")]
|
||||
pub async fn res(
|
||||
account_information: web::Json<AccountInformation>,
|
||||
data: web::Data<Data>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
if !data.config.instance.registration {
|
||||
pub async fn post(
|
||||
State(app_state): State<Arc<AppState>>,
|
||||
Json(account_information): Json<AccountInformation>,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
if !app_state.config.instance.registration {
|
||||
return Err(Error::Forbidden(
|
||||
"registration is disabled on this instance".to_string(),
|
||||
));
|
||||
|
@ -78,36 +77,48 @@ pub async fn res(
|
|||
let uuid = Uuid::now_v7();
|
||||
|
||||
if !EMAIL_REGEX.is_match(&account_information.email) {
|
||||
return Ok(HttpResponse::Forbidden().json(ResponseError {
|
||||
email_valid: false,
|
||||
..Default::default()
|
||||
}));
|
||||
return Ok((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(ResponseError {
|
||||
email_valid: false,
|
||||
..Default::default()
|
||||
}),
|
||||
)
|
||||
.into_response());
|
||||
}
|
||||
|
||||
if !USERNAME_REGEX.is_match(&account_information.identifier)
|
||||
|| account_information.identifier.len() < 3
|
||||
|| account_information.identifier.len() > 32
|
||||
{
|
||||
return Ok(HttpResponse::Forbidden().json(ResponseError {
|
||||
gorb_id_valid: false,
|
||||
..Default::default()
|
||||
}));
|
||||
return Ok((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(ResponseError {
|
||||
gorb_id_valid: false,
|
||||
..Default::default()
|
||||
}),
|
||||
)
|
||||
.into_response());
|
||||
}
|
||||
|
||||
if !PASSWORD_REGEX.is_match(&account_information.password) {
|
||||
return Ok(HttpResponse::Forbidden().json(ResponseError {
|
||||
password_hashed: false,
|
||||
..Default::default()
|
||||
}));
|
||||
return Ok((
|
||||
StatusCode::FORBIDDEN,
|
||||
Json(ResponseError {
|
||||
password_strength: false,
|
||||
..Default::default()
|
||||
}),
|
||||
)
|
||||
.into_response());
|
||||
}
|
||||
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
|
||||
if let Ok(hashed_password) = data
|
||||
if let Ok(hashed_password) = app_state
|
||||
.argon2
|
||||
.hash_password(account_information.password.as_bytes(), &salt)
|
||||
{
|
||||
let mut conn = data.pool.get().await?;
|
||||
let mut conn = app_state.pool.get().await?;
|
||||
|
||||
// TODO: Check security of this implementation
|
||||
insert_into(users::table)
|
||||
|
@ -145,14 +156,27 @@ pub async fn res(
|
|||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
if let Some(initial_guild) = data.config.instance.initial_guild {
|
||||
Member::new(&data, uuid, initial_guild).await?;
|
||||
if let Some(initial_guild) = app_state.config.instance.initial_guild {
|
||||
Member::new(&app_state, uuid, initial_guild).await?;
|
||||
}
|
||||
|
||||
return Ok(HttpResponse::Ok()
|
||||
.cookie(new_refresh_token_cookie(&data.config, refresh_token))
|
||||
.json(Response { access_token }));
|
||||
let mut response = StatusCode::OK.into_response();
|
||||
|
||||
response.headers_mut().append(
|
||||
"Set-Cookie",
|
||||
HeaderValue::from_str(
|
||||
&new_refresh_token_cookie(&app_state.config, refresh_token).to_string(),
|
||||
)?,
|
||||
);
|
||||
response.headers_mut().append(
|
||||
"Set-Cookie2",
|
||||
HeaderValue::from_str(
|
||||
&new_access_token_cookie(&app_state.config, access_token).to_string(),
|
||||
)?,
|
||||
);
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
|
||||
Ok(HttpResponse::InternalServerError().finish())
|
||||
Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
|
||||
}
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
//! `/api/v1/auth/reset-password` Endpoints for resetting user password
|
||||
|
||||
use actix_web::{HttpResponse, get, post, web};
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Query, State},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
};
|
||||
use chrono::{Duration, Utc};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{Data, error::Error, objects::PasswordResetToken};
|
||||
use crate::{AppState, error::Error, objects::PasswordResetToken};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Query {
|
||||
pub struct QueryParams {
|
||||
identifier: String,
|
||||
}
|
||||
|
||||
|
@ -20,17 +27,22 @@ struct Query {
|
|||
///
|
||||
/// ### Responses
|
||||
/// 200 Email sent
|
||||
///
|
||||
/// 429 Too Many Requests
|
||||
///
|
||||
/// 404 Not found
|
||||
///
|
||||
/// 400 Bad request
|
||||
///
|
||||
#[get("/reset-password")]
|
||||
pub async fn get(query: web::Query<Query>, data: web::Data<Data>) -> Result<HttpResponse, Error> {
|
||||
pub async fn get(
|
||||
State(app_state): State<Arc<AppState>>,
|
||||
query: Query<QueryParams>,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
if let Ok(password_reset_token) =
|
||||
PasswordResetToken::get_with_identifier(&data, query.identifier.clone()).await
|
||||
PasswordResetToken::get_with_identifier(&app_state, query.identifier.clone()).await
|
||||
{
|
||||
if Utc::now().signed_duration_since(password_reset_token.created_at) > Duration::hours(1) {
|
||||
password_reset_token.delete(&data).await?;
|
||||
password_reset_token.delete(&app_state).await?;
|
||||
} else {
|
||||
return Err(Error::TooManyRequests(
|
||||
"Please allow 1 hour before sending a new email".to_string(),
|
||||
|
@ -38,13 +50,13 @@ pub async fn get(query: web::Query<Query>, data: web::Data<Data>) -> Result<Http
|
|||
}
|
||||
}
|
||||
|
||||
PasswordResetToken::new(&data, query.identifier.clone()).await?;
|
||||
PasswordResetToken::new(&app_state, query.identifier.clone()).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ResetPassword {
|
||||
pub struct ResetPassword {
|
||||
password: String,
|
||||
token: String,
|
||||
}
|
||||
|
@ -63,20 +75,23 @@ struct ResetPassword {
|
|||
///
|
||||
/// ### Responses
|
||||
/// 200 Success
|
||||
///
|
||||
/// 410 Token Expired
|
||||
///
|
||||
/// 404 Not Found
|
||||
///
|
||||
/// 400 Bad Request
|
||||
///
|
||||
#[post("/reset-password")]
|
||||
pub async fn post(
|
||||
reset_password: web::Json<ResetPassword>,
|
||||
data: web::Data<Data>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let password_reset_token = PasswordResetToken::get(&data, reset_password.token.clone()).await?;
|
||||
State(app_state): State<Arc<AppState>>,
|
||||
reset_password: Json<ResetPassword>,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
let password_reset_token =
|
||||
PasswordResetToken::get(&app_state, reset_password.token.clone()).await?;
|
||||
|
||||
password_reset_token
|
||||
.set_password(&data, reset_password.password.clone())
|
||||
.set_password(&app_state, reset_password.password.clone())
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
|
|
@ -1,38 +1,39 @@
|
|||
use actix_web::{HttpRequest, HttpResponse, post, web};
|
||||
use std::sync::Arc;
|
||||
|
||||
use argon2::{PasswordHash, PasswordVerifier};
|
||||
use axum::{Json, extract::State, http::StatusCode, response::IntoResponse};
|
||||
use axum_extra::{
|
||||
TypedHeader,
|
||||
headers::authorization::{Authorization, Bearer},
|
||||
};
|
||||
use diesel::{ExpressionMethods, QueryDsl, delete};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
Data,
|
||||
AppState,
|
||||
api::v1::auth::check_access_token,
|
||||
error::Error,
|
||||
schema::refresh_tokens::{self, dsl as rdsl},
|
||||
schema::users::dsl as udsl,
|
||||
utils::get_auth_header,
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct RevokeRequest {
|
||||
pub struct RevokeRequest {
|
||||
password: String,
|
||||
device_name: String,
|
||||
}
|
||||
|
||||
// TODO: Should maybe be a delete request?
|
||||
#[post("/revoke")]
|
||||
pub async fn res(
|
||||
req: HttpRequest,
|
||||
revoke_request: web::Json<RevokeRequest>,
|
||||
data: web::Data<Data>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let headers = req.headers();
|
||||
#[axum::debug_handler]
|
||||
pub async fn post(
|
||||
State(app_state): State<Arc<AppState>>,
|
||||
TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
|
||||
Json(revoke_request): Json<RevokeRequest>,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
let mut conn = app_state.pool.get().await?;
|
||||
|
||||
let auth_header = get_auth_header(headers)?;
|
||||
|
||||
let mut conn = data.pool.get().await?;
|
||||
|
||||
let uuid = check_access_token(auth_header, &mut conn).await?;
|
||||
let uuid = check_access_token(auth.token(), &mut conn).await?;
|
||||
|
||||
let database_password: String = udsl::users
|
||||
.filter(udsl::uuid.eq(uuid))
|
||||
|
@ -43,7 +44,7 @@ pub async fn res(
|
|||
let hashed_password = PasswordHash::new(&database_password)
|
||||
.map_err(|e| Error::PasswordHashError(e.to_string()))?;
|
||||
|
||||
if data
|
||||
if app_state
|
||||
.argon2
|
||||
.verify_password(revoke_request.password.as_bytes(), &hashed_password)
|
||||
.is_err()
|
||||
|
@ -59,5 +60,5 @@ pub async fn res(
|
|||
.execute(&mut conn)
|
||||
.await?;
|
||||
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
|
|
@ -1,19 +1,28 @@
|
|||
//! `/api/v1/auth/verify-email` Endpoints for verifying user emails
|
||||
|
||||
use actix_web::{HttpRequest, HttpResponse, get, post, web};
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{
|
||||
extract::{Query, State},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
};
|
||||
use axum_extra::{
|
||||
TypedHeader,
|
||||
headers::{Authorization, authorization::Bearer},
|
||||
};
|
||||
use chrono::{Duration, Utc};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
Data,
|
||||
AppState,
|
||||
api::v1::auth::check_access_token,
|
||||
error::Error,
|
||||
objects::{EmailToken, Me},
|
||||
utils::get_auth_header,
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Query {
|
||||
pub struct QueryParams {
|
||||
token: String,
|
||||
}
|
||||
|
||||
|
@ -35,37 +44,32 @@ struct Query {
|
|||
///
|
||||
/// 401 Unauthorized
|
||||
///
|
||||
#[get("/verify-email")]
|
||||
pub async fn get(
|
||||
req: HttpRequest,
|
||||
query: web::Query<Query>,
|
||||
data: web::Data<Data>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let headers = req.headers();
|
||||
State(app_state): State<Arc<AppState>>,
|
||||
Query(query): Query<QueryParams>,
|
||||
TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
let mut conn = app_state.pool.get().await?;
|
||||
|
||||
let auth_header = get_auth_header(headers)?;
|
||||
|
||||
let mut conn = data.pool.get().await?;
|
||||
|
||||
let uuid = check_access_token(auth_header, &mut conn).await?;
|
||||
let uuid = check_access_token(auth.token(), &mut conn).await?;
|
||||
|
||||
let me = Me::get(&mut conn, uuid).await?;
|
||||
|
||||
if me.email_verified {
|
||||
return Ok(HttpResponse::NoContent().finish());
|
||||
return Ok(StatusCode::NO_CONTENT);
|
||||
}
|
||||
|
||||
let email_token = EmailToken::get(&data, me.uuid).await?;
|
||||
let email_token = EmailToken::get(&app_state, me.uuid).await?;
|
||||
|
||||
if query.token != email_token.token {
|
||||
return Ok(HttpResponse::Unauthorized().finish());
|
||||
return Ok(StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
me.verify_email(&mut conn).await?;
|
||||
|
||||
email_token.delete(&data).await?;
|
||||
email_token.delete(&app_state).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
/// `POST /api/v1/auth/verify-email` Sends user verification email
|
||||
|
@ -81,25 +85,23 @@ pub async fn get(
|
|||
///
|
||||
/// 401 Unauthorized
|
||||
///
|
||||
#[post("/verify-email")]
|
||||
pub async fn post(req: HttpRequest, data: web::Data<Data>) -> Result<HttpResponse, Error> {
|
||||
let headers = req.headers();
|
||||
pub async fn post(
|
||||
State(app_state): State<Arc<AppState>>,
|
||||
TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
|
||||
) -> Result<impl IntoResponse, Error> {
|
||||
let mut conn = app_state.pool.get().await?;
|
||||
|
||||
let auth_header = get_auth_header(headers)?;
|
||||
|
||||
let mut conn = data.pool.get().await?;
|
||||
|
||||
let uuid = check_access_token(auth_header, &mut conn).await?;
|
||||
let uuid = check_access_token(auth.token(), &mut conn).await?;
|
||||
|
||||
let me = Me::get(&mut conn, uuid).await?;
|
||||
|
||||
if me.email_verified {
|
||||
return Ok(HttpResponse::NoContent().finish());
|
||||
return Ok(StatusCode::NO_CONTENT);
|
||||
}
|
||||
|
||||
if let Ok(email_token) = EmailToken::get(&data, me.uuid).await {
|
||||
if let Ok(email_token) = EmailToken::get(&app_state, me.uuid).await {
|
||||
if Utc::now().signed_duration_since(email_token.created_at) > Duration::hours(1) {
|
||||
email_token.delete(&data).await?;
|
||||
email_token.delete(&app_state).await?;
|
||||
} else {
|
||||
return Err(Error::TooManyRequests(
|
||||
"Please allow 1 hour before sending a new email".to_string(),
|
||||
|
@ -107,7 +109,7 @@ pub async fn post(req: HttpRequest, data: web::Data<Data>) -> Result<HttpRespons
|
|||
}
|
||||
}
|
||||
|
||||
EmailToken::new(&data, me).await?;
|
||||
EmailToken::new(&app_state, me).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue