implement auth extension

This commit is contained in:
mbecker20
2022-11-06 20:05:51 -05:00
parent 8d2ea8ae87
commit c70917382f
42 changed files with 4276 additions and 4168 deletions

2
.gitignore vendored
View File

@@ -2,3 +2,5 @@ target
node_modules
dist
.env
config.json

17
core/config.json.example Normal file
View File

@@ -0,0 +1,17 @@
{
"PORT": 9000,
"PASSKEY": "your-default-passkey",
"DOCKER_ACCOUNTS": {},
"GITHUB_ACCOUNTS": {},
"GITHUB_OAUTH": {
"ID": "your_client_id",
"SECRET": "your_client_secret"
},
"GITHUB_WEBHOOK_SECRET": "your_random_webhook_secret",
"JWT_SECRET": "your_jwt_secret",
"JWT_VALID_FOR": "1-wk",
"SLACK_URL": "your_slack_app_webhook_url",
"MONGO_URI": "your_mongo_uri",
"MONGO_APP_NAME": "monitor_core",
"MONGO_DB_NAME": "monitor"
}

View File

@@ -1,5 +1,28 @@
use axum::Router;
use anyhow::anyhow;
use axum::{http::StatusCode, middleware, routing::get, Extension, Json, Router};
use db::DbExtension;
use types::{User, UserId};
use crate::{auth::auth_request, helpers::handle_anyhow_error, ResponseResult};
pub fn router() -> Router {
Router::new()
.route(
"/user",
get(|user_id, db| async { get_user(user_id, db).await.map_err(handle_anyhow_error) }),
)
.layer(middleware::from_fn(auth_request))
}
async fn get_user(
Extension(user_id): Extension<UserId>,
Extension(db): DbExtension,
) -> anyhow::Result<Json<User>> {
let mut user = db
.users
.find_one_by_id(&user_id)
.await?
.ok_or(anyhow!("did not find user"))?;
user.password = None;
Ok(Json(user))
}

View File

