Skip to content

Commit 640edf3

Browse files
committed
fix: auth refactor
1 parent e518d79 commit 640edf3

File tree

5 files changed

+204
-224
lines changed

5 files changed

+204
-224
lines changed

src/auth/github.rs

+69-100
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
use reqwest::{
2-
header::{HeaderMap, HeaderValue},
3-
Client, StatusCode,
4-
};
1+
use reqwest::{header::HeaderValue, Client};
52
use serde::{Deserialize, Serialize};
3+
use serde_json::json;
64
use sqlx::{types::ipnetwork::IpNetwork, PgConnection};
75
use uuid::Uuid;
86

@@ -22,6 +20,13 @@ pub struct GithubClient {
2220
client_secret: String,
2321
}
2422

23+
#[derive(Deserialize)]
24+
pub struct GitHubFetchedUser {
25+
pub id: i64,
26+
#[serde(alias = "login")]
27+
pub username: String,
28+
}
29+
2530
impl GithubClient {
2631
pub fn new(client_id: String, client_secret: String) -> GithubClient {
2732
GithubClient {
@@ -35,10 +40,6 @@ impl GithubClient {
3540
ip: IpNetwork,
3641
pool: &mut PgConnection,
3742
) -> Result<GithubLoginAttempt, ApiError> {
38-
#[derive(Serialize)]
39-
struct GithubStartAuthBody {
40-
client_id: String,
41-
}
4243
let found_request = GithubLoginAttempt::get_one_by_ip(ip, &mut *pool).await?;
4344
if let Some(r) = found_request {
4445
if r.is_expired() {
@@ -53,50 +54,36 @@ impl GithubClient {
5354
});
5455
}
5556
}
56-
let mut headers = HeaderMap::new();
57-
headers.insert("Accept", HeaderValue::from_static("application/json"));
58-
let client = match Client::builder().default_headers(headers).build() {
59-
Err(e) => {
60-
log::error!("{}", e);
61-
return Err(ApiError::InternalError);
62-
}
63-
Ok(c) => c,
64-
};
65-
let body = GithubStartAuthBody {
66-
client_id: String::from(&self.client_id),
67-
};
68-
let json = match serde_json::to_string(&body) {
69-
Err(e) => {
70-
log::error!("{}", e);
71-
return Err(ApiError::InternalError);
72-
}
73-
Ok(j) => j,
74-
};
75-
let result = match client
57+
58+
let res = Client::new()
7659
.post("https://github.com/login/device/code")
60+
.header("Accept", HeaderValue::from_static("application/json"))
7761
.basic_auth(&self.client_id, Some(&self.client_secret))
78-
.body(json)
62+
.json(&json!({
63+
"client_id": &self.client_id
64+
}))
7965
.send()
8066
.await
81-
{
82-
Err(e) => {
83-
log::error!("{}", e);
84-
return Err(ApiError::InternalError);
85-
}
86-
Ok(r) => r,
87-
};
67+
.map_err(|e| {
68+
log::error!("Failed to start OAuth device flow with GitHub: {}", e);
69+
ApiError::InternalError
70+
})?;
8871

89-
if result.status() != StatusCode::OK {
90-
log::error!("Couldn't connect to GitHub");
72+
if !res.status().is_success() {
73+
log::error!(
74+
"GitHub OAuth device flow start request failed with code {}",
75+
res.status()
76+
);
9177
return Err(ApiError::InternalError);
9278
}
93-
let body = match result.json::<GithubStartAuth>().await {
94-
Err(e) => {
95-
log::error!("{}", e);
96-
return Err(ApiError::InternalError);
97-
}
98-
Ok(b) => b,
99-
};
79+
80+
let body = res.json::<GithubStartAuth>().await.map_err(|e| {
81+
log::error!(
82+
"Failed to parse OAuth device flow response from GitHub: {}",
83+
e
84+
);
85+
ApiError::InternalError
86+
})?;
10087
let uuid = GithubLoginAttempt::create(
10188
ip,
10289
body.device_code,
@@ -117,81 +104,63 @@ impl GithubClient {
117104
}
118105

119106
pub async fn poll_github(&self, device_code: &str) -> Result<String, ApiError> {
120-
#[derive(Serialize, Debug)]
121-
struct GithubPollAuthBody {
122-
client_id: String,
123-
device_code: String,
124-
grant_type: String,
125-
}
126-
let body = GithubPollAuthBody {
127-
client_id: String::from(&self.client_id),
128-
device_code: String::from(device_code),
129-
grant_type: String::from("urn:ietf:params:oauth:grant-type:device_code"),
130-
};
131-
let json = match serde_json::to_string(&body) {
132-
Err(e) => {
133-
log::error!("{}", e);
134-
return Err(ApiError::InternalError);
135-
}
136-
Ok(j) => j,
137-
};
138-
let client = Client::new();
139-
let resp = client
107+
let resp = Client::new()
140108
.post("https://github.com/login/oauth/access_token")
141109
.header("Accept", HeaderValue::from_str("application/json").unwrap())
142110
.header(
143111
"Content-Type",
144112
HeaderValue::from_str("application/json").unwrap(),
145113
)
146114
.basic_auth(&self.client_id, Some(&self.client_secret))
147-
.body(json)
115+
.json(&json!({
116+
"client_id": &self.client_id,
117+
"device_code": device_code,
118+
"grant_type": "urn:ietf:params:oauth:grant-type:device_code"
119+
}))
148120
.send()
149-
.await;
150-
if resp.is_err() {
151-
log::info!("{}", resp.err().unwrap());
152-
return Err(ApiError::InternalError);
153-
}
154-
let resp = resp.unwrap();
155-
let body = resp.json::<serde_json::Value>().await.unwrap();
156-
match body.get("access_token") {
157-
None => {
158-
log::error!("{:?}", body);
159-
Err(ApiError::BadRequest(
160-
"Request not accepted by user".to_string(),
161-
))
162-
}
163-
Some(t) => Ok(String::from(t.as_str().unwrap())),
164-
}
121+
.await
122+
.map_err(|e| {
123+
log::error!("Failed to poll GitHub for developer access token: {}", e);
124+
ApiError::InternalError
125+
})?;
126+
127+
Ok(resp
128+
.json::<serde_json::Value>()
129+
.await
130+
.map_err(|e| {
131+
log::error!("Failed to decode GitHub response: {}", e);
132+
ApiError::InternalError
133+
})?
134+
.get("access_token")
135+
.ok_or(ApiError::BadRequest("Request not accepted by user".into()))?
136+
.as_str()
137+
.ok_or_else(|| {
138+
log::error!("Invalid access_token received from GitHub");
139+
ApiError::InternalError
140+
})?
141+
.to_string())
165142
}
166143

167-
pub async fn get_user(&self, token: &str) -> Result<serde_json::Value, ApiError> {
168-
let client = Client::new();
169-
let resp = match client
144+
pub async fn get_user(&self, token: &str) -> Result<GitHubFetchedUser, ApiError> {
145+
let resp = Client::new()
170146
.get("https://api.github.com/user")
171147
.header("Accept", HeaderValue::from_str("application/json").unwrap())
172148
.header("User-Agent", "geode_index")
173149
.bearer_auth(token)
174150
.send()
175151
.await
176-
{
177-
Err(e) => {
178-
log::info!("{}", e);
179-
return Err(ApiError::InternalError);
180-
}
181-
Ok(r) => r,
182-
};
152+
.map_err(|e| {
153+
log::error!("Request to https://api.github.com/user failed: {}", e);
154+
ApiError::InternalError
155+
})?;
183156

184157
if !resp.status().is_success() {
185158
return Err(ApiError::InternalError);
186159
}
187-
let body = match resp.json::<serde_json::Value>().await {
188-
Err(e) => {
189-
log::error!("{}", e);
190-
return Err(ApiError::InternalError);
191-
}
192-
Ok(b) => b,
193-
};
194160

195-
Ok(body)
161+
Ok(resp.json::<GitHubFetchedUser>().await.map_err(|e| {
162+
log::error!("Failed to create GitHubFetchedUser: {}", e);
163+
ApiError::InternalError
164+
})?)
196165
}
197166
}
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use crate::types::api::ApiError;
2+
use sqlx::PgConnection;
3+
use uuid::Uuid;
4+
5+
/// Assumes developer ID exists
6+
pub async fn generate_token(developer_id: i32, conn: &mut PgConnection) -> Result<Uuid, ApiError> {
7+
let token = Uuid::new_v4();
8+
let hash = sha256::digest(token.to_string());
9+
10+
sqlx::query!(
11+
"INSERT INTO auth_tokens(token, developer_id)
12+
VALUES ($1, $2)",
13+
hash,
14+
developer_id
15+
)
16+
.execute(&mut *conn)
17+
.await
18+
.map_err(|e| {
19+
log::error!(
20+
"Failed to insert auth_token for developer {}: {}",
21+
developer_id,
22+
e
23+
);
24+
ApiError::DbError
25+
})?;
26+
27+
Ok(token)
28+
}

src/database/repository/developers.rs

+54
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,60 @@ use crate::types::api::ApiError;
22
use crate::types::models::developer::FetchedDeveloper;
33
use sqlx::PgConnection;
44

5+
pub async fn fetch_or_insert_github(
6+
github_id: i64,
7+
username: &str,
8+
conn: &mut PgConnection,
9+
) -> Result<FetchedDeveloper, ApiError> {
10+
match sqlx::query_as!(
11+
FetchedDeveloper,
12+
"SELECT
13+
id,
14+
username,
15+
display_name,
16+
verified,
17+
admin
18+
FROM developers
19+
WHERE github_user_id = $1",
20+
github_id
21+
)
22+
.fetch_optional(&mut *conn)
23+
.await
24+
.map_err(|e| {
25+
log::error!("Failed to fetch developer for GitHub id: {}", e);
26+
ApiError::DbError
27+
})? {
28+
Some(dev) => Ok(dev),
29+
None => Ok(insert_github(github_id, username, conn).await?),
30+
}
31+
}
32+
33+
async fn insert_github(
34+
github_id: i64,
35+
username: &str,
36+
conn: &mut PgConnection,
37+
) -> Result<FetchedDeveloper, ApiError> {
38+
Ok(sqlx::query_as!(
39+
FetchedDeveloper,
40+
"INSERT INTO developers(username, display_name, github_user_id)
41+
VALUES ($1, $1, $2)
42+
RETURNING
43+
id,
44+
username,
45+
display_name,
46+
verified,
47+
admin",
48+
username,
49+
github_id
50+
)
51+
.fetch_one(&mut *conn)
52+
.await
53+
.map_err(|e| {
54+
log::error!("Failed to insert developer: {}", e);
55+
ApiError::DbError
56+
})?)
57+
}
58+
559
pub async fn get_owner_for_mod(
660
mod_id: &str,
761
conn: &mut PgConnection,

src/database/repository/mod.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
pub mod mod_tags;
2-
pub mod mods;
1+
pub mod auth_tokens;
32
pub mod developers;
43
pub mod mod_downloads;
5-
pub mod mod_versions;
4+
pub mod mod_tags;
5+
pub mod mod_versions;
6+
pub mod mods;

0 commit comments

Comments
 (0)