From f47d57e80c9358dae2ea9c8304981f8b76f38bb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Wed, 14 Sep 2022 19:22:36 +0200 Subject: [PATCH] Add types for the Device Authorization flow --- crates/oauth2-types/src/errors.rs | 46 +++++++++++++++++++ crates/oauth2-types/src/oidc.rs | 5 ++ crates/oauth2-types/src/requests.rs | 71 +++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) diff --git a/crates/oauth2-types/src/errors.rs b/crates/oauth2-types/src/errors.rs index c14179c41..60e8d4db1 100644 --- a/crates/oauth2-types/src/errors.rs +++ b/crates/oauth2-types/src/errors.rs @@ -225,6 +225,43 @@ pub enum ClientErrorCode { /// From [RFC7591](https://www.rfc-editor.org/rfc/rfc7591#section-3.2.2). InvalidClientMetadata, + /// `authorization_pending` + /// + /// The authorization request is still pending as the end user hasn't yet + /// completed the user-interaction steps. + /// + /// The client should repeat the access token request to the token endpoint + /// (a process known as polling). Before each new request, the client + /// must wait at least the number of seconds specified by the `interval` + /// parameter of the device authorization response, or 5 seconds if none was + /// provided, and respect any increase in the polling interval required + /// by the [`ClientErrorCode::SlowDown`] error. + /// + /// From [RFC8628](https://www.rfc-editor.org/rfc/rfc8628#section-3.5). + AuthorizationPending, + + /// `slow_down` + /// + /// A variant of [`ClientErrorCode::AuthorizationPending`], the + /// authorization request is still pending and polling should continue, + /// but the interval must be increased by 5 seconds for this and all + /// subsequent requests. + /// + /// From [RFC8628](https://www.rfc-editor.org/rfc/rfc8628#section-3.5). + SlowDown, + + /// `expired_token` + /// + /// The `device_code` has expired, and the device authorization session has + /// concluded. + /// + /// The client may commence a new device authorization request but should + /// wait for user interaction before restarting to avoid unnecessary + /// polling. + /// + /// From [RFC8628](https://www.rfc-editor.org/rfc/rfc8628#section-3.5). + ExpiredToken, + /// Another error code. #[display("{0}")] Unknown(String), @@ -304,6 +341,15 @@ impl ClientErrorCode { ClientErrorCode::InvalidClientMetadata => { "The value of one of the client metadata fields is invalid" } + ClientErrorCode::AuthorizationPending => { + "The authorization request is still pending" + } + ClientErrorCode::SlowDown => { + "The interval must be increased by 5 seconds for this and all subsequent requests" + } + ClientErrorCode::ExpiredToken => { + "The \"device_code\" has expired, and the device authorization session has concluded" + } ClientErrorCode::Unknown(_) => "", } } diff --git a/crates/oauth2-types/src/oidc.rs b/crates/oauth2-types/src/oidc.rs index f20f372cf..245f5c1d4 100644 --- a/crates/oauth2-types/src/oidc.rs +++ b/crates/oauth2-types/src/oidc.rs @@ -357,6 +357,11 @@ pub struct ProviderMetadata { /// /// [prompt `create`]: https://openid.net/specs/openid-connect-prompt-create-1_0.html pub prompt_values_supported: Option>, + + /// URL of the authorization server's [device authorization endpoint]. + /// + /// [device authorization endpoint]: https://www.rfc-editor.org/rfc/rfc8628 + pub device_authorization_endpoint: Option, } impl ProviderMetadata { diff --git a/crates/oauth2-types/src/requests.rs b/crates/oauth2-types/src/requests.rs index be3f25bfd..b4145247b 100644 --- a/crates/oauth2-types/src/requests.rs +++ b/crates/oauth2-types/src/requests.rs @@ -296,6 +296,65 @@ pub struct AuthorizationResponse { pub response: R, } +/// A request to the [Device Authorization Endpoint]. +/// +/// [Device Authorization Endpoint]: https://www.rfc-editor.org/rfc/rfc8628 +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct DeviceAuthorizationRequest { + /// The scope of the access request. + pub scope: Option, +} + +pub const DEFAULT_DEVICE_AUTHORIZATION_INTERVAL_SECONDS: i64 = 5; + +/// A successful response from the [Device Authorization Endpoint]. +/// +/// [Device Authorization Endpoint]: https://www.rfc-editor.org/rfc/rfc8628 +#[serde_as] +#[skip_serializing_none] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct DeviceAuthorizationResponse { + /// The device verification code. + device_code: String, + + /// The end-user verification code. + user_code: String, + + /// The end-user verification URI on the authorization server. + /// + /// The URI should be short and easy to remember as end users will be asked + /// to manually type it into their user agent. + verification_uri: Url, + + /// A verification URI that includes the `user_code` (or other information + /// with the same function as the `user_code`), which is designed for + /// non-textual transmission. + verification_uri_complete: Option, + + /// The lifetime of the `device_code` and `user_code`. + #[serde_as(as = "DurationSeconds")] + expires_in: Duration, + + /// The minimum amount of time in seconds that the client should wait + /// between polling requests to the token endpoint. + /// + /// Defaults to [`DEFAULT_DEVICE_AUTHORIZATION_INTERVAL_SECONDS`]. + #[serde_as(as = "Option>")] + interval: Option, +} + +impl DeviceAuthorizationResponse { + ///The minimum amount of time in seconds that the client should wait + /// between polling requests to the token endpoint. + /// + /// Defaults to [`DEFAULT_DEVICE_AUTHORIZATION_INTERVAL_SECONDS`]. + #[must_use] + pub fn interval(&self) -> Duration { + self.interval + .unwrap_or_else(|| Duration::seconds(DEFAULT_DEVICE_AUTHORIZATION_INTERVAL_SECONDS)) + } +} + /// A request to the [Token Endpoint] for the [Authorization Code] grant type. /// /// [Token Endpoint]: https://www.rfc-editor.org/rfc/rfc6749#section-3.2 @@ -347,6 +406,16 @@ pub struct ClientCredentialsGrant { pub scope: Option, } +/// A request to the [Token Endpoint] for the [Device Authorization] grant type. +/// +/// [Token Endpoint]: https://www.rfc-editor.org/rfc/rfc6749#section-3.2 +/// [Device Authorization]: https://www.rfc-editor.org/rfc/rfc8628 +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct DeviceCodeGrant { + /// The device verification code, from the device authorization response. + pub device_code: Option, +} + /// All possible values for the `grant_type` parameter. #[derive( Debug, @@ -397,6 +466,8 @@ pub enum AccessTokenRequest { AuthorizationCode(AuthorizationCodeGrant), RefreshToken(RefreshTokenGrant), ClientCredentials(ClientCredentialsGrant), + #[serde(rename = "urn:ietf:params:oauth:grant-type:device_code")] + DeviceCode(DeviceCodeGrant), #[serde(skip, other)] Unsupported, }