1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
// Copyright 2024 New Vector Ltd.
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.

use std::net::IpAddr;

use async_trait::async_trait;
use chrono::Duration;
use mas_data_model::{BrowserSession, Client, DeviceCodeGrant, Session, UserAgent};
use oauth2_types::scope::Scope;
use rand_core::RngCore;
use ulid::Ulid;

use crate::{repository_impl, Clock};

/// Parameters used to create a new [`DeviceCodeGrant`]
pub struct OAuth2DeviceCodeGrantParams<'a> {
    /// The client which requested the device code grant
    pub client: &'a Client,

    /// The scope requested by the client
    pub scope: Scope,

    /// The device code which the client uses to poll for authorisation
    pub device_code: String,

    /// The user code which the client uses to display to the user
    pub user_code: String,

    /// After how long the device code expires
    pub expires_in: Duration,

    /// IP address from which the request was made
    pub ip_address: Option<IpAddr>,

    /// The user agent from which the request was made
    pub user_agent: Option<UserAgent>,
}

/// An [`OAuth2DeviceCodeGrantRepository`] helps interacting with
/// [`DeviceCodeGrant`] saved in the storage backend.
#[async_trait]
pub trait OAuth2DeviceCodeGrantRepository: Send + Sync {
    /// The error type returned by the repository
    type Error;

    /// Create a new device code grant
    ///
    /// Returns the newly created device code grant
    ///
    /// # Parameters
    ///
    /// * `rng`: A random number generator
    /// * `clock`: The clock used to generate timestamps
    /// * `params`: The parameters used to create the device code grant. See the
    ///   fields of [`OAuth2DeviceCodeGrantParams`]
    ///
    /// # Errors
    ///
    /// Returns [`Self::Error`] if the underlying repository fails
    async fn add(
        &mut self,
        rng: &mut (dyn RngCore + Send),
        clock: &dyn Clock,
        params: OAuth2DeviceCodeGrantParams<'_>,
    ) -> Result<DeviceCodeGrant, Self::Error>;

    /// Lookup a device code grant by its ID
    ///
    /// Returns the device code grant if found, [`None`] otherwise
    ///
    /// # Parameters
    ///
    /// * `id`: The ID of the device code grant
    ///
    /// # Errors
    ///
    /// Returns [`Self::Error`] if the underlying repository fails
    async fn lookup(&mut self, id: Ulid) -> Result<Option<DeviceCodeGrant>, Self::Error>;

    /// Lookup a device code grant by its device code
    ///
    /// Returns the device code grant if found, [`None`] otherwise
    ///
    /// # Parameters
    ///
    /// * `device_code`: The device code of the device code grant
    ///
    /// # Errors
    ///
    /// Returns [`Self::Error`] if the underlying repository fails
    async fn find_by_device_code(
        &mut self,
        device_code: &str,
    ) -> Result<Option<DeviceCodeGrant>, Self::Error>;

    /// Lookup a device code grant by its user code
    ///
    /// Returns the device code grant if found, [`None`] otherwise
    ///
    /// # Parameters
    ///
    /// * `user_code`: The user code of the device code grant
    ///
    /// # Errors
    ///
    /// Returns [`Self::Error`] if the underlying repository fails
    async fn find_by_user_code(
        &mut self,
        user_code: &str,
    ) -> Result<Option<DeviceCodeGrant>, Self::Error>;

    /// Mark the device code grant as fulfilled with the given browser session
    ///
    /// Returns the updated device code grant
    ///
    /// # Parameters
    ///
    /// * `clock`: The clock used to generate timestamps
    /// * `device_code_grant`: The device code grant to fulfill
    /// * `browser_session`: The browser session which was used to fulfill the
    ///   device code grant
    ///
    /// # Errors
    ///
    /// Returns [`Self::Error`] if the underlying repository fails or if the
    /// device code grant is not in the [`Pending`] state
    ///
    /// [`Pending`]: mas_data_model::DeviceCodeGrantState::Pending
    async fn fulfill(
        &mut self,
        clock: &dyn Clock,
        device_code_grant: DeviceCodeGrant,
        browser_session: &BrowserSession,
    ) -> Result<DeviceCodeGrant, Self::Error>;

    /// Mark the device code grant as rejected with the given browser session
    ///
    /// Returns the updated device code grant
    ///
    /// # Parameters
    ///
    /// * `clock`: The clock used to generate timestamps
    /// * `device_code_grant`: The device code grant to reject
    /// * `browser_session`: The browser session which was used to reject the
    ///   device code grant
    ///
    /// # Errors
    ///
    /// Returns [`Self::Error`] if the underlying repository fails or if the
    /// device code grant is not in the [`Pending`] state
    ///
    /// [`Pending`]: mas_data_model::DeviceCodeGrantState::Pending
    async fn reject(
        &mut self,
        clock: &dyn Clock,
        device_code_grant: DeviceCodeGrant,
        browser_session: &BrowserSession,
    ) -> Result<DeviceCodeGrant, Self::Error>;

    /// Mark the device code grant as exchanged and store the session which was
    /// created
    ///
    /// Returns the updated device code grant
    ///
    /// # Parameters
    ///
    /// * `clock`: The clock used to generate timestamps
    /// * `device_code_grant`: The device code grant to exchange
    /// * `session`: The OAuth 2.0 session which was created
    ///
    /// # Errors
    ///
    /// Returns [`Self::Error`] if the underlying repository fails or if the
    /// device code grant is not in the [`Fulfilled`] state
    ///
    /// [`Fulfilled`]: mas_data_model::DeviceCodeGrantState::Fulfilled
    async fn exchange(
        &mut self,
        clock: &dyn Clock,
        device_code_grant: DeviceCodeGrant,
        session: &Session,
    ) -> Result<DeviceCodeGrant, Self::Error>;
}

repository_impl!(OAuth2DeviceCodeGrantRepository:
    async fn add(
        &mut self,
        rng: &mut (dyn RngCore + Send),
        clock: &dyn Clock,
        params: OAuth2DeviceCodeGrantParams<'_>,
    ) -> Result<DeviceCodeGrant, Self::Error>;

    async fn lookup(&mut self, id: Ulid) -> Result<Option<DeviceCodeGrant>, Self::Error>;

    async fn find_by_device_code(
        &mut self,
        device_code: &str,
    ) -> Result<Option<DeviceCodeGrant>, Self::Error>;

    async fn find_by_user_code(
        &mut self,
        user_code: &str,
    ) -> Result<Option<DeviceCodeGrant>, Self::Error>;

    async fn fulfill(
        &mut self,
        clock: &dyn Clock,
        device_code_grant: DeviceCodeGrant,
        browser_session: &BrowserSession,
    ) -> Result<DeviceCodeGrant, Self::Error>;

    async fn reject(
        &mut self,
        clock: &dyn Clock,
        device_code_grant: DeviceCodeGrant,
        browser_session: &BrowserSession,
    ) -> Result<DeviceCodeGrant, Self::Error>;

    async fn exchange(
        &mut self,
        clock: &dyn Clock,
        device_code_grant: DeviceCodeGrant,
        session: &Session,
    ) -> Result<DeviceCodeGrant, Self::Error>;
);