Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

merge: token aware routes with middleware (#14) #1

Merged
merged 1 commit into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ resolver = "1"

[workspace.dependencies]
anyhow = "1.0.75"
axum = "0.6.19"
axum = { version = "0.7.4", features = ["tokio"] }
dotenv = "0.15.0"
http = "0.2.11"
reqwest = "0.11.22"
Expand Down
2 changes: 1 addition & 1 deletion crates/core/src/account/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ impl AccountService {
Ok(credentials.access_token)
}

pub async fn whoami(&self, access_token: Secret) -> Result<Account> {
pub async fn whoami(&self, access_token: &Secret) -> Result<Account> {
let session = Session::get(&self.admin, access_token.to_string())
.await
.map_err(|err| {
Expand Down
1 change: 1 addition & 0 deletions crates/server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ serde_json = "1.0.108"
axum = { workspace = true, features = ["tokio"] }
anyhow = { workspace = true }
dotenv = { workspace = true }
http = { workspace = true }
serde = { workspace = true, features = ["derive"] }
tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros"] }
tracing = { workspace = true }
Expand Down
5 changes: 3 additions & 2 deletions crates/server/src/bin/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::net::{SocketAddr, TcpListener};
use std::net::SocketAddr;

use anyhow::Result;
use dotenv::dotenv;
use tokio::net::TcpListener;

#[tokio::main]
async fn main() -> Result<()> {
Expand All @@ -12,7 +13,7 @@ async fn main() -> Result<()> {
tracing_subscriber::fmt::init();

let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
let tcp = TcpListener::bind(addr)?;
let tcp = TcpListener::bind(addr).await?;

tracing::info!("Listening on {}", addr);

Expand Down
15 changes: 7 additions & 8 deletions crates/server/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::net::TcpListener;

use anyhow::Result;
use tokio::net::TcpListener;

pub mod config;
pub mod router;
Expand All @@ -10,15 +9,15 @@ use crate::config::ServerConfig;
use crate::router::make_router;
use crate::services::Services;

pub async fn serve(tcp: TcpListener) -> Result<()> {
pub async fn serve(listener: TcpListener) -> Result<()> {
let config = ServerConfig::from_env();
let services = Services::shared(config).await?;
let router = make_router();
let router = router.with_state(services);
let router = make_router(services);

axum::Server::from_tcp(tcp)?
.serve(router.into_make_service())
.await?;
if let Err(err) = axum::serve(listener, router.into_make_service()).await {
tracing::error!(%err, "Failed to initialize the server");
panic!("An error ocurred running the server!");
}

Ok(())
}
51 changes: 35 additions & 16 deletions crates/server/src/router/api/mod.rs
Original file line number Diff line number Diff line change
@@ -1,46 +1,61 @@
pub mod v1;

use axum::http::StatusCode;
use axum::response::IntoResponse;
use axum::Json;
use axum::Router;
use http::StatusCode;
use serde::Deserialize;
use serde::Serialize;

use commune::error::HttpStatusCode;

use crate::services::SharedServices;

pub struct Api;

impl Api {
pub fn routes() -> Router<SharedServices> {
Router::new().nest("/v1", v1::V1::routes())
pub fn routes() -> Router {
Router::new().nest("/api", Router::new().nest("/v1", v1::V1::routes()))
}
}

#[derive(Debug, Serialize)]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ApiError {
message: String,
code: &'static str,
pub message: String,
pub code: String,
#[serde(skip)]
status: StatusCode,
pub status: StatusCode,
}

impl ApiError {
pub fn new(message: String, code: &'static str, status: StatusCode) -> Self {
pub fn new(message: String, code: String, status: StatusCode) -> Self {
Self {
message,
code,
status,
}
}

pub fn unauthorized() -> Self {
Self::new(
"You must be authenticated to access this resource".to_string(),
"UNAUTHORIZED".to_string(),
StatusCode::UNAUTHORIZED,
)
}

pub fn internal_server_error() -> Self {
Self::new(
"Internal server error".to_string(),
"INTERNAL_SERVER_ERROR".to_string(),
StatusCode::INTERNAL_SERVER_ERROR,
)
}
}

impl From<commune::error::Error> for ApiError {
fn from(err: commune::error::Error) -> Self {
Self {
message: err.to_string(),
code: err.error_code(),
code: err.error_code().to_string(),
status: err.status_code(),
}
}
Expand All @@ -57,18 +72,22 @@ impl From<anyhow::Error> for ApiError {
fn from(err: anyhow::Error) -> Self {
Self {
message: err.to_string(),
code: "UNKNOWN_ERROR",
code: "UNKNOWN_ERROR".to_string(),
status: StatusCode::INTERNAL_SERVER_ERROR,
}
}
}

impl IntoResponse for ApiError {
fn into_response(self) -> axum::response::Response {
let status = self.status;
let mut response = Json(self).into_response();
if let Ok(status) = axum::http::StatusCode::from_u16(self.status.as_u16()) {
let mut response = Json(self).into_response();

*response.status_mut() = status;
return response;
}

*response.status_mut() = status;
response
tracing::error!(status=%self.status, "Failed to convert status code to http::StatusCode");
ApiError::internal_server_error().into_response()
}
}
6 changes: 3 additions & 3 deletions crates/server/src/router/api/v1/account/email.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use axum::extract::{Path, State};
use axum::extract::Path;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use axum::{Extension, Json};
use serde::{Deserialize, Serialize};
use tracing::instrument;

Expand All @@ -10,7 +10,7 @@ use crate::services::SharedServices;

#[instrument(skip(services))]
pub async fn handler(
State(services): State<SharedServices>,
Extension(services): Extension<SharedServices>,
Path(email): Path<String>,
) -> Response {
match services.commune.account.is_email_available(&email).await {
Expand Down
12 changes: 3 additions & 9 deletions crates/server/src/router/api/v1/account/login.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use axum::{Extension, Json};
use commune::Error;
use serde::{Deserialize, Serialize};
use tracing::instrument;
Expand All @@ -15,7 +14,7 @@ use super::root::{AccountMatrixCredentials, AccountSpace};

#[instrument(skip(services, payload))]
pub async fn handler(
State(services): State<SharedServices>,
Extension(services): Extension<SharedServices>,
Json(payload): Json<AccountLoginPayload>,
) -> Response {
let login_credentials = LoginCredentials::from(payload);
Expand All @@ -28,12 +27,7 @@ pub async fn handler(
.into_response();
};

match services
.commune
.account
.whoami(tokens.access_token.clone())
.await
{
match services.commune.account.whoami(&tokens.access_token).await {
Ok(account) => {
let mut response = Json(AccountLoginResponse {
access_token: tokens.access_token.to_string(),
Expand Down
22 changes: 13 additions & 9 deletions crates/server/src/router/api/v1/account/mod.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
pub mod email;
pub mod login;
pub mod root;
pub mod session;
pub mod verify_code;
pub mod verify_code_email;

use axum::routing::{get, post};
use axum::Router;
use axum::{middleware, Router};

use crate::services::SharedServices;
use crate::router::middleware::auth;

pub struct Account;

impl Account {
pub fn routes() -> Router<SharedServices> {
let verify = Router::new()
.route("/code", post(verify_code::handler))
.route("/code/email", post(verify_code_email::handler));

pub fn routes() -> Router {
Router::new()
.route("/session", get(session::handler))
.route_layer(middleware::from_fn(auth))
.route("/", post(root::handler))
.route("/email/:email", get(email::handler))
.route("/login", post(login::handler))
.nest("/verify", verify)
.route("/email/:email", get(email::handler))
.nest(
"/verify",
Router::new()
.route("/code", post(verify_code::handler))
.route("/code/email", post(verify_code_email::handler)),
)
}
}
5 changes: 2 additions & 3 deletions crates/server/src/router/api/v1/account/root.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use axum::{Extension, Json};

use serde::{Deserialize, Serialize};
use tracing::instrument;
Expand All @@ -16,7 +15,7 @@ use crate::services::SharedServices;

#[instrument(skip(services, payload))]
pub async fn handler(
State(services): State<SharedServices>,
Extension(services): Extension<SharedServices>,
Json(payload): Json<AccountRegisterPayload>,
) -> Response {
let dto = CreateAccountDto::from(payload);
Expand Down
46 changes: 46 additions & 0 deletions crates/server/src/router/api/v1/account/session.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use axum::response::{IntoResponse, Response};
use axum::{Extension, Json};
use serde::{Deserialize, Serialize};
use tracing::instrument;

use commune::account::model::Account;

use crate::router::middleware::AccessToken;

use super::root::{AccountMatrixCredentials, AccountSpace};

#[instrument(skip(account))]
pub async fn handler(
Extension(account): Extension<Account>,
Extension(access_token): Extension<AccessToken>,
) -> Response {
let response = Json(AccountSessionResponse {
credentials: AccountMatrixCredentials {
username: account.username,
display_name: account.display_name,
avatar_url: account.avatar_url,
access_token: access_token.to_string(),
matrix_access_token: access_token.to_string(),
matrix_user_id: account.user_id.to_string(),
matrix_device_id: String::new(),
user_space_id: String::new(),
email: account.email,
age: account.age,
admin: account.admin,
verified: account.verified,
},
rooms: vec![],
spaces: vec![],
valid: true,
});

response.into_response()
}

#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct AccountSessionResponse {
pub credentials: AccountMatrixCredentials,
pub rooms: Vec<String>,
pub spaces: Vec<AccountSpace>,
pub valid: bool,
}
5 changes: 2 additions & 3 deletions crates/server/src/router/api/v1/account/verify_code.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use axum::{Extension, Json};
use commune::account::error::AccountErrorCode;
use commune::Error;
use serde::{Deserialize, Serialize};
Expand All @@ -15,7 +14,7 @@ use crate::services::SharedServices;

#[instrument(skip(services, payload))]
pub async fn handler(
State(services): State<SharedServices>,
Extension(services): Extension<SharedServices>,
Json(payload): Json<AccountVerifyCodePayload>,
) -> Response {
let dto = SendCodeDto::from(payload);
Expand Down
5 changes: 2 additions & 3 deletions crates/server/src/router/api/v1/account/verify_code_email.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use axum::{Extension, Json};
use commune::account::error::AccountErrorCode;
use commune::util::secret::Secret;
use commune::Error;
Expand All @@ -16,7 +15,7 @@ use crate::services::SharedServices;

#[instrument(skip(services, payload))]
pub async fn handler(
State(services): State<SharedServices>,
Extension(services): Extension<SharedServices>,
Json(payload): Json<AccountVerifyCodeEmailPayload>,
) -> Response {
let dto = VerifyCodeDto::from(payload);
Expand Down
4 changes: 1 addition & 3 deletions crates/server/src/router/api/v1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ pub mod account;

use axum::Router;

use crate::services::SharedServices;

pub struct V1;

impl V1 {
pub fn routes() -> Router<SharedServices> {
pub fn routes() -> Router {
Router::new().nest("/account", account::Account::routes())
}
}
Loading