Skip to content

Commit

Permalink
Handle user auth (wip)
Browse files Browse the repository at this point in the history
  • Loading branch information
apiraino committed Aug 22, 2023
1 parent fa3bc11 commit d1e8532
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 58 deletions.
5 changes: 5 additions & 0 deletions src/github.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ impl GithubClient {
let (body, _req_dbg) = self.send_req(req).await?;
Ok(serde_json::from_slice(&body)?)
}

// TODO
pub async fn get_profile(&self) -> anyhow::Result<User> {
todo!()
}
}

impl User {
Expand Down
8 changes: 2 additions & 6 deletions src/handlers/assign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -664,18 +664,14 @@ async fn find_reviewer_from_names(
match get_review_candidates_by_username(&db_client, candidates.clone()).await {
Ok(mut reviewers) => {
if reviewers.is_empty() {
return Err(FindReviewerError::NoReviewer {
initial: candidates,
});
return Err(FindReviewerError::NoReviewer { initial: vec![] });
}
reviewers
.pop()
.expect("Something wrong happened when getting the reviewer")
}
Err(_) => {
return Err(FindReviewerError::NoReviewer {
initial: candidates,
});
return Err(FindReviewerError::NoReviewer { initial: vec![] });
}
}
};
Expand Down
7 changes: 5 additions & 2 deletions src/handlers/review_prefs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ ORDER BY avail_slots DESC
LIMIT 1",
usernames.join(",")
);
log::debug!("get reviewers by capacity");
let rec = db.query_one(&q, &[]).await.context("Select DB error")?;
Ok(rec.into())
}
Expand Down Expand Up @@ -220,6 +219,11 @@ pub(super) async fn handle_input<'a>(
.unwrap();
}

// If the action is to unassign/close a PR, nothing else to do
if event.action == IssuesAction::Closed || event.action == IssuesAction::Unassigned {
return Ok(());
}

// 2) assign the PR to the requested team members
let usernames = event
.issue
Expand All @@ -241,7 +245,6 @@ pub(super) async fn handle_input<'a>(

// If Github just assigned a PR to an inactive/unavailable user
// publish a comment notifying the error and rollback the PR assignment
// If the action is to unassign/close a PR, let the update happen anyway
if event.action == IssuesAction::Assigned
&& (!assignee_prefs.active || !assignee_prefs.is_available())
{
Expand Down
136 changes: 119 additions & 17 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use futures::StreamExt;
use hyper::{header, Body, Request, Response, Server, StatusCode};
use reqwest::Client;
use route_recognizer::Router;
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::{env, net::SocketAddr, sync::Arc};
use tokio::io::AsyncReadExt;
Expand Down Expand Up @@ -66,6 +66,88 @@ fn validate_data(prefs: &ReviewCapacityUser) -> anyhow::Result<()> {
Ok(())
}

#[derive(Debug, Deserialize)]
struct Token {
access_token: String,
}

async fn exchange_code(code: &str) -> anyhow::Result<Token> {
// let mut params = HashMap::new();
// params.insert("client_id", CLIENT_ID);
// params.insert("client_secret", CLIENT_SECRET);
// params.insert("code", code);

#[derive(Serialize, Debug)]
struct ReqToken<'a> {
client_id: &'a str,
client_secret: &'a str,
code: &'a str,
}

let client = reqwest::Client::new();

// let token = client
// .post("https://github.com/login/oauth/access_token")
// .form(&params)
// .send()
// .await
// .unwrap()
// .json::<Token>()
// .await
// .unwrap();

let client_id = std::env::var("CLIENT_ID").expect("CLIENT_ID is not set");
let client_secret = std::env::var("CLIENT_SECRET").expect("CLIENT_SECRET is not set");

let payload = ReqToken {
client_id: &client_id,
client_secret: &client_secret,
code,
};

let token = client
.post("https://github.com/login/oauth/access_token")
.header("Accept", "application/json")
.json(&payload)
.send()
.await
.unwrap()
.json::<Token>()
.await
.unwrap();

log::debug!("Token received: {:?}", token);
Ok(token)
}

// TODO: move this to the github module
async fn get_user(access_token: &str) -> anyhow::Result<github::User> {
let client = Client::new();
// let gh = github::GithubClient::new(client, access_token.to_string());
// let user = gh.get_profile().await?;
log::debug!("Getting user profile with token={}", access_token);

let mut headers = header::HeaderMap::new();
headers.insert("Accept", "application/vnd.github.v3+json".parse().unwrap());
headers.insert("Content-Type", "application/json".parse().unwrap());
headers.insert(
"User-Agent",
"anything-is-fine-just-send-this-header".parse().unwrap(),
);

let user = client
.get("https://api.github.com/user")
.bearer_auth(access_token)
.headers(headers)
.send()
.await
.unwrap()
.json::<github::User>()
.await
.unwrap();
Ok(user)
}

