headless_lms_models/
oauth_user_client_scopes.rs1use crate::library::oauth::Digest;
2use crate::prelude::*;
3use chrono::{DateTime, Utc};
4use sqlx::FromRow;
5use utoipa::ToSchema;
6use uuid::Uuid;
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, FromRow)]
9pub struct OAuthUserClientScopes {
10 pub user_id: Uuid,
11 pub client_id: Uuid,
12 pub scopes: Vec<String>,
13 pub granted_at: DateTime<Utc>,
14}
15
16#[derive(Debug, Clone, PartialEq, FromRow, Serialize, Deserialize, ToSchema)]
17pub struct AuthorizedClientInfo {
18 pub client_id: Uuid, pub client_name: String, pub scopes: Vec<String>,
21}
22
23impl OAuthUserClientScopes {
24 pub async fn insert(
25 conn: &mut PgConnection,
26 user_id: Uuid,
27 client_id: Uuid,
28 scopes: &[String],
29 ) -> ModelResult<()> {
30 let mut tx = conn.begin().await?;
31 sqlx::query!(
32 r#"
33 INSERT INTO oauth_user_client_scopes
34 (user_id, client_id, scopes)
35 VALUES ($1, $2, $3)
36 ON CONFLICT (user_id, client_id) DO UPDATE
37 SET scopes = EXCLUDED.scopes,
38 granted_at = NOW()
39 "#,
40 user_id,
41 client_id,
42 scopes
43 )
44 .execute(&mut *tx)
45 .await?;
46 tx.commit().await?;
47 Ok(())
48 }
49
50 pub async fn find_scopes(
51 conn: &mut PgConnection,
52 user_id: Uuid,
53 client_id: Uuid,
54 ) -> ModelResult<Vec<String>> {
55 let mut tx = conn.begin().await?;
56 let rows = sqlx::query!(
57 r#"
58 SELECT scopes
59 FROM oauth_user_client_scopes
60 WHERE user_id = $1 AND client_id = $2
61 "#,
62 user_id,
63 client_id
64 )
65 .fetch_all(&mut *tx)
66 .await?;
67 tx.commit().await?;
68 Ok(rows.into_iter().flat_map(|r| r.scopes).collect())
69 }
70
71 pub async fn find_distinct_clients(
72 conn: &mut PgConnection,
73 user_id: Uuid,
74 ) -> ModelResult<Vec<Uuid>> {
75 let mut tx = conn.begin().await?;
76 let rows = sqlx::query!(
77 r#"
78 SELECT DISTINCT client_id
79 FROM oauth_user_client_scopes
80 WHERE user_id = $1
81 "#,
82 user_id
83 )
84 .fetch_all(&mut *tx)
85 .await?;
86 tx.commit().await?;
87 Ok(rows.into_iter().map(|r| r.client_id).collect())
88 }
89
90 pub async fn delete_all_for_user_client(
91 conn: &mut PgConnection,
92 user_id: Uuid,
93 client_id: Uuid,
94 ) -> ModelResult<()> {
95 let mut tx = conn.begin().await?;
96 sqlx::query!(
97 r#"DELETE FROM oauth_user_client_scopes WHERE user_id = $1 AND client_id = $2"#,
98 user_id,
99 client_id
100 )
101 .execute(&mut *tx)
102 .await?;
103 tx.commit().await?;
104 Ok(())
105 }
106
107 pub async fn list_authorized_clients_for_user(
108 conn: &mut PgConnection,
109 user_id: Uuid,
110 ) -> ModelResult<Vec<AuthorizedClientInfo>> {
111 let mut tx = conn.begin().await?;
112 let rows = sqlx::query_as!(
115 AuthorizedClientInfo,
116 r#"
117 SELECT
118 c.id AS client_id,
119 c.client_id AS client_name,
120 COALESCE(
121 array_agg(DISTINCT s.scope ORDER BY s.scope) FILTER (WHERE s.scope IS NOT NULL),
122 '{}'::text[]
123 ) AS "scopes!"
124 FROM oauth_user_client_scopes ucs
125 JOIN oauth_clients c ON c.id = ucs.client_id
126 LEFT JOIN LATERAL unnest(ucs.scopes) AS s(scope) ON TRUE
127 WHERE ucs.user_id = $1
128 GROUP BY c.id, c.client_id
129 ORDER BY c.client_id
130 "#,
131 user_id
132 )
133 .fetch_all(&mut *tx)
134 .await?;
135
136 tx.commit().await?;
137 Ok(rows)
138 }
139
140 pub async fn revoke_user_client_everything(
145 conn: &mut PgConnection,
146 user_id: Uuid,
147 client_id: Uuid,
148 ) -> ModelResult<Vec<Digest>> {
149 let mut tx = conn.begin().await?;
150
151 sqlx::query!(
152 r#"DELETE FROM oauth_user_client_scopes WHERE user_id = $1 AND client_id = $2"#,
153 user_id,
154 client_id
155 )
156 .execute(&mut *tx)
157 .await?;
158
159 let deleted_digests =
160 crate::oauth_refresh_tokens::OAuthRefreshTokens::revoke_grant_in_transaction(
161 &mut tx, user_id, client_id,
162 )
163 .await?;
164
165 tx.commit().await?;
166 Ok(deleted_digests)
167 }
168}