You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-08-07 17:03:01 +03:00
Simple consent screen and storage
This commit is contained in:
@@ -124,6 +124,10 @@ where
|
||||
"/oauth2/authorize/step",
|
||||
get(self::oauth2::authorization::step_get),
|
||||
)
|
||||
.route(
|
||||
"/consent",
|
||||
get(self::oauth2::consent::get).post(self::oauth2::consent::post),
|
||||
)
|
||||
.merge(api_router)
|
||||
.layer(Extension(pool.clone()))
|
||||
.layer(Extension(templates.clone()))
|
||||
|
@@ -38,6 +38,7 @@ use mas_storage::{
|
||||
derive_session, fulfill_grant, get_grant_by_id, new_authorization_grant,
|
||||
},
|
||||
client::{lookup_client_by_client_id, ClientFetchError},
|
||||
consent::fetch_client_consent,
|
||||
refresh_token::add_refresh_token,
|
||||
},
|
||||
PostgresqlBackend,
|
||||
@@ -62,6 +63,7 @@ use sqlx::{PgConnection, PgPool, Postgres, Transaction};
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
use super::consent::ConsentRequest;
|
||||
use crate::views::{LoginRequest, PostAuthAction, ReauthRequest, RegisterRequest};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -508,8 +510,16 @@ async fn step(
|
||||
return Err(anyhow::anyhow!("authorization grant not pending").into());
|
||||
}
|
||||
|
||||
let reply = match browser_session.last_authentication {
|
||||
Some(Authentication { created_at, .. }) if created_at > grant.max_auth_time() => {
|
||||
let current_consent =
|
||||
fetch_client_consent(&mut txn, &browser_session.user, &grant.client).await?;
|
||||
|
||||
let lacks_consent = grant
|
||||
.scope
|
||||
.difference(¤t_consent)
|
||||
.any(|scope| !scope.starts_with("urn:matrix:device:"));
|
||||
|
||||
let reply = match (lacks_consent, &browser_session.last_authentication) {
|
||||
(false, Some(Authentication { created_at, .. })) if created_at > &grant.max_auth_time() => {
|
||||
let session = derive_session(&mut txn, &grant, browser_session).await?;
|
||||
|
||||
let grant = fulfill_grant(&mut txn, grant, session.clone()).await?;
|
||||
@@ -562,6 +572,12 @@ async fn step(
|
||||
)
|
||||
.await?
|
||||
}
|
||||
(true, Some(Authentication { created_at, .. })) if created_at > &grant.max_auth_time() => {
|
||||
let next: ConsentRequest = next.into();
|
||||
let next = next.build_uri()?;
|
||||
|
||||
Redirect::to(&next.to_string()).into_response()
|
||||
}
|
||||
_ => {
|
||||
let next: PostAuthAction = next.into();
|
||||
let next: ReauthRequest = next.into();
|
||||
|
165
crates/handlers/src/oauth2/consent.rs
Normal file
165
crates/handlers/src/oauth2/consent.rs
Normal file
@@ -0,0 +1,165 @@
|
||||
// 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.
|
||||
|
||||
use anyhow::Context;
|
||||
use axum::{
|
||||
extract::{Extension, Form, Query},
|
||||
http::uri::{Parts, PathAndQuery},
|
||||
response::{Html, IntoResponse, Redirect, Response},
|
||||
};
|
||||
use hyper::{StatusCode, Uri};
|
||||
use mas_axum_utils::{
|
||||
csrf::{CsrfExt, ProtectedForm},
|
||||
PrivateCookieJar, SessionInfoExt,
|
||||
};
|
||||
use mas_config::Encrypter;
|
||||
use mas_data_model::AuthorizationGrantStage;
|
||||
use mas_storage::oauth2::consent::insert_client_consent;
|
||||
use mas_templates::{ConsentContext, TemplateContext, Templates};
|
||||
use sqlx::PgPool;
|
||||
use thiserror::Error;
|
||||
|
||||
use super::ContinueAuthorizationGrant;
|
||||
use crate::views::{LoginRequest, PostAuthAction};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RouteError {
|
||||
#[error(transparent)]
|
||||
Anyhow(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
impl IntoResponse for RouteError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ConsentRequest {
|
||||
grant: ContinueAuthorizationGrant,
|
||||
}
|
||||
|
||||
impl From<ContinueAuthorizationGrant> for ConsentRequest {
|
||||
fn from(grant: ContinueAuthorizationGrant) -> Self {
|
||||
Self { grant }
|
||||
}
|
||||
}
|
||||
|
||||
impl ConsentRequest {
|
||||
pub fn build_uri(&self) -> anyhow::Result<Uri> {
|
||||
let qs = serde_urlencoded::to_string(&self.grant)?;
|
||||
let path_and_query = PathAndQuery::try_from(format!("/consent?{}", qs))?;
|
||||
let uri = Uri::from_parts({
|
||||
let mut parts = Parts::default();
|
||||
parts.path_and_query = Some(path_and_query);
|
||||
parts
|
||||
})?;
|
||||
Ok(uri)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get(
|
||||
Extension(templates): Extension<Templates>,
|
||||
Extension(pool): Extension<PgPool>,
|
||||
cookie_jar: PrivateCookieJar<Encrypter>,
|
||||
Query(next): Query<ContinueAuthorizationGrant>,
|
||||
) -> Result<Response, RouteError> {
|
||||
let mut conn = pool
|
||||
.acquire()
|
||||
.await
|
||||
.context("failed to acquire db connection")?;
|
||||
|
||||
let (session_info, cookie_jar) = cookie_jar.session_info();
|
||||
|
||||
let maybe_session = session_info
|
||||
.load_session(&mut conn)
|
||||
.await
|
||||
.context("could not load session")?;
|
||||
|
||||
let grant = next.fetch_authorization_grant(&mut conn).await?;
|
||||
|
||||
if !matches!(grant.stage, AuthorizationGrantStage::Pending) {
|
||||
return Err(anyhow::anyhow!("authorization grant not pending").into());
|
||||
}
|
||||
|
||||
if let Some(session) = maybe_session {
|
||||
let (csrf_token, cookie_jar) = cookie_jar.csrf_token();
|
||||
|
||||
let ctx = ConsentContext::new(grant)
|
||||
.with_session(session)
|
||||
.with_csrf(csrf_token.form_value());
|
||||
|
||||
let content = templates
|
||||
.render_consent(&ctx)
|
||||
.await
|
||||
.context("failed to render template")?;
|
||||
|
||||
Ok((cookie_jar, Html(content)).into_response())
|
||||
} else {
|
||||
let login = LoginRequest::from(PostAuthAction::from(next));
|
||||
let login = login.build_uri()?;
|
||||
Ok((cookie_jar, Redirect::to(&login.to_string())).into_response())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn post(
|
||||
Extension(pool): Extension<PgPool>,
|
||||
cookie_jar: PrivateCookieJar<Encrypter>,
|
||||
Query(next): Query<ContinueAuthorizationGrant>,
|
||||
Form(form): Form<ProtectedForm<()>>,
|
||||
) -> Result<Response, RouteError> {
|
||||
let mut txn = pool
|
||||
.begin()
|
||||
.await
|
||||
.context("failed to begin db transaction")?;
|
||||
|
||||
cookie_jar
|
||||
.verify_form(form)
|
||||
.context("csrf verification failed")?;
|
||||
|
||||
let (session_info, cookie_jar) = cookie_jar.session_info();
|
||||
|
||||
let maybe_session = session_info
|
||||
.load_session(&mut txn)
|
||||
.await
|
||||
.context("could not load session")?;
|
||||
|
||||
let session = if let Some(session) = maybe_session {
|
||||
session
|
||||
} else {
|
||||
let login = LoginRequest::from(PostAuthAction::from(next));
|
||||
let login = login.build_uri()?;
|
||||
return Ok((cookie_jar, Redirect::to(&login.to_string())).into_response());
|
||||
};
|
||||
|
||||
let grant = next.fetch_authorization_grant(&mut txn).await?;
|
||||
// Do not consent for the "urn:matrix:device:*" scope
|
||||
let scope_without_device = grant
|
||||
.scope
|
||||
.iter()
|
||||
.filter(|s| !s.starts_with("urn:matrix:device:"))
|
||||
.cloned()
|
||||
.collect();
|
||||
insert_client_consent(
|
||||
&mut txn,
|
||||
&session.user,
|
||||
&grant.client,
|
||||
&scope_without_device,
|
||||
)
|
||||
.await?;
|
||||
|
||||
txn.commit().await.context("could not commit txn")?;
|
||||
|
||||
let uri = next.build_uri()?;
|
||||
Ok((cookie_jar, Redirect::to(&uri.to_string())).into_response())
|
||||
}
|
@@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
pub mod authorization;
|
||||
pub mod consent;
|
||||
pub mod discovery;
|
||||
pub mod introspection;
|
||||
pub mod keys;
|
||||
|
Reference in New Issue
Block a user