mas_handlers/graphql/model/
oauth.rs1use anyhow::Context as _;
8use async_graphql::{Context, Description, Enum, ID, Object};
9use chrono::{DateTime, Utc};
10use mas_storage::{oauth2::OAuth2ClientRepository, user::BrowserSessionRepository};
11use oauth2_types::{oidc::ApplicationType, scope::Scope};
12use ulid::Ulid;
13use url::Url;
14
15use super::{BrowserSession, NodeType, SessionState, User, UserAgent};
16use crate::graphql::{UserId, state::ContextExt};
17
18#[derive(Description)]
21pub struct OAuth2Session(pub mas_data_model::Session);
22
23#[Object(use_type_description)]
24impl OAuth2Session {
25 pub async fn id(&self) -> ID {
27 NodeType::OAuth2Session.id(self.0.id)
28 }
29
30 pub async fn client(&self, ctx: &Context<'_>) -> Result<OAuth2Client, async_graphql::Error> {
32 let state = ctx.state();
33 let mut repo = state.repository().await?;
34 let client = repo
35 .oauth2_client()
36 .lookup(self.0.client_id)
37 .await?
38 .context("Could not load client")?;
39 repo.cancel().await?;
40
41 Ok(OAuth2Client(client))
42 }
43
44 pub async fn scope(&self) -> String {
46 self.0.scope.to_string()
47 }
48
49 pub async fn created_at(&self) -> DateTime<Utc> {
51 self.0.created_at
52 }
53
54 pub async fn finished_at(&self) -> Option<DateTime<Utc>> {
56 match &self.0.state {
57 mas_data_model::SessionState::Valid => None,
58 mas_data_model::SessionState::Finished { finished_at } => Some(*finished_at),
59 }
60 }
61
62 pub async fn user_agent(&self) -> Option<UserAgent> {
64 self.0.user_agent.clone().map(UserAgent::from)
65 }
66
67 pub async fn state(&self) -> SessionState {
69 match &self.0.state {
70 mas_data_model::SessionState::Valid => SessionState::Active,
71 mas_data_model::SessionState::Finished { .. } => SessionState::Finished,
72 }
73 }
74
75 pub async fn browser_session(
77 &self,
78 ctx: &Context<'_>,
79 ) -> Result<Option<BrowserSession>, async_graphql::Error> {
80 let Some(user_session_id) = self.0.user_session_id else {
81 return Ok(None);
82 };
83
84 let state = ctx.state();
85 let mut repo = state.repository().await?;
86 let browser_session = repo
87 .browser_session()
88 .lookup(user_session_id)
89 .await?
90 .context("Could not load browser session")?;
91 repo.cancel().await?;
92
93 Ok(Some(BrowserSession(browser_session)))
94 }
95
96 pub async fn user(&self, ctx: &Context<'_>) -> Result<Option<User>, async_graphql::Error> {
98 let state = ctx.state();
99 let Some(user_id) = self.0.user_id else {
100 return Ok(None);
101 };
102
103 if !ctx.requester().is_owner_or_admin(&UserId(user_id)) {
104 return Err(async_graphql::Error::new("Unauthorized"));
105 }
106
107 let mut repo = state.repository().await?;
108 let user = repo
109 .user()
110 .lookup(user_id)
111 .await?
112 .context("Could not load user")?;
113 repo.cancel().await?;
114
115 Ok(Some(User(user)))
116 }
117
118 pub async fn last_active_ip(&self) -> Option<String> {
120 self.0.last_active_ip.map(|ip| ip.to_string())
121 }
122
123 pub async fn last_active_at(&self) -> Option<DateTime<Utc>> {
125 self.0.last_active_at
126 }
127}
128
129#[derive(Enum, Copy, Clone, Eq, PartialEq)]
131pub enum OAuth2ApplicationType {
132 Web,
134
135 Native,
137}
138
139#[derive(Description)]
141pub struct OAuth2Client(pub mas_data_model::Client);
142
143#[Object(use_type_description)]
144impl OAuth2Client {
145 pub async fn id(&self) -> ID {
147 NodeType::OAuth2Client.id(self.0.id)
148 }
149
150 pub async fn client_id(&self) -> &str {
152 &self.0.client_id
153 }
154
155 pub async fn client_name(&self) -> Option<&str> {
157 self.0.client_name.as_deref()
158 }
159
160 pub async fn client_uri(&self) -> Option<&Url> {
162 self.0.client_uri.as_ref()
163 }
164
165 pub async fn logo_uri(&self) -> Option<&Url> {
167 self.0.logo_uri.as_ref()
168 }
169
170 pub async fn tos_uri(&self) -> Option<&Url> {
172 self.0.tos_uri.as_ref()
173 }
174
175 pub async fn policy_uri(&self) -> Option<&Url> {
177 self.0.policy_uri.as_ref()
178 }
179
180 pub async fn redirect_uris(&self) -> &[Url] {
182 &self.0.redirect_uris
183 }
184
185 pub async fn application_type(&self) -> Option<OAuth2ApplicationType> {
187 match self.0.application_type.as_ref()? {
188 ApplicationType::Web => Some(OAuth2ApplicationType::Web),
189 ApplicationType::Native => Some(OAuth2ApplicationType::Native),
190 ApplicationType::Unknown(_) => None,
191 }
192 }
193}
194
195#[derive(Description)]
198pub struct OAuth2Consent {
199 scope: Scope,
200 client_id: Ulid,
201}
202
203#[Object(use_type_description)]
204impl OAuth2Consent {
205 pub async fn scope(&self) -> String {
207 self.scope.to_string()
208 }
209
210 pub async fn client(&self, ctx: &Context<'_>) -> Result<OAuth2Client, async_graphql::Error> {
212 let state = ctx.state();
213 let mut repo = state.repository().await?;
214 let client = repo
215 .oauth2_client()
216 .lookup(self.client_id)
217 .await?
218 .context("Could not load client")?;
219 repo.cancel().await?;
220
221 Ok(OAuth2Client(client))
222 }
223}