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