You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-11-20 12:02:22 +03:00
Axum migration: /account/* routes
This commit is contained in:
@@ -114,7 +114,10 @@ pub trait CsrfExt {
|
|||||||
impl<K> CsrfExt for PrivateCookieJar<K> {
|
impl<K> CsrfExt for PrivateCookieJar<K> {
|
||||||
fn csrf_token(self) -> (CsrfToken, Self) {
|
fn csrf_token(self) -> (CsrfToken, Self) {
|
||||||
let jar = self;
|
let jar = self;
|
||||||
let cookie = jar.get("csrf").unwrap_or_else(|| Cookie::new("csrf", ""));
|
let mut cookie = jar.get("csrf").unwrap_or_else(|| Cookie::new("csrf", ""));
|
||||||
|
cookie.set_path("/");
|
||||||
|
cookie.set_http_only(true);
|
||||||
|
|
||||||
let new_token = cookie
|
let new_token = cookie
|
||||||
.decode()
|
.decode()
|
||||||
.ok()
|
.ok()
|
||||||
|
|||||||
@@ -76,9 +76,11 @@ pub trait SessionInfoExt {
|
|||||||
impl<K> SessionInfoExt for PrivateCookieJar<K> {
|
impl<K> SessionInfoExt for PrivateCookieJar<K> {
|
||||||
fn session_info(self) -> (SessionInfo, Self) {
|
fn session_info(self) -> (SessionInfo, Self) {
|
||||||
let jar = self;
|
let jar = self;
|
||||||
let cookie = jar
|
let mut cookie = jar
|
||||||
.get("session")
|
.get("session")
|
||||||
.unwrap_or_else(|| Cookie::new("session", ""));
|
.unwrap_or_else(|| Cookie::new("session", ""));
|
||||||
|
cookie.set_path("/");
|
||||||
|
cookie.set_http_only(true);
|
||||||
let session_info = cookie.decode().unwrap_or_default();
|
let session_info = cookie.decode().unwrap_or_default();
|
||||||
|
|
||||||
let cookie = cookie.encode(&session_info);
|
let cookie = cookie.encode(&session_info);
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ mod health;
|
|||||||
mod oauth2;
|
mod oauth2;
|
||||||
mod views;
|
mod views;
|
||||||
|
|
||||||
use self::{oauth2::filter as oauth2, views::filter as views};
|
use self::oauth2::filter as oauth2;
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn root(
|
pub fn root(
|
||||||
@@ -47,20 +47,11 @@ pub fn root(
|
|||||||
templates: &Templates,
|
templates: &Templates,
|
||||||
key_store: &Arc<StaticKeystore>,
|
key_store: &Arc<StaticKeystore>,
|
||||||
encrypter: &Encrypter,
|
encrypter: &Encrypter,
|
||||||
mailer: &Mailer,
|
|
||||||
config: &RootConfig,
|
config: &RootConfig,
|
||||||
) -> BoxedFilter<(impl Reply,)> {
|
) -> BoxedFilter<(impl Reply,)> {
|
||||||
let oauth2 = oauth2(pool, templates, key_store, encrypter, &config.http);
|
let oauth2 = oauth2(pool, templates, key_store, encrypter, &config.http);
|
||||||
let views = views(
|
|
||||||
pool,
|
|
||||||
templates,
|
|
||||||
mailer,
|
|
||||||
encrypter,
|
|
||||||
&config.http,
|
|
||||||
&config.csrf,
|
|
||||||
);
|
|
||||||
|
|
||||||
let filter = views.or(oauth2);
|
let filter = oauth2;
|
||||||
|
|
||||||
filter.with(warp::log(module_path!())).boxed()
|
filter.with(warp::log(module_path!())).boxed()
|
||||||
}
|
}
|
||||||
@@ -96,6 +87,15 @@ where
|
|||||||
get(self::views::register::get).post(self::views::register::post),
|
get(self::views::register::get).post(self::views::register::post),
|
||||||
)
|
)
|
||||||
.route("/verify/:code", get(self::views::verify::get))
|
.route("/verify/:code", get(self::views::verify::get))
|
||||||
|
.route("/account", get(self::views::account::get))
|
||||||
|
.route(
|
||||||
|
"/account/password",
|
||||||
|
get(self::views::account::password::get).post(self::views::account::password::post),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/account/emails",
|
||||||
|
get(self::views::account::emails::get).post(self::views::account::emails::post),
|
||||||
|
)
|
||||||
.fallback(mas_static_files::Assets)
|
.fallback(mas_static_files::Assets)
|
||||||
.layer(Extension(pool.clone()))
|
.layer(Extension(pool.clone()))
|
||||||
.layer(Extension(templates.clone()))
|
.layer(Extension(templates.clone()))
|
||||||
|
|||||||
@@ -12,8 +12,16 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
extract::{Extension, Form},
|
||||||
|
response::{Html, IntoResponse, Redirect, Response},
|
||||||
|
};
|
||||||
use lettre::{message::Mailbox, Address};
|
use lettre::{message::Mailbox, Address};
|
||||||
use mas_config::{CsrfConfig, Encrypter, HttpConfig};
|
use mas_axum_utils::{
|
||||||
|
csrf::{CsrfExt, ProtectedForm},
|
||||||
|
fancy_error, FancyError, PrivateCookieJar, SessionInfoExt, UrlBuilder,
|
||||||
|
};
|
||||||
|
use mas_config::Encrypter;
|
||||||
use mas_data_model::{BrowserSession, User, UserEmail};
|
use mas_data_model::{BrowserSession, User, UserEmail};
|
||||||
use mas_email::Mailer;
|
use mas_email::Mailer;
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
@@ -24,99 +32,70 @@ use mas_storage::{
|
|||||||
PostgresqlBackend,
|
PostgresqlBackend,
|
||||||
};
|
};
|
||||||
use mas_templates::{AccountEmailsContext, EmailVerificationContext, TemplateContext, Templates};
|
use mas_templates::{AccountEmailsContext, EmailVerificationContext, TemplateContext, Templates};
|
||||||
use mas_warp_utils::{
|
|
||||||
errors::WrapError,
|
|
||||||
filters::{
|
|
||||||
self,
|
|
||||||
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
|
||||||
csrf::{protected_form, updated_csrf_token},
|
|
||||||
database::{connection, transaction},
|
|
||||||
session::session,
|
|
||||||
url_builder::{url_builder, UrlBuilder},
|
|
||||||
with_templates, CsrfToken,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::{pool::PoolConnection, PgExecutor, PgPool, Postgres, Transaction};
|
use sqlx::{PgExecutor, PgPool};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
use warp::{filters::BoxedFilter, reply::html, Filter, Rejection, Reply};
|
|
||||||
|
|
||||||
pub(super) fn filter(
|
use crate::views::LoginRequest;
|
||||||
pool: &PgPool,
|
|
||||||
templates: &Templates,
|
|
||||||
mailer: &Mailer,
|
|
||||||
encrypter: &Encrypter,
|
|
||||||
http_config: &HttpConfig,
|
|
||||||
csrf_config: &CsrfConfig,
|
|
||||||
) -> BoxedFilter<(Box<dyn Reply>,)> {
|
|
||||||
let mailer = mailer.clone();
|
|
||||||
|
|
||||||
let get = with_templates(templates)
|
|
||||||
.and(filters::trace::name("GET /account/emails"))
|
|
||||||
.and(encrypted_cookie_saver(encrypter))
|
|
||||||
.and(updated_csrf_token(encrypter, csrf_config))
|
|
||||||
.and(session(pool, encrypter))
|
|
||||||
.and(connection(pool))
|
|
||||||
.and_then(get);
|
|
||||||
|
|
||||||
let post = with_templates(templates)
|
|
||||||
.and(filters::trace::name("POST /account/emails"))
|
|
||||||
.and(warp::any().map(move || mailer.clone()))
|
|
||||||
.and(url_builder(http_config))
|
|
||||||
.and(encrypted_cookie_saver(encrypter))
|
|
||||||
.and(updated_csrf_token(encrypter, csrf_config))
|
|
||||||
.and(session(pool, encrypter))
|
|
||||||
.and(transaction(pool))
|
|
||||||
.and(protected_form(encrypter))
|
|
||||||
.and_then(post);
|
|
||||||
|
|
||||||
let get = warp::get().and(get);
|
|
||||||
let post = warp::post().and(post);
|
|
||||||
let filter = get.or(post).unify();
|
|
||||||
|
|
||||||
warp::path!("emails").and(filter).boxed()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
#[serde(tag = "action", rename_all = "snake_case")]
|
#[serde(tag = "action", rename_all = "snake_case")]
|
||||||
enum Form {
|
pub enum ManagementForm {
|
||||||
Add { email: String },
|
Add { email: String },
|
||||||
ResendConfirmation { data: String },
|
ResendConfirmation { data: String },
|
||||||
SetPrimary { data: String },
|
SetPrimary { data: String },
|
||||||
Remove { data: String },
|
Remove { data: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get(
|
pub(crate) async fn get(
|
||||||
templates: Templates,
|
Extension(templates): Extension<Templates>,
|
||||||
cookie_saver: EncryptedCookieSaver,
|
Extension(pool): Extension<PgPool>,
|
||||||
csrf_token: CsrfToken,
|
cookie_jar: PrivateCookieJar<Encrypter>,
|
||||||
session: BrowserSession<PostgresqlBackend>,
|
) -> Result<Response, FancyError> {
|
||||||
mut conn: PoolConnection<Postgres>,
|
let mut conn = pool
|
||||||
) -> Result<Box<dyn Reply>, Rejection> {
|
.acquire()
|
||||||
render(templates, cookie_saver, csrf_token, session, &mut conn).await
|
.await
|
||||||
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
|
let (session_info, cookie_jar) = cookie_jar.session_info();
|
||||||
|
|
||||||
|
let maybe_session = session_info
|
||||||
|
.load_session(&mut conn)
|
||||||
|
.await
|
||||||
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
|
if let Some(session) = maybe_session {
|
||||||
|
render(templates, session, cookie_jar, &mut conn).await
|
||||||
|
} else {
|
||||||
|
let login = LoginRequest::default();
|
||||||
|
let login = login.build_uri().map_err(fancy_error(templates.clone()))?;
|
||||||
|
Ok((cookie_jar.headers(), Redirect::to(login)).into_response())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn render(
|
async fn render(
|
||||||
templates: Templates,
|
templates: Templates,
|
||||||
cookie_saver: EncryptedCookieSaver,
|
|
||||||
csrf_token: CsrfToken,
|
|
||||||
session: BrowserSession<PostgresqlBackend>,
|
session: BrowserSession<PostgresqlBackend>,
|
||||||
|
cookie_jar: PrivateCookieJar<Encrypter>,
|
||||||
executor: impl PgExecutor<'_>,
|
executor: impl PgExecutor<'_>,
|
||||||
) -> Result<Box<dyn Reply>, Rejection> {
|
) -> Result<Response, FancyError> {
|
||||||
|
let (csrf_token, cookie_jar) = cookie_jar.csrf_token();
|
||||||
|
|
||||||
let emails = get_user_emails(executor, &session.user)
|
let emails = get_user_emails(executor, &session.user)
|
||||||
.await
|
.await
|
||||||
.wrap_error()?;
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
let ctx = AccountEmailsContext::new(emails)
|
let ctx = AccountEmailsContext::new(emails)
|
||||||
.with_session(session)
|
.with_session(session)
|
||||||
.with_csrf(csrf_token.form_value());
|
.with_csrf(csrf_token.form_value());
|
||||||
|
|
||||||
let content = templates.render_account_emails(&ctx).await?;
|
let content = templates
|
||||||
let reply = html(content);
|
.render_account_emails(&ctx)
|
||||||
let reply = cookie_saver.save_encrypted(&csrf_token, reply)?;
|
.await
|
||||||
|
.map_err(fancy_error(templates))?;
|
||||||
|
|
||||||
Ok(Box::new(reply))
|
Ok((cookie_jar.headers(), Html(content)).into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn start_email_verification(
|
async fn start_email_verification(
|
||||||
@@ -150,59 +129,80 @@ async fn start_email_verification(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
pub(crate) async fn post(
|
||||||
async fn post(
|
Extension(templates): Extension<Templates>,
|
||||||
templates: Templates,
|
Extension(pool): Extension<PgPool>,
|
||||||
mailer: Mailer,
|
Extension(url_builder): Extension<UrlBuilder>,
|
||||||
url_builder: UrlBuilder,
|
Extension(mailer): Extension<Mailer>,
|
||||||
cookie_saver: EncryptedCookieSaver,
|
cookie_jar: PrivateCookieJar<Encrypter>,
|
||||||
csrf_token: CsrfToken,
|
Form(form): Form<ProtectedForm<ManagementForm>>,
|
||||||
mut session: BrowserSession<PostgresqlBackend>,
|
) -> Result<Response, FancyError> {
|
||||||
mut txn: Transaction<'_, Postgres>,
|
let mut txn = pool.begin().await.map_err(fancy_error(templates.clone()))?;
|
||||||
form: Form,
|
|
||||||
) -> Result<Box<dyn Reply>, Rejection> {
|
let (session_info, cookie_jar) = cookie_jar.session_info();
|
||||||
|
|
||||||
|
let maybe_session = session_info
|
||||||
|
.load_session(&mut txn)
|
||||||
|
.await
|
||||||
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
|
let mut session = if let Some(session) = maybe_session {
|
||||||
|
session
|
||||||
|
} else {
|
||||||
|
let login = LoginRequest::default();
|
||||||
|
let login = login.build_uri().map_err(fancy_error(templates.clone()))?;
|
||||||
|
return Ok((cookie_jar.headers(), Redirect::to(login)).into_response());
|
||||||
|
};
|
||||||
|
|
||||||
|
let form = cookie_jar
|
||||||
|
.verify_form(form)
|
||||||
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
match form {
|
match form {
|
||||||
Form::Add { email } => {
|
ManagementForm::Add { email } => {
|
||||||
let user_email = add_user_email(&mut txn, &session.user, email)
|
let user_email = add_user_email(&mut txn, &session.user, email)
|
||||||
.await
|
.await
|
||||||
.wrap_error()?;
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
start_email_verification(&mailer, &url_builder, &mut txn, &session.user, &user_email)
|
start_email_verification(&mailer, &url_builder, &mut txn, &session.user, &user_email)
|
||||||
.await
|
.await
|
||||||
.wrap_error()?;
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
}
|
}
|
||||||
Form::Remove { data } => {
|
ManagementForm::Remove { data } => {
|
||||||
let id = data.parse().wrap_error()?;
|
let id = data.parse().map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
let email = get_user_email(&mut txn, &session.user, id)
|
let email = get_user_email(&mut txn, &session.user, id)
|
||||||
.await
|
.await
|
||||||
.wrap_error()?;
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
remove_user_email(&mut txn, email).await.wrap_error()?;
|
remove_user_email(&mut txn, email)
|
||||||
|
.await
|
||||||
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
}
|
}
|
||||||
Form::ResendConfirmation { data } => {
|
ManagementForm::ResendConfirmation { data } => {
|
||||||
let id: i64 = data.parse().wrap_error()?;
|
let id = data.parse().map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
let user_email = get_user_email(&mut txn, &session.user, id)
|
let user_email = get_user_email(&mut txn, &session.user, id)
|
||||||
.await
|
.await
|
||||||
.wrap_error()?;
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
start_email_verification(&mailer, &url_builder, &mut txn, &session.user, &user_email)
|
start_email_verification(&mailer, &url_builder, &mut txn, &session.user, &user_email)
|
||||||
.await
|
.await
|
||||||
.wrap_error()?;
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
}
|
}
|
||||||
Form::SetPrimary { data } => {
|
ManagementForm::SetPrimary { data } => {
|
||||||
let id = data.parse().wrap_error()?;
|
let id = data.parse().map_err(fancy_error(templates.clone()))?;
|
||||||
let email = get_user_email(&mut txn, &session.user, id)
|
let email = get_user_email(&mut txn, &session.user, id)
|
||||||
.await
|
.await
|
||||||
.wrap_error()?;
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
set_user_email_as_primary(&mut txn, &email)
|
set_user_email_as_primary(&mut txn, &email)
|
||||||
.await
|
.await
|
||||||
.wrap_error()?;
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
session.user.primary_email = Some(email);
|
session.user.primary_email = Some(email);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let reply = render(templates, cookie_saver, csrf_token, session, &mut txn).await?;
|
let reply = render(templates.clone(), session, cookie_jar, &mut txn).await?;
|
||||||
|
|
||||||
txn.commit().await.wrap_error()?;
|
txn.commit().await.map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
Ok(reply)
|
Ok(reply)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021-2022 The Matrix.org Foundation C.I.C.
|
// Copyright 2021, 2022 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -12,81 +12,63 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
mod emails;
|
pub mod emails;
|
||||||
mod password;
|
pub mod password;
|
||||||
|
|
||||||
use mas_config::{CsrfConfig, Encrypter, HttpConfig};
|
use axum::{
|
||||||
use mas_data_model::BrowserSession;
|
extract::Extension,
|
||||||
use mas_email::Mailer;
|
response::{Html, IntoResponse, Redirect, Response},
|
||||||
use mas_storage::{
|
|
||||||
user::{count_active_sessions, get_user_emails},
|
|
||||||
PostgresqlBackend,
|
|
||||||
};
|
};
|
||||||
|
use mas_axum_utils::{csrf::CsrfExt, fancy_error, FancyError, PrivateCookieJar, SessionInfoExt};
|
||||||
|
use mas_config::Encrypter;
|
||||||
|
use mas_storage::user::{count_active_sessions, get_user_emails};
|
||||||
use mas_templates::{AccountContext, TemplateContext, Templates};
|
use mas_templates::{AccountContext, TemplateContext, Templates};
|
||||||
use mas_warp_utils::{
|
use sqlx::PgPool;
|
||||||
errors::WrapError,
|
|
||||||
filters::{
|
use super::LoginRequest;
|
||||||
self,
|
|
||||||
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
pub(crate) async fn get(
|
||||||
csrf::updated_csrf_token,
|
Extension(templates): Extension<Templates>,
|
||||||
database::connection,
|
Extension(pool): Extension<PgPool>,
|
||||||
session::session,
|
cookie_jar: PrivateCookieJar<Encrypter>,
|
||||||
with_templates, CsrfToken,
|
) -> Result<Response, FancyError> {
|
||||||
},
|
let mut conn = pool
|
||||||
|
.acquire()
|
||||||
|
.await
|
||||||
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
|
let (csrf_token, cookie_jar) = cookie_jar.csrf_token();
|
||||||
|
let (session_info, cookie_jar) = cookie_jar.session_info();
|
||||||
|
|
||||||
|
let maybe_session = session_info
|
||||||
|
.load_session(&mut conn)
|
||||||
|
.await
|
||||||
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
|
let session = if let Some(session) = maybe_session {
|
||||||
|
session
|
||||||
|
} else {
|
||||||
|
let login = LoginRequest::default();
|
||||||
|
let login = login.build_uri().map_err(fancy_error(templates.clone()))?;
|
||||||
|
return Ok((cookie_jar.headers(), Redirect::to(login)).into_response());
|
||||||
};
|
};
|
||||||
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
|
||||||
use warp::{filters::BoxedFilter, reply::html, Filter, Rejection, Reply};
|
|
||||||
|
|
||||||
use self::{emails::filter as emails, password::filter as password};
|
|
||||||
|
|
||||||
pub(super) fn filter(
|
|
||||||
pool: &PgPool,
|
|
||||||
templates: &Templates,
|
|
||||||
mailer: &Mailer,
|
|
||||||
encrypter: &Encrypter,
|
|
||||||
http_config: &HttpConfig,
|
|
||||||
csrf_config: &CsrfConfig,
|
|
||||||
) -> BoxedFilter<(Box<dyn Reply>,)> {
|
|
||||||
let get = warp::get()
|
|
||||||
.and(filters::trace::name("GET /account"))
|
|
||||||
.and(with_templates(templates))
|
|
||||||
.and(encrypted_cookie_saver(encrypter))
|
|
||||||
.and(updated_csrf_token(encrypter, csrf_config))
|
|
||||||
.and(session(pool, encrypter))
|
|
||||||
.and(connection(pool))
|
|
||||||
.and_then(get);
|
|
||||||
|
|
||||||
let index = warp::path::end().and(get);
|
|
||||||
let password = password(pool, templates, encrypter, csrf_config);
|
|
||||||
let emails = emails(pool, templates, mailer, encrypter, http_config, csrf_config);
|
|
||||||
|
|
||||||
let filter = index.or(password).unify().or(emails).unify();
|
|
||||||
|
|
||||||
warp::path::path("account").and(filter).boxed()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get(
|
|
||||||
templates: Templates,
|
|
||||||
cookie_saver: EncryptedCookieSaver,
|
|
||||||
csrf_token: CsrfToken,
|
|
||||||
session: BrowserSession<PostgresqlBackend>,
|
|
||||||
mut conn: PoolConnection<Postgres>,
|
|
||||||
) -> Result<Box<dyn Reply>, Rejection> {
|
|
||||||
let active_sessions = count_active_sessions(&mut conn, &session.user)
|
let active_sessions = count_active_sessions(&mut conn, &session.user)
|
||||||
.await
|
.await
|
||||||
.wrap_error()?;
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
let emails = get_user_emails(&mut conn, &session.user)
|
let emails = get_user_emails(&mut conn, &session.user)
|
||||||
.await
|
.await
|
||||||
.wrap_error()?;
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
let ctx = AccountContext::new(active_sessions, emails)
|
let ctx = AccountContext::new(active_sessions, emails)
|
||||||
.with_session(session)
|
.with_session(session)
|
||||||
.with_csrf(csrf_token.form_value());
|
.with_csrf(csrf_token.form_value());
|
||||||
|
|
||||||
let content = templates.render_account_index(&ctx).await?;
|
let content = templates
|
||||||
let reply = html(content);
|
.render_account_index(&ctx)
|
||||||
let reply = cookie_saver.save_encrypted(&csrf_token, reply)?;
|
.await
|
||||||
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
Ok(Box::new(reply))
|
Ok((cookie_jar.headers(), Html(content)).into_response())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,117 +13,122 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use argon2::Argon2;
|
use argon2::Argon2;
|
||||||
use mas_config::{CsrfConfig, Encrypter};
|
use axum::{
|
||||||
|
extract::{Extension, Form},
|
||||||
|
response::{Html, IntoResponse, Redirect, Response},
|
||||||
|
};
|
||||||
|
use mas_axum_utils::{
|
||||||
|
csrf::{CsrfExt, ProtectedForm},
|
||||||
|
fancy_error, FancyError, PrivateCookieJar, SessionInfoExt,
|
||||||
|
};
|
||||||
|
use mas_config::Encrypter;
|
||||||
use mas_data_model::BrowserSession;
|
use mas_data_model::BrowserSession;
|
||||||
use mas_storage::{
|
use mas_storage::{
|
||||||
user::{authenticate_session, set_password},
|
user::{authenticate_session, set_password},
|
||||||
PostgresqlBackend,
|
PostgresqlBackend,
|
||||||
};
|
};
|
||||||
use mas_templates::{EmptyContext, TemplateContext, Templates};
|
use mas_templates::{EmptyContext, TemplateContext, Templates};
|
||||||
use mas_warp_utils::{
|
|
||||||
errors::WrapError,
|
|
||||||
filters::{
|
|
||||||
self,
|
|
||||||
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
|
||||||
csrf::{protected_form, updated_csrf_token},
|
|
||||||
database::transaction,
|
|
||||||
session::session,
|
|
||||||
with_templates, CsrfToken,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::{PgPool, Postgres, Transaction};
|
use sqlx::PgPool;
|
||||||
use warp::{filters::BoxedFilter, reply::html, Filter, Rejection, Reply};
|
|
||||||
|
|
||||||
pub(super) fn filter(
|
use crate::views::LoginRequest;
|
||||||
pool: &PgPool,
|
|
||||||
templates: &Templates,
|
|
||||||
encrypter: &Encrypter,
|
|
||||||
csrf_config: &CsrfConfig,
|
|
||||||
) -> BoxedFilter<(Box<dyn Reply>,)> {
|
|
||||||
let get = with_templates(templates)
|
|
||||||
.and(encrypted_cookie_saver(encrypter))
|
|
||||||
.and(updated_csrf_token(encrypter, csrf_config))
|
|
||||||
.and(session(pool, encrypter))
|
|
||||||
.and_then(get);
|
|
||||||
|
|
||||||
let post = with_templates(templates)
|
|
||||||
.and(encrypted_cookie_saver(encrypter))
|
|
||||||
.and(updated_csrf_token(encrypter, csrf_config))
|
|
||||||
.and(session(pool, encrypter))
|
|
||||||
.and(transaction(pool))
|
|
||||||
.and(protected_form(encrypter))
|
|
||||||
.and_then(post);
|
|
||||||
|
|
||||||
let get = warp::get()
|
|
||||||
.and(get)
|
|
||||||
.and(filters::trace::name("GET /account/passwords"));
|
|
||||||
let post = warp::post()
|
|
||||||
.and(post)
|
|
||||||
.and(filters::trace::name("POST /account/passwords"));
|
|
||||||
let filter = get.or(post).unify();
|
|
||||||
|
|
||||||
warp::path!("password").and(filter).boxed()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Form {
|
pub struct ChangeForm {
|
||||||
current_password: String,
|
current_password: String,
|
||||||
new_password: String,
|
new_password: String,
|
||||||
new_password_confirm: String,
|
new_password_confirm: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get(
|
pub(crate) async fn get(
|
||||||
templates: Templates,
|
Extension(templates): Extension<Templates>,
|
||||||
cookie_saver: EncryptedCookieSaver,
|
Extension(pool): Extension<PgPool>,
|
||||||
csrf_token: CsrfToken,
|
cookie_jar: PrivateCookieJar<Encrypter>,
|
||||||
session: BrowserSession<PostgresqlBackend>,
|
) -> Result<Response, FancyError> {
|
||||||
) -> Result<Box<dyn Reply>, Rejection> {
|
let mut conn = pool
|
||||||
render(templates, cookie_saver, csrf_token, session).await
|
.acquire()
|
||||||
|
.await
|
||||||
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
|
let (session_info, cookie_jar) = cookie_jar.session_info();
|
||||||
|
|
||||||
|
let maybe_session = session_info
|
||||||
|
.load_session(&mut conn)
|
||||||
|
.await
|
||||||
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
|
if let Some(session) = maybe_session {
|
||||||
|
render(templates, session, cookie_jar).await
|
||||||
|
} else {
|
||||||
|
let login = LoginRequest::default();
|
||||||
|
let login = login.build_uri().map_err(fancy_error(templates.clone()))?;
|
||||||
|
Ok((cookie_jar.headers(), Redirect::to(login)).into_response())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn render(
|
async fn render(
|
||||||
templates: Templates,
|
templates: Templates,
|
||||||
cookie_saver: EncryptedCookieSaver,
|
|
||||||
csrf_token: CsrfToken,
|
|
||||||
session: BrowserSession<PostgresqlBackend>,
|
session: BrowserSession<PostgresqlBackend>,
|
||||||
) -> Result<Box<dyn Reply>, Rejection> {
|
cookie_jar: PrivateCookieJar<Encrypter>,
|
||||||
|
) -> Result<Response, FancyError> {
|
||||||
|
let (csrf_token, cookie_jar) = cookie_jar.csrf_token();
|
||||||
|
|
||||||
let ctx = EmptyContext
|
let ctx = EmptyContext
|
||||||
.with_session(session)
|
.with_session(session)
|
||||||
.with_csrf(csrf_token.form_value());
|
.with_csrf(csrf_token.form_value());
|
||||||
|
|
||||||
let content = templates.render_account_password(&ctx).await?;
|
let content = templates
|
||||||
let reply = html(content);
|
.render_account_password(&ctx)
|
||||||
let reply = cookie_saver.save_encrypted(&csrf_token, reply)?;
|
.await
|
||||||
|
.map_err(fancy_error(templates))?;
|
||||||
|
|
||||||
Ok(Box::new(reply))
|
Ok((cookie_jar.headers(), Html(content)).into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn post(
|
pub(crate) async fn post(
|
||||||
templates: Templates,
|
Extension(templates): Extension<Templates>,
|
||||||
cookie_saver: EncryptedCookieSaver,
|
Extension(pool): Extension<PgPool>,
|
||||||
csrf_token: CsrfToken,
|
cookie_jar: PrivateCookieJar<Encrypter>,
|
||||||
mut session: BrowserSession<PostgresqlBackend>,
|
Form(form): Form<ProtectedForm<ChangeForm>>,
|
||||||
mut txn: Transaction<'_, Postgres>,
|
) -> Result<Response, FancyError> {
|
||||||
form: Form,
|
let mut txn = pool.begin().await.map_err(fancy_error(templates.clone()))?;
|
||||||
) -> Result<Box<dyn Reply>, Rejection> {
|
|
||||||
|
let form = cookie_jar
|
||||||
|
.verify_form(form)
|
||||||
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
|
let (session_info, cookie_jar) = cookie_jar.session_info();
|
||||||
|
|
||||||
|
let maybe_session = session_info
|
||||||
|
.load_session(&mut txn)
|
||||||
|
.await
|
||||||
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
|
let mut session = if let Some(session) = maybe_session {
|
||||||
|
session
|
||||||
|
} else {
|
||||||
|
let login = LoginRequest::default();
|
||||||
|
let login = login.build_uri().map_err(fancy_error(templates.clone()))?;
|
||||||
|
return Ok((cookie_jar.headers(), Redirect::to(login)).into_response());
|
||||||
|
};
|
||||||
|
|
||||||
authenticate_session(&mut txn, &mut session, form.current_password)
|
authenticate_session(&mut txn, &mut session, form.current_password)
|
||||||
.await
|
.await
|
||||||
.wrap_error()?;
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
// TODO: display nice form errors
|
// TODO: display nice form errors
|
||||||
if form.new_password != form.new_password_confirm {
|
if form.new_password != form.new_password_confirm {
|
||||||
return Err(anyhow::anyhow!("password mismatch")).wrap_error();
|
return Err(anyhow::anyhow!("password mismatch")).map_err(fancy_error(templates.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let phf = Argon2::default();
|
let phf = Argon2::default();
|
||||||
set_password(&mut txn, phf, &session.user, &form.new_password)
|
set_password(&mut txn, phf, &session.user, &form.new_password)
|
||||||
.await
|
.await
|
||||||
.wrap_error()?;
|
.map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
let reply = render(templates, cookie_saver, csrf_token, session).await?;
|
let reply = render(templates.clone(), session, cookie_jar).await?;
|
||||||
|
|
||||||
txn.commit().await.wrap_error()?;
|
txn.commit().await.map_err(fancy_error(templates.clone()))?;
|
||||||
|
|
||||||
Ok(reply)
|
Ok(reply)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use super::{shared::PostAuthAction, RegisterRequest};
|
use super::{shared::PostAuthAction, RegisterRequest};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, Default)]
|
||||||
pub(crate) struct LoginRequest {
|
pub(crate) struct LoginRequest {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
post_auth_action: Option<PostAuthAction>,
|
post_auth_action: Option<PostAuthAction>,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021-2022 The Matrix.org Foundation C.I.C.
|
// Copyright 2021, 2022 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@@ -12,12 +12,6 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use mas_config::{CsrfConfig, Encrypter, HttpConfig};
|
|
||||||
use mas_email::Mailer;
|
|
||||||
use mas_templates::Templates;
|
|
||||||
use sqlx::PgPool;
|
|
||||||
use warp::{filters::BoxedFilter, Filter, Reply};
|
|
||||||
|
|
||||||
pub mod account;
|
pub mod account;
|
||||||
pub mod index;
|
pub mod index;
|
||||||
pub mod login;
|
pub mod login;
|
||||||
@@ -27,20 +21,6 @@ pub mod register;
|
|||||||
pub mod shared;
|
pub mod shared;
|
||||||
pub mod verify;
|
pub mod verify;
|
||||||
|
|
||||||
use self::account::filter as account;
|
|
||||||
pub(crate) use self::{
|
pub(crate) use self::{
|
||||||
login::LoginRequest, reauth::ReauthRequest, register::RegisterRequest, shared::PostAuthAction,
|
login::LoginRequest, reauth::ReauthRequest, register::RegisterRequest, shared::PostAuthAction,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) fn filter(
|
|
||||||
pool: &PgPool,
|
|
||||||
templates: &Templates,
|
|
||||||
mailer: &Mailer,
|
|
||||||
encrypter: &Encrypter,
|
|
||||||
http_config: &HttpConfig,
|
|
||||||
csrf_config: &CsrfConfig,
|
|
||||||
) -> BoxedFilter<(Box<dyn Reply>,)> {
|
|
||||||
let account = account(pool, templates, mailer, encrypter, http_config, csrf_config);
|
|
||||||
|
|
||||||
account.boxed()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 The Matrix.org Foundation C.I.C.
|
// Copyright 2021, 2022 The Matrix.org Foundation C.I.C.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
|||||||
Reference in New Issue
Block a user