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
65 .user_agent
66 .clone()
67 .map(mas_data_model::UserAgent::parse)
68 .map(UserAgent::from)
69 }
70
71 pub async fn state(&self) -> SessionState {
73 match &self.0.state {
74 mas_data_model::SessionState::Valid => SessionState::Active,
75 mas_data_model::SessionState::Finished { .. } => SessionState::Finished,
76 }
77 }
78
79 pub async fn browser_session(
81 &self,
82 ctx: &Context<'_>,
83 ) -> Result<Option<BrowserSession>, async_graphql::Error> {
84 let Some(user_session_id) = self.0.user_session_id else {
85 return Ok(None);
86 };
87
88 let state = ctx.state();
89 let mut repo = state.repository().await?;
90 let browser_session = repo
91 .browser_session()
92 .lookup(user_session_id)
93 .await?
94 .context("Could not load browser session")?;
95 repo.cancel().await?;
96
97 Ok(Some(BrowserSession(browser_session)))
98 }
99
100 pub async fn user(&self, ctx: &Context<'_>) -> Result<Option<User>, async_graphql::Error> {
102 let state = ctx.state();
103 let Some(user_id) = self.0.user_id else {
104 return Ok(None);
105 };
106
107 if !ctx.requester().is_owner_or_admin(&UserId(user_id)) {
108 return Err(async_graphql::Error::new("Unauthorized"));
109 }
110
111 let mut repo = state.repository().await?;
112 let user = repo
113 .user()
114 .lookup(user_id)
115 .await?
116 .context("Could not load user")?;
117 repo.cancel().await?;
118
119 Ok(Some(User(user)))
120 }
121
122 pub async fn last_active_ip(&self) -> Option<String> {
124 self.0.last_active_ip.map(|ip| ip.to_string())
125 }
126
127 pub async fn last_active_at(&self) -> Option<DateTime<Utc>> {
129 self.0.last_active_at
130 }
131}
132
133#[derive(Enum, Copy, Clone, Eq, PartialEq)]
135pub enum OAuth2ApplicationType {
136 Web,
138
139 Native,
141}
142
143#[derive(Description)]
145pub struct OAuth2Client(pub mas_data_model::Client);
146
147#[Object(use_type_description)]
148impl OAuth2Client {
149 pub async fn id(&self) -> ID {
151 NodeType::OAuth2Client.id(self.0.id)
152 }
153
154 pub async fn client_id(&self) -> &str {
156 &self.0.client_id
157 }
158
159 pub async fn client_name(&self) -> Option<&str> {
161 self.0.client_name.as_deref()
162 }
163
164 pub async fn client_uri(&self) -> Option<&Url> {
166 self.0.client_uri.as_ref()
167 }
168
169 pub async fn logo_uri(&self) -> Option<&Url> {
171 self.0.logo_uri.as_ref()
172 }
173
174 pub async fn tos_uri(&self) -> Option<&Url> {
176 self.0.tos_uri.as_ref()
177 }
178
179 pub async fn policy_uri(&self) -> Option<&Url> {
181 self.0.policy_uri.as_ref()
182 }
183
184 pub async fn redirect_uris(&self) -> &[Url] {
186 &self.0.redirect_uris
187 }
188
189 pub async fn application_type(&self) -> Option<OAuth2ApplicationType> {
191 match self.0.application_type.as_ref()? {
192 ApplicationType::Web => Some(OAuth2ApplicationType::Web),
193 ApplicationType::Native => Some(OAuth2ApplicationType::Native),
194 ApplicationType::Unknown(_) => None,
195 }
196 }
197}
198
199#[derive(Description)]
202pub struct OAuth2Consent {
203 scope: Scope,
204 client_id: Ulid,
205}
206
207#[Object(use_type_description)]
208impl OAuth2Consent {
209 pub async fn scope(&self) -> String {
211 self.scope.to_string()
212 }
213
214 pub async fn client(&self, ctx: &Context<'_>) -> Result<OAuth2Client, async_graphql::Error> {
216 let state = ctx.state();
217 let mut repo = state.repository().await?;
218 let client = repo
219 .oauth2_client()
220 .lookup(self.client_id)
221 .await?
222 .context("Could not load client")?;
223 repo.cancel().await?;
224
225 Ok(OAuth2Client(client))
226 }
227}