1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-29 22:01:14 +03:00

Move password change to its own page

Also restructure the templates structure a bit
This commit is contained in:
Quentin Gliech
2022-01-18 11:58:42 +01:00
parent 565f5cda1b
commit 1b35f96f29
11 changed files with 143 additions and 65 deletions

View File

@ -0,0 +1,85 @@
// Copyright 2021-2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
mod password;
use mas_config::{CookiesConfig, CsrfConfig};
use mas_data_model::BrowserSession;
use mas_storage::{
user::{count_active_sessions, get_user_emails},
PostgresqlBackend,
};
use mas_templates::{AccountContext, TemplateContext, Templates};
use mas_warp_utils::{
errors::WrapError,
filters::{
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
csrf::updated_csrf_token,
database::connection,
session::session,
with_templates, CsrfToken,
},
};
use sqlx::{pool::PoolConnection, PgPool, Postgres};
use warp::{filters::BoxedFilter, reply::html, Filter, Rejection, Reply};
use self::password::filter as password;
pub(super) fn filter(
pool: &PgPool,
templates: &Templates,
csrf_config: &CsrfConfig,
cookies_config: &CookiesConfig,
) -> BoxedFilter<(Box<dyn Reply>,)> {
let get = warp::get()
.and(with_templates(templates))
.and(encrypted_cookie_saver(cookies_config))
.and(updated_csrf_token(cookies_config, csrf_config))
.and(session(pool, cookies_config))
.and(connection(pool))
.and_then(get);
let index = warp::path::end().and(get);
let password = password(pool, templates, csrf_config, cookies_config);
let filter = index.or(password).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)
.await
.wrap_error()?;
let emails = get_user_emails(&mut conn, &session.user)
.await
.wrap_error()?;
let ctx = AccountContext::new(active_sessions, emails)
.with_session(session)
.with_csrf(csrf_token.form_value());
let content = templates.render_account_index(&ctx).await?;
let reply = html(content);
let reply = cookie_saver.save_encrypted(&csrf_token, reply)?;
Ok(Box::new(reply))
}

View File

