Skip to content

Commit

Permalink
start implementing authorisation
Browse files Browse the repository at this point in the history
  • Loading branch information
Kappamalone committed Nov 1, 2023
1 parent 2c2ce07 commit ba965e4
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 26 deletions.
4 changes: 3 additions & 1 deletion backend/server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ sqlx = { version = "0.7.1", features = ["runtime-tokio-rustls", "postgres", "tim
# Important secondary crates
anyhow = "1.0.75"
serde = { version = "1.0.188", features = ["derive"] }
reqwest = "0.11.20"
reqwest = { version = "0.11.20", features = ["json"] }
serde_json = "1.0.105"
chrono = { version = "0.4.26", features = ["serde"] }
oauth2 = "4.4.1"
log = "0.4.20"
uuid = { version = "1", features = ["serde", "v4"] }
jsonwebtoken = "8"
35 changes: 21 additions & 14 deletions backend/server/src/handler/auth.rs
Original file line number Diff line number Diff line change
@@ -1,44 +1,51 @@
use axum::Extension;
use crate::models::app::AppState;
use crate::models::auth::{AuthRequest, UserProfile};
use crate::service::auth::create_or_get_user_id;
use axum::extract::{Query, State};
use axum::http::StatusCode;
use axum::response::IntoResponse;
use axum::Extension;
use log::error;
use oauth2::{AuthorizationCode, TokenResponse};
use oauth2::basic::BasicClient;
use oauth2::reqwest::async_http_client;
use crate::models::app::AppState;
use crate::models::auth::{AuthRequest, UserProfile};
use crate::service::auth::create_or_get_user_id;
use oauth2::{AuthorizationCode, TokenResponse};

/// This function handles the passing in of the Google OAuth code. After allowing our app the
/// requested permissions, the user is redirected to this url on our server, where we use the
/// code to get the user's email address from Google's OpenID Connect API.
pub async fn google_callback(
State(state): State<AppState>,
Query(query): Query<AuthRequest>,
Extension(oauth_client): Extension<BasicClient>
Extension(oauth_client): Extension<BasicClient>,
) -> Result<impl IntoResponse, impl IntoResponse> {
let token = match oauth_client
.exchange_code(AuthorizationCode::new(query.code))
.request_async(async_http_client)
.await {
.await
{
Ok(res) => res,
Err(e) => {
error!("An error occured while exchanging Google OAuth code");
return Err((StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))
return Err((StatusCode::INTERNAL_SERVER_ERROR, e.to_string()));
}
};

let profile = match state.ctx.get("https://openidconnect.googleapis.com/v1/userinfo")
let profile = match state
.ctx
.get("https://openidconnect.googleapis.com/v1/userinfo")
.bearer_auth(token.access_token().secret().to_owned())
.send().await {
.send()
.await
{
Ok(res) => res,
Err(e) => return Err((StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))
Err(e) => return Err((StatusCode::INTERNAL_SERVER_ERROR, e.to_string())),
};

let profile = profile.json::<UserProfile>().await.unwrap();
// let profile = profile.json::<UserProfile>().await?;

let user_id = create_or_get_user_id(profile.email, state.db).await?;
// let user_id = create_or_get_user_id(profile.email, state.db).await?;

// TODO: Create a JWT from this user_id and return to the user.
}
Ok("woohoo")
}

29 changes: 25 additions & 4 deletions backend/server/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,33 @@
use axum::{routing::get, Router};

mod service;
mod models;
use jsonwebtoken::{DecodingKey, EncodingKey};
use models::app::AppState;
mod handler;
mod models;
mod service;

#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(|| async { "Hello, World!" }));
// Initialise JWT settings

/*
let jwt_secret = env::var("JWT_SECRET")
.expect("Error getting JWT_SECRET")
.to_string();
*/

let jwt_secret = "I want to cry";
let encoding_key = EncodingKey::from_secret(jwt_secret.as_bytes());
let decoding_key = DecodingKey::from_secret(jwt_secret.as_bytes());

// TODO: create context, connect to db, return jwt's
let state = AppState {
encoding_key,
decoding_key,
};

let app = Router::new()
.route("/", get(|| async { "Hello, World!" }))
.with_state(state);

axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
Expand Down
10 changes: 7 additions & 3 deletions backend/server/src/models/app.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use sqlx::{Pool, Postgres};
use jsonwebtoken::{DecodingKey, EncodingKey};
use reqwest::Client as ReqwestClient;
use sqlx::{Pool, Postgres};

#[derive(Clone)]
pub struct AppState {
pub db: Pool<Postgres>,
pub ctx: ReqwestClient
}
pub ctx: ReqwestClient,
pub decoding_key: DecodingKey,
pub encoding_key: EncodingKey,
}
29 changes: 26 additions & 3 deletions backend/server/src/models/auth.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,34 @@
use axum::{
async_trait,
extract::FromRequest,
http::{self, Request},
};
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
pub struct AuthRequest {
pub code: String
pub code: String,
}

#[derive(Deserialize, Serialize)]
pub struct UserProfile {
pub email: String
}
pub email: String,
}

#[derive(Deserialize, Serialize)]
pub struct AuthUser {
pub user_id: i64,
}

#[async_trait]
impl<S, B> FromRequest<S, B> for AuthUser
where
B: Send + 'static,
S: Send + Sync,
{
type Rejection = http::StatusCode;

async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
Ok(AuthUser { user_id: 1 })
}
}
4 changes: 3 additions & 1 deletion backend/server/src/service/auth.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use anyhow::Result;
use jsonwebtoken::{DecodingKey, EncodingKey};
use sqlx::{Pool, Postgres};

/// Checks if a user exists in DB based on given email address. If so, their user_id is returned.
Expand All @@ -11,4 +12,5 @@ pub async fn create_or_get_user_id(email: String, pool: Pool<Postgres>) -> Resul

let user_id = 1;
return Ok(user_id);
}
}

38 changes: 38 additions & 0 deletions backend/server/src/service/jwt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use axum::extract::State;
use jsonwebtoken::Algorithm;
use jsonwebtoken::{decode, Validation};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::AppState;

#[derive(Debug, Deserialize, Serialize)]
pub struct AuthorizationJwtPayload {
pub iss: String, // issuer
pub sub: String, // subject (user's id)
pub jti: Uuid, // id
pub aud: Vec<String>, // audience (uri the JWT is meant for)

// Time-based validity
pub exp: i64, // expiry (UNIX timestamp)
pub nbf: i64, // not-valid-before (UNIX timestamp)
pub iat: i64, // issued-at (UNIX timestamp)

pub username: String, // username
}

pub fn decode_auth_token(
token: String,
State(state): State<AppState>,
) -> Option<AuthorizationJwtPayload> {
let decode_token = decode::<AuthorizationJwtPayload>(
token.as_str(),
&state.decoding_key,
&Validation::new(Algorithm::HS256),
);

return match decode_token {
Ok(token) => Option::from(token.claims),
Err(_err) => None::<AuthorizationJwtPayload>,
};
}
1 change: 1 addition & 0 deletions backend/server/src/service/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod auth;
pub mod jwt;
pub mod oauth2;

0 comments on commit ba965e4

Please sign in to comment.