Skip to content

Commit

Permalink
feat: login endpoint and Account rename (#7)
Browse files Browse the repository at this point in the history
<!--
Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.
-->

Introduces the `POST /api/v1/account/login` endpoint to generate access
tokens for registered users and also migrates from `User` to `Account`.
  • Loading branch information
EstebanBorai authored Dec 3, 2023
1 parent 84021a8 commit bffda89
Show file tree
Hide file tree
Showing 30 changed files with 340 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}
}
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#[derive(Debug, Clone)]
pub struct User {
pub struct Account {
pub username: String,
pub email: String,
pub session: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::sync::Arc;

use tracing::instrument;
use url::Url;
use validator::{Validate, ValidationError};
Expand All @@ -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")]
Expand Down Expand Up @@ -71,20 +68,20 @@ impl CreateAccountDto {
}
}

pub struct UserService {
admin: MatrixAdminClient,
pub struct AccountService {
admin: Arc<MatrixAdminClient>,
}

impl UserService {
pub fn new(admin: MatrixAdminClient) -> Self {
impl AccountService {
pub fn new(admin: Arc<MatrixAdminClient>) -> Self {
Self { admin }
}

#[instrument(skip(self, dto))]
pub async fn register(&self, dto: CreateAccountDto) -> Result<User> {
pub async fn register(&self, dto: CreateAccountDto) -> Result<Account> {
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());
Expand All @@ -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| {
Expand Down Expand Up @@ -147,7 +144,7 @@ impl UserService {
return Err(Error::Unknown);
};

Ok(User {
Ok(Account {
username: displayname,
email: threepid.address.to_owned(),
session: dto.session,
Expand Down
24 changes: 24 additions & 0 deletions crates/core/src/auth/error.rs
Original file line number Diff line number Diff line change
@@ -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",
}
}
}
2 changes: 2 additions & 0 deletions crates/core/src/auth/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod error;
pub mod service;
40 changes: 40 additions & 0 deletions crates/core/src/auth/service.rs
Original file line number Diff line number Diff line change
@@ -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<MatrixAdminClient>,
}

impl AuthService {
pub fn new(admin: Arc<MatrixAdminClient>) -> Self {
Self { admin }
}

pub async fn login(&self, credentials: LoginCredentials) -> Result<LoginCredentialsResponse> {
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),
})
}
}
8 changes: 4 additions & 4 deletions crates/core/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use http::StatusCode;
use thiserror::Error;

use crate::user::error::UserErrorCode;
use crate::account::error::AccountErrorCode;

pub type Result<T> = std::result::Result<T, Error>;

Expand All @@ -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<UserErrorCode> for Error {
fn from(err: UserErrorCode) -> Self {
impl From<AccountErrorCode> for Error {
fn from(err: AccountErrorCode) -> Self {
Error::User(err)
}
}
Expand Down
34 changes: 22 additions & 12 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -19,25 +22,32 @@ pub struct CommuneConfig {
}

pub struct Commune {
pub user: UserService,
pub account: Arc<AccountService>,
pub auth: Arc<AuthService>,
}

impl Commune {
pub fn new<C: Into<CommuneConfig>>(config: C) -> Result<Self> {
pub async fn new<C: Into<CommuneConfig>>(config: C) -> Result<Self> {
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),
})
}
}
1 change: 0 additions & 1 deletion crates/matrix/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 0 additions & 3 deletions crates/matrix/src/admin/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
pub mod client;
pub mod resources;

pub use client::Client;
2 changes: 1 addition & 1 deletion crates/matrix/src/admin/resources/token/shared_secret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Sha1>;

Expand Down
3 changes: 1 addition & 2 deletions crates/matrix/src/admin/resources/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -141,7 +141,6 @@ impl User {
let resp = client
.get_query("/_synapse/admin/v2/users", &params)
.await?;
println!("{:?}", resp);
let data: ListUsersResponse = resp.json().await?;

Ok(data)
Expand Down
1 change: 1 addition & 0 deletions crates/matrix/src/client/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod resources;
48 changes: 48 additions & 0 deletions crates/matrix/src/client/resources/login.rs
Original file line number Diff line number Diff line change
@@ -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<str>,
password: impl AsRef<str>,
) -> Result<LoginCredentials> {
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?)
}
}
1 change: 1 addition & 0 deletions crates/matrix/src/client/resources/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod login;
File renamed without changes.
16 changes: 8 additions & 8 deletions crates/matrix/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Loading

0 comments on commit bffda89

Please sign in to comment.