diff --git a/run.sh b/run.sh index cd3282c..94ccb52 100755 --- a/run.sh +++ b/run.sh @@ -1 +1 @@ -GOOGLE_CLIENT_ID=735264084619-clsmvgdqdmum4rvrcj0kuk28k9agir1c.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=L6uI7FQGoMJd-ay1HO_iGJ6M cargo run \ No newline at end of file +GOOGLE_CLIENT_ID=735264084619-clsmvgdqdmum4rvrcj0kuk28k9agir1c.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=L6uI7FQGoMJd-ay1HO_iGJ6M DISCORD_CLIENT_ID=956189108559036427 DISCORD_CLIENT_SECRET=dx2DZxjDhVMCCnGX4xpz5MxSTgZ4lHBI cargo run \ No newline at end of file diff --git a/runprod.sh b/runprod.sh new file mode 100755 index 0000000..fbb79b8 --- /dev/null +++ b/runprod.sh @@ -0,0 +1 @@ +GOOGLE_CLIENT_ID=735264084619-clsmvgdqdmum4rvrcj0kuk28k9agir1c.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=L6uI7FQGoMJd-ay1HO_iGJ6M REDIRECT_URL=https://www.jean-marie.ca/auth cargo run \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index c35c807..68da044 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,8 @@ use axum::{ use http::header; use oauth2::{ basic::BasicClient, reqwest::async_http_client, AuthUrl, AuthorizationCode, ClientId, - ClientSecret, RedirectUrl, TokenResponse, TokenUrl, + PkceCodeChallenge, RedirectUrl, RevocationUrl, Scope, TokenUrl, + ClientSecret, TokenResponse, CsrfToken, }; use serde::{Deserialize, Serialize}; use std::{env, net::SocketAddr}; @@ -50,7 +51,8 @@ async fn main() { // `MemoryStore` just used as an example. Don't use this in production. let store = MemoryStore::new(); - let oauth_client = oauth_client(); + let google_oauth_client = google_oauth_client(); + let discord_oauth_client = discord_oauth_client(); // build our application with a route let app = Router::new() @@ -67,15 +69,19 @@ async fn main() { ), ) .route("/", get(index)) + .route("/google_auth", get(google_auth)) + .route("/discord_auth", get(discord_auth)) .route("/login", get(login)) - .route("/login_authorized", get(login_authorized)) .route("/dashboard", get(dashboard)) + .route("/auth/callback", get(google_authorized)) + .route("/auth/discord", get(discord_authorized)) .layer(Extension(store)) - .layer(Extension(oauth_client)); + .layer(Extension(discord_oauth_client)) + .layer(Extension(google_oauth_client)); // run our app with hyper // `axum::Server` is a re-export of `hyper::Server` - let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); + let addr = SocketAddr::from(([0, 0, 0, 0], 40192)); tracing::debug!("listening on {}", addr); axum::Server::bind(&addr) .serve(app.into_make_service()) @@ -87,7 +93,7 @@ async fn main() { async fn index(user: Option) -> impl IntoResponse { let name = match user { Some(u) => u.username, - None => "You're not logged in.\nVisit `/auth/discord` to do so.".to_string(), + None => "You're not logged in.".to_string(), }; let template = IndexTemplate { name }; HtmlTemplate(template) @@ -99,31 +105,44 @@ async fn login() -> impl IntoResponse { HtmlTemplate(template) } -fn oauth_client() -> BasicClient { - // Environment variables (* = required): - // *"CLIENT_ID" "REPLACE_ME"; - // *"CLIENT_SECRET" "REPLACE_ME"; - // "REDIRECT_URL" "http://127.0.0.1:3000/auth/authorized"; - // "AUTH_URL" "https://discord.com/api/oauth2/authorize?response_type=code"; - // "TOKEN_URL" "https://discord.com/api/oauth2/token"; - - let client_id = env::var("GOOGLE_CLIENT_ID").expect("Missing CLIENT_ID!"); - let client_secret = env::var("GOOGLE_CLIENT_SECRET").expect("Missing CLIENT_SECRET!"); +fn google_oauth_client() -> BasicClient { let redirect_url = env::var("REDIRECT_URL") - .unwrap_or_else(|_| "http://127.0.0.1:3000/auth/authorized".to_string()); + .unwrap_or_else(|_| "http://localhost:40192/auth/callback".to_string()); - let auth_url = env::var("AUTH_URL").unwrap_or_else(|_| { + let google_client_id = env::var("GOOGLE_CLIENT_ID").expect("Missing GOOGLE_CLIENT_ID!"); + let google_client_secret = env::var("GOOGLE_CLIENT_SECRET").expect("Missing GOOGLE_CLIENT_SECRET!"); + let google_auth_url = env::var("GOOGLE_AUTH_URL").unwrap_or_else(|_| { + "https://accounts.google.com/o/oauth2/v2/auth".to_string() + }); + let google_token_url = env::var("GOOGLE_TOKEN_URL") + .unwrap_or_else(|_| "https://www.googleapis.com/oauth2/v3/token".to_string()); + + BasicClient::new( + ClientId::new(google_client_id), + Some(ClientSecret::new(google_client_secret)), + AuthUrl::new(google_auth_url).unwrap(), + Some(TokenUrl::new(google_token_url).unwrap()), + ) + .set_redirect_uri(RedirectUrl::new(redirect_url).unwrap()) +} + +fn discord_oauth_client() -> BasicClient { + let redirect_url = env::var("REDIRECT_URL") + .unwrap_or_else(|_| "http://localhost:40192/auth/discord".to_string()); + + let discord_client_id = env::var("DISCORD_CLIENT_ID").expect("Missing DISCORD_CLIENT_ID!"); + let discord_client_secret = env::var("DISCORD_CLIENT_SECRET").expect("Missing DISCORD_CLIENT_SECRET!"); + let discord_auth_url = env::var("DISCORD_AUTH_URL").unwrap_or_else(|_| { "https://discord.com/api/oauth2/authorize?response_type=code".to_string() }); - - let token_url = env::var("TOKEN_URL") + let discord_token_url = env::var("DISCORD_TOKEN_URL") .unwrap_or_else(|_| "https://discord.com/api/oauth2/token".to_string()); BasicClient::new( - ClientId::new(client_id), - Some(ClientSecret::new(client_secret)), - AuthUrl::new(auth_url).unwrap(), - Some(TokenUrl::new(token_url).unwrap()), + ClientId::new(discord_client_id), + Some(ClientSecret::new(discord_client_secret)), + AuthUrl::new(discord_auth_url).unwrap(), + Some(TokenUrl::new(discord_token_url).unwrap()), ) .set_redirect_uri(RedirectUrl::new(redirect_url).unwrap()) } @@ -267,6 +286,35 @@ async fn logout( Redirect::to("/".parse().unwrap()) } +async fn google_auth(Extension(google_oauth_client): Extension) -> impl IntoResponse { + let (pkce_code_challenge, pkce_code_verifier) = PkceCodeChallenge::new_random_sha256(); + + // Generate the authorization URL to which we'll redirect the user. + let (auth_url, csrf_state) = google_oauth_client + .authorize_url(CsrfToken::new_random) + .add_scope(Scope::new( + "https://www.googleapis.com/auth/userinfo.profile".to_string(), + )) + .add_scope(Scope::new( + "https://www.googleapis.com/auth/userinfo.email".to_string(), + )) + .set_pkce_challenge(pkce_code_challenge) + .url(); + + // Redirect to Google's oauth service + Redirect::to(auth_url.to_string().parse().unwrap()) +} + +async fn discord_auth(Extension(discord_oauth_client): Extension) -> impl IntoResponse { + let (auth_url, _csrf_token) = discord_oauth_client + .authorize_url(CsrfToken::new_random) + .add_scope(Scope::new("identify".to_string())) + .url(); + + // Redirect to Discord's oauth service + Redirect::to(auth_url.to_string().parse().unwrap()) +} + #[derive(Debug, Deserialize)] #[allow(dead_code)] struct AuthRequest { @@ -274,13 +322,69 @@ struct AuthRequest { state: String, } -async fn login_authorized( +async fn google_authorized( Query(query): Query, Extension(store): Extension, - Extension(oauth_client): Extension, + Extension(google_oauth_client): Extension, +) -> impl IntoResponse { + + print!("{}", query.code); +/* + // Get an auth token + let token = google_oauth_client + .exchange_code(AuthorizationCode::new(query.code.clone())) + .request_async(async_http_client) + .await + .unwrap(); + + // Fetch user data from discord + let client = reqwest::Client::new(); + let user_data: User = client + // https://discord.com/developers/docs/resources/user#get-current-user + .get("https://discordapp.com/api/users/@me") + .bearer_auth(token.access_token().secret()) + .send() + .await + .unwrap() + .json::() + .await + .unwrap(); +*/ + + // Create a new session filled with user data + let mut session = Session::new(); + //session.insert("user", &user_data).unwrap(); + + // Store session and get corresponding cookie + let cookie = store.store_session(session).await.unwrap().unwrap(); + + // Build the cookie + let cookie = format!("{}={}; SameSite=Lax; Path=/", COOKIE_NAME, cookie); + + // Set cookie + let mut headers = HeaderMap::new(); + headers.insert(SET_COOKIE, cookie.parse().unwrap()); + + //(headers, Redirect::to("/dashboard".parse().unwrap())) + + let mut page = String::new(); + page.push_str(&"Display the data returned by Google\n".to_string()); + page.push_str(&"\nState: ".to_string()); + page.push_str(&query.state); + page.push_str(&"\nCode: ".to_string()); + page.push_str(&query.code); + page.push_str(&"\nScope: ".to_string()); + + page +} + +async fn discord_authorized( + Query(query): Query, + Extension(store): Extension, + Extension(discord_oauth_client): Extension, ) -> impl IntoResponse { // Get an auth token - let token = oauth_client + let token = discord_oauth_client .exchange_code(AuthorizationCode::new(query.code.clone())) .request_async(async_http_client) .await @@ -320,7 +424,7 @@ struct AuthRedirect; impl IntoResponse for AuthRedirect { fn into_response(self) -> Response { - Redirect::temporary("/auth/discord".parse().unwrap()).into_response() + Redirect::temporary("/login".parse().unwrap()).into_response() } } diff --git a/templates/login.html b/templates/login.html index cf8c580..8b7afbf 100644 --- a/templates/login.html +++ b/templates/login.html @@ -34,7 +34,8 @@
- Sign in with google + Sign in with google + Sign in with discord