client-api: Add support for refresh tokens

According to MSC2918
This commit is contained in:
Kévin Commaille 2022-06-11 15:11:16 +02:00 committed by Kévin Commaille
parent 1605fcc027
commit 90cef5a50b
7 changed files with 206 additions and 3 deletions

View File

@ -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:

View File

@ -23,6 +23,7 @@ unstable-msc2448 = []
unstable-msc2654 = []
unstable-msc2676 = []
unstable-msc2677 = []
unstable-msc2918 = []
unstable-msc3440 = []
unstable-msc3488 = []
client = []

View File

@ -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<String>,
@ -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<OwnedDeviceId>,
/// 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<String>,
/// 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<Duration>,
}
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,
}
}
}
}

View File

@ -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;

View File

@ -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<DiscoveryInfo>,
/// 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<String>,
/// 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<Duration>,
}
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",

View File

@ -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<String>,
/// 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<Duration>,
}
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 }
}
}
}

View File

@ -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",