Skip to content
This repository was archived by the owner on Sep 10, 2024. It is now read-only.

Commit 17f8dc4

Browse files
Kerrysandhose
andauthored
Implement MSC2965 action parameter (#1673)
* redirect session_end action to session detail * fix react key warning in oauth session detail * move Route type to /routing * test getRouteActionRedirection * comment * frontend: Split the routing-related stuff in multiple files under routing/ * frontend: Cover all the redirections defined by MSC2965 * frontend: fix test * Make the backend keep query parameters through login to the /account/ interface * Fix frontend tests & clippy lints --------- Co-authored-by: Quentin Gliech <[email protected]>
1 parent be5b527 commit 17f8dc4

37 files changed

+662
-328
lines changed

crates/handlers/src/lib.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@
3030
clippy::let_with_type_underscore,
3131
)]
3232

33-
use std::{convert::Infallible, time::Duration};
33+
use std::{borrow::Cow, convert::Infallible, time::Duration};
3434

3535
use axum::{
3636
body::{Bytes, HttpBody},
37-
extract::{FromRef, FromRequestParts, OriginalUri, State},
37+
extract::{FromRef, FromRequestParts, OriginalUri, RawQuery, State},
3838
http::Method,
3939
response::{Html, IntoResponse},
4040
routing::{get, on, post, MethodFilter},
@@ -265,6 +265,7 @@ where
265265
)
266266
}
267267