@@ -7,7 +7,10 @@ use types::CoreConfig;
pub type GithubOauthExtension = Extension<Arc<BasicClient>>;
pub fn router(config: &CoreConfig) -> Router {
Router::new().layer(github_oauth_extension(config, format!("")))
Router::new().layer(github_oauth_extension(
config,
format!("http://localhost:9000/auth/github/callback"),
))
}
fn github_oauth_extension(config: &CoreConfig, redirect_url: String) -> GithubOauthExtension {

View File

@@ -2,22 +2,24 @@ use std::sync::Arc;
use anyhow::{anyhow, Context};
use async_timing_util::{get_timelength_in_ms, unix_timestamp_ms, Timelength};
use axum::Extension;
use axum::{body::Body, http::Request, Extension};
use db::DbClient;
use hmac::{Hmac, Mac};
use jwt::{SignWithKey, VerifyWithKey};
use mungos::{Deserialize, Serialize};
use sha2::Sha256;
use types::CoreConfig;
use types::{CoreConfig, User, UserId};
pub type JwtExtension = Extension<Arc<JwtClient>>;
#[derive(Serialize, Deserialize)]
pub struct JwtClaims {
pub id: String,
pub id: UserId,
pub iat: u128,
pub exp: u128,
}
#[derive(Clone)]
pub struct JwtClient {
key: Hmac<Sha256>,
valid_for_ms: u128,
@@ -47,12 +49,34 @@ impl JwtClient {
Ok(jwt)
}
pub fn validate(&self, jwt: &str) -> anyhow::Result<JwtClaims> {
pub async fn authenticate(&self, req: &Request<Body>) -> anyhow::Result<UserId> {
let jwt = req
.headers()
.get("authorization")
.ok_or(anyhow!(
"no authorization header provided. must be Bearer <jwt_token>"
))?
.to_str()?
.replace("Bearer ", "")
.replace("bearer ", "");
let claims: JwtClaims = jwt
.verify_with_key(&self.key)
.context("failed to verify claims")?;
if claims.exp < unix_timestamp_ms() {
Ok(claims)
if claims.exp > unix_timestamp_ms() {
let users_collection = &req
.extensions()
.get::<Arc<DbClient>>()
.ok_or(anyhow!("failed at getting db handle"))?
.users;
let user = users_collection
.find_one_by_id(&claims.id)
.await?
.ok_or(anyhow!("did not find user with id {}", claims.id))?;
if user.enabled {
Ok(claims.id)
} else {
Err(anyhow!("user not enabled"))
}
} else {
Err(anyhow!("token has expired"))
}

View File

@@ -1,5 +1,14 @@
use axum::Router;
use types::CoreConfig;
use std::sync::Arc;
use anyhow::Context;
use axum::{
body::Body,
http::{header, Request, StatusCode},
middleware::Next,
response::{IntoResponse, Response},
Router,
};
use types::{CoreConfig, UserId};
mod github;
mod jwt;
@@ -12,3 +21,19 @@ pub fn router(config: &CoreConfig) -> Router {
.nest("/local", local::router())
.nest("/github", github::router(config))
}
pub async fn auth_request(
mut req: Request<Body>,
next: Next<Body>,
) -> Result<Response, (StatusCode, String)> {
let jwt_client = req.extensions().get::<Arc<JwtClient>>().ok_or((
StatusCode::UNAUTHORIZED,
"failed to get jwt client extension".to_string(),
))?;
let user_id: UserId = jwt_client
.authenticate(&req)
.await
.map_err(|e| (StatusCode::UNAUTHORIZED, format!("error: {e:#?}")))?;
req.extensions_mut().insert(user_id);
Ok(next.run(req).await)
}

View File

@@ -1,4 +1,5 @@
use std::{
env,
fs::File,
net::{IpAddr, SocketAddr},
str::FromStr,
@@ -10,13 +11,8 @@ use dotenv::dotenv;
use mungos::{Deserialize, Mungos};
use types::CoreConfig;
pub async fn load() -> CoreConfig {
let config = load_config();
config
}
fn load_config() -> CoreConfig {
let file = File::open("/secrets/secrets.json");
todo!()
pub fn load() -> CoreConfig {
let config_path = env::var("CONFIG_PATH").unwrap_or("./config.json".to_string());
let file = File::open(config_path).expect("failed to open config file");
serde_json::from_reader(file).unwrap()
}

View File

@@ -1,7 +1,7 @@
#![allow(unused)]
use auth::JwtClient;
use axum::Router;
use axum::{http::StatusCode, Router};
use db::DbClient;
use docker::DockerClient;
use helpers::get_socket_addr;
@@ -11,15 +11,19 @@ mod auth;
mod config;
mod helpers;
type ResponseResult<T> = Result<T, (StatusCode, String)>;
#[tokio::main]
async fn main() {
let config = config::load().await;
let config = config::load();
let app = Router::new()
.nest("/api", api::router())
.nest("/auth", auth::router(&config))
.layer(DbClient::extension((&config).into()).await)
.layer(JwtClient::extension(&config));
.layer(JwtClient::extension(&config))
.layer(DbClient::extension((&config).into()).await);
println!("starting monitor_core on localhost:{}", config.port);
axum::Server::bind(&get_socket_addr(&config))
.serve(app.into_make_service())

View File

@@ -7,6 +7,7 @@ use strum_macros::{Display, EnumString};
pub type PermissionsMap = HashMap<String, PermissionLevel>;
pub type UserId = String;
pub type ServerId = String;
pub type DeploymentId = String;
pub type BuildId = String;
@@ -223,15 +224,28 @@ pub struct OauthCredentials {
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "UPPERCASE")]
pub struct CoreConfig {
// port the core web server runs on
pub port: u16,
// default periphery passkey
pub passkey: String,
// docker integration
pub docker_accounts: DockerAccounts,
// github integration
pub github_accounts: GithubAccounts,
pub github_oauth: OauthCredentials,
pub jwt_secret: String,
pub slack_token: Option<String>,
pub github_webhook_secret: String,
// jwt config
pub jwt_secret: String,
pub jwt_valid_for: Timelength,
// integration with slack app
pub slack_url: Option<String>,
//mongo config
pub mongo_uri: String,
#[serde(default = "default_core_mongo_app_name")]
pub mongo_app_name: String,