@ -1,4 +1,4 @@
// Copyright 2021 The Matrix.org Foundation C.I.C. // Copyright 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.
@ -16,10 +16,10 @@ use argon2::Argon2;
use mas_config::{CookiesConfig, CsrfConfig}; use mas_config::{CookiesConfig, CsrfConfig};
use mas_data_model::BrowserSession; use mas_data_model::BrowserSession;
use mas_storage::{ use mas_storage::{
user::{authenticate_session, count_active_sessions, get_user_emails, set_password}, user::{authenticate_session, set_password},
PostgresqlBackend, PostgresqlBackend,
}; };
use mas_templates::{AccountContext, TemplateContext, Templates}; use mas_templates::{EmptyContext, TemplateContext, Templates};
use mas_warp_utils::{ use mas_warp_utils::{
errors::WrapError, errors::WrapError,
filters::{ filters::{
@ -44,7 +44,6 @@ pub(super) fn filter(
.and(encrypted_cookie_saver(cookies_config)) .and(encrypted_cookie_saver(cookies_config))
.and(updated_csrf_token(cookies_config, csrf_config)) .and(updated_csrf_token(cookies_config, csrf_config))
.and(session(pool, cookies_config)) .and(session(pool, cookies_config))
.and(transaction(pool))
.and_then(get); .and_then(get);
let post = with_templates(templates) let post = with_templates(templates)
@ -55,9 +54,11 @@ pub(super) fn filter(
.and(protected_form(cookies_config)) .and(protected_form(cookies_config))
.and_then(post); .and_then(post);
let filter = warp::get().and(get).or(warp::post().and(post)).unify(); let get = warp::get().and(get);
let post = warp::post().and(post);
let filter = get.or(post).unify();
warp::path!("account").and(filter).boxed() warp::path!("password").and(filter).boxed()
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -66,14 +67,14 @@ struct Form {
new_password: String, new_password: String,
new_password_confirm: String, new_password_confirm: String,
} }
async fn get( async fn get(
templates: Templates, templates: Templates,
cookie_saver: EncryptedCookieSaver, cookie_saver: EncryptedCookieSaver,
csrf_token: CsrfToken, csrf_token: CsrfToken,
session: BrowserSession<PostgresqlBackend>, session: BrowserSession<PostgresqlBackend>,
txn: Transaction<'_, Postgres>,
) -> Result<Box<dyn Reply>, Rejection> { ) -> Result<Box<dyn Reply>, Rejection> {
render(templates, cookie_saver, csrf_token, session, txn).await render(templates, cookie_saver, csrf_token, session).await
} }
async fn render( async fn render(
@ -81,23 +82,12 @@ async fn render(
cookie_saver: EncryptedCookieSaver, cookie_saver: EncryptedCookieSaver,
csrf_token: CsrfToken, csrf_token: CsrfToken,
session: BrowserSession<PostgresqlBackend>, session: BrowserSession<PostgresqlBackend>,
mut txn: Transaction<'_, Postgres>,
) -> Result<Box<dyn Reply>, Rejection> { ) -> Result<Box<dyn Reply>, Rejection> {
let active_sessions = count_active_sessions(&mut txn, &session.user) let ctx = EmptyContext
.await
.wrap_error()?;
let emails = get_user_emails(&mut txn, &session.user)
.await
.wrap_error()?;
txn.commit().await.wrap_error()?;
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(&ctx).await?; let content = templates.render_account_password(&ctx).await?;
let reply = html(content); let reply = html(content);
let reply = cookie_saver.save_encrypted(&csrf_token, reply)?; let reply = cookie_saver.save_encrypted(&csrf_token, reply)?;
@ -126,7 +116,9 @@ async fn post(
.await .await
.wrap_error()?; .wrap_error()?;
let reply = render(templates, cookie_saver, csrf_token, session, txn).await?; let reply = render(templates, cookie_saver, csrf_token, session).await?;
txn.commit().await.wrap_error()?;
Ok(reply) Ok(reply)
} }

View File

@ -285,25 +285,28 @@ register_templates! {
}; };
/// Render the login page /// Render the login page
pub fn render_login(WithCsrf<LoginContext>) { "login.html" } pub fn render_login(WithCsrf<LoginContext>) { "pages/login.html" }
/// Render the registration page /// Render the registration page
pub fn render_register(WithCsrf<RegisterContext>) { "register.html" } pub fn render_register(WithCsrf<RegisterContext>) { "pages/register.html" }
/// Render the home page /// Render the home page
pub fn render_index(WithCsrf<WithOptionalSession<IndexContext>>) { "index.html" } pub fn render_index(WithCsrf<WithOptionalSession<IndexContext>>) { "pages/index.html" }
/// Render the account management page /// Render the account management page
pub fn render_account(WithCsrf<WithSession<AccountContext>>) { "account.html" } pub fn render_account_index(WithCsrf<WithSession<AccountContext>>) { "pages/account/index.html" }
/// Render the password change page
pub fn render_account_password(WithCsrf<WithSession<EmptyContext>>) { "pages/account/password.html" }
/// Render the re-authentication form /// Render the re-authentication form
pub fn render_reauth(WithCsrf<WithSession<ReauthContext>>) { "reauth.html" } pub fn render_reauth(WithCsrf<WithSession<ReauthContext>>) { "pages/reauth.html" }
/// Render the form used by the form_post response mode /// Render the form used by the form_post response mode
pub fn render_form_post<T: Serialize>(FormPostContext<T>) { "form_post.html" } pub fn render_form_post<T: Serialize>(FormPostContext<T>) { "form_post.html" }
/// Render the HTML error page /// Render the HTML error page
pub fn render_error(ErrorContext) { "error.html" } pub fn render_error(ErrorContext) { "pages/error.html" }
} }
impl Templates { impl Templates {
@ -313,7 +316,8 @@ impl Templates {
check::render_login(self).await?; check::render_login(self).await?;
check::render_register(self).await?; check::render_register(self).await?;
check::render_index(self).await?; check::render_index(self).await?;
check::render_account(self).await?; check::render_account_index(self).await?;
check::render_account_password(self).await?;
check::render_reauth(self).await?; check::render_reauth(self).await?;
check::render_form_post::<EmptyContext>(self).await?; check::render_form_post::<EmptyContext>(self).await?;
check::render_error(self).await?; check::render_error(self).await?;

View File

@ -1,27 +0,0 @@
{#
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
#}
{% if code %}
{{- code }}
{% endif %}
{%- if description %}
{{ description }}
{% endif %}
{%- if details %}
{{ details }}
{% endif %}

View File

@ -1,5 +1,5 @@
{# {#
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.
@ -30,15 +30,8 @@ limitations under the License.
<div class="font-bold">Primary email</div> <div class="font-bold">Primary email</div>
<div>{{ current_session.user.primary_email.email }}</div> <div>{{ current_session.user.primary_email.email }}</div>
{% endif %} {% endif %}
{{ button::link_ghost(text="Change password", href="/account/password", class="col-span-2 place-self-end") }}
</div> </div>
<form class="rounded border-2 border-grey-50 dark:border-grey-450 p-4 grid gap-4 xl:grid-cols-2 grid-cols-1 place-content-start" method="POST">
<h2 class="text-xl font-bold xl:col-span-2">Change my password</h2>
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
{{ field::input(label="Current password", name="current_password", type="password", class="xl:col-span-2") }}
{{ field::input(label="New password", name="new_password", type="password") }}
{{ field::input(label="Confirm password", name="new_password_confirm", type="password") }}
{{ button::button(text="Change password", type="password", class="xl:col-span-2 place-self-end") }}
</form>
<div class="rounded border-2 border-grey-50 dark:border-grey-450 p-4 grid gap-4 xl:grid-cols-2 grid-cols-1 place-content-start"> <div class="rounded border-2 border-grey-50 dark:border-grey-450 p-4 grid gap-4 xl:grid-cols-2 grid-cols-1 place-content-start">
<h2 class="text-xl font-bold xl:col-span-2">Current session</h2> <h2 class="text-xl font-bold xl:col-span-2">Current session</h2>
<div class="font-bold">Started at</div> <div class="font-bold">Started at</div>

View File

@ -0,0 +1,31 @@
{#
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
#}
{% extends "base.html" %}
{% block content %}
<section class="container mx-auto grid gap-4 grid-cols-1 md:grid-cols-2 xl:grid-cols-3 p-2">
<form class="rounded border-2 border-grey-50 dark:border-grey-450 p-4 grid gap-4 xl:grid-cols-2 grid-cols-1 place-content-start" method="POST">
<h2 class="text-xl font-bold xl:col-span-2">Change my password</h2>
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
{{ field::input(label="Current password", name="current_password", type="password", class="xl:col-span-2") }}
{{ field::input(label="New password", name="new_password", type="password") }}
{{ field::input(label="Confirm password", name="new_password_confirm", type="password") }}
{{ button::button(text="Change password", type="password", class="xl:col-span-2 place-self-end") }}
</form>
</section>
{% endblock content %}