Add registration view

This commit is contained in:
Quentin Gliech
2021-08-15 09:56:28 +00:00
committed by GitHub
parent 9f4673918d
commit bad765f522
7 changed files with 212 additions and 3 deletions

View File

@@ -24,11 +24,12 @@ mod index;
mod login;
mod logout;
mod reauth;
mod register;
pub use self::login::LoginRequest;
use self::{
index::filter as index, login::filter as login, logout::filter as logout,
reauth::filter as reauth,
reauth::filter as reauth, register::filter as register,
};
pub(super) fn filter(
@@ -39,6 +40,7 @@ pub(super) fn filter(
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone + Send + Sync + 'static {
index(pool, templates, csrf_config, cookies_config)
.or(login(pool, templates, csrf_config, cookies_config))
.or(register(pool, templates, csrf_config, cookies_config))
.or(logout(pool, cookies_config))
.or(reauth(pool, templates, csrf_config, cookies_config))
.boxed()

View File

@@ -0,0 +1,143 @@
// 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.
use std::convert::TryFrom;
use argon2::Argon2;
use hyper::http::uri::{Parts, PathAndQuery, Uri};
use serde::{Deserialize, Serialize};
use sqlx::{pool::PoolConnection, PgPool, Postgres};
use warp::{reply::html, wrap_fn, Filter, Rejection, Reply};
use crate::{
config::{CookiesConfig, CsrfConfig},
errors::WrapError,
filters::{
csrf::{protected_form, save_csrf_token, updated_csrf_token},
database::with_connection,
session::{save_session, with_optional_session},
with_templates, CsrfToken,
},
storage::{register_user, user::start_session, SessionInfo},
templates::{TemplateContext, Templates},
};
#[derive(Serialize, Deserialize)]
pub struct RegisterRequest {
next: Option<String>,
}
impl RegisterRequest {
#[allow(dead_code)]
pub fn new(next: Option<String>) -> Self {
Self { next }
}
#[allow(dead_code)]
pub fn build_uri(&self) -> anyhow::Result<Uri> {
let qs = serde_urlencoded::to_string(self)?;
let path_and_query = PathAndQuery::try_from(format!("/register?{}", qs))?;
let uri = Uri::from_parts({
let mut parts = Parts::default();
parts.path_and_query = Some(path_and_query);
parts
})?;
Ok(uri)
}
fn redirect(self) -> Result<impl Reply, Rejection> {
let uri: Uri = Uri::from_parts({
let mut parts = Parts::default();
parts.path_and_query = Some(
self.next
.map(warp::http::uri::PathAndQuery::try_from)
.transpose()
.wrap_error()?
.unwrap_or_else(|| PathAndQuery::from_static("/")),
);
parts
})
.wrap_error()?;
Ok(warp::redirect::see_other(uri))
}
}
#[derive(Deserialize)]
struct RegisterForm {
username: String,
password: String,
password_confirm: String,
}
pub(super) fn filter(
pool: &PgPool,
templates: &Templates,
csrf_config: &CsrfConfig,
cookies_config: &CookiesConfig,
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone + Send + Sync + 'static {
let get = warp::get()
.and(with_templates(templates))
.and(updated_csrf_token(cookies_config, csrf_config))
.and(warp::query())
.and(with_optional_session(pool, cookies_config))
.and_then(get)
.untuple_one()
.with(wrap_fn(save_csrf_token(cookies_config)));
let post = warp::post()
.and(with_connection(pool))
.and(protected_form(cookies_config))
.and(warp::query())
.and_then(post)
.untuple_one()
.with(wrap_fn(save_session(cookies_config)));
warp::path!("register").and(get.or(post))
}
async fn get(
templates: Templates,
csrf_token: CsrfToken,
query: RegisterRequest,
maybe_session: Option<SessionInfo>,
) -> Result<(CsrfToken, Box<dyn Reply>), Rejection> {
if maybe_session.is_some() {
Ok((csrf_token, Box::new(query.redirect()?)))
} else {
let ctx = ().with_csrf(&csrf_token);
let content = templates.render_register(&ctx)?;
Ok((csrf_token, Box::new(html(content))))
}
}
async fn post(
mut conn: PoolConnection<Postgres>,
form: RegisterForm,
query: RegisterRequest,
) -> Result<(SessionInfo, impl Reply), Rejection> {
if form.password != form.password_confirm {
return Err(anyhow::anyhow!("password mismatch"))
.wrap_error()
.map_err(warp::reject::custom);
}
let pfh = Argon2::default();
let user = register_user(&mut conn, pfh, &form.username, &form.password)
.await
.wrap_error()?;
let session_info = start_session(&mut conn, user).await.wrap_error()?;
Ok((session_info, query.redirect()?))
}

View File

@@ -17,7 +17,7 @@
use sqlx::migrate::Migrator;
pub mod oauth2;
mod user;
pub mod user;
pub use self::user::{login, lookup_active_session, register_user, SessionInfo, User};

View File

@@ -135,6 +135,9 @@ register_templates! {
/// Render the login page
pub fn render_login(WithCsrf<()>) { "login.html" }
/// Render the registration page
pub fn render_register(WithCsrf<()>) { "register.html" }
/// Render the home page
pub fn render_index(WithCsrf<WithOptionalSession<()>>) { "index.html" }

View File

@@ -50,6 +50,12 @@ limitations under the License.
Log in
</a>
</div>
<div class="navbar-item">
<a class="button is-light" href="/register">
Register
</a>
</div>
{% endif %}
</div>
</div>

View File

@@ -38,7 +38,7 @@ limitations under the License.
</div>
<div class="control">
<button type="submit" class="button is-link">Submit</button>
<button type="submit" class="button is-link">Login</button>
</div>
</form>
</div>

View File

@@ -0,0 +1,55 @@
{#
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.
#}
{% extends "base.html" %}
{% block content %}
<section class="section">
<div class="container is-max-desktop">
<div class="columns">
<div class="column is-one-third">
<form method="POST">
<input type="hidden" name="csrf" value="{{ csrf_token }}" />
<div class="field">
<label class="label" for="register-username">Username</label>
<div class="control">
<input class="input" name="username" id="register-username" type="text">
</div>
</div>
<div class="field">
<label class="label" for="register-password">Password</label>
<div class="control">
<input class="input" name="password" id="register-password" type="password">
</div>
</div>
<div class="field">
<label class="label" for="register-password">Confirm password</label>
<div class="control">
<input class="input" name="password_confirm" id="register-password-confirm" type="password">
</div>
</div>
<div class="control">
<button type="submit" class="button is-link">Register</button>
</div>
</form>
</div>
</div>
</div>
</section>
{% endblock content %}