feat: Add a webpage for threepid validation links

This commit is contained in:
Ginger
2026-03-22 19:34:13 -04:00
parent 4426437130
commit ec52428e06
14 changed files with 68 additions and 21 deletions
+1 -1
View File
@@ -122,7 +122,7 @@ impl Service {
/// if they were not.
pub async fn empower_first_user(&self, user: &UserId) -> Result<bool> {
#[derive(Template)]
#[template(path = "welcome.md.j2")]
#[template(path = "welcome.md")]
struct WelcomeMessage<'a> {
config: &'a Dep<config::Service>,
domain: &'a str,
+4 -4
View File
@@ -6,7 +6,7 @@ pub trait MessageTemplate: Template {
}
#[derive(Template)]
#[template(path = "mail/change_email.txt.j2")]
#[template(path = "mail/change_email.txt")]
pub struct ChangeEmail<'a> {
pub user_id: &'a UserId,
pub verification_link: String,
@@ -17,7 +17,7 @@ impl MessageTemplate for ChangeEmail<'_> {
}
#[derive(Template)]
#[template(path = "mail/new_account.txt.j2")]
#[template(path = "mail/new_account.txt")]
pub struct NewAccount<'a> {
pub server_name: &'a str,
pub verification_link: String,
@@ -28,7 +28,7 @@ impl MessageTemplate for NewAccount<'_> {
}
#[derive(Template)]
#[template(path = "mail/password_reset.txt.j2")]
#[template(path = "mail/password_reset.txt")]
pub struct PasswordReset<'a> {
pub display_name: Option<&'a str>,
pub user_id: &'a UserId,
@@ -40,7 +40,7 @@ impl MessageTemplate for PasswordReset<'_> {
}
#[derive(Template)]
#[template(path = "mail/test.txt.j2")]
#[template(path = "mail/test.txt")]
pub struct Test;
impl MessageTemplate for Test {
@@ -1,4 +1,4 @@
{% extends "_base.txt.j2" %}
{% extends "_base.txt" %}
{% block content -%}
Hello!
@@ -1,4 +1,4 @@
{% extends "_base.txt.j2" %}
{% extends "_base.txt" %}
{% block content -%}
Hello!
@@ -1,4 +1,4 @@
{% extends "_base.txt.j2" %}
{% extends "_base.txt" %}
{% block content -%}
{%- if let Some(display_name) = display_name -%}
@@ -1,4 +1,4 @@
{% extends "_base.txt.j2" %}
{% extends "_base.txt" %}
{% block content -%}
If you're seeing this, SMTP is configured correctly. :3
+1
View File
@@ -90,6 +90,7 @@ pub fn build() -> Router<state::State> {
.merge(resources::build())
.merge(password_reset::build())
.merge(debug::build())
.merge(threepid::build())
.fallback(async || WebError::NotFound),
)
.layer(CatchPanicLayer::custom(|panic: Box<dyn Any + Send + 'static>| {
+3 -6
View File
@@ -1,17 +1,14 @@
use askama::Template;
use axum::{Router, extract::State, response::IntoResponse, routing::get};
use crate::{WebError, template};
pub(crate) fn build() -> Router<crate::State> {
Router::new()
.route("/", get(index_handler))
.route("/_continuwuity/", get(index_handler))
.route("/", get(index))
.route("/_continuwuity/", get(index))
}
async fn index_handler(
State(services): State<crate::State>,
) -> Result<impl IntoResponse, WebError> {
async fn index(State(services): State<crate::State>) -> Result<impl IntoResponse, WebError> {
template! {
struct Index<'a> use "index.html.j2" {
server_name: &'a str,
+3
View File
@@ -3,6 +3,7 @@ pub(super) mod debug;
pub(super) mod index;
pub(super) mod password_reset;
pub(super) mod resources;
pub(super) mod threepid;
#[derive(Debug)]
pub(crate) struct TemplateContext {
@@ -43,6 +44,8 @@ macro_rules! template {
#[allow(single_use_lifetimes)]
impl$(<$lifetime>)? axum::response::IntoResponse for $name$(<$lifetime>)? {
fn into_response(self) -> axum::response::Response {
use askama::Template;
match self.render() {
Ok(rendered) => axum::response::Html(rendered).into_response(),
Err(err) => $crate::WebError::from(err).into_response()
+5 -6
View File
@@ -1,4 +1,3 @@
use askama::Template;
use axum::{
Router,
extract::{
@@ -20,11 +19,6 @@ use crate::{
const INVALID_TOKEN_ERROR: &str = "Invalid reset token. Your reset link may have expired.";
#[derive(Deserialize)]
struct PasswordResetQuery {
token: String,
}
template! {
struct PasswordReset<'a> use "password_reset.html.j2" {
user_card: UserCard<'a>,
@@ -63,6 +57,11 @@ pub(crate) fn build() -> Router<crate::State> {
.route("/account/reset_password", get(get_password_reset).post(post_password_reset))
}
#[derive(Deserialize)]
struct PasswordResetQuery {
token: String,
}
async fn password_reset_form(
services: crate::State,
query: PasswordResetQuery,
@@ -0,0 +1,8 @@
{% extends "_layout.html.j2" %}
{%- block content -%}
<div class="panel">
<h1>Email verification</h1>
<p>Your email address has been verified. Return to your Matrix client to continue.</p>
</div>
{%- endblock content -%}
+39
View File
@@ -0,0 +1,39 @@
use axum::{
Router,
extract::{Query, State, rejection::QueryRejection},
response::IntoResponse,
routing::get,
};
use ruma::OwnedSessionId;
use serde::Deserialize;
use crate::{WebError, template};
template! {
struct ThreepidValidation use "threepid_validation.html.j2" {}
}
pub(crate) fn build() -> Router<crate::State> {
Router::new().route("/3pid/email/validate", get(threepid_validation))
}
#[derive(Deserialize)]
struct ThreepidValidationQuery {
session: OwnedSessionId,
token: String,
}
async fn threepid_validation(
State(services): State<crate::State>,
query: Result<Query<ThreepidValidationQuery>, QueryRejection>,
) -> Result<impl IntoResponse, WebError> {
let Query(query) = query?;
services
.threepid
.try_validate_session(&query.session, &query.token)
.await
.map_err(|message| WebError::BadRequest(message.into_owned()))?;
Ok(ThreepidValidation::new(&services))
}