Skip to content

Commit

Permalink
make the login attempt a bit more user friendly
Browse files Browse the repository at this point in the history
  • Loading branch information
Fleeym committed Feb 19, 2024
1 parent 98eb7a8 commit e405aff
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 55 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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;
24 changes: 16 additions & 8 deletions src/auth/github.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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"));
Expand Down Expand Up @@ -94,6 +100,8 @@ impl GithubClient {
body.device_code,
body.interval,
body.expires_in,
&body.verification_uri,
&body.user_code,
&mut *pool,
)
.await?;
Expand Down
103 changes: 89 additions & 14 deletions src/endpoints/auth/github.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand Down Expand Up @@ -48,19 +51,21 @@ pub async fn poll_github_login(
connection_info: ConnectionInfo,
) -> Result<impl Responder, ApiError> {
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);
return Err(ApiError::BadRequest(format!("Invalid uuid {}", json.uuid)));
}
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,
};
Expand All @@ -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(),
Expand Down
5 changes: 1 addition & 4 deletions src/endpoints/mod_versions.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
Loading

0 comments on commit e405aff

Please sign in to comment.