Skip to content

Commit

Permalink
Merge pull request #1 from commune-os/main
Browse files Browse the repository at this point in the history
merge: token aware routes with middleware (commune-sh#14)
  • Loading branch information
avdb13 authored Jan 25, 2024
2 parents 5e7593b + 5f55f71 commit 26aa000
Show file tree
Hide file tree
Showing 21 changed files with 354 additions and 67 deletions.
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

0 comments on commit 26aa000

Please sign in to comment.