diff --git a/.sqlx/query-bf999ca230419894e5a2c7281596fe82e1a2ebae23511b4cab1ae841309b0a11.json b/.sqlx/query-0a82d7353541cf5f6f2275fc89324ca7b1b724bf76e85da46c646b071103a0ba.json similarity index 65% rename from .sqlx/query-bf999ca230419894e5a2c7281596fe82e1a2ebae23511b4cab1ae841309b0a11.json rename to .sqlx/query-0a82d7353541cf5f6f2275fc89324ca7b1b724bf76e85da46c646b071103a0ba.json index 20d2df9..28dca6b 100644 --- a/.sqlx/query-bf999ca230419894e5a2c7281596fe82e1a2ebae23511b4cab1ae841309b0a11.json +++ b/.sqlx/query-0a82d7353541cf5f6f2275fc89324ca7b1b724bf76e85da46c646b071103a0ba.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT uid as uuid, ip, device_code, interval, expires_in, created_at, last_poll\n FROM github_login_attempts\n WHERE uid = $1", + "query": "SELECT uid as uuid, ip, interval, expires_in, created_at, last_poll, challenge_uri as uri, device_code, user_code\n FROM github_login_attempts\n WHERE uid = $1", "describe": { "columns": [ { @@ -15,28 +15,38 @@ }, { "ordinal": 2, - "name": "device_code", - "type_info": "Text" - }, - { - "ordinal": 3, "name": "interval", "type_info": "Int4" }, { - "ordinal": 4, + "ordinal": 3, "name": "expires_in", "type_info": "Int4" }, { - "ordinal": 5, + "ordinal": 4, "name": "created_at", "type_info": "Timestamptz" }, { - "ordinal": 6, + "ordinal": 5, "name": "last_poll", "type_info": "Timestamptz" + }, + { + "ordinal": 6, + "name": "uri", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "device_code", + "type_info": "Text" + }, + { + "ordinal": 8, + "name": "user_code", + "type_info": "Text" } ], "parameters": { @@ -51,8 +61,10 @@ false, false, false, + false, + false, false ] }, - "hash": "bf999ca230419894e5a2c7281596fe82e1a2ebae23511b4cab1ae841309b0a11" + "hash": "0a82d7353541cf5f6f2275fc89324ca7b1b724bf76e85da46c646b071103a0ba" } diff --git a/.sqlx/query-38b0c32de5c085b0cd407f67b9d14747b936ce248d49a7dfbe549b25bfaa2058.json b/.sqlx/query-2fd39f20004726269bea6f08403777c8f810747054b90a272e74529cdda0b14e.json similarity index 70% rename from .sqlx/query-38b0c32de5c085b0cd407f67b9d14747b936ce248d49a7dfbe549b25bfaa2058.json rename to .sqlx/query-2fd39f20004726269bea6f08403777c8f810747054b90a272e74529cdda0b14e.json index 0e2db23..187febf 100644 --- a/.sqlx/query-38b0c32de5c085b0cd407f67b9d14747b936ce248d49a7dfbe549b25bfaa2058.json +++ b/.sqlx/query-2fd39f20004726269bea6f08403777c8f810747054b90a272e74529cdda0b14e.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT uid as uuid, ip, device_code, interval, expires_in, created_at, last_poll\n FROM github_login_attempts\n WHERE ip = $1", + "query": "SELECT uid as uuid, ip, device_code, interval, expires_in, created_at, last_poll, challenge_uri as uri, user_code\n FROM github_login_attempts\n WHERE ip = $1", "describe": { "columns": [ { @@ -37,6 +37,16 @@ "ordinal": 6, "name": "last_poll", "type_info": "Timestamptz" + }, + { + "ordinal": 7, + "name": "uri", + "type_info": "Text" + }, + { + "ordinal": 8, + "name": "user_code", + "type_info": "Text" } ], "parameters": { @@ -51,8 +61,10 @@ false, false, false, + false, + false, false ] }, - "hash": "38b0c32de5c085b0cd407f67b9d14747b936ce248d49a7dfbe549b25bfaa2058" + "hash": "2fd39f20004726269bea6f08403777c8f810747054b90a272e74529cdda0b14e" } diff --git a/.sqlx/query-d69a5379c794703a8ba7831e3ac09880c9cc8f7b8f3ed7115beef051951869ca.json b/.sqlx/query-47c8fd9d7a33f1d5fba1c24cef75f99600235809bc11acf20eb6e80ee4284303.json similarity index 57% rename from .sqlx/query-d69a5379c794703a8ba7831e3ac09880c9cc8f7b8f3ed7115beef051951869ca.json rename to .sqlx/query-47c8fd9d7a33f1d5fba1c24cef75f99600235809bc11acf20eb6e80ee4284303.json index 8386dcb..bd23225 100644 --- a/.sqlx/query-d69a5379c794703a8ba7831e3ac09880c9cc8f7b8f3ed7115beef051951869ca.json +++ b/.sqlx/query-47c8fd9d7a33f1d5fba1c24cef75f99600235809bc11acf20eb6e80ee4284303.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO github_login_attempts\n (ip, device_code, interval, expires_in) VALUES\n ($1, $2, $3, $4) RETURNING uid\n ", + "query": "\n INSERT INTO github_login_attempts\n (ip, device_code, interval, expires_in, challenge_uri, user_code) VALUES\n ($1, $2, $3, $4, $5, $6) RETURNING uid\n ", "describe": { "columns": [ { @@ -14,12 +14,14 @@ "Inet", "Text", "Int4", - "Int4" + "Int4", + "Text", + "Text" ] }, "nullable": [ false ] }, - "hash": "d69a5379c794703a8ba7831e3ac09880c9cc8f7b8f3ed7115beef051951869ca" + "hash": "47c8fd9d7a33f1d5fba1c24cef75f99600235809bc11acf20eb6e80ee4284303" } diff --git a/migrations/20240219224335_add_more_data_to_login_attempt.down.sql b/migrations/20240219224335_add_more_data_to_login_attempt.down.sql new file mode 100644 index 0000000..f8e774a --- /dev/null +++ b/migrations/20240219224335_add_more_data_to_login_attempt.down.sql @@ -0,0 +1,4 @@ +-- Add down migration script here + +alter table github_login_attempts drop column if exists challenge_uri; +alter table github_login_attempts drop column if exists user_code; \ No newline at end of file diff --git a/migrations/20240219224335_add_more_data_to_login_attempt.up.sql b/migrations/20240219224335_add_more_data_to_login_attempt.up.sql new file mode 100644 index 0000000..a43f1f3 --- /dev/null +++ b/migrations/20240219224335_add_more_data_to_login_attempt.up.sql @@ -0,0 +1,4 @@ +-- Add up migration script here + +alter table github_login_attempts add column challenge_uri text not null; +alter table github_login_attempts add column user_code text not null; \ No newline at end of file diff --git a/src/auth/github.rs b/src/auth/github.rs index 2b6bb9e..8b09dc1 100644 --- a/src/auth/github.rs +++ b/src/auth/github.rs @@ -3,10 +3,8 @@ use reqwest::{ Client, StatusCode, }; use serde::{Deserialize, Serialize}; -use sqlx::{ - types::ipnetwork::{IpNetwork, Ipv4Network}, - PgConnection, -}; +use sqlx::{types::ipnetwork::IpNetwork, PgConnection}; +use uuid::Uuid; use crate::types::{api::ApiError, models::github_login_attempt::GithubLoginAttempt}; @@ -42,10 +40,18 @@ impl GithubClient { client_id: String, } let found_request = GithubLoginAttempt::get_one_by_ip(ip, &mut *pool).await?; - if found_request.is_some() { - return Err(ApiError::BadRequest( - "Login attempt already running".to_string(), - )); + if let Some(r) = found_request { + if r.is_expired() { + let uuid = Uuid::parse_str(&r.uuid).unwrap(); + GithubLoginAttempt::remove(uuid, &mut *pool).await?; + } else { + return Ok(GithubLoginAttempt { + uuid: r.uuid.to_string(), + interval: r.interval, + uri: r.uri, + code: r.user_code, + }); + } } let mut headers = HeaderMap::new(); headers.insert("Accept", HeaderValue::from_static("application/json")); @@ -94,6 +100,8 @@ impl GithubClient { body.device_code, body.interval, body.expires_in, + &body.verification_uri, + &body.user_code, &mut *pool, ) .await?; diff --git a/src/endpoints/auth/github.rs b/src/endpoints/auth/github.rs index 4f8eeb0..3311266 100644 --- a/src/endpoints/auth/github.rs +++ b/src/endpoints/auth/github.rs @@ -1,6 +1,9 @@ use actix_web::{dev::ConnectionInfo, post, web, Responder}; use serde::Deserialize; -use sqlx::types::ipnetwork::{IpNetwork, Ipv4Network}; +use sqlx::{ + types::ipnetwork::{IpNetwork, Ipv4Network}, + Acquire, +}; use uuid::Uuid; use crate::{ @@ -48,6 +51,7 @@ pub async fn poll_github_login( connection_info: ConnectionInfo, ) -> Result { let mut pool = data.db.acquire().await.or(Err(ApiError::DbAcquireError))?; + let mut transaction = pool.begin().await.or(Err(ApiError::DbAcquireError))?; let uuid = match Uuid::parse_str(&json.uuid) { Err(e) => { log::error!("{}", e); @@ -55,12 +59,13 @@ pub async fn poll_github_login( } Ok(u) => u, }; - let attempt = match GithubLoginAttempt::get_one(uuid, &mut pool).await? { + let attempt = match GithubLoginAttempt::get_one(uuid, &mut transaction).await? { None => { + let _ = transaction.rollback().await; return Err(ApiError::BadRequest(format!( "No attempt made for uuid {}", json.uuid - ))) + ))); } Some(a) => a, }; @@ -69,46 +74,116 @@ pub async fn poll_github_login( None => return Err(ApiError::InternalError), Some(i) => i, }; - let net: Ipv4Network = ip.parse().or(Err(ApiError::InternalError))?; + let net: Ipv4Network = match ip.parse() { + Err(e) => { + let _ = transaction.rollback().await; + log::error!("{}", e); + return Err(ApiError::BadRequest("Invalid IP".to_string())); + } + Ok(n) => n, + }; if attempt.ip.ip() != net.ip() { + let _ = transaction.rollback().await; log::error!("{} compared to {}", attempt.ip, net); return Err(ApiError::BadRequest( "Request IP does not match stored attempt IP".to_string(), )); } if !attempt.interval_passed() { + let _ = transaction.rollback().await; return Err(ApiError::BadRequest("Too fast".to_string())); } if attempt.is_expired() { - GithubLoginAttempt::remove(uuid, &mut pool).await; - return Err(ApiError::BadRequest("Login attempt expired".to_string())); + match GithubLoginAttempt::remove(uuid, &mut transaction).await { + Err(e) => { + let _ = transaction.rollback().await; + log::error!("{}", e); + return Err(ApiError::InternalError); + } + Ok(_) => { + let _ = transaction.commit().await; + return Err(ApiError::BadRequest("Login attempt expired".to_string())); + } + }; } let client = github::GithubClient::new( data.github_client_id.to_string(), data.github_client_secret.to_string(), ); - GithubLoginAttempt::poll(uuid, &mut pool).await; + GithubLoginAttempt::poll(uuid, &mut transaction).await; let token = client.poll_github(&attempt.device_code).await?; - GithubLoginAttempt::remove(uuid, &mut pool).await; - let user = client.get_user(token).await?; + if let Err(e) = GithubLoginAttempt::remove(uuid, &mut transaction).await { + let _ = transaction.rollback().await; + log::error!("{}", e); + return Err(ApiError::InternalError); + }; + let user = match client.get_user(token).await { + Err(e) => { + let _ = transaction.rollback().await; + log::error!("{}", e); + return Err(ApiError::InternalError); + } + Ok(u) => u, + }; + + // Create a new transaction after this point, because we need to commit the removal of the login attempt + + if let Err(e) = transaction.commit().await { + log::error!("{}", e); + return Err(ApiError::InternalError); + } + + let mut transaction = pool.begin().await.or(Err(ApiError::DbAcquireError))?; + let id = match user.get("id") { None => return Err(ApiError::InternalError), Some(id) => id.as_i64().unwrap(), }; - if let Some(x) = Developer::get_by_github_id(id, &mut pool).await? { - let token = create_token_for_developer(x.id, &mut pool).await?; + if let Some(x) = Developer::get_by_github_id(id, &mut transaction).await? { + let token = match create_token_for_developer(x.id, &mut transaction).await { + Err(_) => { + let _ = transaction.rollback().await; + return Err(ApiError::InternalError); + } + Ok(t) => t, + }; + if let Err(e) = transaction.commit().await { + log::error!("{}", e); + return Err(ApiError::InternalError); + } return Ok(web::Json(ApiResponse { error: "".to_string(), payload: token.to_string(), })); } let username = match user.get("login") { - None => return Err(ApiError::InternalError), + None => { + let _ = transaction.rollback().await; + return Err(ApiError::InternalError); + } Some(user) => user.to_string(), }; - let id = Developer::create(id, username, &mut pool).await?; - let token = create_token_for_developer(id, &mut pool).await?; + let id = match Developer::create(id, username, &mut transaction).await { + Err(e) => { + let _ = transaction.rollback().await; + log::error!("{}", e); + return Err(ApiError::InternalError); + } + Ok(i) => i, + }; + let token = match create_token_for_developer(id, &mut transaction).await { + Err(e) => { + let _ = transaction.rollback().await; + log::error!("{}", e); + return Err(ApiError::InternalError); + } + Ok(t) => t, + }; + if let Err(e) = transaction.commit().await { + log::error!("{}", e); + return Err(ApiError::InternalError); + } Ok(web::Json(ApiResponse { error: "".to_string(), diff --git a/src/endpoints/mod_versions.rs b/src/endpoints/mod_versions.rs index 1a13edb..204fbb6 100644 --- a/src/endpoints/mod_versions.rs +++ b/src/endpoints/mod_versions.rs @@ -1,9 +1,6 @@ use actix_web::{dev::ConnectionInfo, get, post, put, web, HttpResponse, Responder}; use serde::Deserialize; -use sqlx::{ - types::ipnetwork::{self, IpNetwork, Ipv4Network}, - Acquire, -}; +use sqlx::{types::ipnetwork::IpNetwork, Acquire}; use crate::{ extractors::auth::Auth, diff --git a/src/types/models/github_login_attempt.rs b/src/types/models/github_login_attempt.rs index 94aa93d..61e3c02 100644 --- a/src/types/models/github_login_attempt.rs +++ b/src/types/models/github_login_attempt.rs @@ -24,6 +24,8 @@ pub struct StoredLoginAttempt { pub uuid: String, pub ip: IpNetwork, pub device_code: String, + pub uri: String, + pub user_code: String, pub interval: i32, pub expires_in: i32, pub created_at: DateTime, @@ -51,18 +53,22 @@ impl GithubLoginAttempt { device_code: String, interval: i32, expires_in: i32, + uri: &str, + user_code: &str, pool: &mut PgConnection, ) -> Result { let result = sqlx::query!( " INSERT INTO github_login_attempts - (ip, device_code, interval, expires_in) VALUES - ($1, $2, $3, $4) RETURNING uid + (ip, device_code, interval, expires_in, challenge_uri, user_code) VALUES + ($1, $2, $3, $4, $5, $6) RETURNING uid ", ip, device_code, interval, - expires_in + expires_in, + uri, + user_code ) .fetch_one(&mut *pool) .await; @@ -81,7 +87,7 @@ impl GithubLoginAttempt { ) -> Result, ApiError> { let result = sqlx::query_as!( StoredLoginAttempt, - "SELECT uid as uuid, ip, device_code, interval, expires_in, created_at, last_poll + "SELECT uid as uuid, ip, interval, expires_in, created_at, last_poll, challenge_uri as uri, device_code, user_code FROM github_login_attempts WHERE uid = $1", uuid @@ -104,7 +110,7 @@ impl GithubLoginAttempt { ) -> Result, ApiError> { let result = sqlx::query_as!( StoredLoginAttempt, - "SELECT uid as uuid, ip, device_code, interval, expires_in, created_at, last_poll + "SELECT uid as uuid, ip, device_code, interval, expires_in, created_at, last_poll, challenge_uri as uri, user_code FROM github_login_attempts WHERE ip = $1", ip @@ -121,10 +127,17 @@ impl GithubLoginAttempt { } } - pub async fn remove(uuid: Uuid, pool: &mut PgConnection) { - let _ = sqlx::query!("DELETE FROM github_login_attempts WHERE uid = $1", uuid) + pub async fn remove(uuid: Uuid, pool: &mut PgConnection) -> Result<(), ApiError> { + match sqlx::query!("DELETE FROM github_login_attempts WHERE uid = $1", uuid) .execute(&mut *pool) - .await; + .await + { + Err(e) => { + log::error!("{}", e); + Err(ApiError::DbError) + } + Ok(_) => Ok(()), + } } pub async fn poll(uuid: Uuid, pool: &mut PgConnection) { diff --git a/src/types/models/mod_entity.rs b/src/types/models/mod_entity.rs index 697289d..9605281 100644 --- a/src/types/models/mod_entity.rs +++ b/src/types/models/mod_entity.rs @@ -15,7 +15,7 @@ use sqlx::{ use std::{io::Cursor, str::FromStr}; use super::{ - developer::{self, Developer, FetchedDeveloper}, + developer::{Developer, FetchedDeveloper}, mod_gd_version::{DetailedGDVersion, ModGDVersion, VerPlatform}, tag::Tag, }; diff --git a/src/types/models/mod_version.rs b/src/types/models/mod_version.rs index cf7e404..4e575f4 100644 --- a/src/types/models/mod_version.rs +++ b/src/types/models/mod_version.rs @@ -1,7 +1,4 @@ -use std::{ - collections::{hash_map::Entry, HashMap}, - vec, -}; +use std::collections::HashMap; use serde::Serialize; use sqlx::{PgConnection, Postgres, QueryBuilder, Row}; diff --git a/src/types/models/tag.rs b/src/types/models/tag.rs index 9c6598d..275807d 100644 --- a/src/types/models/tag.rs +++ b/src/types/models/tag.rs @@ -213,7 +213,7 @@ impl Tag { } }; - let fetched_ids = fetched.iter().map(|t| t.id.clone()).collect::>(); + let fetched_ids = fetched.iter().map(|t| t.id).collect::>(); let fetched_names = fetched .iter() .map(|t| t.name.clone())