async fn serve_req(
req: Request<Body>,
ctx: Arc<Context>,
Expand Down Expand Up @@ -188,7 +270,7 @@ async fn serve_req(
}
if req.uri.path() == "/review-settings" {
// TODO:
// - Get auth token
// - Get auth token // HERE // !
// - query Github for the user handle
// - download the TOML file(s) and update the members team roster

Expand All @@ -197,7 +279,7 @@ async fn serve_req(
// TODO: get these files from github
get_string_from_file(&mut members, "static/compiler.toml").await;
get_string_from_file(&mut members, "static/compiler-contributors.toml").await;
get_string_from_file(&mut members, "static/wg-prioritization.toml").await;
members.sort();
log::debug!("Members loaded {:?}", members);

let mut body = serde_json::json!({});
Expand All @@ -224,21 +306,41 @@ async fn serve_req(
body = serde_json::json!(&review_capacity);
}

let admins = vec!["pnkfelix", "apiraino"];
if req.method == hyper::Method::GET {
// TODO: infer these from the authentication
let user = req
.headers
.get("Role")
.ok_or_else(|| header::HeaderValue::from_static(""))
.unwrap()
.to_str()
.unwrap_or("pnkfelix");
let is_admin = user == "pnkfelix";
log::debug!("user={}, is admin: {}", user, is_admin);

// query the DB, pull all users that are members in the TOML file
let review_capacity = get_prefs(&db_client, &mut members, user, is_admin).await;
body = serde_json::json!(&review_capacity);
if let Some(query) = req.uri.query() {
let code = url::form_urlencoded::parse(query.as_bytes()).find(|(k, _)| k == "code");
if let Some((_, code)) = code {
log::debug!("Successfully authorized! Got code {}", code);
// generate a token to impersonate the user
let token = exchange_code(&code).await.unwrap();
// get GH username
let user = get_user(&token.access_token).await.unwrap();
// TODO: figure out if the user is admin or normal user
// let user = req
// .headers
// .get("Role")
// .ok_or_else(|| header::HeaderValue::from_static(""))
// .unwrap()
// .to_str()
// .unwrap_or("pnkfelix");
let is_admin = admins.contains(&user.login.as_str());
log::debug!("user={}, is admin: {}", user.login, is_admin);
// TODO: now set a cookie
// query the DB, pull all users that are members in the TOML file
let review_capacity =
get_prefs(&db_client, &mut members, &user.login, is_admin).await;
body = serde_json::json!(&review_capacity);
}
// XXX: only for the demo
let user = url::form_urlencoded::parse(query.as_bytes()).find(|(k, _)| k == "user");
if let Some((_, username)) = user {
let is_admin = admins.contains(&username.as_ref());
let review_capacity =
get_prefs(&db_client, &mut members, &username, is_admin).await;
body = serde_json::json!(&review_capacity);
}
}
}

return Ok(Response::builder()
Expand Down
42 changes: 9 additions & 33 deletions static/js/utils.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,3 @@
const url = "http://127.0.0.1:8000/review-settings";

if (!window.fetch) {
postData = postDataLikeIts1992
}

function postDataLikeIts1992() {
const form = new URLSearchParams(new FormData(document.getElementById("review-capacity-form")));
var http = new XMLHttpRequest();
var params = '';
for (var i=0; i<form.length; i++) {
params = form[i] + '&=' + form[i];
}
http.open('POST', url, true);
http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
http.onreadystatechange = function() {
if (http.readyState == 4 && http.status == 201) {
console.debug(http.responseText);
update_view(JSON.parse(http.responseText), "View updated", "success");
}
}
http.send(params);
}


async function update_view(content, msg, result_class) {
var container = document.getElementById("message");
container.innerHTML = msg;
Expand Down Expand Up @@ -85,11 +60,11 @@ async function update_view(content, msg, result_class) {
other_prefs.insertAdjacentHTML('beforeend', other_rows);
}

const url = "http://127.0.0.1:8000/review-settings";

// TODO: remove this once auth is implemented
async function getData(user) {
const response = await fetch(url, {
method: "GET",
headers: { "Role": user}
});
const response = await fetch(`${url}?user=${user}`);
return response.json();
}

Expand All @@ -99,6 +74,7 @@ async function postData() {
const response = await fetch(url, {
method: "POST",
headers: {
// TODO: remove this once auth is implemented
"Authorization": "Bearer XXX",
"Content-Type": "application/x-www-form-urlencoded",
},
Expand Down Expand Up @@ -132,10 +108,10 @@ function enableDisableFields(is_checked) {
}

// load initial data
var isAdmin = false;
if (document.location.href.includes('admin') === true) {
isAdmin = true;
}
// var isAdmin = false;
// if (document.location.href.includes('admin') === true) {
// isAdmin = true;
// }

var u = document.location.search.split('=')[1];
const user = u !== undefined ? u : 'pnkfelix';
Expand Down

0 comments on commit d1e8532

Please sign in to comment.