clients.rs (6212B)
1 // Database operations for clients table 2 3 use sqlx::PgPool; 4 use anyhow::Result; 5 use uuid::Uuid; 6 use chrono::{DateTime, Utc}; 7 8 /// Client registration record 9 #[derive(Debug, Clone, sqlx::FromRow)] 10 pub struct Client { 11 pub id: Uuid, 12 pub client_id: String, 13 pub secret_hash: String, 14 pub verifier_url: String, 15 pub verifier_management_api_path: String, 16 pub redirect_uri: String, 17 pub accepted_issuer_dids: Option<String>, 18 pub created_at: DateTime<Utc>, 19 pub updated_at: DateTime<Utc>, 20 } 21 22 /// Register a new client 23 pub async fn register_client( 24 pool: &PgPool, 25 client_id: &str, 26 client_secret: &str, 27 verifier_url: &str, 28 verifier_management_api_path: Option<&str>, 29 redirect_uri: &str, 30 accepted_issuer_dids: Option<&str>, 31 ) -> Result<Client> { 32 let api_path = verifier_management_api_path 33 .unwrap_or("/management/api/verifications"); 34 35 let secret_hash = bcrypt::hash(client_secret, bcrypt::DEFAULT_COST)?; 36 37 let client = sqlx::query_as::<_, Client>( 38 r#" 39 INSERT INTO oauth2gw.clients 40 (client_id, secret_hash, verifier_url, verifier_management_api_path, redirect_uri, accepted_issuer_dids) 41 VALUES ($1, $2, $3, $4, $5, $6) 42 RETURNING id, client_id, secret_hash, verifier_url, 43 verifier_management_api_path, redirect_uri, accepted_issuer_dids, created_at, updated_at 44 "# 45 ) 46 .bind(client_id) 47 .bind(secret_hash) 48 .bind(verifier_url) 49 .bind(api_path) 50 .bind(redirect_uri) 51 .bind(accepted_issuer_dids) 52 .fetch_one(pool) 53 .await?; 54 55 Ok(client) 56 } 57 58 /// Lookup client by client_id 59 pub async fn get_client_by_id( 60 pool: &PgPool, 61 client_id: &str 62 ) -> Result<Option<Client>> { 63 let client = sqlx::query_as::<_, Client>( 64 r#" 65 SELECT id, client_id, secret_hash, verifier_url, 66 verifier_management_api_path, redirect_uri, accepted_issuer_dids, created_at, updated_at 67 FROM oauth2gw.clients 68 WHERE client_id = $1 69 "# 70 ) 71 .bind(client_id) 72 .fetch_optional(pool) 73 .await?; 74 75 Ok(client) 76 } 77 78 /// Lookup client by UUID 79 pub async fn get_client_by_uuid( 80 pool: &PgPool, 81 id: Uuid 82 ) -> Result<Option<Client>> { 83 let client = sqlx::query_as::<_, Client>( 84 r#" 85 SELECT id, client_id, secret_hash, verifier_url, 86 verifier_management_api_path, redirect_uri, accepted_issuer_dids, created_at, updated_at 87 FROM oauth2gw.clients 88 WHERE id = $1 89 "# 90 ) 91 .bind(id) 92 .fetch_optional(pool) 93 .await?; 94 95 Ok(client) 96 } 97 98 /// Get client secret hash by client_id 99 /// 100 /// Returns the secret hash if client exists, None otherwise 101 pub async fn get_client_secret_hash( 102 pool: &PgPool, 103 client_id: &str, 104 ) -> Result<Option<String>> { 105 let result = sqlx::query_scalar::<_, String>( 106 r#" 107 SELECT secret_hash 108 FROM oauth2gw.clients 109 WHERE client_id = $1 110 "# 111 ) 112 .bind(client_id) 113 .fetch_optional(pool) 114 .await?; 115 116 Ok(result) 117 } 118 119 /// Authenticate a client by validating client_secret 120 /// 121 /// Returns the client if authentication succeeds, None otherwise 122 pub async fn authenticate_client( 123 pool: &PgPool, 124 client_id: &str, 125 client_secret: &str 126 ) -> Result<Option<Client>> { 127 let client = sqlx::query_as::<_, Client>( 128 r#" 129 SELECT id, client_id, secret_hash, verifier_url, 130 verifier_management_api_path, redirect_uri, accepted_issuer_dids, created_at, updated_at 131 FROM oauth2gw.clients 132 WHERE client_id = $1 133 "# 134 ) 135 .bind(client_id) 136 .fetch_optional(pool) 137 .await?; 138 139 match client { 140 Some(c) => { 141 if bcrypt::verify(client_secret, &c.secret_hash)? { 142 Ok(Some(c)) 143 } else { 144 Ok(None) 145 } 146 } 147 None => Ok(None) 148 } 149 } 150 151 /// Update client configuration 152 pub async fn update_client( 153 pool: &PgPool, 154 id: Uuid, 155 verifier_url: Option<&str>, 156 verifier_management_api_path: Option<&str>, 157 redirect_uri: Option<&str>, 158 accepted_issuer_dids: Option<&str>, 159 ) -> Result<Client> { 160 let current = get_client_by_uuid(pool, id).await? 161 .ok_or_else(|| anyhow::anyhow!("Client not found"))?; 162 163 let new_verifier_url = verifier_url.unwrap_or(¤t.verifier_url); 164 let new_verifier_api_path = verifier_management_api_path 165 .unwrap_or(¤t.verifier_management_api_path); 166 let new_redirect_uri = redirect_uri.unwrap_or(¤t.redirect_uri); 167 let new_accepted_issuer_dids = accepted_issuer_dids.or(current.accepted_issuer_dids.as_deref()); 168 169 let client = sqlx::query_as::<_, Client>( 170 r#" 171 UPDATE oauth2gw.clients 172 SET 173 verifier_url = $1, 174 verifier_management_api_path = $2, 175 redirect_uri = $3, 176 accepted_issuer_dids = $4, 177 updated_at = CURRENT_TIMESTAMP 178 WHERE id = $5 179 RETURNING id, client_id, secret_hash, verifier_url, 180 verifier_management_api_path, redirect_uri, accepted_issuer_dids, created_at, updated_at 181 "# 182 ) 183 .bind(new_verifier_url) 184 .bind(new_verifier_api_path) 185 .bind(new_redirect_uri) 186 .bind(new_accepted_issuer_dids) 187 .bind(id) 188 .fetch_one(pool) 189 .await?; 190 191 Ok(client) 192 } 193 194 /// Delete a client (and cascade delete all associated sessions) 195 /// 196 /// WARNING: Deletes all associated data as well! (e.g., verifications)! 197 pub async fn delete_client( 198 pool: &PgPool, 199 id: Uuid 200 ) -> Result<bool> { 201 let result = sqlx::query( 202 r#" 203 DELETE FROM oauth2gw.clients 204 WHERE id = $1 205 "# 206 ) 207 .bind(id) 208 .execute(pool) 209 .await?; 210 211 Ok(result.rows_affected() > 0) 212 } 213 214 /// List all registered clients 215 pub async fn list_clients(pool: &PgPool) -> Result<Vec<Client>> { 216 let clients = sqlx::query_as::<_, Client>( 217 r#" 218 SELECT id, client_id, secret_hash, verifier_url, 219 verifier_management_api_path, redirect_uri, accepted_issuer_dids, created_at, updated_at 220 FROM oauth2gw.clients 221 ORDER BY created_at DESC 222 "# 223 ) 224 .fetch_all(pool) 225 .await?; 226 227 Ok(clients) 228 }