feat: Add support for registering accounts with the web UI

This commit is contained in:
Ginger committed 2026-06-29 09:35:50 -04:00
1 parent 47af5d3a39
commit e8d6fa565e
25 files changed
+1275 -508

No files matched your search

+1 -2
View File
@@ -28,8 +28,7 @@ Device information
{% else %}
This device can access and control all features of your Matrix account.
<br>
<small>❖ <i>This is a legacy device. Legacy devices
always have full access to your account.</i></small>
<small>❖ <i>This is a legacy device. Legacy devices always have full access to your account.</i></small>
{% endif %}
</p>
</section>
+10 -2
View File
@@ -11,7 +11,7 @@ Log in
{%- block content -%}
<div class="panel narrow">
{% match body %}
{% when LoginBody::Unauthenticated { server_name } %}
{% when LoginBody::Unauthenticated { server_name, registration_available } %}
<h1 class="with-matrix-icon">
{% if has_next %}
Log in to continue
@@ -37,6 +37,12 @@ Log in
</p>
<button type="submit">Log in</button>
</form>
<div class="centered-links">
{% if registration_available %}
<a href="{{ crate::ROUTE_PREFIX }}/account/register/">Sign up</a>
{% endif %}
<a href="{{ crate::ROUTE_PREFIX }}/account/password/reset/">Forgot your password?</a>
</div>
{% when LoginBody::Authenticated { user_card } %}
<h1>Confirm your identity</h1>
{{ user_card }}
@@ -48,10 +54,12 @@ Log in
</p>
<button type="submit">Continue</button>
</form>
<div class="centered-links">
<a href="{{ crate::ROUTE_PREFIX }}/account/password/reset/">Forgot your password?</a>
</div>
{% endmatch %}
{% if let Some(error) = login_error %}
<small class="error">{{ error }}</small>
{% endif %}
<a class="reset-password" href="password/reset/"><i>Forgot your password?</i></a>
</div>
{%- endblock -%}
+159
View File
@@ -0,0 +1,159 @@
{% extends "_layout.html.j2" %}
{% import "_components/form.html.j2" as form %}
{%- block head -%}
<link rel="stylesheet" href="{{ crate::ROUTE_PREFIX }}/resources/login.css">
{%- endblock -%}
{%- block title -%}
Sign up
{%- endblock -%}
{%- block content -%}
<div class="panel narrow">
<h1 class="with-matrix-icon">
{% if false %}
Sign up to continue
{% else %}
Sign up
{% endif %}
<a href="https://matrix.org" target="_blank" noreferer>
<img class="matrix-icon" alt="Matrix logo" aria-ignore src="{{ crate::ROUTE_PREFIX }}/resources/matrix-icon.svg">
</a>
</h1>
{% match body %}
{% when RegisterBody::Unavailable %}
<p>
This server is not currently accepting new accounts.
</p>
{% when RegisterBody::UsernamePrompt { allow_federation, untrusted_flow_status, trusted_flow_status, username_error } %}
<p>
You're about to register a new Matrix account on <em>{{ server_name }}</em>.
</p>
{% if allow_federation %}
<p>
Like email, Matrix is a network of servers. Your account will be able to talk to
users on hundreds of different Matrix servers across the world.
</p>
{% endif %}
<hr>
<p>
Choose a username to continue.
</p>
<form method="get">
<input type="hidden" name="from_landing" value="true">
<p>
<label for="username">Username</label>
<span class="username-input">
<span>@</span>
<input type="text" name="username" autocomplete="username" required>
<span>:{{ server_name }}</span>
</span>
{% if let Some(username_error) = username_error %}
<small class="error">
{{ username_error }}
</small>
{% endif %}
</p>
{% if let UntrustedFlowStatus::Available { require_email } = untrusted_flow_status %}
{% if require_email %}
<button type="submit" name="flow" value="untrusted">Continue with email</button>
{% else %}
<button type="submit" name="flow" value="untrusted">Continue</button>
{% endif %}
{% endif %}
{% if let UntrustedFlowStatus::Available { .. } = untrusted_flow_status && let TrustedFlowStatus::Available = trusted_flow_status %}
<div class="text-rule">or</div>
{% endif %}
{% if let TrustedFlowStatus::Available = trusted_flow_status %}
<button type="submit" name="flow" value="trusted">Continue with a registration token</button>
{% endif %}
</form>
{% when RegisterBody::DetailsPrompt { username, require_email, flow, terms, validation_errors } %}
{% let validation_errors = validation_errors.clone() %}
{% let field_errors = validation_errors.field_errors() %}
<form method="post">
<p>
<label for="username">Username</label>
<span class="username-input">
<span>@</span>
{% if let Some(username) = username %}
<input type="text" name="username" value="{{ username }}" autocomplete="username" required>
{% else %}
<input type="text" name="username" autocomplete="username" required>
{% endif %}
<span>:{{ server_name }}</span>
</span>
{{ form::errors(field_errors, std::borrow::Cow::Borrowed("username")) }}
</p>
<p>
Just a few more details to finish creating your account.
</p>
<p>
<label for="password">Password</label>
<input type="password" name="password" autocomplete="new-password" required>
{{ form::errors(field_errors, std::borrow::Cow::Borrowed("password")) }}
</p>
<p>
<label for="confirm_password">Confirm password</label>
<input type="password" name="confirm_password" autocomplete="new-password" required>
{{ form::errors(field_errors, std::borrow::Cow::Borrowed("confirm_password")) }}
</p>
{% if require_email %}
<p>
<label for="email">Email address</label>
<input type="email" name="email" required>
{{ form::errors(field_errors, std::borrow::Cow::Borrowed("email")) }}
</p>
{% endif %}
{% match flow %}
{% when RegistrationFlowParameters::Untrusted { recaptcha_sitekey } %}
<input type="hidden" name="flow" value="untrusted">
{% if let Some(recaptcha_sitekey) = recaptcha_sitekey %}
<script src="https://www.google.com/recaptcha/enterprise.js" nonce="{{ context.csp_nonce }}" async defer></script>
<noscript>
<p>
⚠️ Please enable JavaScript to complete the reCAPTCHA challenge.
</p>
</noscript>
<p>
<span class="g-recaptcha" data-sitekey="{{ recaptcha_sitekey }}"></span>
{{ form::errors(field_errors, std::borrow::Cow::Borrowed("recaptcha")) }}
</p>
{% endif %}
{% when RegistrationFlowParameters::Trusted { registration_token } %}
<input type="hidden" name="flow" value="trusted">
{% if let Some(registration_token) = registration_token %}
<input type="hidden" name="registration_token" value="{{ registration_token }}">
<div>
{{ form::errors(field_errors, std::borrow::Cow::Borrowed("registration_token")) }}
</div>
{% else %}
<p>
<label for="username">Registration token</label>
<input type="text" name="registration_token" autocomplete="none" required>
{{ form::errors(field_errors, std::borrow::Cow::Borrowed("registration_token")) }}
</p>
{% endif %}
{% endmatch %}
{% if !terms.is_empty() %}
<p>
{% for (id, document) in terms %}
<label for="policy-{{ id }}">
<input
type="checkbox"
id="policy-{{ id }}"
required
>
I agree to the <a target="_blank" href="{{ document.url }}">{{ document.name }}</a>
</label>
{% endfor %}
<small><i>All policy links will open in a new tab.</i></small>
</p>
{% endif %}
<button type="submit">Continue</button>
</form>
{% endmatch %}
</div>
{%- endblock -%}
@@ -0,0 +1,27 @@
{% extends "_layout.html.j2" %}
{%- block title -%}
Confirm your email
{%- endblock -%}
{%- block content -%}
<div class="panel narrow">
<h1>Confirm your email</h1>
<p>
A message has been sent to your new email address with a validation link.
To finish creating your account, click the link and then return to this page.
If you do not receive the email:
<ul>
<li>Check your spam filter.</li>
</ul>
</p>
{% if validation_error %}
<small class="error">Validation failed. Have you clicked the link in the email that was sent to you?</small>
{% endif %}
<form method="get" action="validate">
<input type="hidden" name="session_id" value="{{ session_id }}">
<input type="hidden" name="client_secret" value="{{ client_secret }}">
<button type="submit">Continue</button>
</form>
</div>
{%- endblock -%}