Files
komodo/core/src/auth/google.rs
2023-04-16 03:23:30 -04:00

124 lines
3.9 KiB
Rust

use std::sync::Arc;
use anyhow::{anyhow, Context};
use axum::{extract::Query, response::Redirect, routing::get, Extension, Router};
use axum_oauth2::google::{GoogleOauthClient, GoogleOauthExtension};
use helpers::handle_anyhow_error;
use mungos::{doc, Deserialize};
use types::{monitor_timestamp, CoreConfig, User};
use crate::{response, state::StateExtension};
use super::JwtExtension;
pub fn router(config: &CoreConfig) -> Router {
let client = GoogleOauthClient::new(
config.google_oauth.id.clone(),
config.google_oauth.secret.clone(),
format!("{}/auth/google/callback", config.host),
&[
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/userinfo.email",
],
"monitor".to_string(),
);
Router::new()
.route(
"/login",
get(|Extension(client): GoogleOauthExtension| async move {
Redirect::to(&client.get_login_redirect_url())
}),
)
.route(
"/callback",
get(|client, jwt, state, query| async {
let redirect = callback(client, jwt, state, query)
.await
.map_err(handle_anyhow_error)?;
response!(redirect)
}),
)
.layer(Extension(Arc::new(client)))
}
#[derive(Deserialize)]
struct CallbackQuery {
state: Option<String>,
code: Option<String>,
error: Option<String>,
}
async fn callback(
Extension(client): GoogleOauthExtension,
Extension(jwt_client): JwtExtension,
Extension(state): StateExtension,
Query(query): Query<CallbackQuery>,
) -> anyhow::Result<Redirect> {
if let Some(error) = query.error {
return Err(anyhow!("auth error from google: {error}"));
}
if !client.check_state(
&query
.state
.ok_or(anyhow!("callback query does not contain state"))?,
) {
return Err(anyhow!("state mismatch"));
}
let token = client
.get_access_token(
&query
.code
.ok_or(anyhow!("callback query does not contain code"))?,
)
.await?;
let google_user = client.get_google_user(&token.id_token)?;
let google_id = google_user.id.to_string();
let user = state
.db
.users
.find_one(doc! { "google_id": &google_id }, None)
.await
.context("failed at find user query from mongo")?;
let jwt = match user {
Some(user) => jwt_client
.generate(user.id)
.context("failed to generate jwt")?,
None => {
let ts = monitor_timestamp();
let no_users_exist = state.db.users.find_one(None, None).await?.is_none();
let user = User {
username: google_user
.email
.split("@")
.collect::<Vec<&str>>()
.get(0)
.unwrap()
.to_string(),
avatar: google_user.picture.into(),
google_id: google_id.into(),
enabled: no_users_exist,
admin: no_users_exist,
create_server_permissions: no_users_exist,
create_build_permissions: no_users_exist,
created_at: ts.clone(),
updated_at: ts,
..Default::default()
};
let user_id = state
.db
.users
.create_one(user)
.await
.context("failed to create user on mongo")?;
jwt_client
.generate(user_id)
.context("failed to generate jwt")?
}
};
let exchange_token = jwt_client.create_exchange_token(jwt);
Ok(Redirect::to(&format!(
"{}?token={exchange_token}",
state.config.host
)))
}