mirror of
https://github.com/moghtech/komodo.git
synced 2026-03-12 02:04:44 -05:00
move GetUser to auth api. Doesn't require user enabled to call
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ mod user;
|
||||
#[serde(tag = "type", content = "params")]
|
||||
enum ReadRequest {
|
||||
GetVersion(GetVersion),
|
||||
GetUser(GetUser),
|
||||
GetUsers(GetUsers),
|
||||
GetUsername(GetUsername),
|
||||
GetCoreInfo(GetCoreInfo),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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;
|
||||
|
||||
//
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user