diff --git a/crates/core/src/user/error.rs b/crates/core/src/account/error.rs similarity index 56% rename from crates/core/src/user/error.rs rename to crates/core/src/account/error.rs index 7b1a270..ce82e69 100644 --- a/crates/core/src/user/error.rs +++ b/crates/core/src/account/error.rs @@ -5,25 +5,25 @@ use validator::ValidationErrors; use crate::error::HttpStatusCode; #[derive(Debug, Error)] -pub enum UserErrorCode { +pub enum AccountErrorCode { #[error("Vaildation error. {0}")] ValidationError(#[from] ValidationErrors), #[error("The username {0} is already taken")] UsernameTaken(String), } -impl HttpStatusCode for UserErrorCode { +impl HttpStatusCode for AccountErrorCode { fn status_code(&self) -> StatusCode { match self { - UserErrorCode::ValidationError(_) => StatusCode::BAD_REQUEST, - UserErrorCode::UsernameTaken(_) => StatusCode::CONFLICT, + AccountErrorCode::ValidationError(_) => StatusCode::BAD_REQUEST, + AccountErrorCode::UsernameTaken(_) => StatusCode::CONFLICT, } } fn error_code(&self) -> &'static str { match self { - UserErrorCode::ValidationError(_) => "VALIDATION_ERROR", - UserErrorCode::UsernameTaken(_) => "USERNAME_TAKEN", + AccountErrorCode::ValidationError(_) => "VALIDATION_ERROR", + AccountErrorCode::UsernameTaken(_) => "USERNAME_TAKEN", } } } diff --git a/crates/core/src/user/mod.rs b/crates/core/src/account/mod.rs similarity index 100% rename from crates/core/src/user/mod.rs rename to crates/core/src/account/mod.rs diff --git a/crates/core/src/user/model.rs b/crates/core/src/account/model.rs similarity index 85% rename from crates/core/src/user/model.rs rename to crates/core/src/account/model.rs index 107ac12..bfac873 100644 --- a/crates/core/src/user/model.rs +++ b/crates/core/src/account/model.rs @@ -1,5 +1,5 @@ #[derive(Debug, Clone)] -pub struct User { +pub struct Account { pub username: String, pub email: String, pub session: String, diff --git a/crates/core/src/user/service.rs b/crates/core/src/account/service.rs similarity index 93% rename from crates/core/src/user/service.rs rename to crates/core/src/account/service.rs index 9a73ff9..831c60b 100644 --- a/crates/core/src/user/service.rs +++ b/crates/core/src/account/service.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use tracing::instrument; use url::Url; use validator::{Validate, ValidationError}; @@ -6,25 +8,20 @@ use matrix::admin::resources::user::{ ListUsersParams, ThreePid, User as MatrixUser, UserCreateDto, }; use matrix::admin::resources::user_id::UserId; -use matrix::admin::Client as MatrixAdminClient; +use matrix::Client as MatrixAdminClient; use crate::util::secret::Secret; use crate::util::time::timestamp; use crate::{Error, Result}; -use super::error::UserErrorCode; -use super::model::User; +use super::error::AccountErrorCode; +use super::model::Account; const DEFAULT_AVATAR_URL: &str = "https://via.placeholder.com/150"; const MIN_USERNAME_LENGTH: usize = 3; const MAX_USERNAME_LENGTH: usize = 12; const MIN_PASSWORD_LENGTH: usize = 8; -pub struct LoginDto { - pub username: String, - pub password: String, -} - #[derive(Debug, Validate)] pub struct CreateAccountDto { #[validate(custom = "CreateAccountDto::validate_username")] @@ -71,20 +68,20 @@ impl CreateAccountDto { } } -pub struct UserService { - admin: MatrixAdminClient, +pub struct AccountService { + admin: Arc, } -impl UserService { - pub fn new(admin: MatrixAdminClient) -> Self { +impl AccountService { + pub fn new(admin: Arc) -> Self { Self { admin } } #[instrument(skip(self, dto))] - pub async fn register(&self, dto: CreateAccountDto) -> Result { + pub async fn register(&self, dto: CreateAccountDto) -> Result { dto.validate().map_err(|err| { tracing::warn!(?err, "Failed to validate user creation dto"); - UserErrorCode::from(err) + AccountErrorCode::from(err) })?; let user_id = UserId::new(dto.username.clone(), self.admin.server_name().to_string()); @@ -102,7 +99,7 @@ impl UserService { })?; if !exists.users.is_empty() { - return Err(UserErrorCode::UsernameTaken(dto.username).into()); + return Err(AccountErrorCode::UsernameTaken(dto.username).into()); } let avatar_url = Url::parse(DEFAULT_AVATAR_URL).map_err(|err| { @@ -147,7 +144,7 @@ impl UserService { return Err(Error::Unknown); }; - Ok(User { + Ok(Account { username: displayname, email: threepid.address.to_owned(), session: dto.session, diff --git a/crates/core/src/auth/error.rs b/crates/core/src/auth/error.rs new file mode 100644 index 0000000..b895c13 --- /dev/null +++ b/crates/core/src/auth/error.rs @@ -0,0 +1,24 @@ +use http::StatusCode; +use thiserror::Error; + +use crate::error::HttpStatusCode; + +#[derive(Debug, Error)] +pub enum AuthErrorCode { + #[error("Provided credentials are not valid")] + InvalidCredentials, +} + +impl HttpStatusCode for AuthErrorCode { + fn status_code(&self) -> StatusCode { + match self { + AuthErrorCode::InvalidCredentials => StatusCode::BAD_REQUEST, + } + } + + fn error_code(&self) -> &'static str { + match self { + AuthErrorCode::InvalidCredentials => "INVALID_CREDENTIALS", + } + } +} diff --git a/crates/core/src/auth/mod.rs b/crates/core/src/auth/mod.rs new file mode 100644 index 0000000..03141d7 --- /dev/null +++ b/crates/core/src/auth/mod.rs @@ -0,0 +1,2 @@ +pub mod error; +pub mod service; diff --git a/crates/core/src/auth/service.rs b/crates/core/src/auth/service.rs new file mode 100644 index 0000000..b5fe323 --- /dev/null +++ b/crates/core/src/auth/service.rs @@ -0,0 +1,40 @@ +use std::sync::Arc; + +use matrix::client::resources::login::Login; +use matrix::Client as MatrixAdminClient; + +use crate::util::secret::Secret; +use crate::Result; + +pub struct LoginCredentials { + pub username: String, + pub password: Secret, +} + +pub struct LoginCredentialsResponse { + pub access_token: Secret, +} + +pub struct AuthService { + admin: Arc, +} + +impl AuthService { + pub fn new(admin: Arc) -> Self { + Self { admin } + } + + pub async fn login(&self, credentials: LoginCredentials) -> Result { + let login_response = Login::login_credentials( + &self.admin, + credentials.username, + credentials.password.inner(), + ) + .await + .unwrap(); + + Ok(LoginCredentialsResponse { + access_token: Secret::new(login_response.access_token), + }) + } +} diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index f29b621..ad5b854 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -1,7 +1,7 @@ use http::StatusCode; use thiserror::Error; -use crate::user::error::UserErrorCode; +use crate::account::error::AccountErrorCode; pub type Result = std::result::Result; @@ -13,13 +13,13 @@ pub trait HttpStatusCode { #[derive(Debug, Error)] pub enum Error { #[error("User Error. {0}")] - User(UserErrorCode), + User(AccountErrorCode), #[error("Unknown Error Occured")] Unknown, } -impl From for Error { - fn from(err: UserErrorCode) -> Self { +impl From for Error { + fn from(err: AccountErrorCode) -> Self { Error::User(err) } } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 5f1fdcf..2b09b69 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,14 +1,17 @@ +pub mod account; +pub mod auth; pub mod error; -pub mod user; pub mod util; pub use error::{Error, HttpStatusCode, Result}; use std::fmt::Debug; +use std::sync::Arc; -use matrix::admin::Client as MatrixAdminClient; +use matrix::Client as MatrixAdminClient; -use self::user::service::UserService; +use self::account::service::AccountService; +use self::auth::service::AuthService; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct CommuneConfig { @@ -19,25 +22,32 @@ pub struct CommuneConfig { } pub struct Commune { - pub user: UserService, + pub account: Arc, + pub auth: Arc, } impl Commune { - pub fn new>(config: C) -> Result { + pub async fn new>(config: C) -> Result { let config: CommuneConfig = config.into(); - let mut admin = MatrixAdminClient::new(config.synapse_host, config.synapse_server_name) + let mut admin = MatrixAdminClient::new(&config.synapse_host, &config.synapse_server_name) .map_err(|err| { - tracing::error!(?err, "Failed to create admin client"); + tracing::error!(?err, "Failed to create admin client"); + Error::Unknown + })?; + + admin + .set_token(&config.synapse_admin_token) + .map_err(|err| { + tracing::error!(?err, "Failed to set admin token"); Error::Unknown })?; - admin.set_token(config.synapse_admin_token).map_err(|err| { - tracing::error!(?err, "Failed to set admin token"); - Error::Unknown - })?; + let admin_client = Arc::new(admin); + let auth = AuthService::new(Arc::clone(&admin_client)); Ok(Self { - user: UserService::new(admin), + account: Arc::new(AccountService::new(Arc::clone(&admin_client))), + auth: Arc::new(auth), }) } } diff --git a/crates/matrix/Cargo.toml b/crates/matrix/Cargo.toml index 50ddad3..98c8fbb 100644 --- a/crates/matrix/Cargo.toml +++ b/crates/matrix/Cargo.toml @@ -8,7 +8,6 @@ publish = false async-trait = "0.1.74" hex = "0.4.3" hmac = "0.12.1" -matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", rev = "e43a25a" } serde_path_to_error = "0.1.14" serde_qs = "0.12.0" sha1 = "0.10.6" diff --git a/crates/matrix/src/admin/mod.rs b/crates/matrix/src/admin/mod.rs index 18bbbeb..6d0fe39 100644 --- a/crates/matrix/src/admin/mod.rs +++ b/crates/matrix/src/admin/mod.rs @@ -1,4 +1 @@ -pub mod client; pub mod resources; - -pub use client::Client; diff --git a/crates/matrix/src/admin/resources/token/shared_secret.rs b/crates/matrix/src/admin/resources/token/shared_secret.rs index b7c732a..c1f415c 100644 --- a/crates/matrix/src/admin/resources/token/shared_secret.rs +++ b/crates/matrix/src/admin/resources/token/shared_secret.rs @@ -19,7 +19,7 @@ use hmac::{Hmac, Mac}; use serde::{Deserialize, Serialize}; use sha1::Sha1; -use crate::admin::Client; +use crate::http::Client; type HmacSha1 = Hmac; diff --git a/crates/matrix/src/admin/resources/user.rs b/crates/matrix/src/admin/resources/user.rs index 52841e0..bfc684b 100644 --- a/crates/matrix/src/admin/resources/user.rs +++ b/crates/matrix/src/admin/resources/user.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use tracing::instrument; use url::Url; -use crate::admin::Client; +use crate::http::Client; use super::user_id::UserId; @@ -141,7 +141,6 @@ impl User { let resp = client .get_query("/_synapse/admin/v2/users", ¶ms) .await?; - println!("{:?}", resp); let data: ListUsersResponse = resp.json().await?; Ok(data) diff --git a/crates/matrix/src/client/mod.rs b/crates/matrix/src/client/mod.rs new file mode 100644 index 0000000..6d0fe39 --- /dev/null +++ b/crates/matrix/src/client/mod.rs @@ -0,0 +1 @@ +pub mod resources; diff --git a/crates/matrix/src/client/resources/login.rs b/crates/matrix/src/client/resources/login.rs new file mode 100644 index 0000000..5163fbe --- /dev/null +++ b/crates/matrix/src/client/resources/login.rs @@ -0,0 +1,48 @@ +use anyhow::Result; +use serde::{Deserialize, Serialize}; +use tracing::instrument; + +use crate::http::Client; + +#[derive(Debug, Serialize, Deserialize)] +pub struct LoginCredentials { + pub access_token: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LoginCredentialsPayload { + pub r#type: &'static str, + pub user: String, + pub password: String, +} + +pub struct Login; + +impl Login { + /// Retrieves an access token by logging in with Username and Password + /// + /// This is equivalent to executing: + /// + /// ```ignore + /// curl -sS -d '{"type":"m.login.password", "user":"X", "password":"Y"}' http://server:port/_matrix/client/v3/login + /// ``` + #[instrument(skip(client, username, password))] + pub async fn login_credentials( + client: &Client, + username: impl AsRef, + password: impl AsRef, + ) -> Result { + let resp = client + .post_json( + "/_matrix/client/v3/login", + &LoginCredentialsPayload { + r#type: "m.login.password", + user: username.as_ref().to_string(), + password: password.as_ref().to_string(), + }, + ) + .await?; + + Ok(resp.json().await?) + } +} diff --git a/crates/matrix/src/client/resources/mod.rs b/crates/matrix/src/client/resources/mod.rs new file mode 100644 index 0000000..320cbbb --- /dev/null +++ b/crates/matrix/src/client/resources/mod.rs @@ -0,0 +1 @@ +pub mod login; diff --git a/crates/matrix/src/admin/client.rs b/crates/matrix/src/http.rs similarity index 100% rename from crates/matrix/src/admin/client.rs rename to crates/matrix/src/http.rs diff --git a/crates/matrix/src/lib.rs b/crates/matrix/src/lib.rs index 68a704a..b04d625 100644 --- a/crates/matrix/src/lib.rs +++ b/crates/matrix/src/lib.rs @@ -2,17 +2,17 @@ //! //! Reexports `matrix_sdk` and provides implementations on Matrix Admin API. +mod http; + +pub use http::Client; + /// Implementation on the Administrator API of Matrix /// /// Refer: https://matrix-org.github.io/synapse/latest/usage/administration/index.html pub mod admin; -/// The official Matrix Rust SDK. -/// -/// # Project State -/// -/// As of today this SDK is still in beta and is not yet ready for production, -/// so we make use of the repo at a specific commit. +/// Implementation on the Client API of Matrix /// -/// Refer: https://github.com/matrix-org/matrix-rust-sdk -pub use matrix_sdk as sdk; +/// Different to the Matrix SDK, no user state is kept in the Client instance, +/// this is equivalent to making cURL requests to the Matrix server. +pub mod client; diff --git a/crates/server/src/config.rs b/crates/server/src/config.rs index 9f5fe94..3c25756 100644 --- a/crates/server/src/config.rs +++ b/crates/server/src/config.rs @@ -1,5 +1,10 @@ use commune::CommuneConfig; +pub const COMMUNE_SYNAPSE_HOST: &str = "COMMUNE_SYNAPSE_HOST"; +pub const COMMUNE_SYNAPSE_ADMIN_TOKEN: &str = "COMMUNE_SYNAPSE_ADMIN_TOKEN"; +pub const COMMUNE_SYNAPSE_SERVER_NAME: &str = "COMMUNE_SYNAPSE_SERVER_NAME"; +pub const COMMUNE_REGISTRATION_SHARED_SECRET: &str = "COMMUNE_REGISTRATION_SHARED_SECRET"; + pub struct ServerConfig { pub synapse_host: String, pub synapse_admin_token: String, @@ -10,10 +15,10 @@ pub struct ServerConfig { impl ServerConfig { pub fn from_env() -> ServerConfig { ServerConfig { - synapse_host: Self::var("COMMUNE_SYNAPSE_HOST"), - synapse_admin_token: Self::var("COMMUNE_SYNAPSE_ADMIN_TOKEN"), - synapse_server_name: Self::var("COMMUNE_SYNAPSE_SERVER_NAME"), - synapse_registration_shared_secret: Self::var("COMMUNE_REGISTRATION_SHARED_SECRET"), + synapse_host: Self::var(COMMUNE_SYNAPSE_HOST), + synapse_admin_token: Self::var(COMMUNE_SYNAPSE_ADMIN_TOKEN), + synapse_server_name: Self::var(COMMUNE_SYNAPSE_SERVER_NAME), + synapse_registration_shared_secret: Self::var(COMMUNE_REGISTRATION_SHARED_SECRET), } } diff --git a/crates/server/src/lib.rs b/crates/server/src/lib.rs index cba0070..726fcf3 100644 --- a/crates/server/src/lib.rs +++ b/crates/server/src/lib.rs @@ -12,7 +12,7 @@ use crate::services::Services; pub async fn serve(tcp: TcpListener) -> Result<()> { let config = ServerConfig::from_env(); - let services = Services::shared(config)?; + let services = Services::shared(config).await?; let router = make_router(); let router = router.with_state(services); diff --git a/crates/server/src/router/api/v1/account/create.rs b/crates/server/src/router/api/v1/account/create.rs index b1036b9..8a7de56 100644 --- a/crates/server/src/router/api/v1/account/create.rs +++ b/crates/server/src/router/api/v1/account/create.rs @@ -5,8 +5,8 @@ use axum::Json; use serde::{Deserialize, Serialize}; use tracing::instrument; -use commune::user::model::User; -use commune::user::service::CreateAccountDto; +use commune::account::model::Account; +use commune::account::service::CreateAccountDto; use crate::router::api::ApiError; use crate::services::SharedServices; @@ -14,13 +14,13 @@ use crate::services::SharedServices; #[instrument(skip(services, payload))] pub async fn handler( State(services): State, - Json(payload): Json, + Json(payload): Json, ) -> Response { let dto = CreateAccountDto::from(payload); - match services.commune.user.register(dto).await { - Ok(user) => { - let mut response = Json(UserRegisterResponse::from(user)).into_response(); + match services.commune.account.register(dto).await { + Ok(account) => { + let mut response = Json(AccountRegisterResponse::from(account)).into_response(); *response.status_mut() = StatusCode::CREATED; response @@ -33,14 +33,14 @@ pub async fn handler( } #[derive(Deserialize, Serialize)] -pub struct UserRegisterPayload { +pub struct AccountRegisterPayload { pub username: String, pub password: String, pub email: String, } -impl From for CreateAccountDto { - fn from(payload: UserRegisterPayload) -> Self { +impl From for CreateAccountDto { + fn from(payload: AccountRegisterPayload) -> Self { Self { username: payload.username, password: payload.password.into(), @@ -53,14 +53,14 @@ impl From for CreateAccountDto { } #[derive(Deserialize, Serialize)] -pub struct UserRegisterResponse { +pub struct AccountRegisterResponse { pub username: String, } -impl From for UserRegisterResponse { - fn from(user: User) -> Self { +impl From for AccountRegisterResponse { + fn from(acc: Account) -> Self { Self { - username: user.username, + username: acc.username, } } } diff --git a/crates/server/src/router/api/v1/account/login.rs b/crates/server/src/router/api/v1/account/login.rs new file mode 100644 index 0000000..a752bdb --- /dev/null +++ b/crates/server/src/router/api/v1/account/login.rs @@ -0,0 +1,60 @@ +use axum::extract::State; +use axum::http::StatusCode; +use axum::response::{IntoResponse, Response}; +use axum::Json; +use serde::{Deserialize, Serialize}; +use tracing::instrument; + +use commune::auth::service::{LoginCredentials, LoginCredentialsResponse}; + +use crate::router::api::ApiError; +use crate::services::SharedServices; + +#[instrument(skip(services, payload))] +pub async fn handler( + State(services): State, + Json(payload): Json, +) -> Response { + let login_credentials = LoginCredentials::from(payload); + + match services.commune.auth.login(login_credentials).await { + Ok(tokens) => { + let mut response = Json(AccountLoginResponse::from(tokens)).into_response(); + + *response.status_mut() = StatusCode::OK; + response + } + Err(err) => { + tracing::warn!(?err, "Failed to authenticate user"); + ApiError::from(err).into_response() + } + } +} + +#[derive(Deserialize, Serialize)] +pub struct AccountLoginPayload { + pub username: String, + pub password: String, +} + +impl From for LoginCredentials { + fn from(payload: AccountLoginPayload) -> Self { + Self { + username: payload.username, + password: payload.password.into(), + } + } +} + +#[derive(Deserialize, Serialize)] +pub struct AccountLoginResponse { + pub access_token: String, +} + +impl From for AccountLoginResponse { + fn from(tokens: LoginCredentialsResponse) -> Self { + Self { + access_token: tokens.access_token.to_string(), + } + } +} diff --git a/crates/server/src/router/api/v1/account/mod.rs b/crates/server/src/router/api/v1/account/mod.rs index 22ee352..f96b12d 100644 --- a/crates/server/src/router/api/v1/account/mod.rs +++ b/crates/server/src/router/api/v1/account/mod.rs @@ -1,4 +1,5 @@ pub mod create; +pub mod login; use axum::routing::post; use axum::Router; @@ -9,6 +10,8 @@ pub struct Account; impl Account { pub fn routes() -> Router { - Router::new().route("/", post(create::handler)) + Router::new() + .route("/", post(create::handler)) + .route("/login", post(login::handler)) } } diff --git a/crates/server/src/services.rs b/crates/server/src/services.rs index b215c8c..ce3542f 100644 --- a/crates/server/src/services.rs +++ b/crates/server/src/services.rs @@ -13,14 +13,14 @@ pub struct Services { } impl Services { - pub fn new(config: ServerConfig) -> Result { + pub async fn new(config: ServerConfig) -> Result { let commune_config: CommuneConfig = config.into(); - let commune = Commune::new(commune_config)?; + let commune = Commune::new(commune_config).await?; Ok(Self { commune }) } - pub fn shared(config: ServerConfig) -> Result { - Ok(Arc::new(Self::new(config)?)) + pub async fn shared(config: ServerConfig) -> Result { + Ok(Arc::new(Self::new(config).await?)) } } diff --git a/crates/test/Cargo.toml b/crates/test/Cargo.toml index 914a2af..935efbe 100644 --- a/crates/test/Cargo.toml +++ b/crates/test/Cargo.toml @@ -13,11 +13,12 @@ path = "src/lib.rs" # Workspace Dependencies axum = { workspace = true, features = ["tokio"] } dotenv = { workspace = true } -reqwest = { workspace = true, default-features = false, features = ["json", "stream", "multipart"] } +reqwest = { workspace = true, features = ["json", "stream", "multipart"] } serde = { workspace = true } tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros"] } url = { workspace = true } # Local Dependencies +core = { path = "../core" } matrix = { path = "../matrix" } server = { path = "../server" } diff --git a/crates/test/src/matrix/shared_token_registration.rs b/crates/test/src/matrix/shared_token_registration.rs index 6a30a7f..891fc41 100644 --- a/crates/test/src/matrix/shared_token_registration.rs +++ b/crates/test/src/matrix/shared_token_registration.rs @@ -6,13 +6,13 @@ use crate::tools::environment::Environment; #[tokio::test] async fn creates_user_using_shared_secret() { - let env = Environment::new(); + let env = Environment::new().await; let nonce = SharedSecretRegistration::get_nonce(&env.client) .await .unwrap() .nonce; let mac = SharedSecretRegistration::generate_mac( - env.registration_shared_secret, + env.config.synapse_registration_shared_secret.clone(), nonce.clone(), "steve".into(), "verysecure".into(), diff --git a/crates/test/src/server/api/v1/account/create.rs b/crates/test/src/server/api/v1/account/create.rs index 6c7a080..515a0a8 100644 --- a/crates/test/src/server/api/v1/account/create.rs +++ b/crates/test/src/server/api/v1/account/create.rs @@ -1,13 +1,15 @@ use reqwest::StatusCode; -use commune_server::router::api::v1::account::create::{UserRegisterPayload, UserRegisterResponse}; +use commune_server::router::api::v1::account::create::{ + AccountRegisterPayload, AccountRegisterResponse, +}; use crate::tools::http::HttpClient; #[tokio::test] async fn register_account_with_success() { let http_client = HttpClient::new().await; - let request_payload = UserRegisterPayload { + let request_payload = AccountRegisterPayload { username: String::from("john_wick"), password: String::from("P@ssW0Rd$"), email: String::from("donttrythisathome@gmail.com"), @@ -18,7 +20,7 @@ async fn register_account_with_success() { .send() .await; let response_status = response.status(); - let response_payload = response.json::().await; + let response_payload = response.json::().await; assert_eq!( response_status, diff --git a/crates/test/src/server/api/v1/account/login.rs b/crates/test/src/server/api/v1/account/login.rs new file mode 100644 index 0000000..b9046a7 --- /dev/null +++ b/crates/test/src/server/api/v1/account/login.rs @@ -0,0 +1,45 @@ +use reqwest::StatusCode; + +use commune::account::service::CreateAccountDto; +use commune_server::router::api::v1::account::login::{AccountLoginPayload, AccountLoginResponse}; + +use crate::tools::environment::Environment; +use crate::tools::http::HttpClient; + +#[tokio::test] +async fn logs_into_account() { + let environment = Environment::new().await; + + let username = "lucy".to_string(); + let password = "P@ssW0Rd$".to_string(); + + environment + .commune + .account + .register(CreateAccountDto { + username: username.clone(), + password: password.clone().into(), + email: "lucyinthesky@gmail.com".to_string(), + session: "TEST".to_string(), + code: "TEST".to_string(), + }) + .await + .unwrap(); + + let http_client = HttpClient::new().await; + + let response = http_client + .post("/api/v1/account/login") + .json(&AccountLoginPayload { username, password }) + .send() + .await; + let response_status = response.status(); + let response_payload = response.json::().await; + + assert_eq!( + response_status, + StatusCode::OK, + "should return 200 for successful login" + ); + assert!(!response_payload.access_token.is_empty(),) +} diff --git a/crates/test/src/server/api/v1/account/mod.rs b/crates/test/src/server/api/v1/account/mod.rs index 0f562a4..92b5bc1 100644 --- a/crates/test/src/server/api/v1/account/mod.rs +++ b/crates/test/src/server/api/v1/account/mod.rs @@ -1 +1,2 @@ mod create; +mod login; diff --git a/crates/test/src/tools/environment.rs b/crates/test/src/tools/environment.rs index 03aa39d..84f0e12 100644 --- a/crates/test/src/tools/environment.rs +++ b/crates/test/src/tools/environment.rs @@ -1,28 +1,40 @@ use std::env::var; -use matrix::admin::Client; - -const COMMUNE_REGISTRATION_SHARED_SECRET: &str = "COMMUNE_REGISTRATION_SHARED_SECRET"; -const COMMUNE_SYNAPSE_HOST: &str = "COMMUNE_SYNAPSE_HOST"; -const COMMUNE_SYNAPSE_SERVER_NAME: &str = "COMMUNE_SYNAPSE_SERVER_NAME"; +use commune::{Commune, CommuneConfig}; +use commune_server::config::{ + COMMUNE_REGISTRATION_SHARED_SECRET, COMMUNE_SYNAPSE_ADMIN_TOKEN, COMMUNE_SYNAPSE_HOST, + COMMUNE_SYNAPSE_SERVER_NAME, +}; +use matrix::Client; pub struct Environment { pub client: Client, - pub registration_shared_secret: String, + pub commune: Commune, + pub config: CommuneConfig, } impl Environment { - pub fn new() -> Self { + pub async fn new() -> Self { dotenv::dotenv().ok(); let synapse_host = Self::env_var(COMMUNE_SYNAPSE_HOST); let synapse_server_name = Self::env_var(COMMUNE_SYNAPSE_SERVER_NAME); - let client = Client::new(synapse_host, synapse_server_name).unwrap(); - let registration_shared_secret = Self::env_var(COMMUNE_REGISTRATION_SHARED_SECRET); + let synapse_admin_token = Self::env_var(COMMUNE_SYNAPSE_ADMIN_TOKEN); + let synapse_registration_shared_secret = Self::env_var(COMMUNE_REGISTRATION_SHARED_SECRET); + let client = Client::new(synapse_host.clone(), synapse_server_name.clone()).unwrap(); + + let config = CommuneConfig { + synapse_host, + synapse_admin_token, + synapse_server_name, + synapse_registration_shared_secret, + }; + let commune = Commune::new(config.clone()).await.unwrap(); Self { client, - registration_shared_secret, + commune, + config, } }