mas_storage/oauth2/
device_code_grant.rs

1// Copyright 2024 New Vector Ltd.
2// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
3//
4// SPDX-License-Identifier: AGPL-3.0-only
5// Please see LICENSE in the repository root for full details.
6
7use std::net::IpAddr;
8
9use async_trait::async_trait;
10use chrono::Duration;
11use mas_data_model::{BrowserSession, Client, DeviceCodeGrant, Session, UserAgent};
12use oauth2_types::scope::Scope;
13use rand_core::RngCore;
14use ulid::Ulid;
15
16use crate::{Clock, repository_impl};
17
18/// Parameters used to create a new [`DeviceCodeGrant`]
19pub struct OAuth2DeviceCodeGrantParams<'a> {
20    /// The client which requested the device code grant
21    pub client: &'a Client,
22
23    /// The scope requested by the client
24    pub scope: Scope,
25
26    /// The device code which the client uses to poll for authorisation
27    pub device_code: String,
28
29    /// The user code which the client uses to display to the user
30    pub user_code: String,
31
32    /// After how long the device code expires
33    pub expires_in: Duration,
34
35    /// IP address from which the request was made
36    pub ip_address: Option<IpAddr>,
37
38    /// The user agent from which the request was made
39    pub user_agent: Option<UserAgent>,
40}
41
42/// An [`OAuth2DeviceCodeGrantRepository`] helps interacting with
43/// [`DeviceCodeGrant`] saved in the storage backend.
44#[async_trait]
45pub trait OAuth2DeviceCodeGrantRepository: Send + Sync {
46    /// The error type returned by the repository
47    type Error;
48
49    /// Create a new device code grant
50    ///
51    /// Returns the newly created device code grant
52    ///
53    /// # Parameters
54    ///
55    /// * `rng`: A random number generator
56    /// * `clock`: The clock used to generate timestamps
57    /// * `params`: The parameters used to create the device code grant. See the
58    ///   fields of [`OAuth2DeviceCodeGrantParams`]
59    ///
60    /// # Errors
61    ///
62    /// Returns [`Self::Error`] if the underlying repository fails
63    async fn add(
64        &mut self,
65        rng: &mut (dyn RngCore + Send),
66        clock: &dyn Clock,
67        params: OAuth2DeviceCodeGrantParams<'_>,
68    ) -> Result<DeviceCodeGrant, Self::Error>;
69
70    /// Lookup a device code grant by its ID
71    ///
72    /// Returns the device code grant if found, [`None`] otherwise
73    ///
74    /// # Parameters
75    ///
76    /// * `id`: The ID of the device code grant
77    ///
78    /// # Errors
79    ///
80    /// Returns [`Self::Error`] if the underlying repository fails
81    async fn lookup(&mut self, id: Ulid) -> Result<Option<DeviceCodeGrant>, Self::Error>;
82
83    /// Lookup a device code grant by its device code
84    ///
85    /// Returns the device code grant if found, [`None`] otherwise
86    ///
87    /// # Parameters
88    ///
89    /// * `device_code`: The device code of the device code grant
90    ///
91    /// # Errors
92    ///
93    /// Returns [`Self::Error`] if the underlying repository fails
94    async fn find_by_device_code(
95        &mut self,
96        device_code: &str,
97    ) -> Result<Option<DeviceCodeGrant>, Self::Error>;
98
99    /// Lookup a device code grant by its user code
100    ///
101    /// Returns the device code grant if found, [`None`] otherwise
102    ///
103    /// # Parameters
104    ///
105    /// * `user_code`: The user code of the device code grant
106    ///
107    /// # Errors
108    ///
109    /// Returns [`Self::Error`] if the underlying repository fails
110    async fn find_by_user_code(
111        &mut self,
112        user_code: &str,
113    ) -> Result<Option<DeviceCodeGrant>, Self::Error>;
114
115    /// Mark the device code grant as fulfilled with the given browser session
116    ///
117    /// Returns the updated device code grant
118    ///
119    /// # Parameters
120    ///
121    /// * `clock`: The clock used to generate timestamps
122    /// * `device_code_grant`: The device code grant to fulfill
123    /// * `browser_session`: The browser session which was used to fulfill the
124    ///   device code grant
125    ///
126    /// # Errors
127    ///
128    /// Returns [`Self::Error`] if the underlying repository fails or if the
129    /// device code grant is not in the [`Pending`] state
130    ///
131    /// [`Pending`]: mas_data_model::DeviceCodeGrantState::Pending
132    async fn fulfill(
133        &mut self,
134        clock: &dyn Clock,
135        device_code_grant: DeviceCodeGrant,
136        browser_session: &BrowserSession,
137    ) -> Result<DeviceCodeGrant, Self::Error>;
138
139    /// Mark the device code grant as rejected with the given browser session
140    ///
141    /// Returns the updated device code grant
142    ///
143    /// # Parameters
144    ///
145    /// * `clock`: The clock used to generate timestamps
146    /// * `device_code_grant`: The device code grant to reject
147    /// * `browser_session`: The browser session which was used to reject the
148    ///   device code grant
149    ///
150    /// # Errors
151    ///
152    /// Returns [`Self::Error`] if the underlying repository fails or if the
153    /// device code grant is not in the [`Pending`] state
154    ///
155    /// [`Pending`]: mas_data_model::DeviceCodeGrantState::Pending
156    async fn reject(
157        &mut self,
158        clock: &dyn Clock,
159        device_code_grant: DeviceCodeGrant,
160        browser_session: &BrowserSession,
161    ) -> Result<DeviceCodeGrant, Self::Error>;
162
163    /// Mark the device code grant as exchanged and store the session which was
164    /// created
165    ///
166    /// Returns the updated device code grant
167    ///
168    /// # Parameters
169    ///
170    /// * `clock`: The clock used to generate timestamps
171    /// * `device_code_grant`: The device code grant to exchange
172    /// * `session`: The OAuth 2.0 session which was created
173    ///
174    /// # Errors
175    ///
176    /// Returns [`Self::Error`] if the underlying repository fails or if the
177    /// device code grant is not in the [`Fulfilled`] state
178    ///
179    /// [`Fulfilled`]: mas_data_model::DeviceCodeGrantState::Fulfilled
180    async fn exchange(
181        &mut self,
182        clock: &dyn Clock,
183        device_code_grant: DeviceCodeGrant,
184        session: &Session,
185    ) -> Result<DeviceCodeGrant, Self::Error>;
186}
187
188repository_impl!(OAuth2DeviceCodeGrantRepository:
189    async fn add(
190        &mut self,
191        rng: &mut (dyn RngCore + Send),
192        clock: &dyn Clock,
193        params: OAuth2DeviceCodeGrantParams<'_>,
194    ) -> Result<DeviceCodeGrant, Self::Error>;
195
196    async fn lookup(&mut self, id: Ulid) -> Result<Option<DeviceCodeGrant>, Self::Error>;
197
198    async fn find_by_device_code(
199        &mut self,
200        device_code: &str,
201    ) -> Result<Option<DeviceCodeGrant>, Self::Error>;
202
203    async fn find_by_user_code(
204        &mut self,
205        user_code: &str,
206    ) -> Result<Option<DeviceCodeGrant>, Self::Error>;
207
208    async fn fulfill(
209        &mut self,
210        clock: &dyn Clock,
211        device_code_grant: DeviceCodeGrant,
212        browser_session: &BrowserSession,
213    ) -> Result<DeviceCodeGrant, Self::Error>;
214
215    async fn reject(
216        &mut self,
217        clock: &dyn Clock,
218        device_code_grant: DeviceCodeGrant,
219        browser_session: &BrowserSession,
220    ) -> Result<DeviceCodeGrant, Self::Error>;
221
222    async fn exchange(
223        &mut self,
224        clock: &dyn Clock,
225        device_code_grant: DeviceCodeGrant,
226        session: &Session,
227    ) -> Result<DeviceCodeGrant, Self::Error>;
228);