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

Commit

Permalink
Implement account management discovery as per MSC2965
Browse files Browse the repository at this point in the history
  • Loading branch information
sandhose committed Dec 5, 2023
1 parent 5b272df commit bc3e676
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 2 deletions.
13 changes: 13 additions & 0 deletions crates/handlers/src/oauth2/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ struct DiscoveryResponse {

#[serde(rename = "org.matrix.matrix-authentication-service.graphql_endpoint")]
graphql_endpoint: url::Url,

// As per MSC2965
account_management_uri: url::Url,
account_management_actions_supported: Vec<String>,
}

#[tracing::instrument(name = "handlers.oauth2.discovery.get", skip_all)]
Expand Down Expand Up @@ -168,6 +172,15 @@ pub(crate) async fn get(
Json(DiscoveryResponse {
standard,
graphql_endpoint: url_builder.graphql_endpoint(),
account_management_uri: url_builder.account_management_uri(),
// This needs to be kept in sync with what is supported in the frontend,
// see frontend/src/routing/actions.ts
account_management_actions_supported: vec![
"org.matrix.profile".to_owned(),
"org.matrix.sessions_list".to_owned(),
"org.matrix.session_view".to_owned(),
"org.matrix.session_end".to_owned(),
],
})
}

Expand Down
17 changes: 16 additions & 1 deletion crates/router/src/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -429,11 +429,26 @@ impl AccountAddEmail {

/// Actions parameters as defined by MSC2965
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "action")]
#[serde(tag = "action")]
pub enum AccountAction {
#[serde(rename = "org.matrix.profile")]
OrgMatrixProfile,
#[serde(rename = "profile")]
Profile,

#[serde(rename = "org.matrix.sessions_list")]
OrgMatrixSessionsList,
#[serde(rename = "sessions_list")]
SessionsList,

#[serde(rename = "org.matrix.session_view")]
OrgMatrixSessionView { device_id: String },
#[serde(rename = "session_view")]
SessionView { device_id: String },

#[serde(rename = "org.matrix.session_end")]
OrgMatrixSessionEnd { device_id: String },
#[serde(rename = "session_end")]
SessionEnd { device_id: String },
}

Expand Down
6 changes: 6 additions & 0 deletions crates/router/src/url_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,12 @@ impl UrlBuilder {
pub fn upstream_oauth_authorize(&self, id: Ulid) -> Url {
self.absolute_url_for(&crate::endpoints::UpstreamOAuth2Authorize::new(id))
}

/// Account management URI
#[must_use]
pub fn account_management_uri(&self) -> Url {
self.absolute_url_for(&crate::endpoints::Account::default())
}
}

#[cfg(test)]
Expand Down
57 changes: 57 additions & 0 deletions frontend/src/routing/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,61 @@ describe("getRouteActionRedirection()", () => {
searchParams: new URLSearchParams(),
});
});

it("redirects to session detail when location has a action=org.matrix.session_end", () => {
const searchParams = new URLSearchParams();
searchParams.set("action", "org.matrix.session_end");
searchParams.set("device_id", "test-device-id");
searchParams.set("something_else", "should-remain");
expect(
getRouteActionRedirection({ pathname: "/account/", searchParams }),
).toEqual({
route: {
type: "session",
id: "test-device-id",
},
searchParams: new URLSearchParams("?something_else=should-remain"),
});
});

it("redirects to session detail when location has a action=org.matrix.session_view", () => {
const searchParams = new URLSearchParams();
searchParams.set("action", "org.matrix.session_view");
searchParams.set("device_id", "test-device-id");
expect(
getRouteActionRedirection({ pathname: "/account/", searchParams }),
).toEqual({
route: {
type: "session",
id: "test-device-id",
},
searchParams: new URLSearchParams(),
});
});

it("redirects to sessions overview when location has a action=org.matrix.sessions_list", () => {
const searchParams = new URLSearchParams();
searchParams.set("action", "org.matrix.sessions_list");
expect(
getRouteActionRedirection({ pathname: "/account/", searchParams }),
).toEqual({
route: {
type: "sessions-overview",
},
searchParams: new URLSearchParams(),
});
});

it("redirects to profile when location has a action=org.matrix.profile", () => {
const searchParams = new URLSearchParams();
searchParams.set("action", "org.matrix.profile");
expect(
getRouteActionRedirection({ pathname: "/account/", searchParams }),
).toEqual({
route: {
type: "profile",
},
searchParams: new URLSearchParams(),
});
});
});
8 changes: 7 additions & 1 deletion frontend/src/routing/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,17 @@ export const getRouteActionRedirection = (
} => {
// Clone the search params so we can modify them
const searchParams = new URLSearchParams(location.searchParams?.toString());
const action = searchParams.get("action");
let action = searchParams.get("action");
const deviceId = searchParams.get("device_id");
searchParams.delete("action");
searchParams.delete("device_id");

// Actions are actually prefixed with org.matrix. in the latest version of MSC2965
// but we still want to support non-prefixed actions for backwards compatibility
if (action) {
action = action.replace(/^org.matrix./, "");
}

let route: Route;
switch (action) {
case RouteAction.EndSession:
Expand Down

0 comments on commit bc3e676

Please sign in to comment.