diff --git a/Cargo.lock b/Cargo.lock index b822ae3..2303100 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -886,6 +886,7 @@ checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" name = "jean-marie" version = "0.1.0" dependencies = [ + "anyhow", "askama", "async-session", "axum", diff --git a/Cargo.toml b/Cargo.toml index a6d2ab9..337a4c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,3 +21,4 @@ headers = "0.3" http = "0.2" tower-http = { version = "0.3.4", features = ["full"] } sqlx = { version = "0.6.2", features = ["runtime-tokio-rustls", "postgres", "macros", "migrate", "chrono", "json"]} +anyhow = "1.0" \ No newline at end of file diff --git a/src/db.rs b/src/db.rs index e69de29..540d8a5 100644 --- a/src/db.rs +++ b/src/db.rs @@ -0,0 +1,18 @@ +use sqlx::{PgPool}; +use anyhow::*; +use sqlx::postgres::PgPoolOptions; + +#[derive(Clone)] +pub struct DBApplication { + pool: PgPool +} + +impl DBApplication { + pub async fn new(config: String) -> Result { + let pool = PgPoolOptions::new() + .max_connections(5) + .connect(&config) + .await?; + Ok(DBApplication { pool }) + } +} \ No newline at end of file diff --git a/src/discord_oauth.rs b/src/discord_oauth.rs index d8945d8..a596a0a 100644 --- a/src/discord_oauth.rs +++ b/src/discord_oauth.rs @@ -1,31 +1,15 @@ use async_session::{MemoryStore, Session, SessionStore as _}; use axum::{ - async_trait, - extract::{ - rejection::TypedHeaderRejectionReason, Extension, FromRequest, Query, RequestParts, - TypedHeader, - }, - headers::Cookie, - http::{ - self, - header::SET_COOKIE, - header::{HeaderMap, HeaderValue}, - StatusCode - }, - response::{Html, IntoResponse, Redirect, Response}, - routing::{get, get_service}, - Router, body::{BoxBody, boxed}, + extract::{Extension, Query}, + http::{header::HeaderMap, header::SET_COOKIE}, + response::{IntoResponse, Redirect}, }; -use http::{header}; use oauth2::{ basic::BasicClient, reqwest::async_http_client, AuthUrl, AuthorizationCode, ClientId, - PkceCodeChallenge, RedirectUrl, Scope, TokenUrl, - ClientSecret, TokenResponse, CsrfToken, + ClientSecret, CsrfToken, PkceCodeChallenge, RedirectUrl, Scope, TokenResponse, TokenUrl, }; -use serde::{Deserialize, Serialize}; -use std::{env, net::SocketAddr, collections::HashMap}; -use tower_http::services::ServeDir; -use uuid::Uuid; +use serde::Deserialize; +use std::{collections::HashMap, env}; use crate::User; use crate::COOKIE_NAME; @@ -51,62 +35,63 @@ pub async fn discord_auth() -> impl IntoResponse { pub async fn discord_authorized( Query(query): Query, Extension(store): Extension, - Extension(oauth_clients): Extension>, + Extension(oauth_clients): Extension>, ) -> impl IntoResponse { // Check for Discord client if oauth_clients.contains_key("Discord") { - // Get Discord client - let discord_oauth_client = oauth_clients.get(&"Discord").unwrap(); + // Get Discord client + let discord_oauth_client = oauth_clients.get(&"Discord").unwrap(); - // Get an auth token - let token = discord_oauth_client - .exchange_code(AuthorizationCode::new(query.code.clone())) - .request_async(async_http_client) - .await - .unwrap(); + // Get an auth token + let token = discord_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(); + // 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(); + // 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(); + // 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); + // 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()); + // Set cookie + let mut headers = HeaderMap::new(); + headers.insert(SET_COOKIE, cookie.parse().unwrap()); - (headers, Redirect::to(&"/dashboard")) + (headers, Redirect::to(&"/dashboard")) } else { let headers = HeaderMap::new(); - + (headers, Redirect::to(&"/")) } } pub fn discord_oauth_client() -> BasicClient { let redirect_url = env::var("REDIRECT_URL") - //.unwrap_or_else(|_| "http://localhost:40192/auth/discord".to_string()); - .unwrap_or_else(|_| "https://www.jean-marie.ca/auth/discord".to_string()); + .unwrap_or_else(|_| "http://localhost:40192/auth/discord".to_string()); + // .unwrap_or_else(|_| "https://www.jean-marie.ca/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_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() }); diff --git a/src/facebook_oauth.rs b/src/facebook_oauth.rs index 36f86dd..ba55e13 100644 --- a/src/facebook_oauth.rs +++ b/src/facebook_oauth.rs @@ -1,34 +1,23 @@ -use async_session::{MemoryStore, Session, SessionStore as _}; +use async_session::{MemoryStore}; use axum::{ - async_trait, extract::{ - rejection::TypedHeaderRejectionReason, Extension, FromRequest, Query, RequestParts, - TypedHeader, + Extension, Query }, - headers::Cookie, http::{ - self, - header::SET_COOKIE, - header::{HeaderMap, HeaderValue}, - StatusCode + header::{HeaderMap} }, - response::{Html, IntoResponse, Redirect, Response}, - routing::{get, get_service}, - Router, body::{BoxBody, boxed}, + response::{IntoResponse, Redirect}, }; -use http::{header}; use oauth2::{ basic::BasicClient, reqwest::async_http_client, AuthUrl, AuthorizationCode, ClientId, PkceCodeChallenge, RedirectUrl, Scope, TokenUrl, ClientSecret, TokenResponse, CsrfToken, }; -use serde::{Deserialize, Serialize}; -use std::{env, net::SocketAddr, collections::HashMap}; -use tower_http::services::ServeDir; -use uuid::Uuid; +use serde::{Deserialize}; +use std::{env, collections::HashMap}; use crate::User; -use crate::COOKIE_NAME; +// use crate::COOKIE_NAME; #[derive(Debug, Deserialize)] #[allow(dead_code)] @@ -91,11 +80,14 @@ pub async fn facebook_authorized( // Fetch user data from facebook let client = reqwest::Client::new(); - let user_data: = client + let user_data: User = client .get("https://graph.facebook.com/v15.0/dialog/oauth") .bearer_auth(token.access_token().secret()) .send() .await + .unwrap() + .json::() + .await .unwrap(); // // Create a new session filled with user data diff --git a/src/google_oauth.rs b/src/google_oauth.rs index 10695dd..98df5a9 100644 --- a/src/google_oauth.rs +++ b/src/google_oauth.rs @@ -1,24 +1,17 @@ use async_session::{MemoryStore, Session, SessionStore as _}; use axum::{ - extract::{ - Extension, Query, - }, - http::{ - self, - header::{SET_COOKIE, HeaderMap} - }, + extract::{Extension, Query}, + http::header::{HeaderMap, SET_COOKIE}, response::{IntoResponse, Redirect}, }; -use http::{header}; use oauth2::{ basic::BasicClient, reqwest::async_http_client, AuthUrl, AuthorizationCode, ClientId, - PkceCodeChallenge, RedirectUrl, Scope, TokenUrl, - ClientSecret, TokenResponse, CsrfToken, + ClientSecret, CsrfToken, PkceCodeChallenge, RedirectUrl, Scope, TokenUrl, }; -use serde::{Deserialize, Serialize}; -use std::{env, net::SocketAddr, collections::HashMap}; +use serde::Deserialize; +use std::{collections::HashMap, env}; -use crate::User; +// use crate::User; use crate::COOKIE_NAME; #[derive(Debug, Deserialize)] @@ -47,74 +40,52 @@ pub async fn google_auth() -> impl IntoResponse { Redirect::to(&auth_url.to_string()) } -pub fn google_oauth_client() -> BasicClient { - let redirect_url = env::var("REDIRECT_URL") - //.unwrap_or_else(|_| "http://localhost:40192/auth/callback".to_string()); - .unwrap_or_else(|_| "https://www.jean-marie.ca/auth/google".to_string()); - - 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()) -} - pub async fn google_authorized( Query(query): Query, Extension(store): Extension, - Extension(oauth_clients): Extension>, + Extension(oauth_clients): Extension>, ) -> impl IntoResponse { // Check for Google client if oauth_clients.contains_key("Google") { // Get Google client - let oauth_client = oauth_clients.get(&"Google").unwrap(); -/* - // Get an auth token - let token = google_oauth_client - .exchange_code(AuthorizationCode::new(query.code.clone())) - .request_async(async_http_client) - .await - .unwrap(); + let google_oauth_client = oauth_clients.get(&"Google").unwrap(); - // Fetch user data from google - 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(); -*/ + // 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 google + 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 session = Session::new(); - //session.insert("user", &user_data).unwrap(); + // Create a new session filled with user data + let session = Session::new(); + //session.insert("user", &user_data).unwrap(); - // Store session and get corresponding cookie - let cookie = store.store_session(session).await.unwrap().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); + // 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()); + // Set cookie + let mut headers = HeaderMap::new(); + headers.insert(SET_COOKIE, cookie.parse().unwrap()); - //(headers, Redirect::to("/dashboard".parse().unwrap())) + //(headers, Redirect::to("/dashboard".parse().unwrap())) } let mut page = String::new(); @@ -127,3 +98,25 @@ pub async fn google_authorized( page } + +pub fn google_oauth_client() -> BasicClient { + let redirect_url = env::var("REDIRECT_URL") + .unwrap_or_else(|_| "http://localhost:40192/auth/google".to_string()); + // .unwrap_or_else(|_| "https://www.jean-marie.ca/auth/google".to_string()); + + 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()) +} diff --git a/src/main.rs b/src/main.rs index 29e0544..394c6c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,14 @@ use std::{net::SocketAddr, collections::HashMap}; use tower_http::services::ServeDir; use uuid::Uuid; +use sqlx::{PgPool}; +use anyhow::*; +use sqlx::postgres::PgPoolOptions; + +mod db; + +use db::*; + mod google_oauth; mod facebook_oauth; mod discord_oauth; @@ -73,6 +81,11 @@ async fn main() { // initialize tracing tracing_subscriber::fmt::init(); + // Initialize database + let db = DBApplication::new("postgres://postgres:postgres@localhost/sqlx-demo".into()).await?; + println!("Connection acquired!"); + + // `MemoryStore` just used as an example. Don't use this in production. let store = MemoryStore::new(); diff --git a/src/oauth.rs b/src/oauth.rs new file mode 100644 index 0000000..0bd5470 --- /dev/null +++ b/src/oauth.rs @@ -0,0 +1,5 @@ +pub struct OauthSession { + client: BasicClient, + pkce_code_verifier: PkceCodeVerifier, + csrf_state: CsrfToken +}