update: oauth state more understandable

This commit is contained in:
maix0 2024-09-28 12:19:02 +02:00
parent e151a799ef
commit 180503a244
3 changed files with 47 additions and 48 deletions

12
Cargo.lock generated
View file

@ -77,9 +77,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]] [[package]]
name = "axum" name = "axum"
version = "0.7.6" version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f43644eed690f5374f1af436ecd6aea01cd201f6fbdf0178adaf6907afb2cec" checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum-core", "axum-core",
@ -113,9 +113,9 @@ dependencies = [
[[package]] [[package]]
name = "axum-core" name = "axum-core"
version = "0.4.4" version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6b8ba012a258d63c9adfa28b9ddcf66149da6f986c5b5452e629d5ee64bf00" checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bytes", "bytes",
@ -1330,9 +1330,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.78" version = "2.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81b9b4733a9c8b8aaa20634df36eeb68cc0c0669f2e18fb287006b496a14195d" checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View file

@ -6,7 +6,7 @@
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
sync::{Arc, RwLock}, sync::Arc,
time::Duration, time::Duration,
}; };
@ -14,21 +14,21 @@ use axum::{
async_trait, async_trait,
extract::{FromRef, FromRequestParts, Query, State}, extract::{FromRef, FromRequestParts, Query, State},
http::{request::Parts, StatusCode}, http::{request::Parts, StatusCode},
response::{AppendHeaders, Html, IntoResponse, Redirect}, response::{Html, IntoResponse, Redirect},
routing::get, routing::get,
Json, Router, Router,
}; };
use axum_extra::extract::{ use axum_extra::extract::{
cookie::{Cookie, Expiration, Key, SameSite}, cookie::{Cookie, Key, SameSite},
CookieJar, PrivateCookieJar, CookieJar, PrivateCookieJar,
}; };
use base64::Engine; use base64::Engine;
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use reqwest::tls::Version; use reqwest::tls::Version;
use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{json, Value}; use serde_json::json;
use tokio::{io::AsyncReadExt, sync::Mutex}; use tokio::sync::Mutex;
use tracing::{error, info}; use tracing::{error, info, warn};
macro_rules! unwrap_env { macro_rules! unwrap_env {
($name:literal) => { ($name:literal) => {
@ -78,6 +78,7 @@ async fn tutors(config: AppState) {
"page[number]": page_nb, "page[number]": page_nb,
"page[size]": 100, "page[size]": 100,
}), }),
Option::<&oauth2::Token>::None,
) )
.await .await
.unwrap(); .unwrap();
@ -119,7 +120,7 @@ async fn main() {
http.clone(), http.clone(),
unwrap_env!("CLIENT_ID"), unwrap_env!("CLIENT_ID"),
unwrap_env!("CLIENT_SECRET"), unwrap_env!("CLIENT_SECRET"),
"http://local.maix.me/auth/callback", "http://local.maix.me:9911/auth/callback",
) )
.await .await
.unwrap(); .unwrap();
@ -147,7 +148,7 @@ async fn main() {
// run our app with hyper // run our app with hyper
let listener = tokio::net::TcpListener::bind(format!( let listener = tokio::net::TcpListener::bind(format!(
"127.0.0.1:{}", "0.0.0.0:{}",
std::env::args() std::env::args()
.nth(1) .nth(1)
.and_then(|s| s.parse::<u16>().ok()) .and_then(|s| s.parse::<u16>().ok())
@ -175,9 +176,6 @@ async fn oauth2_login(State(state): State<AppState>) -> Result<Redirect, StatusC
)) ))
} }
use time::Duration as TDuration;
use time::OffsetDateTime;
#[axum::debug_handler] #[axum::debug_handler]
async fn oauth2_callback( async fn oauth2_callback(
State(state): State<AppState>, State(state): State<AppState>,
@ -197,18 +195,15 @@ async fn oauth2_callback(
.await .await
.wrap_err("callback")?; .wrap_err("callback")?;
let rep = state let res: User42 = state
.http .oauth
.get("https://api.intra.42.fr/v2/users/me") .do_request("https://api.intra.42.fr/v2/me", &(), Some(&token))
.bearer_auth(&token.access_token)
.send()
.await .await
.wrap_err("Unable to get user self")?; .wrap_err("Unable to get user self")?;
let json: User42 = rep.json().await.wrap_err("unable to parse api reply")?; let mut cookie = Cookie::new("token", res.id.to_string());
let mut cookie = Cookie::new("token", json.id.to_string());
cookie.set_same_site(SameSite::None); cookie.set_same_site(SameSite::None);
cookie.set_secure(true); cookie.set_secure(false);
cookie.set_path("/"); cookie.set_path("/");
// cookie.set_domain("localhost:3000"); // cookie.set_domain("localhost:3000");
// cookie.set_http_only(Some(false)); // cookie.set_http_only(Some(false));
@ -225,23 +220,20 @@ async fn oauth2_callback(
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct UserLoggedIn { struct UserLoggedIn;
id: u64,
}
#[async_trait] #[async_trait]
impl FromRequestParts<AppState> for UserLoggedIn { impl FromRequestParts<AppState> for UserLoggedIn {
type Rejection = (StatusCode, CookieJar, Redirect); type Rejection = (StatusCode, PrivateCookieJar, Redirect);
async fn from_request_parts( async fn from_request_parts(
parts: &mut Parts, parts: &mut Parts,
state: &AppState, state: &AppState,
) -> Result<Self, Self::Rejection> { ) -> Result<Self, Self::Rejection> {
info!("banane"); let jar = PrivateCookieJar::from_request_parts(parts, state)
let jar = CookieJar::from_request_parts(parts, state).await.unwrap(); .await
dbg!(&jar); .unwrap();
let Some(id) = jar.get("token") else { let Some(id) = jar.get("token") else {
info!("no cookie");
return Err(( return Err((
StatusCode::TEMPORARY_REDIRECT, StatusCode::TEMPORARY_REDIRECT,
jar, jar,
@ -251,7 +243,6 @@ impl FromRequestParts<AppState> for UserLoggedIn {
let Ok(user_id) = id.value().parse::<u64>() else { let Ok(user_id) = id.value().parse::<u64>() else {
let jar = jar.remove("token"); let jar = jar.remove("token");
info!("not id");
return Err(( return Err((
StatusCode::TEMPORARY_REDIRECT, StatusCode::TEMPORARY_REDIRECT,
jar, jar,
@ -260,10 +251,8 @@ impl FromRequestParts<AppState> for UserLoggedIn {
}; };
if state.tutors.lock().await.contains(&user_id) { if state.tutors.lock().await.contains(&user_id) {
info!("is tut"); Ok(UserLoggedIn)
Ok(UserLoggedIn { id: user_id })
} else { } else {
info!("not tut");
let jar = jar.remove("token"); let jar = jar.remove("token");
Err(( Err((
StatusCode::TEMPORARY_REDIRECT, StatusCode::TEMPORARY_REDIRECT,
@ -283,9 +272,8 @@ async fn root(_user: UserLoggedIn) -> Html<&'static str> {
<a href="/stop">stop</a><br> <a href="/stop">stop</a><br>
<a href="/start">start</a><br> <a href="/start">start</a><br>
<a href="/status">status</a><br> <a href="/status">status</a><br>
<a href="/db">db</a><br>
<a href="/pull">git pull (ask before!)</a><br> <a href="/pull">git pull (ask before!)</a><br>
"#, "#,
) )
} }

View file

@ -17,13 +17,23 @@ pub struct OauthClient {
pub struct Token { pub struct Token {
#[serde(default)] #[serde(default)]
refresh_token: Option<String>, refresh_token: Option<String>,
pub access_token: String, access_token: String,
token_type: String, token_type: String,
expires_in: u64, expires_in: u64,
scope: String, scope: String,
created_at: u64, created_at: u64,
} }
pub trait IntoToken {
fn get_token(&self) -> &str;
}
impl IntoToken for Token {
fn get_token(&self) -> &str {
&self.access_token
}
}
impl OauthClient { impl OauthClient {
async fn get_app_token( async fn get_app_token(
client: reqwest::Client, client: reqwest::Client,
@ -43,10 +53,7 @@ impl OauthClient {
.send() .send()
.await .await
.wrap_err("Sending request to fetch 42 API token")?; .wrap_err("Sending request to fetch 42 API token")?;
let body = response.bytes().await?; let json: Token = response.json().await.wrap_err("API response to json")?;
let text = String::from_utf8_lossy(&body);
println!("{}", text);
let json: Token = serde_json::from_slice(&body).unwrap(); // response.json().await.wrap_err("API response to json")?;
Ok(json) Ok(json)
} }
pub async fn new( pub async fn new(
@ -77,7 +84,7 @@ impl OauthClient {
.scheme("https") .scheme("https")
.authority("api.intra.42.fr") .authority("api.intra.42.fr")
.path_and_query(format!( .path_and_query(format!(
"/oauth/authorize?client_id={}&scope=public&response_type=code&redirect_uri={redirect_uri}&code={}", "/oauth/authorize?client_id={}&scope=public&response_type=code&redirect_uri={redirect_uri}&state={}",
self.client_id, base64::engine::general_purpose::URL_SAFE.encode(csrf) self.client_id, base64::engine::general_purpose::URL_SAFE.encode(csrf)
)) ))
.build() .build()
@ -115,13 +122,17 @@ impl OauthClient {
&self, &self,
url: impl AsRef<str>, url: impl AsRef<str>,
qs: &impl Serialize, qs: &impl Serialize,
token: Option<&impl IntoToken>,
) -> eyre::Result<R> { ) -> eyre::Result<R> {
let url = url.as_ref(); let url = url.as_ref();
let token = token
.map(IntoToken::get_token)
.unwrap_or_else(|| self.token.get_token());
let req = self let req = self
.http .http
.get(url) .get(url)
.query(qs) .query(qs)
.bearer_auth(&self.token.access_token) .bearer_auth(token)
.send() .send()
.await .await
.wrap_err("Failed to send request")?; .wrap_err("Failed to send request")?;