268+
#[allow(clippy::too_many_lines)]
268269
pub fn human_router<S, B>(templates: Templates) -> Router<S, B>
269270
where
270271
B: HttpBody + Send + 'static,
@@ -286,7 +287,19 @@ where
286287
{
287288
Router::new()
288289
// XXX: hard-coded redirect from /account to /account/
289-
.route("/account", get(|| async { mas_router::Account.go() }))
290+
.route(
291+
"/account",
292+
get(|RawQuery(query): RawQuery| async {
293+
let route = mas_router::Account::route();
294+
let destination = if let Some(query) = query {
295+
Cow::Owned(format!("{route}?{query}"))
296+
} else {
297+
Cow::Borrowed(route)
298+
};
299+
300+
axum::response::Redirect::to(&destination)
301+
}),
302+
)
290303
.route(mas_router::Account::route(), get(self::views::app::get))
291304
.route(
292305
mas_router::AccountWildcard::route(),

crates/handlers/src/views/account/emails/add.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ pub(crate) async fn post(
128128

129129
next.go()
130130
} else {
131-
query.go_next_or_default(&mas_router::Account)
131+
query.go_next_or_default(&mas_router::Account::default())
132132
};
133133

134134
repo.save().await?;

crates/handlers/src/views/account/emails/verify.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ pub(crate) async fn get(
7373

7474
if user_email.confirmed_at.is_some() {
7575
// This email was already verified, skip
76-
let destination = query.go_next_or_default(&mas_router::Account);
76+
let destination = query.go_next_or_default(&mas_router::Account::default());
7777
return Ok((cookie_jar, destination).into_response());
7878
}
7979

@@ -145,6 +145,6 @@ pub(crate) async fn post(
145145

146146
repo.save().await?;
147147

148-
let destination = query.go_next_or_default(&mas_router::Account);
148+
let destination = query.go_next_or_default(&mas_router::Account::default());
149149
Ok((cookie_jar, destination).into_response())
150150
}

crates/handlers/src/views/account/password.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ pub(crate) async fn get(
5555
) -> Result<Response, FancyError> {
5656
// If the password manager is disabled, we can go back to the account page.
5757
if !password_manager.is_enabled() {
58-
return Ok(mas_router::Account.go().into_response());
58+
return Ok(mas_router::Account::default().go().into_response());
5959
}
6060

6161
let (session_info, cookie_jar) = cookie_jar.session_info();

crates/handlers/src/views/app.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// limitations under the License.
1414

1515
use axum::{
16-
extract::State,
16+
extract::{Query, State},
1717
response::{Html, IntoResponse},
1818
};
1919
use mas_axum_utils::{cookies::CookieJar, FancyError, SessionInfoExt};
@@ -24,17 +24,19 @@ use mas_templates::{AppContext, Templates};
2424
#[tracing::instrument(name = "handlers.views.app.get", skip_all, err)]
2525
pub async fn get(
2626
State(templates): State<Templates>,
27+
action: Option<Query<mas_router::AccountAction>>,
2728
mut repo: BoxRepository,
2829
cookie_jar: CookieJar,
2930
) -> Result<impl IntoResponse, FancyError> {
3031
let (session_info, cookie_jar) = cookie_jar.session_info();
3132
let session = session_info.load_session(&mut repo).await?;
33+
let action = action.map(|Query(a)| a);
3234

33-
// TODO: keep the full path
35+
// TODO: keep the full path, not just the action
3436
if session.is_none() {
3537
return Ok((
3638
cookie_jar,
37-
mas_router::Login::and_then(PostAuthAction::ManageAccount).go(),
39+
mas_router::Login::and_then(PostAuthAction::manage_account(action)).go(),
3840
)
3941
.into_response());
4042
}

crates/handlers/src/views/reauth.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ pub(crate) async fn get(
5252
) -> Result<Response, FancyError> {
5353
if !password_manager.is_enabled() {
5454
// XXX: do something better here
55-
return Ok(mas_router::Account.go().into_response());
55+
return Ok(mas_router::Account::default().go().into_response());
5656
}
5757

5858
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(&clock, &mut rng);

crates/handlers/src/views/shared.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ impl OptionalPostAuthAction {
8888
PostAuthContextInner::LinkUpstream { provider, link }
8989
}
9090

91-
PostAuthAction::ManageAccount => PostAuthContextInner::ManageAccount,
91+
PostAuthAction::ManageAccount { .. } => PostAuthContextInner::ManageAccount,
9292
};
9393

9494
Ok(Some(PostAuthContext {

crates/router/src/endpoints.rs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,20 @@ pub use crate::traits::*;
2020
#[derive(Deserialize, Serialize, Clone, Debug)]
2121
#[serde(rename_all = "snake_case", tag = "next")]
2222
pub enum PostAuthAction {
23-
ContinueAuthorizationGrant { id: Ulid },
24-
ContinueCompatSsoLogin { id: Ulid },
23+
ContinueAuthorizationGrant {
24+
id: Ulid,
25+
},
26+
ContinueCompatSsoLogin {
27+
id: Ulid,
28+
},
2529
ChangePassword,
26-
LinkUpstream { id: Ulid },
27-
ManageAccount,
30+
LinkUpstream {
31+
id: Ulid,
32+
},
33+
ManageAccount {
34+
#[serde(flatten)]
35+
action: Option<AccountAction>,
36+
},
2837
}
2938

3039
impl PostAuthAction {
@@ -43,13 +52,21 @@ impl PostAuthAction {
4352
PostAuthAction::LinkUpstream { id }
4453
}
4554

55+
#[must_use]
56+
pub const fn manage_account(action: Option<AccountAction>) -> Self {
57+
PostAuthAction::ManageAccount { action }
58+
}
59+
4660
pub fn go_next(&self) -> axum::response::Redirect {
4761
match self {
4862
Self::ContinueAuthorizationGrant { id } => ContinueAuthorizationGrant(*id).go(),
4963
Self::ContinueCompatSsoLogin { id } => CompatLoginSsoComplete::new(*id, None).go(),
5064
Self::ChangePassword => AccountPassword.go(),
5165
Self::LinkUpstream { id } => UpstreamOAuth2Link::new(*id).go(),
52-
Self::ManageAccount => Account.go(),
66+
Self::ManageAccount { action } => Account {
67+
action: action.clone(),
68+
}
69+
.go(),
5370
}
5471
}
5572
}
@@ -406,12 +423,32 @@ impl AccountAddEmail {
406423
}
407424
}
408425

426+
/// Actions parameters as defined by MSC2965
427+
#[derive(Debug, Clone, Serialize, Deserialize)]
428+
#[serde(rename_all = "snake_case", tag = "action")]
429+
pub enum AccountAction {
430+
Profile,
431+
SessionsList,
432+
SessionView { device_id: String },
433+
SessionEnd { device_id: String },
434+
}
435+
409436
/// `GET /account/`
410437
#[derive(Default, Debug, Clone)]
411-
pub struct Account;
438+
pub struct Account {
439+
action: Option<AccountAction>,
440+
}
412441

413-
impl SimpleRoute for Account {
414-
const PATH: &'static str = "/account/";
442+
impl Route for Account {
443+
type Query = AccountAction;
444+
445+
fn route() -> &'static str {
446+
"/account/"
447+
}
448+
449+
fn query(&self) -> Option<&Self::Query> {
450+
self.action.as_ref()
451+
}
415452
}
416453

417454
/// `GET /account/*`

0 commit comments

Comments
 (0)