You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-08-09 04:22:45 +03:00
Link between login & register + "back to client" link
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1605,6 +1605,7 @@ dependencies = [
|
|||||||
"oauth2-types",
|
"oauth2-types",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
"tera",
|
"tera",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@@ -441,7 +441,7 @@ async fn get(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub(crate) struct ContinueAuthorizationGrant<S: StorageBackend> {
|
pub(crate) struct ContinueAuthorizationGrant<S: StorageBackend> {
|
||||||
#[serde(
|
#[serde(
|
||||||
with = "serde_with::rust::display_fromstr",
|
with = "serde_with::rust::display_fromstr",
|
||||||
|
@@ -20,7 +20,7 @@ use serde::Deserialize;
|
|||||||
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
||||||
use warp::{reply::html, Filter, Rejection, Reply};
|
use warp::{reply::html, Filter, Rejection, Reply};
|
||||||
|
|
||||||
use super::shared::PostAuthAction;
|
use super::{shared::PostAuthAction, RegisterRequest};
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::WrapError,
|
errors::WrapError,
|
||||||
filters::{
|
filters::{
|
||||||
@@ -130,8 +130,12 @@ async fn get(
|
|||||||
let ctx = LoginContext::default();
|
let ctx = LoginContext::default();
|
||||||
let ctx = match query.post_auth_action {
|
let ctx = match query.post_auth_action {
|
||||||
Some(next) => {
|
Some(next) => {
|
||||||
|
let register_link = RegisterRequest::from(next.clone())
|
||||||
|
.build_uri()
|
||||||
|
.wrap_error()?;
|
||||||
let next = next.load_context(&mut conn).await.wrap_error()?;
|
let next = next.load_context(&mut conn).await.wrap_error()?;
|
||||||
ctx.with_post_action(next)
|
ctx.with_post_action(next)
|
||||||
|
.with_register_link(register_link.to_string())
|
||||||
}
|
}
|
||||||
None => ctx,
|
None => ctx,
|
||||||
};
|
};
|
||||||
|
@@ -28,7 +28,9 @@ use self::{
|
|||||||
index::filter as index, login::filter as login, logout::filter as logout,
|
index::filter as index, login::filter as login, logout::filter as logout,
|
||||||
reauth::filter as reauth, register::filter as register,
|
reauth::filter as reauth, register::filter as register,
|
||||||
};
|
};
|
||||||
pub(crate) use self::{login::LoginRequest, reauth::ReauthRequest, shared::PostAuthAction};
|
pub(crate) use self::{
|
||||||
|
login::LoginRequest, reauth::ReauthRequest, register::RegisterRequest, shared::PostAuthAction,
|
||||||
|
};
|
||||||
|
|
||||||
pub(super) fn filter(
|
pub(super) fn filter(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
|
@@ -15,12 +15,13 @@
|
|||||||
use argon2::Argon2;
|
use argon2::Argon2;
|
||||||
use hyper::http::uri::{Parts, PathAndQuery, Uri};
|
use hyper::http::uri::{Parts, PathAndQuery, Uri};
|
||||||
use mas_config::{CookiesConfig, CsrfConfig};
|
use mas_config::{CookiesConfig, CsrfConfig};
|
||||||
use mas_data_model::BrowserSession;
|
use mas_data_model::{BrowserSession, StorageBackend};
|
||||||
use mas_templates::{EmptyContext, TemplateContext, Templates};
|
use mas_templates::{RegisterContext, TemplateContext, Templates};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Deserialize;
|
||||||
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
||||||
use warp::{reply::html, Filter, Rejection, Reply};
|
use warp::{reply::html, Filter, Rejection, Reply};
|
||||||
|
|
||||||
|
use super::{LoginRequest, PostAuthAction};
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::WrapError,
|
errors::WrapError,
|
||||||
filters::{
|
filters::{
|
||||||
@@ -33,21 +34,34 @@ use crate::{
|
|||||||
storage::{register_user, user::start_session, PostgresqlBackend},
|
storage::{register_user, user::start_session, PostgresqlBackend},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct RegisterRequest {
|
#[serde(bound(deserialize = "S::AuthorizationGrantData: std::str::FromStr,
|
||||||
next: Option<String>,
|
<S::AuthorizationGrantData as std::str::FromStr>::Err: std::fmt::Display"))]
|
||||||
|
pub struct RegisterRequest<S: StorageBackend> {
|
||||||
|
#[serde(flatten)]
|
||||||
|
post_auth_action: Option<PostAuthAction<S>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RegisterRequest {
|
impl<S: StorageBackend> From<PostAuthAction<S>> for RegisterRequest<S> {
|
||||||
#[allow(dead_code)]
|
fn from(post_auth_action: PostAuthAction<S>) -> Self {
|
||||||
pub fn new(next: Option<String>) -> Self {
|
Self {
|
||||||
Self { next }
|
post_auth_action: Some(post_auth_action),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<S: StorageBackend> RegisterRequest<S> {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn build_uri(&self) -> anyhow::Result<Uri> {
|
pub fn build_uri(&self) -> anyhow::Result<Uri>
|
||||||
let qs = serde_urlencoded::to_string(self)?;
|
where
|
||||||
let path_and_query = PathAndQuery::try_from(format!("/register?{}", qs))?;
|
S::AuthorizationGrantData: std::fmt::Display,
|
||||||
|
{
|
||||||
|
let path_and_query = if let Some(next) = &self.post_auth_action {
|
||||||
|
let qs = serde_urlencoded::to_string(next)?;
|
||||||
|
PathAndQuery::try_from(format!("/register?{}", qs))?
|
||||||
|
} else {
|
||||||
|
PathAndQuery::from_static("/register")
|
||||||
|
};
|
||||||
let uri = Uri::from_parts({
|
let uri = Uri::from_parts({
|
||||||
let mut parts = Parts::default();
|
let mut parts = Parts::default();
|
||||||
parts.path_and_query = Some(path_and_query);
|
parts.path_and_query = Some(path_and_query);
|
||||||
@@ -56,19 +70,17 @@ impl RegisterRequest {
|
|||||||
Ok(uri)
|
Ok(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn redirect(self) -> Result<impl Reply, Rejection> {
|
fn redirect(self) -> Result<impl Reply, Rejection>
|
||||||
let uri: Uri = Uri::from_parts({
|
where
|
||||||
let mut parts = Parts::default();
|
S::AuthorizationGrantData: std::fmt::Display,
|
||||||
parts.path_and_query = Some(
|
{
|
||||||
self.next
|
let uri = self
|
||||||
.map(warp::http::uri::PathAndQuery::try_from)
|
.post_auth_action
|
||||||
|
.as_ref()
|
||||||
|
.map(PostAuthAction::build_uri)
|
||||||
.transpose()
|
.transpose()
|
||||||
.wrap_error()?
|
.wrap_error()?
|
||||||
.unwrap_or_else(|| PathAndQuery::from_static("/")),
|
.unwrap_or_else(|| Uri::from_static("/"));
|
||||||
);
|
|
||||||
parts
|
|
||||||
})
|
|
||||||
.wrap_error()?;
|
|
||||||
Ok(warp::redirect::see_other(uri))
|
Ok(warp::redirect::see_other(uri))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,6 +100,7 @@ pub(super) fn filter(
|
|||||||
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone + Send + Sync + 'static {
|
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone + Send + Sync + 'static {
|
||||||
let get = warp::get()
|
let get = warp::get()
|
||||||
.and(with_templates(templates))
|
.and(with_templates(templates))
|
||||||
|
.and(connection(pool))
|
||||||
.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(warp::query())
|
.and(warp::query())
|
||||||
@@ -106,15 +119,26 @@ pub(super) fn filter(
|
|||||||
|
|
||||||
async fn get(
|
async fn get(
|
||||||
templates: Templates,
|
templates: Templates,
|
||||||
|
mut conn: PoolConnection<Postgres>,
|
||||||
cookie_saver: EncryptedCookieSaver,
|
cookie_saver: EncryptedCookieSaver,
|
||||||
csrf_token: CsrfToken,
|
csrf_token: CsrfToken,
|
||||||
query: RegisterRequest,
|
query: RegisterRequest<PostgresqlBackend>,
|
||||||
maybe_session: Option<BrowserSession<PostgresqlBackend>>,
|
maybe_session: Option<BrowserSession<PostgresqlBackend>>,
|
||||||
) -> Result<Box<dyn Reply>, Rejection> {
|
) -> Result<Box<dyn Reply>, Rejection> {
|
||||||
if maybe_session.is_some() {
|
if maybe_session.is_some() {
|
||||||
Ok(Box::new(query.redirect()?))
|
Ok(Box::new(query.redirect()?))
|
||||||
} else {
|
} else {
|
||||||
let ctx = EmptyContext.with_csrf(csrf_token.form_value());
|
let ctx = RegisterContext::default();
|
||||||
|
let ctx = match query.post_auth_action {
|
||||||
|
Some(next) => {
|
||||||
|
let login_link = LoginRequest::from(next.clone()).build_uri().wrap_error()?;
|
||||||
|
let next = next.load_context(&mut conn).await.wrap_error()?;
|
||||||
|
ctx.with_post_action(next)
|
||||||
|
.with_login_link(login_link.to_string())
|
||||||
|
}
|
||||||
|
None => ctx,
|
||||||
|
};
|
||||||
|
let ctx = ctx.with_csrf(csrf_token.form_value());
|
||||||
let content = templates.render_register(&ctx).await?;
|
let content = templates.render_register(&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,8 +150,9 @@ async fn post(
|
|||||||
mut conn: PoolConnection<Postgres>,
|
mut conn: PoolConnection<Postgres>,
|
||||||
cookie_saver: EncryptedCookieSaver,
|
cookie_saver: EncryptedCookieSaver,
|
||||||
form: RegisterForm,
|
form: RegisterForm,
|
||||||
query: RegisterRequest,
|
query: RegisterRequest<PostgresqlBackend>,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
|
// TODO: display nice form errors
|
||||||
if form.password != form.password_confirm {
|
if form.password != form.password_confirm {
|
||||||
return Err(anyhow::anyhow!("password mismatch")).wrap_error();
|
return Err(anyhow::anyhow!("password mismatch")).wrap_error();
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,7 @@ use sqlx::PgExecutor;
|
|||||||
use super::super::oauth2::ContinueAuthorizationGrant;
|
use super::super::oauth2::ContinueAuthorizationGrant;
|
||||||
use crate::storage::PostgresqlBackend;
|
use crate::storage::PostgresqlBackend;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
#[serde(rename_all = "snake_case", tag = "next")]
|
#[serde(rename_all = "snake_case", tag = "next")]
|
||||||
pub(crate) enum PostAuthAction<S: StorageBackend> {
|
pub(crate) enum PostAuthAction<S: StorageBackend> {
|
||||||
#[serde(bound(
|
#[serde(bound(
|
||||||
|
@@ -18,6 +18,7 @@ thiserror = "1.0.30"
|
|||||||
tera = "1.15.0"
|
tera = "1.15.0"
|
||||||
serde = { version = "1.0.131", features = ["derive"] }
|
serde = { version = "1.0.131", features = ["derive"] }
|
||||||
serde_json = "1.0.72"
|
serde_json = "1.0.72"
|
||||||
|
serde_urlencoded = "0.7.0"
|
||||||
|
|
||||||
url = "2.2.2"
|
url = "2.2.2"
|
||||||
warp = "0.3.2"
|
warp = "0.3.2"
|
||||||
|
@@ -204,7 +204,7 @@ impl TemplateContext for IndexContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
#[derive(Serialize, Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum LoginFormField {
|
pub enum LoginFormField {
|
||||||
Username,
|
Username,
|
||||||
Password,
|
Password,
|
||||||
@@ -222,6 +222,7 @@ pub enum PostAuthContext {
|
|||||||
pub struct LoginContext {
|
pub struct LoginContext {
|
||||||
form: ErroredForm<LoginFormField>,
|
form: ErroredForm<LoginFormField>,
|
||||||
next: Option<PostAuthContext>,
|
next: Option<PostAuthContext>,
|
||||||
|
register_link: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TemplateContext for LoginContext {
|
impl TemplateContext for LoginContext {
|
||||||
@@ -233,6 +234,7 @@ impl TemplateContext for LoginContext {
|
|||||||
vec![LoginContext {
|
vec![LoginContext {
|
||||||
form: ErroredForm::default(),
|
form: ErroredForm::default(),
|
||||||
next: None,
|
next: None,
|
||||||
|
register_link: "/register".to_string(),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -250,6 +252,14 @@ impl LoginContext {
|
|||||||
..self
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_register_link(self, register_link: String) -> Self {
|
||||||
|
Self {
|
||||||
|
register_link,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LoginContext {
|
impl Default for LoginContext {
|
||||||
@@ -257,6 +267,67 @@ impl Default for LoginContext {
|
|||||||
Self {
|
Self {
|
||||||
form: ErroredForm::new(),
|
form: ErroredForm::new(),
|
||||||
next: None,
|
next: None,
|
||||||
|
register_link: "/register".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum RegisterFormField {
|
||||||
|
Username,
|
||||||
|
Password,
|
||||||
|
PasswordConfirm,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Context used by the `register.html` template
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct RegisterContext {
|
||||||
|
form: ErroredForm<LoginFormField>,
|
||||||
|
next: Option<PostAuthContext>,
|
||||||
|
login_link: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TemplateContext for RegisterContext {
|
||||||
|
fn sample() -> Vec<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
// TODO: samples with errors
|
||||||
|
vec![RegisterContext {
|
||||||
|
form: ErroredForm::default(),
|
||||||
|
next: None,
|
||||||
|
login_link: "/login".to_string(),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegisterContext {
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_form_error(self, form: ErroredForm<LoginFormField>) -> Self {
|
||||||
|
Self { form, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_post_action(self, next: PostAuthContext) -> Self {
|
||||||
|
Self {
|
||||||
|
next: Some(next),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_login_link(self, login_link: String) -> Self {
|
||||||
|
Self { login_link, ..self }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RegisterContext {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
form: ErroredForm::new(),
|
||||||
|
next: None,
|
||||||
|
login_link: "/login".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,10 +14,16 @@
|
|||||||
|
|
||||||
//! Additional functions, tests and filters used in templates
|
//! Additional functions, tests and filters used in templates
|
||||||
|
|
||||||
|
use std::{collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
use tera::{helpers::tests::number_args_allowed, Tera, Value};
|
use tera::{helpers::tests::number_args_allowed, Tera, Value};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
pub fn register(tera: &mut Tera) {
|
pub fn register(tera: &mut Tera) {
|
||||||
tera.register_tester("empty", self::tester_empty);
|
tera.register_tester("empty", self::tester_empty);
|
||||||
|
tera.register_function("add_params_to_uri", function_add_params_to_uri);
|
||||||
|
tera.register_function("merge", function_merge);
|
||||||
|
tera.register_function("dict", function_dict);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tester_empty(value: Option<&Value>, params: &[Value]) -> Result<bool, tera::Error> {
|
fn tester_empty(value: Option<&Value>, params: &[Value]) -> Result<bool, tera::Error> {
|
||||||
@@ -28,3 +34,84 @@ fn tester_empty(value: Option<&Value>, params: &[Value]) -> Result<bool, tera::E
|
|||||||
Some(_) => Ok(false),
|
Some(_) => Ok(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ParamsWhere {
|
||||||
|
Fragment,
|
||||||
|
Query,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn function_add_params_to_uri(params: &HashMap<String, Value>) -> Result<Value, tera::Error> {
|
||||||
|
use ParamsWhere::{Fragment, Query};
|
||||||
|
|
||||||
|
// First, get the `uri`, `mode` and `params` parameters
|
||||||
|
let uri = params
|
||||||
|
.get("uri")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.ok_or_else(|| tera::Error::msg("Invalid parameter `uri`"))?;
|
||||||
|
let uri = Url::from_str(uri).map_err(|e| tera::Error::chain(uri, e))?;
|
||||||
|
let mode = params
|
||||||
|
.get("mode")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.ok_or_else(|| tera::Error::msg("Invalid parameter `mode`"))?;
|
||||||
|
let mode = match mode {
|
||||||
|
"fragment" => Fragment,
|
||||||
|
"query" => Query,
|
||||||
|
_ => return Err(tera::Error::msg("Invalid mode")),
|
||||||
|
};
|
||||||
|
let params = params
|
||||||
|
.get("params")
|
||||||
|
.and_then(Value::as_object)
|
||||||
|
.ok_or_else(|| tera::Error::msg("Invalid parameter `params`"))?;
|
||||||
|
|
||||||
|
// Get the relevant part of the URI and parse for existing parameters
|
||||||
|
let existing = match mode {
|
||||||
|
Fragment => uri.fragment(),
|
||||||
|
Query => uri.query(),
|
||||||
|
};
|
||||||
|
let existing: HashMap<String, Value> = existing
|
||||||
|
.map(serde_urlencoded::from_str)
|
||||||
|
.transpose()
|
||||||
|
.map_err(|e| tera::Error::chain(e, "Could not parse existing `uri` parameters"))?
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// Merge the exising and the additional parameters together
|
||||||
|
let params: HashMap<&String, &Value> = params
|
||||||
|
.iter()
|
||||||
|
// Filter out the `uri` and `mode` params
|
||||||
|
.filter(|(k, _v)| k != &"uri" && k != &"mode")
|
||||||
|
.chain(existing.iter())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Transform them back to urlencoded
|
||||||
|
let params = serde_urlencoded::to_string(params)
|
||||||
|
.map_err(|e| tera::Error::chain(e, "Could not serialize back parameters"))?;
|
||||||
|
|
||||||
|
let uri = {
|
||||||
|
let mut uri = uri;
|
||||||
|
match mode {
|
||||||
|
Fragment => uri.set_fragment(Some(¶ms)),
|
||||||
|
Query => uri.set_query(Some(¶ms)),
|
||||||
|
};
|
||||||
|
uri
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Value::String(uri.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn function_merge(params: &HashMap<String, Value>) -> Result<Value, tera::Error> {
|
||||||
|
let mut ret = serde_json::Map::new();
|
||||||
|
for (k, v) in params {
|
||||||
|
let v = v
|
||||||
|
.as_object()
|
||||||
|
.ok_or_else(|| tera::Error::msg(format!("Parameter {:?} should be an object", k)))?;
|
||||||
|
ret.extend(v.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::Object(ret))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
|
fn function_dict(params: &HashMap<String, Value>) -> Result<Value, tera::Error> {
|
||||||
|
let ret = params.clone().into_iter().collect();
|
||||||
|
Ok(Value::Object(ret))
|
||||||
|
}
|
||||||
|
@@ -48,8 +48,8 @@ mod macros;
|
|||||||
|
|
||||||
pub use self::context::{
|
pub use self::context::{
|
||||||
EmptyContext, ErrorContext, FormPostContext, IndexContext, LoginContext, LoginFormField,
|
EmptyContext, ErrorContext, FormPostContext, IndexContext, LoginContext, LoginFormField,
|
||||||
PostAuthContext, ReauthContext, ReauthFormField, TemplateContext, WithCsrf,
|
PostAuthContext, ReauthContext, ReauthFormField, RegisterContext, RegisterFormField,
|
||||||
WithOptionalSession, WithSession,
|
TemplateContext, WithCsrf, WithOptionalSession, WithSession,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Wrapper around [`tera::Tera`] helping rendering the various templates
|
/// Wrapper around [`tera::Tera`] helping rendering the various templates
|
||||||
@@ -280,6 +280,7 @@ register_templates! {
|
|||||||
extra = {
|
extra = {
|
||||||
"components/button.html",
|
"components/button.html",
|
||||||
"components/field.html",
|
"components/field.html",
|
||||||
|
"components/back_to_client.html",
|
||||||
"base.html",
|
"base.html",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -287,7 +288,7 @@ register_templates! {
|
|||||||
pub fn render_login(WithCsrf<LoginContext>) { "login.html" }
|
pub fn render_login(WithCsrf<LoginContext>) { "login.html" }
|
||||||
|
|
||||||
/// Render the registration page
|
/// Render the registration page
|
||||||
pub fn render_register(WithCsrf<EmptyContext>) { "register.html" }
|
pub fn render_register(WithCsrf<RegisterContext>) { "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>>) { "index.html" }
|
||||||
|
@@ -16,6 +16,7 @@ limitations under the License.
|
|||||||
|
|
||||||
{% import "components/button.html" as button %}
|
{% import "components/button.html" as button %}
|
||||||
{% import "components/field.html" as field %}
|
{% import "components/field.html" as field %}
|
||||||
|
{% import "components/back_to_client.html" as back_to_client %}
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
30
crates/templates/src/res/components/back_to_client.html
Normal file
30
crates/templates/src/res/components/back_to_client.html
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{#
|
||||||
|
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.
|
||||||
|
#}
|
||||||
|
|
||||||
|
{% macro link(text, class="", uri, mode, params) %}
|
||||||
|
{% if mode == "form_post" %}
|
||||||
|
<form method="post" action="{{ uri }}">
|
||||||
|
{% for key, value in params %}
|
||||||
|
<input type="hidden" name="{{ key }}" value="{{ value }}" />
|
||||||
|
{% endfor %}
|
||||||
|
<button class="{{ class }}" type="submit">{{ text }}</button>
|
||||||
|
</form>
|
||||||
|
{% elif mode == "fragment" or mode == "query" %}
|
||||||
|
<a class="{{ class }}" href="{{ add_params_to_uri(uri=uri, mode=mode, params=params) }}">{{ text }}</a>
|
||||||
|
{% else %}
|
||||||
|
{{ throw(message="Invalid mode") }}
|
||||||
|
{% endif %}
|
||||||
|
{% endmacro %}
|
@@ -18,7 +18,12 @@ limitations under the License.
|
|||||||
|
|
||||||
{% block navbar_start %}
|
{% block navbar_start %}
|
||||||
{% if next and next.kind == "continue_authorization_grant" %}
|
{% if next and next.kind == "continue_authorization_grant" %}
|
||||||
<a href="#">← Back</a>
|
{{ back_to_client::link(
|
||||||
|
text="← Back",
|
||||||
|
uri=next.grant.redirect_uri,
|
||||||
|
mode=next.grant.response_mode,
|
||||||
|
params=dict(error="access_denied", state=next.grant.state)
|
||||||
|
) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@@ -40,14 +45,19 @@ limitations under the License.
|
|||||||
{{ field::input(label="Username", name="username") }}
|
{{ field::input(label="Username", name="username") }}
|
||||||
{{ field::input(label="Password", name="password", type="password") }}
|
{{ field::input(label="Password", name="password", type="password") }}
|
||||||
{{ button::button(text="Next") }}
|
{{ button::button(text="Next") }}
|
||||||
{{ button::link_text(text="Create account", href="/register") }}
|
{{ button::link_text(text="Create account", href=register_link) }}
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{% if next and next.kind == "continue_authorization_grant" %}
|
{% if next and next.kind == "continue_authorization_grant" %}
|
||||||
<section class="self-center my-6">
|
<section class="self-center my-6">
|
||||||
{# TODO: proper back link #}
|
{{ back_to_client::link(
|
||||||
{{ button::link_text(text="Return to application", href="/") }}
|
text="Return to application",
|
||||||
|
class=button::text_class(),
|
||||||
|
uri=next.grant.redirect_uri,
|
||||||
|
mode=next.grant.response_mode,
|
||||||
|
params=dict(error="access_denied", state=next.grant.state)
|
||||||
|
) }}
|
||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
@@ -16,6 +16,17 @@ limitations under the License.
|
|||||||
|
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block navbar_start %}
|
||||||
|
{% if next and next.kind == "continue_authorization_grant" %}
|
||||||
|
{{ back_to_client::link(
|
||||||
|
text="← Back",
|
||||||
|
uri=next.grant.redirect_uri,
|
||||||
|
mode=next.grant.response_mode,
|
||||||
|
params=dict(error="access_denied", state=next.grant.state)
|
||||||
|
) }}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<section class="flex items-center justify-center flex-1">
|
<section class="flex items-center justify-center flex-1">
|
||||||
<form method="POST" class="grid grid-cols-1 gap-6 w-96 m-4">
|
<form method="POST" class="grid grid-cols-1 gap-6 w-96 m-4">
|
||||||
@@ -44,5 +55,17 @@ limitations under the License.
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if next and next.kind == "continue_authorization_grant" %}
|
||||||
|
<section class="self-center my-6">
|
||||||
|
{{ back_to_client::link(
|
||||||
|
text="Return to application",
|
||||||
|
class=button::text_class(),
|
||||||
|
uri=next.grant.redirect_uri,
|
||||||
|
mode=next.grant.response_mode,
|
||||||
|
params=dict(error="access_denied", state=next.grant.state)
|
||||||
|
) }}
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
|
@@ -18,7 +18,12 @@ limitations under the License.
|
|||||||
|
|
||||||
{% block navbar_start %}
|
{% block navbar_start %}
|
||||||
{% if next and next.kind == "continue_authorization_grant" %}
|
{% if next and next.kind == "continue_authorization_grant" %}
|
||||||
<a href="#">← Back</a>
|
{{ back_to_client::link(
|
||||||
|
text="← Back",
|
||||||
|
uri=next.grant.redirect_uri,
|
||||||
|
mode=next.grant.response_mode,
|
||||||
|
params=dict(error="access_denied", state=next.grant.state)
|
||||||
|
) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@@ -40,14 +45,19 @@ limitations under the License.
|
|||||||
{{ field::input(label="Confirm Password", name="password_confirm", type="password") }}
|
{{ field::input(label="Confirm Password", name="password_confirm", type="password") }}
|
||||||
{{ button::button(text="Next") }}
|
{{ button::button(text="Next") }}
|
||||||
{# TODO: proper link #}
|
{# TODO: proper link #}
|
||||||
{{ button::link_text(text="Login instead", href="/login") }}
|
{{ button::link_text(text="Login instead", href=login_link) }}
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{% if next %}
|
{% if next and next.kind == "continue_authorization_grant" %}
|
||||||
<section class="self-center my-6">
|
<section class="self-center my-6">
|
||||||
{# TODO: proper back link #}
|
{{ back_to_client::link(
|
||||||
{{ button::link_text(text="Return to application", href="/") }}
|
text="Return to application",
|
||||||
|
class=button::text_class(),
|
||||||
|
uri=next.grant.redirect_uri,
|
||||||
|
mode=next.grant.response_mode,
|
||||||
|
params=dict(error="access_denied", state=next.grant.state)
|
||||||
|
) }}
|
||||||
</section>
|
</section>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
Reference in New Issue
Block a user