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;
12use url::Url;
13
14use super::{BrowserSession, NodeType, SessionState, User, UserAgent};
15use crate::graphql::{UserId, state::ContextExt};
16
17#[derive(Description)]
20pub struct OAuth2Session(pub mas_data_model::Session);
21
22#[Object(use_type_description)]
23impl OAuth2Session {
24 pub async fn id(&self) -> ID {
26 NodeType::OAuth2Session.id(self.0.id)
27 }
28
29 pub async fn client(&self, ctx: &Context<'_>) -> Result<OAuth2Client, async_graphql::Error> {
31 let state = ctx.state();
32 let mut repo = state.repository().await?;
33 let client = repo
34 .oauth2_client()
35 .lookup(self.0.client_id)
36 .await?
37 .context("Could not load client")?;
38 repo.cancel().await?;
39
40 Ok(OAuth2Client(client))
41 }
42
43 pub async fn scope(&self) -> String {
45 self.0.scope.to_string()
46 }
47
48 pub async fn created_at(&self) -> DateTime<Utc> {
50 self.0.created_at
51 }
52
53 pub async fn finished_at(&self) -> Option<DateTime<Utc>> {
55 match &self.0.state {
56 mas_data_model::SessionState::Valid => None,
57 mas_data_model::SessionState::Finished { finished_at } => Some(*finished_at),
58 }
59 }
60
61 pub async fn user_agent(&self) -> Option<UserAgent> {
63 self.0
64 .user_agent
65 .clone()
66 .map(mas_data_model::UserAgent::parse)
67 .map(UserAgent::from)
68 }
69
70 pub async fn state(&self) -> SessionState {
72 match &self.0.state {
73 mas_data_model::SessionState::Valid => SessionState::Active,
74 mas_data_model::SessionState::Finished { .. } => SessionState::Finished,
75 }
76 }
77
78 pub async fn browser_session(
80 &self,
81 ctx: &Context<'_>,
82 ) -> Result<Option<BrowserSession>, async_graphql::Error> {
83 let Some(user_session_id) = self.0.user_session_id else {
84 return Ok(None);
85 };
86
87 let state = ctx.state();
88 let mut repo = state.repository().await?;
89 let browser_session = repo
90 .browser_session()
91 .lookup(user_session_id)
92 .await?
93 .context("Could not load browser session")?;
94 repo.cancel().await?;
95
96 Ok(Some(BrowserSession(browser_session)))
97 }
98
99 pub async fn user(&self, ctx: &Context<'_>) -> Result<Option<User>, async_graphql::Error> {
101 let state = ctx.state();
102 let Some(user_id) = self.0.user_id else {
103 return Ok(None);
104 };
105
106 if !ctx.requester().is_owner_or_admin(&UserId(user_id)) {
107 return Err(async_graphql::Error::new("Unauthorized"));
108 }
109
110 let mut repo = state.repository().await?;
111 let user = repo
112 .user()
113 .lookup(user_id)
114 .await?
115 .context("Could not load user")?;
116 repo.cancel().await?;
117
118 Ok(Some(User(user)))
119 }
120
121 pub async fn last_active_ip(&self) -> Option<String> {
123 self.0.last_active_ip.map(|ip| ip.to_string())
124 }
125
126 pub async fn last_active_at(&self) -> Option<DateTime<Utc>> {
128 self.0.last_active_at
129 }
130
131 pub async fn human_name(&self) -> Option<&str> {
133 self.0.human_name.as_deref()
134 }
135}
136
137#[derive(Enum, Copy, Clone, Eq, PartialEq)]
139pub enum OAuth2ApplicationType {
140 Web,
142
143 Native,
145}
146
147#[derive(Description)]
149pub struct OAuth2Client(pub mas_data_model::Client);
150
151#[Object(use_type_description)]
152impl OAuth2Client {
153 pub async fn id(&self) -> ID {
155 NodeType::OAuth2Client.id(self.0.id)
156 }
157
158 pub async fn client_id(&self) -> &str {
160 &self.0.client_id
161 }
162
163 pub async fn client_name(&self) -> Option<&str> {
165 self.0.client_name.as_deref()
166 }
167
168 pub async fn client_uri(&self) -> Option<&Url> {
170 self.0.client_uri.as_ref()
171 }
172
173 pub async fn logo_uri(&self) -> Option<&Url> {
175 self.0.logo_uri.as_ref()
176 }
177
178 pub async fn tos_uri(&self) -> Option<&Url> {
180 self.0.tos_uri.as_ref()
181 }
182
183 pub async fn policy_uri(&self) -> Option<&Url> {
185 self.0.policy_uri.as_ref()
186 }
187
188 pub async fn redirect_uris(&self) -> &[Url] {
190 &self.0.redirect_uris
191 }
192
193 pub async fn application_type(&self) -> Option<OAuth2ApplicationType> {
195 match self.0.application_type.as_ref()? {
196 ApplicationType::Web => Some(OAuth2ApplicationType::Web),
197 ApplicationType::Native => Some(OAuth2ApplicationType::Native),
198 ApplicationType::Unknown(_) => None,
199 }
200 }
201}