clean up api error and login stub
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Patrick Michl 2022-04-28 23:15:27 +02:00
parent 304f82baa4
commit b0895145de
11 changed files with 167 additions and 64 deletions

View File

@ -10,7 +10,7 @@ use axum::{
use rand_core::OsRng; use rand_core::OsRng;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use crate::responses::registration::RegistrationResponse; use crate::{responses::{registration::RegistrationResponse, authentication::{AuthenticationResponse, AuthenticationSuccess}}, api::client_server::errors::authentication_error::AuthenticationError};
use crate::{ use crate::{
models::devices::Device, models::devices::Device,
responses::{flow::Flows, registration::RegistrationSuccess}, responses::{flow::Flows, registration::RegistrationSuccess},
@ -38,9 +38,16 @@ async fn get_login() -> Result<Json<Flows>, ApiError> {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn post_login(body: String) -> StatusCode { async fn post_login(
dbg!(body); Extension(config): Extension<Arc<Config>>,
StatusCode::INTERNAL_SERVER_ERROR Extension(db): Extension<SqlitePool>,
Json(body): Json<AuthenticationData>
) -> Result<Json<AuthenticationResponse>, ApiError> {
let user = UserId::new("name", "server_name").ok().ok_or(AuthenticationError::InvalidUserId)?;
todo!("Flesh this out more");
let resp = AuthenticationSuccess::new("", "", &user);
Ok(Json(AuthenticationResponse::Success(resp)))
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
@ -76,17 +83,17 @@ async fn post_register(
.ok() .ok()
.ok_or(RegistrationError::InvalidUserId)?; .ok_or(RegistrationError::InvalidUserId)?;
User::exists(&db, &user_id) if User::exists(&db, &user_id).await? {
.await? return Err(ApiError::from(RegistrationError::UserIdTaken));
.then(|| ()) }
.ok_or(RegistrationError::UserIdTaken)?;
let display_name = match body.initial_device_display_name() { let display_name = match body.initial_device_display_name() {
Some(display_name) => display_name.as_ref(), Some(display_name) => display_name.as_ref(),
None => "Random displayname", None => "Random displayname",
}; };
let user = User::create(&db, &user_id, &user_id.to_string(), auth_data.password()).await?; let user =
User::create(&db, &user_id, &user_id.to_string(), auth_data.password()).await?;
let device = Device::create(&db, &user, "test", display_name).await?; let device = Device::create(&db, &user, "test", display_name).await?;
(user, device) (user, device)

View File

@ -6,7 +6,9 @@ use sqlx::Statement;
use crate::responses::registration::RegistrationResponse; use crate::responses::registration::RegistrationResponse;
use crate::types::error_code::ErrorCode; use crate::types::error_code::ErrorCode;
use super::authentication_error::AuthenticationError;
use super::registration_error::RegistrationError; use super::registration_error::RegistrationError;
use super::ErrorResponse;
macro_rules! map_err { macro_rules! map_err {
($err:ident, $($type:path => $target:path),+) => { ($err:ident, $($type:path => $target:path),+) => {
@ -18,34 +20,19 @@ macro_rules! map_err {
} }
} }
#[derive(Debug, serde::Serialize)]
struct ErrorResponse {
errcode: ErrorCode,
error: String,
#[serde(skip_serializing_if = "Option::is_none")]
retry_after_ms: Option<u64>,
}
impl ErrorResponse {
fn new(errcode: ErrorCode, error: &str, retry_after_ms: Option<u64>) -> Self {
Self {
errcode,
error: error.to_owned(),
retry_after_ms,
}
}
}
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum ApiError { pub enum ApiError {
#[error("Registration Error")] #[error("Registration Error")]
RegistrationError(#[from] RegistrationError), RegistrationError(#[from] RegistrationError),
#[error("Authentication Error")]
AuthenticationError(#[from] AuthenticationError),
#[error("Database Error")] #[error("Database Error")]
DBError(#[from] sqlx::Error), DBError(#[from] sqlx::Error),
#[error("Generic Error")] #[error("Generic Error")]
Generic(anyhow::Error) Generic(anyhow::Error),
} }
impl From<anyhow::Error> for ApiError { impl From<anyhow::Error> for ApiError {
@ -62,29 +49,8 @@ impl From<anyhow::Error> for ApiError {
impl IntoResponse for ApiError { impl IntoResponse for ApiError {
fn into_response(self) -> axum::response::Response { fn into_response(self) -> axum::response::Response {
match self { match self {
ApiError::RegistrationError(registration_error) => match registration_error { ApiError::RegistrationError(e) => e.into_response(),
RegistrationError::AdditionalAuthenticationInformation => ( ApiError::AuthenticationError(e) => e.into_response(),
StatusCode::UNAUTHORIZED,
Json(RegistrationResponse::user_interactive_authorization_info()),
).into_response(),
RegistrationError::InvalidUserId | RegistrationError::MissingUserId => (
StatusCode::BAD_REQUEST,
Json(ErrorResponse::new(
ErrorCode::InvalidUsername,
&registration_error.to_string(),
None,
)
)).into_response(),
RegistrationError::UserIdTaken => (
StatusCode::BAD_REQUEST,
Json(ErrorResponse::new(
ErrorCode::UserInUse,
&registration_error.to_string(),
None,
)),
)
.into_response(),
},
ApiError::DBError(err) => { ApiError::DBError(err) => {
tracing::error!("{}", err.to_string()); tracing::error!("{}", err.to_string());
( (

View File

@ -0,0 +1,41 @@
use axum::{http::StatusCode, response::IntoResponse, Json};
use crate::types::error_code::ErrorCode;
use super::ErrorResponse;
#[derive(Debug, thiserror::Error)]
pub enum AuthenticationError {
#[error("UserId is missing")]
MissingUserId,
#[error("The user ID is not a valid user name")]
InvalidUserId,
#[error("The provided authentication data was incorrect")]
Forbidden,
#[error("The user has been deactivated")]
UserDeactivated,
}
impl IntoResponse for AuthenticationError {
fn into_response(self) -> axum::response::Response {
match self {
Self::InvalidUserId | Self::MissingUserId => (
StatusCode::BAD_REQUEST,
Json(ErrorResponse::new(
ErrorCode::InvalidUsername,
&self.to_string(),
None,
)),
)
.into_response(),
Self::Forbidden => (
StatusCode::FORBIDDEN,
Json(ErrorResponse::new(ErrorCode::Forbidden, &self.to_string(), None)),
).into_response(),
Self::UserDeactivated => (
StatusCode::FORBIDDEN,
Json(ErrorResponse::new(ErrorCode::UserDeactivated, &self.to_string(), None)),
).into_response(),
}
}
}

View File

@ -1,2 +1,23 @@
use crate::types::error_code::ErrorCode;
pub mod api_error; pub mod api_error;
pub mod authentication_error;
pub mod registration_error; pub mod registration_error;
#[derive(Debug, serde::Serialize)]
pub struct ErrorResponse {
errcode: ErrorCode,
error: String,
#[serde(skip_serializing_if = "Option::is_none")]
retry_after_ms: Option<u64>,
}
impl ErrorResponse {
fn new(errcode: ErrorCode, error: &str, retry_after_ms: Option<u64>) -> Self {
Self {
errcode,
error: error.to_owned(),
retry_after_ms,
}
}
}

View File

@ -1,3 +1,9 @@
use axum::{http::StatusCode, response::IntoResponse, Json};
use crate::{responses::registration::RegistrationResponse, types::error_code::ErrorCode};
use super::ErrorResponse;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum RegistrationError { pub enum RegistrationError {
#[error("The homeserver requires additional authentication information")] #[error("The homeserver requires additional authentication information")]
@ -7,5 +13,35 @@ pub enum RegistrationError {
#[error("The desired user ID is not a valid user name")] #[error("The desired user ID is not a valid user name")]
InvalidUserId, InvalidUserId,
#[error("The desired user ID is already taken")] #[error("The desired user ID is already taken")]
UserIdTaken UserIdTaken,
}
impl IntoResponse for RegistrationError {
fn into_response(self) -> axum::response::Response {
match self {
RegistrationError::AdditionalAuthenticationInformation => (
StatusCode::UNAUTHORIZED,
Json(RegistrationResponse::user_interactive_authorization_info()),
)
.into_response(),
RegistrationError::InvalidUserId | RegistrationError::MissingUserId => (
StatusCode::BAD_REQUEST,
Json(ErrorResponse::new(
ErrorCode::InvalidUsername,
&self.to_string(),
None,
)),
)
.into_response(),
RegistrationError::UserIdTaken => (
StatusCode::BAD_REQUEST,
Json(ErrorResponse::new(
ErrorCode::UserInUse,
&self.to_string(),
None,
)),
)
.into_response(),
}
}
} }

View File

@ -2,22 +2,11 @@ use crate::types::{authentication_data::AuthenticationData, flow::Flow, identifi
#[derive(Debug, serde::Deserialize)] #[derive(Debug, serde::Deserialize)]
pub struct RegistrationRequest { pub struct RegistrationRequest {
#[serde(skip_serializing_if = "Option::is_none")]
auth: Option<AuthenticationData>, auth: Option<AuthenticationData>,
#[serde(skip_serializing_if = "Option::is_none")]
device_id: Option<String>, device_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
inhibit_login: Option<bool>, inhibit_login: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
initial_device_display_name: Option<String>, initial_device_display_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
password: Option<String>, password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
username: Option<String>, username: Option<String>,
} }

View File

@ -0,0 +1,26 @@
use axum::{response::IntoResponse, Json};
use crate::types::user_id::UserId;
#[derive(Debug, serde::Serialize)]
#[serde(untagged)]
pub enum AuthenticationResponse {
Success(AuthenticationSuccess)
}
#[derive(Debug, serde::Serialize)]
pub struct AuthenticationSuccess {
access_token: String,
device_id: String,
user_id: String,
}
impl AuthenticationSuccess {
pub fn new(access_token: &str, device_id: &str, user_id: &UserId) -> Self {
Self {
access_token: access_token.to_owned(),
device_id: device_id.to_owned(),
user_id: user_id.to_string(),
}
}
}

View File

@ -2,3 +2,4 @@ pub mod flow;
pub mod registration; pub mod registration;
pub mod username_available; pub mod username_available;
pub mod versions; pub mod versions;
pub mod authentication;

View File

@ -13,6 +13,8 @@ pub struct AuthenticationPassword {
identifier: Identifier, identifier: Identifier,
password: String, password: String,
user: Option<String>, user: Option<String>,
device_id: Option<String>,
initial_device_display_name: Option<String>,
} }
impl AuthenticationPassword { impl AuthenticationPassword {
@ -33,4 +35,16 @@ impl AuthenticationPassword {
pub fn user(&self) -> Option<&String> { pub fn user(&self) -> Option<&String> {
self.user.as_ref() self.user.as_ref()
} }
/// Get a reference to the authentication password's device id.
#[must_use]
pub fn device_id(&self) -> Option<&String> {
self.device_id.as_ref()
}
/// Get a mutable reference to the authentication password's initial device display name.
#[must_use]
pub fn initial_device_display_name_mut(&self) -> Option<&String> {
self.initial_device_display_name.as_ref()
}
} }

View File

@ -12,6 +12,7 @@ pub enum ErrorCode {
UserInUse, UserInUse,
InvalidUsername, InvalidUsername,
Exclusive, Exclusive,
UserDeactivated
} }
impl serde::Serialize for ErrorCode { impl serde::Serialize for ErrorCode {
@ -31,6 +32,7 @@ impl serde::Serialize for ErrorCode {
ErrorCode::UserInUse => "M_USER_IN_USE", ErrorCode::UserInUse => "M_USER_IN_USE",
ErrorCode::InvalidUsername => "M_INVALID_USERNAME", ErrorCode::InvalidUsername => "M_INVALID_USERNAME",
ErrorCode::Exclusive => "M_EXCLUSIVE", ErrorCode::Exclusive => "M_EXCLUSIVE",
ErrorCode::UserDeactivated => "M_USER_DEACTIVATED",
}) })
} }
} }