use anyhow::Context; use sqlx::Executor; // use sqlx::QueryBuilder; use sqlx::Row; use crate::domain::api::models::oauth::*; use crate::domain::api::ports::OAuthRepository; use super::Postgres; impl OAuthRepository for Postgres { async fn find_client_by_id(&self, id: uuid::Uuid) -> Result, anyhow::Error> { let query = sqlx::query( r#" SELECT id, user_id, name, secret, redirect_uri FROM clients WHERE id = $1 "#, ) .bind(id); let row = self .pool .fetch_optional(query) .await .context("failed to execute SQL transaction")?; let Some(row) = row else { return Ok(None); }; let id = row.get("id"); let user_id = row.get("user_id"); let name = ClientName::new(row.get("name")); let secret = ClientSecret::from(row.get::<&str, &str>("secret")); let redirect_uri = RedirectUri::new(row.get("redirect_uri")); Ok(Some(Client::new(id, user_id, name, secret, redirect_uri))) } async fn create_authorization_code( &self, user_id: uuid::Uuid, client_id: uuid::Uuid, code_challenge: String, code_challenge_method: CodeChallengeMethod, ) -> Result { let mut tx = self .pool .begin() .await .context("Failed to start sql transaction")?; let code = AuthorizationCode::new(); let query = sqlx::query( r#" INSERT INTO authorization_code (code, user_id, client_id, code_challenge, code_challenge_method) VALUES ($1, $2, $3, $4, $5) "#, ) .bind(code.to_string()) .bind(user_id) .bind(client_id) .bind(code_challenge) .bind(code_challenge_method.to_string()); tx.execute(query) .await .map_err(|e| CreateAuthorizationCodeError::Unknown(e.into()))?; let query = sqlx::query( r#" INSERT INTO authorized_clients (user_id, client_id) VALUES ($1, $2) ON CONFLICT DO NOTHING "#, ) .bind(user_id) .bind(client_id); tx.execute(query) .await .map_err(|e| CreateAuthorizationCodeError::Unknown(e.into()))?; tx.commit() .await .context("failed to commit SQL transaction")?; Ok(code) } async fn is_authorized_client( &self, user_id: uuid::Uuid, client_id: uuid::Uuid, ) -> Result { let query = sqlx::query( r#" SELECT * FROM authorized_clients WHERE user_id = $1 AND client_id = $2 "#, ) .bind(user_id) .bind(client_id); Ok(self .pool .fetch_optional(query) .await .context("failed to execute SQL transaction")? .is_some()) } async fn get_token_subject( &self, code: AuthorizationCode, ) -> Result, anyhow::Error> { let query = sqlx::query( r#" SELECT user_id, client_id, code_challenge, code_challenge_method FROM authorization_code WHERE code = $1 "#, ) .bind(code.to_string()); let Some(row) = self .pool .fetch_optional(query) .await .context("failed to execute SQL transaction")? else { return Ok(None); }; let user_id: uuid::Uuid = row.get("user_id"); let client_id: uuid::Uuid = row.get("client_id"); let code_challenge: String = row.get("code_challenge"); let code_challenge_method: CodeChallengeMethod = row .get::("code_challenge_method") .try_into()?; Ok(Some(TokenSubject::new( user_id, client_id, code_challenge, code_challenge_method, ))) } async fn delete_token(&self, code: AuthorizationCode) -> Result<(), anyhow::Error> { let mut tx = self .pool .begin() .await .context("Failed to start sql transaction")?; let query = sqlx::query("DELETE FROM authorization_code WHERE code = $1").bind(code.to_string()); tx.execute(query) .await .context("failed to execute SQL transaction")?; tx.commit() .await .context("failed to commit SQL transaction")?; Ok(()) } }