move GetUser to auth api. Doesn't require user enabled to call

This commit is contained in:
mbecker20
2024-03-30 04:16:04 -07:00
parent 74a5f429e9
commit 1b97d85023
9 changed files with 117 additions and 116 deletions

View File

@@ -1,18 +1,31 @@
use std::sync::OnceLock;
use std::{sync::OnceLock, time::Instant};
use async_trait::async_trait;
use monitor_client::api::auth::*;
use resolver_api::{derive::Resolver, Resolve};
use axum::{http::HeaderMap, routing::post, Json, Router};
use axum_extra::{headers::ContentType, TypedHeader};
use monitor_client::{api::auth::*, entities::user::User};
use resolver_api::{derive::Resolver, Resolve, Resolver};
use serde::{Deserialize, Serialize};
use serror::AppResult;
use typeshare::typeshare;
use uuid::Uuid;
use crate::{
auth::jwt::jwt_client, config::core_config, state::State,
auth::{
get_user_id_from_headers,
github::{self, client::github_oauth_client},
google::{self, client::google_oauth_client},
jwt::jwt_client,
},
config::core_config,
helpers::get_user,
state::State,
};
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone, Resolver)]
#[resolver_target(State)]
#[resolver_args(HeaderMap)]
#[serde(tag = "type", content = "params")]
#[allow(clippy::enum_variant_names, clippy::large_enum_variant)]
pub enum AuthRequest {
@@ -20,6 +33,40 @@ pub enum AuthRequest {
CreateLocalUser(CreateLocalUser),
LoginLocalUser(LoginLocalUser),
ExchangeForJwt(ExchangeForJwt),
GetUser(GetUser),
}
pub fn router() -> Router {
let mut router = Router::new().route(
"/",
post(|headers: HeaderMap, Json(request): Json<AuthRequest>| async move {
let timer = Instant::now();
let req_id = Uuid::new_v4();
info!(
"/auth request {req_id} | METHOD: {}",
request.req_type()
);
let res = State.resolve_request(request, headers).await;
if let Err(e) = &res {
info!("/auth request {req_id} | ERROR: {e:?}");
}
let res = res?;
let elapsed = timer.elapsed();
info!("/auth request {req_id} | resolve time: {elapsed:?}");
debug!("/auth request {req_id} | RESPONSE: {res}");
AppResult::Ok((TypedHeader(ContentType::json()), res))
}),
);
if github_oauth_client().is_some() {
router = router.nest("/github", github::router())
}
if google_oauth_client().is_some() {
router = router.nest("/google", google::router())
}
router
}
fn login_options_reponse() -> &'static GetLoginOptionsResponse {
@@ -41,25 +88,37 @@ fn login_options_reponse() -> &'static GetLoginOptionsResponse {
}
#[async_trait]
impl Resolve<GetLoginOptions> for State {
impl Resolve<GetLoginOptions, HeaderMap> for State {
async fn resolve(
&self,
_: GetLoginOptions,
_: (),
_: HeaderMap,
) -> anyhow::Result<GetLoginOptionsResponse> {
Ok(*login_options_reponse())
}
}
#[async_trait]
impl Resolve<ExchangeForJwt> for State {
impl Resolve<ExchangeForJwt, HeaderMap> for State {
async fn resolve(
&self,
ExchangeForJwt { token }: ExchangeForJwt,
_: (),
_: HeaderMap,
) -> anyhow::Result<ExchangeForJwtResponse> {
let jwt = jwt_client().redeem_exchange_token(&token).await?;
let res = ExchangeForJwtResponse { jwt };
Ok(res)
}
}
#[async_trait]
impl Resolve<GetUser, HeaderMap> for State {
async fn resolve(
&self,
GetUser {}: GetUser,
headers: HeaderMap,
) -> anyhow::Result<User> {
let user_id = get_user_id_from_headers(&headers).await?;
get_user(&user_id).await
}
}

View File

@@ -34,7 +34,6 @@ mod user;
#[serde(tag = "type", content = "params")]
enum ReadRequest {
GetVersion(GetVersion),
GetUser(GetUser),
GetUsers(GetUsers),
GetUsername(GetUsername),
GetCoreInfo(GetCoreInfo),

View File

@@ -2,7 +2,7 @@ use anyhow::{anyhow, Context};
use async_trait::async_trait;
use monitor_client::{
api::read::{
GetUser, GetUsername, GetUsernameResponse, GetUsers, ListApiKeys,
GetUsername, GetUsernameResponse, GetUsers, ListApiKeys,
ListApiKeysResponse,
},
entities::user::User,
@@ -14,22 +14,6 @@ use resolver_api::Resolve;
use crate::{db::db_client, state::State};
#[async_trait]
impl Resolve<GetUser, User> for State {
async fn resolve(
&self,
GetUser {}: GetUser,
user: User,
) -> anyhow::Result<User> {
let mut user = find_one_by_id(&db_client().await.users, &user.id)
.await
.context("failed at mongo query")?
.context("no user found with id")?;
user.sanitize();
Ok(user)
}
}
#[async_trait]
impl Resolve<GetUsername, User> for State {
async fn resolve(

View File

@@ -1,6 +1,7 @@
use anyhow::{anyhow, Context};
use async_timing_util::unix_timestamp_ms;
use async_trait::async_trait;
use axum::http::HeaderMap;
use monitor_client::{
api::auth::{
CreateLocalUser, CreateLocalUserResponse, LoginLocalUser,
@@ -18,11 +19,11 @@ use super::jwt::jwt_client;
const BCRYPT_COST: u32 = 10;
#[async_trait]
impl Resolve<CreateLocalUser> for State {
impl Resolve<CreateLocalUser, HeaderMap> for State {
async fn resolve(
&self,
CreateLocalUser { username, password }: CreateLocalUser,
_: (),
_: HeaderMap,
) -> anyhow::Result<CreateLocalUserResponse> {
if !core_config().local_auth {
return Err(anyhow!("local auth is not enabled"));
@@ -77,11 +78,11 @@ impl Resolve<CreateLocalUser> for State {
}
#[async_trait]
impl Resolve<LoginLocalUser> for State {
impl Resolve<LoginLocalUser, HeaderMap> for State {
async fn resolve(
&self,
LoginLocalUser { username, password }: LoginLocalUser,
_: (),
_: HeaderMap,
) -> anyhow::Result<LoginLocalUserResponse> {
if !core_config().local_auth {
return Err(anyhow!("local auth is not enabled"));

View File

@@ -1,38 +1,26 @@
use std::time::Instant;
use ::jwt::VerifyWithKey;
use anyhow::{anyhow, Context};
use async_timing_util::unix_timestamp_ms;
use axum::{
extract::Request, http::HeaderMap, middleware::Next,
response::Response, routing::post, Json, Router,
response::Response,
};
use axum_extra::{headers::ContentType, TypedHeader};
use monitor_client::entities::{monitor_timestamp, user::User};
use mungos::mongodb::bson::doc;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
use resolver_api::Resolver;
use serde::Deserialize;
use serror::{AppError, AuthError};
use uuid::Uuid;
use serror::AuthError;
use crate::{db::db_client, helpers::get_user};
use self::jwt::{jwt_client, JwtClaims};
pub mod github;
pub mod google;
pub mod jwt;
mod github;
mod google;
mod local;
use crate::{
api::auth::AuthRequest, db::db_client, helpers::get_user,
state::State,
};
use self::{
github::client::github_oauth_client,
google::client::google_oauth_client, jwt::jwt_client,
jwt::JwtClaims,
};
const STATE_PREFIX_LENGTH: usize = 20;
#[derive(Deserialize)]
@@ -50,42 +38,6 @@ pub async fn auth_request(
Ok(next.run(req).await)
}
pub fn router() -> Router {
let mut router = Router::new().route(
"/",
post(|Json(request): Json<AuthRequest>| async move {
let timer = Instant::now();
let req_id = Uuid::new_v4();
info!(
"/auth request {req_id} | METHOD: {}",
request.req_type()
);
let res = State.resolve_request(request, ()).await;
if let Err(e) = &res {
info!("/auth request {req_id} | ERROR: {e:?}");
}
let res = res?;
let elapsed = timer.elapsed();
info!("/auth request {req_id} | resolve time: {elapsed:?}");
debug!("/auth request {req_id} | RESPONSE: {res}");
Result::<_, AppError>::Ok((
TypedHeader(ContentType::json()),
res,
))
}),
);
if github_oauth_client().is_some() {
router = router.nest("/github", github::router())
}
if google_oauth_client().is_some() {
router = router.nest("/google", google::router())
}
router
}
pub fn random_string(length: usize) -> String {
thread_rng()
.sample_iter(&Alphanumeric)
@@ -94,24 +46,20 @@ pub fn random_string(length: usize) -> String {
.collect()
}
pub async fn authenticate_check_enabled(
pub async fn get_user_id_from_headers(
headers: &HeaderMap,
) -> anyhow::Result<User> {
let user_id = match (
) -> anyhow::Result<String> {
match (
headers.get("authorization"),
headers.get("x-api-key"),
headers.get("x-api-secret"),
) {
(Some(jwt), _, _) => {
// USE JWT
let jwt = jwt
.to_str()
.context("jwt is not str")?
.replace("Bearer ", "")
.replace("bearer ", "");
auth_jwt_get_user_id(&jwt)
let jwt = jwt.to_str().context("jwt is not str")?;
auth_jwt_get_user_id(jwt)
.await
.context("failed to authenticate jwt")?
.context("failed to authenticate jwt")
}
(None, Some(key), Some(secret)) => {
// USE API KEY / SECRET
@@ -119,13 +67,19 @@ pub async fn authenticate_check_enabled(
let secret = secret.to_str().context("secret is not str")?;
auth_api_key_get_user_id(key, secret)
.await
.context("failed to authenticate api key")?
.context("failed to authenticate api key")
}
_ => {
// AUTH FAIL
return Err(anyhow!("must attach either AUTHORIZATION header with jwt OR pass X-API-KEY and X-API-SECRET"));
Err(anyhow!("must attach either AUTHORIZATION header with jwt OR pass X-API-KEY and X-API-SECRET"))
}
};
}
}
pub async fn authenticate_check_enabled(
headers: &HeaderMap,
) -> anyhow::Result<User> {
let user_id = get_user_id_from_headers(headers).await?;
let user = get_user(&user_id).await?;
if user.enabled {
Ok(user)

View File

@@ -36,7 +36,7 @@ async fn app() -> anyhow::Result<()> {
.context("failed to parse socket addr")?;
let app = Router::new()
.nest("/auth", auth::router())
.nest("/auth", api::auth::router())
.nest("/read", api::read::router())
.nest("/write", api::write::router())
.nest("/execute", api::execute::router())

View File

@@ -3,6 +3,8 @@ use resolver_api::{derive::Request, HasResponse};
use serde::{Deserialize, Serialize};
use typeshare::typeshare;
use crate::entities::user::User;
pub trait MonitorAuthRequest: HasResponse {}
//
@@ -80,3 +82,16 @@ pub struct ExchangeForJwtResponse {
}
//
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
)]
#[empty_traits(MonitorAuthRequest)]
#[response(GetUserResponse)]
pub struct GetUser {}
#[typeshare]
pub type GetUserResponse = User;
//

View File

@@ -49,19 +49,6 @@ pub struct GetVersionResponse {
//
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,
)]
#[empty_traits(MonitorReadRequest)]
#[response(GetUserResponse)]
pub struct GetUser {}
#[typeshare]
pub type GetUserResponse = User;
//
#[typeshare]
#[derive(
Serialize, Deserialize, Debug, Clone, Request, EmptyTraits,

View File

@@ -72,9 +72,11 @@ const Info = ({ id }: { id: string }) => {
<HardDrive className="w-4 h-4" />
{useDeployment(id)?.info.image || "N/A"}
</Link>
<Link to={`/servers/${server?.id}`} className="flex items-center gap-2">
<Server className="w-4 h-4" />
{server?.name ?? "N/A"}
<Link to={`/servers/${server?.id}`}>
<Button variant="link" className="flex items-center gap-2">
<Server className="w-4 h-4" />
{server?.name ?? "N/A"}
</Button>
</Link>
</>
);