-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add new routers for handle oauth requests
- Loading branch information
1 parent
c352e3b
commit abba553
Showing
9 changed files
with
270 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -70,3 +70,4 @@ config = "0.14.0" | |
shadow-rs = "0.30.0" | ||
reqwest = "0.12.5" | ||
lazy_static = "1.5.0" | ||
uuid = "1.10.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
use axum::async_trait; | ||
|
||
use common::errors::MegaError; | ||
use jupiter::context::Context; | ||
|
||
use crate::api::oauth::model::{AuthorizeParams, GitHubAccessTokenJson, OauthCallbackParams}; | ||
use crate::api::oauth::OauthHandler; | ||
|
||
use super::model::GitHubUserJson; | ||
|
||
#[derive(Clone)] | ||
pub struct GithubOauthService { | ||
pub context: Context, | ||
pub client_id: String, | ||
pub client_secret: String, | ||
} | ||
|
||
const GITHUB_ENDPOINT: &str = "https://github.com"; | ||
const GITHUB_API_ENDPOINT: &str = "https://api.github.com"; | ||
|
||
#[async_trait] | ||
impl OauthHandler for GithubOauthService { | ||
fn authorize_url(&self, params: &AuthorizeParams, state: &str) -> String { | ||
let auth_url = format!( | ||
"https://github.com/login/oauth/authorize?client_id={}&redirect_uri={}&state={}", | ||
self.client_id, params.redirect_uri, state | ||
); | ||
auth_url | ||
} | ||
|
||
async fn access_token( | ||
&self, | ||
params: OauthCallbackParams, | ||
redirect_uri: &str, | ||
) -> Result<String, MegaError> { | ||
tracing::debug!("{:?}", params); | ||
// get access_token and user for persist | ||
let url = format!( | ||
"{}/login/oauth/access_token?client_id={}&client_secret={}&code={}&redirect_uri={}", | ||
GITHUB_ENDPOINT, self.client_id, self.client_secret, params.code, redirect_uri | ||
); | ||
let client = reqwest::Client::new(); | ||
let resp = client | ||
.post(url) | ||
.header("Accept", "application/json") | ||
.send() | ||
.await | ||
.unwrap(); | ||
let access_token = resp | ||
.json::<GitHubAccessTokenJson>() | ||
.await | ||
.unwrap() | ||
.access_token; | ||
Ok(access_token) | ||
} | ||
|
||
async fn user_info(&self, access_token: &str) -> Result<GitHubUserJson, MegaError> { | ||
let user_url = format!("{}/user", GITHUB_API_ENDPOINT); | ||
let client = reqwest::Client::new(); | ||
let resp = client | ||
.get(user_url) | ||
.header("Authorization", format!("Bearer {}", access_token)) | ||
.header("Accept", "application/json") | ||
.header("User-Agent", format!("Mega/{}", "0.0.1")) | ||
.send() | ||
.await | ||
.unwrap(); | ||
// tracing::debug!("user_resp: {:?}", resp.text().await.unwrap()); | ||
let user_info = resp.json::<GitHubUserJson>().await.unwrap(); | ||
Ok(user_info) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
use std::collections::HashMap; | ||
use std::sync::Arc; | ||
|
||
use axum::async_trait; | ||
use axum::response::Redirect; | ||
use axum::{ | ||
extract::{Path, Query, State}, | ||
http::StatusCode, | ||
routing::get, | ||
Json, Router, | ||
}; | ||
use tokio::sync::Mutex; | ||
use uuid::Uuid; | ||
|
||
use common::enums::SupportOauthType; | ||
use common::errors::MegaError; | ||
use github::GithubOauthService; | ||
use jupiter::context::Context; | ||
use model::{AuthorizeParams, GitHubUserJson, OauthCallbackParams}; | ||
|
||
pub mod github; | ||
pub mod model; | ||
|
||
#[derive(Clone)] | ||
pub struct OauthServiceState { | ||
pub context: Context, | ||
pub sessions: Arc<Mutex<HashMap<String, String>>>, | ||
} | ||
|
||
impl OauthServiceState { | ||
pub fn oauth_handler(&self, ouath_type: SupportOauthType) -> impl OauthHandler { | ||
match ouath_type { | ||
SupportOauthType::GitHub => GithubOauthService { | ||
context: self.context.clone(), | ||
client_id: String::from("Ov23li3y0koaZFzk8CUE"), | ||
client_secret: String::from("58babfc9794ca137feff59c57c82ef6f5318ec37"), | ||
}, | ||
} | ||
} | ||
} | ||
|
||
#[async_trait] | ||
pub trait OauthHandler: Send + Sync { | ||
fn authorize_url(&self, params: &AuthorizeParams, state: &str) -> String; | ||
|
||
async fn access_token( | ||
&self, | ||
params: OauthCallbackParams, | ||
redirect_uri: &str, | ||
) -> Result<String, MegaError>; | ||
|
||
async fn user_info(&self, access_token: &str) -> Result<GitHubUserJson, MegaError>; | ||
} | ||
|
||
pub fn routers() -> Router<OauthServiceState> { | ||
Router::new() | ||
.route("/:oauth_type/authorize", get(redirect_authorize)) | ||
.route("/:oauth_type/callback", get(oauth_callback)) | ||
.route("/:oauth_type/user", get(user)) | ||
} | ||
|
||
async fn redirect_authorize( | ||
Path(oauth_type): Path<String>, | ||
Query(query): Query<AuthorizeParams>, | ||
service_state: State<OauthServiceState>, | ||
) -> Result<Redirect, (StatusCode, String)> { | ||
let oauth_type: SupportOauthType = match oauth_type.parse::<SupportOauthType>() { | ||
Ok(value) => value, | ||
Err(err) => return Err((StatusCode::BAD_REQUEST, err)), | ||
}; | ||
|
||
let mut sessions = service_state.sessions.lock().await; | ||
let state = Uuid::new_v4().to_string(); | ||
sessions.insert(state.clone(), query.redirect_uri.clone()); | ||
let auth_url = service_state | ||
.oauth_handler(oauth_type) | ||
.authorize_url(&query, &state); | ||
Ok(Redirect::temporary(&auth_url)) | ||
} | ||
|
||
async fn oauth_callback( | ||
Path(oauth_type): Path<String>, | ||
Query(query): Query<OauthCallbackParams>, | ||
service_state: State<OauthServiceState>, | ||
) -> Result<Redirect, (StatusCode, String)> { | ||
let oauth_type: SupportOauthType = match oauth_type.parse::<SupportOauthType>() { | ||
Ok(value) => value, | ||
Err(err) => return Err((StatusCode::BAD_REQUEST, err)), | ||
}; | ||
// chcek state, | ||
// TODO storage can be replaced by redis, otherwise invalid state can't be expired | ||
let mut sessions = service_state.sessions.lock().await; | ||
|
||
let redirect_uri = match sessions.get(&query.state) { | ||
Some(uri) => uri.clone(), | ||
None => return Err((StatusCode::BAD_REQUEST, "Invalid state".to_string())), | ||
}; | ||
let access_token = service_state | ||
.oauth_handler(oauth_type) | ||
.access_token(query.clone(), &redirect_uri) | ||
.await | ||
.unwrap(); | ||
sessions.remove(&query.state); | ||
|
||
let callback_url = format!("{}/login?access_token={}", redirect_uri, access_token); | ||
Ok(Redirect::temporary(&callback_url)) | ||
} | ||
|
||
async fn user( | ||
Path(oauth_type): Path<String>, | ||
Query(query): Query<HashMap<String, String>>, | ||
service_state: State<OauthServiceState>, | ||
) -> Result<Json<GitHubUserJson>, (StatusCode, String)> { | ||
let oauth_type: SupportOauthType = match oauth_type.parse::<SupportOauthType>() { | ||
Ok(value) => value, | ||
Err(err) => return Err((StatusCode::BAD_REQUEST, err)), | ||
}; | ||
let access_token = query.get("access_token").unwrap(); | ||
|
||
let res = service_state | ||
.oauth_handler(oauth_type) | ||
.user_info(access_token) | ||
.await | ||
.unwrap(); | ||
Ok(Json(res)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
use serde::{Deserialize, Serialize}; | ||
|
||
#[derive(Serialize, Deserialize, Debug)] | ||
pub struct AuthorizeParams { | ||
pub redirect_uri: String, | ||
} | ||
|
||
#[derive(Serialize, Deserialize, Clone, Debug)] | ||
pub struct OauthCallbackParams { | ||
pub code: String, | ||
pub state: String, | ||
} | ||
|
||
|
||
#[derive(Serialize, Deserialize, Clone, Debug)] | ||
pub struct GitHubAccessTokenJson { | ||
pub access_token: String, | ||
pub scope: Option<String>, | ||
pub token_type: String, | ||
} | ||
|
||
#[derive(Serialize, Deserialize, Clone, Debug)] | ||
pub struct GitHubUserJson { | ||
pub login: String, | ||
pub id: u32, | ||
pub avatar_url: String, | ||
pub email: String, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters