From 90cef5a50b91f99177d7b3df1971d7efff2c6755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Sat, 11 Jun 2022 15:11:16 +0200 Subject: [PATCH] client-api: Add support for refresh tokens According to MSC2918 --- crates/ruma-client-api/CHANGELOG.md | 4 + crates/ruma-client-api/Cargo.toml | 1 + .../ruma-client-api/src/account/register.rs | 55 +++++++++++- crates/ruma-client-api/src/session.rs | 2 + crates/ruma-client-api/src/session/login.rs | 61 +++++++++++++- .../src/session/refresh_token.rs | 84 +++++++++++++++++++ crates/ruma/Cargo.toml | 2 + 7 files changed, 206 insertions(+), 3 deletions(-) create mode 100644 crates/ruma-client-api/src/session/refresh_token.rs diff --git a/crates/ruma-client-api/CHANGELOG.md b/crates/ruma-client-api/CHANGELOG.md index e938da2c..14eaf8c8 100644 --- a/crates/ruma-client-api/CHANGELOG.md +++ b/crates/ruma-client-api/CHANGELOG.md @@ -5,6 +5,10 @@ Breaking changes: * Remove `PartialEq` implementations for a number of types * If the lack of such an `impl` causes problems, please open a GitHub issue +Improvements: + +* Add unstable support for refresh tokens (MSC2918) + # 0.14.1 Improvements: diff --git a/crates/ruma-client-api/Cargo.toml b/crates/ruma-client-api/Cargo.toml index 9f385433..5abe36ff 100644 --- a/crates/ruma-client-api/Cargo.toml +++ b/crates/ruma-client-api/Cargo.toml @@ -23,6 +23,7 @@ unstable-msc2448 = [] unstable-msc2654 = [] unstable-msc2676 = [] unstable-msc2677 = [] +unstable-msc2918 = [] unstable-msc3440 = [] unstable-msc3488 = [] client = [] diff --git a/crates/ruma-client-api/src/account/register.rs b/crates/ruma-client-api/src/account/register.rs index 77cd1d9d..ccbd7ad8 100644 --- a/crates/ruma-client-api/src/account/register.rs +++ b/crates/ruma-client-api/src/account/register.rs @@ -7,6 +7,9 @@ pub mod v3 { //! //! [spec]: https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3register + #[cfg(feature = "unstable-msc2918")] + use std::time::Duration; + use ruma_common::{api::ruma_api, DeviceId, OwnedDeviceId, OwnedUserId}; use super::{LoginType, RegistrationKind}; @@ -81,12 +84,24 @@ pub mod v3 { /// [admin]: https://spec.matrix.org/v1.2/application-service-api/#server-admin-style-permissions #[serde(rename = "type", skip_serializing_if = "Option::is_none")] pub login_type: Option<&'a LoginType>, + + /// If set to `true`, the client supports refresh tokens. + #[cfg(feature = "unstable-msc2918")] + #[serde( + default, + skip_serializing_if = "ruma_common::serde::is_default", + rename = "org.matrix.msc2918.refresh_token", + alias = "refresh_token", + )] + pub refresh_token: bool, } response: { /// An access token for the account. /// /// This access token can then be used to authorize other requests. + /// + /// Required if the request's `inhibit_login` was set to `false`. #[serde(skip_serializing_if = "Option::is_none")] pub access_token: Option, @@ -96,7 +111,37 @@ pub mod v3 { /// ID of the registered device. /// /// Will be the same as the corresponding parameter in the request, if one was specified. + /// + /// Required if the request's `inhibit_login` was set to `false`. pub device_id: Option, + + /// A refresh token for the account. + /// + /// This token can be used to obtain a new access token when it expires by calling the + /// `/refresh` endpoint. + /// + /// Omitted if the request's `inhibit_login` was set to `true`. + #[cfg(feature = "unstable-msc2918")] + #[serde(skip_serializing_if = "Option::is_none")] + pub refresh_token: Option, + + /// The lifetime of the access token, in milliseconds. + /// + /// Once the access token has expired, a new access token can be obtained by using the + /// provided refresh token. If no refresh token is provided, the client will need to + /// re-login to obtain a new access token. + /// + /// If this is `None`, the client can assume that the access token will not expire. + /// + /// Omitted if the request's `inhibit_login` was set to `true`. + #[cfg(feature = "unstable-msc2918")] + #[serde( + with = "ruma_common::serde::duration::opt_ms", + default, + skip_serializing_if = "Option::is_none", + rename = "expires_in_ms", + )] + pub expires_in: Option, } error: UiaaResponse @@ -112,7 +157,15 @@ pub mod v3 { impl Response { /// Creates a new `Response` with the given user ID. pub fn new(user_id: OwnedUserId) -> Self { - Self { access_token: None, user_id, device_id: None } + Self { + access_token: None, + user_id, + device_id: None, + #[cfg(feature = "unstable-msc2918")] + refresh_token: None, + #[cfg(feature = "unstable-msc2918")] + expires_in: None, + } } } } diff --git a/crates/ruma-client-api/src/session.rs b/crates/ruma-client-api/src/session.rs index f2f60d2d..dd9c4b0c 100644 --- a/crates/ruma-client-api/src/session.rs +++ b/crates/ruma-client-api/src/session.rs @@ -5,5 +5,7 @@ pub mod login; pub mod login_fallback; pub mod logout; pub mod logout_all; +#[cfg(feature = "unstable-msc2918")] +pub mod refresh_token; pub mod sso_login; pub mod sso_login_with_provider; diff --git a/crates/ruma-client-api/src/session/login.rs b/crates/ruma-client-api/src/session/login.rs index d406c5c7..652f462f 100644 --- a/crates/ruma-client-api/src/session/login.rs +++ b/crates/ruma-client-api/src/session/login.rs @@ -5,6 +5,9 @@ pub mod v3 { //! //! [spec]: https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3login + #[cfg(feature = "unstable-msc2918")] + use std::time::Duration; + use ruma_common::{ api::ruma_api, serde::{Incoming, JsonObject}, @@ -44,6 +47,16 @@ pub mod v3 { /// Ignored if `device_id` corresponds to a known device. #[serde(skip_serializing_if = "Option::is_none")] pub initial_device_display_name: Option<&'a str>, + + /// If set to `true`, the client supports refresh tokens. + #[cfg(feature = "unstable-msc2918")] + #[serde( + default, + skip_serializing_if = "ruma_common::serde::is_default", + rename = "org.matrix.msc2918.refresh_token", + alias = "refresh_token", + )] + pub refresh_token: bool, } response: { @@ -71,6 +84,30 @@ pub mod v3 { /// If present, clients SHOULD use the provided object to reconfigure themselves. #[serde(skip_serializing_if = "Option::is_none")] pub well_known: Option, + + /// A refresh token for the account. + /// + /// This token can be used to obtain a new access token when it expires by calling the + /// `/refresh` endpoint. + #[cfg(feature = "unstable-msc2918")] + #[serde(skip_serializing_if = "Option::is_none")] + pub refresh_token: Option, + + /// The lifetime of the access token, in milliseconds. + /// + /// Once the access token has expired, a new access token can be obtained by using the + /// provided refresh token. If no refresh token is provided, the client will need to + /// re-login to obtain a new access token. + /// + /// If this is `None`, the client can assume that the access token will not expire. + #[cfg(feature = "unstable-msc2918")] + #[serde( + with = "ruma_common::serde::duration::opt_ms", + default, + skip_serializing_if = "Option::is_none", + rename = "expires_in_ms", + )] + pub expires_in: Option, } error: crate::Error @@ -79,14 +116,30 @@ pub mod v3 { impl<'a> Request<'a> { /// Creates a new `Request` with the given login info. pub fn new(login_info: LoginInfo<'a>) -> Self { - Self { login_info, device_id: None, initial_device_display_name: None } + Self { + login_info, + device_id: None, + initial_device_display_name: None, + #[cfg(feature = "unstable-msc2918")] + refresh_token: false, + } } } impl Response { /// Creates a new `Response` with the given user ID, access token and device ID. pub fn new(user_id: OwnedUserId, access_token: String, device_id: OwnedDeviceId) -> Self { - Self { user_id, access_token, home_server: None, device_id, well_known: None } + Self { + user_id, + access_token, + home_server: None, + device_id, + well_known: None, + #[cfg(feature = "unstable-msc2918")] + refresh_token: None, + #[cfg(feature = "unstable-msc2918")] + expires_in: None, + } } } @@ -374,6 +427,8 @@ pub mod v3 { login_info: LoginInfo::Token(Token { token: "0xdeadbeef" }), device_id: None, initial_device_display_name: Some("test"), + #[cfg(feature = "unstable-msc2918")] + refresh_token: false, } .try_into_http_request( "https://homeserver.tld", @@ -402,6 +457,8 @@ pub mod v3 { }), device_id: None, initial_device_display_name: Some("test"), + #[cfg(feature = "unstable-msc2918")] + refresh_token: false, } .try_into_http_request( "https://homeserver.tld", diff --git a/crates/ruma-client-api/src/session/refresh_token.rs b/crates/ruma-client-api/src/session/refresh_token.rs new file mode 100644 index 00000000..6ec4dd74 --- /dev/null +++ b/crates/ruma-client-api/src/session/refresh_token.rs @@ -0,0 +1,84 @@ +//! `POST /_matrix/client/*/refresh` +//! +//! Refresh an access token. +//! +//! Clients should use the returned access token when making subsequent API +//! calls, and store the returned refresh token (if given) in order to refresh +//! the new access token when necessary. +//! +//! After an access token has been refreshed, a server can choose to invalidate +//! the old access token immediately, or can choose not to, for example if the +//! access token would expire soon anyways. Clients should not make any +//! assumptions about the old access token still being valid, and should use the +//! newly provided access token instead. +//! +//! The old refresh token remains valid until the new access token or refresh +//! token is used, at which point the old refresh token is revoked. +//! +//! Note that this endpoint does not require authentication via an access token. +//! Authentication is provided via the refresh token. +//! +//! Application Service identity assertion is disabled for this endpoint. + +pub mod unstable { + //! `/unstable/` (MSC2918) + //! + //! [MSC2918]: https://github.com/matrix-org/matrix-spec-proposals/pull/2918 + + use std::time::Duration; + + use ruma_common::api::ruma_api; + + ruma_api! { + metadata: { + description: "Refresh an access token.", + method: POST, + name: "refresh", + unstable_path: "/_matrix/client/unstable/org.matrix.msc2918/refresh", + rate_limited: true, + authentication: None, + } + + request: { + /// The refresh token. + pub refresh_token: String, + } + + response: { + /// The new access token to use. + pub access_token: String, + + /// The new refresh token to use when the access token needs to be refreshed again. + /// + /// If this is `None`, the old refresh token can be re-used. + #[serde(skip_serializing_if = "Option::is_none")] + pub refresh_token: Option, + + /// The lifetime of the access token, in milliseconds. + /// + /// If this is `None`, the client can assume that the access token will not expire. + #[serde( + with = "ruma_common::serde::duration::opt_ms", + default, + skip_serializing_if = "Option::is_none" + )] + pub expires_in_ms: Option, + } + + error: crate::Error + } + + impl Request { + /// Creates a new `Request` with the given refresh token. + pub fn new(refresh_token: String) -> Self { + Self { refresh_token } + } + } + + impl Response { + /// Creates a new `Response` with the given access token. + pub fn new(access_token: String) -> Self { + Self { access_token, refresh_token: None, expires_in_ms: None } + } + } +} diff --git a/crates/ruma/Cargo.toml b/crates/ruma/Cargo.toml index 3b1d4738..89629207 100644 --- a/crates/ruma/Cargo.toml +++ b/crates/ruma/Cargo.toml @@ -135,6 +135,7 @@ unstable-msc2677 = [ ] unstable-msc2746 = ["ruma-common/unstable-msc2746"] unstable-msc2870 = ["ruma-signatures?/unstable-msc2870"] +unstable-msc2918 = ["ruma-client-api?/unstable-msc2918"] unstable-msc3245 = ["ruma-common/unstable-msc3245"] unstable-msc3246 = ["ruma-common/unstable-msc3246"] unstable-msc3381 = ["ruma-common/unstable-msc3381"] @@ -165,6 +166,7 @@ __ci = [ "unstable-msc2677", "unstable-msc2746", "unstable-msc2870", + "unstable-msc2918", "unstable-msc3245", "unstable-msc3246", "unstable-msc3381",