Files
komodo/bin/core/src/auth/local.rs
Maxwell Becker 8c6f38cafb v1.11 Improve permission management (#6)
* add "all permissions" feature on user and user group schema

* prepare support for group all

* implement user.all and user_group.all for broad base permissioning

* clean up unused deps

* sync support user group permissions regex

* 1.11

* fix fe ? issue

* this doesn't work

* sync handle user group all set

* retain above non earlier

* remove permissions that already exist

* update docs

* add user group docs

* minimize user group permissions for execute

* sync toml

* add sync name to slack alert title

* add syncs to alerter white/blacklist

* use \\ instead of $reg

* share resource type base permissions api users and user groups

* manage user / group base permissions ui

* manage user / group base resource type permissions

* update api permission handling

* manage all resource permissions in table

* user show group membership

* update client to 1.11
2024-07-19 02:11:36 -07:00

133 lines
3.3 KiB
Rust

use std::str::FromStr;
use anyhow::{anyhow, Context};
use async_timing_util::unix_timestamp_ms;
use axum::http::HeaderMap;
use mongo_indexed::Document;
use monitor_client::{
api::auth::{
CreateLocalUser, CreateLocalUserResponse, LoginLocalUser,
LoginLocalUserResponse,
},
entities::user::{User, UserConfig},
};
use mungos::mongodb::bson::{doc, oid::ObjectId};
use resolver_api::Resolve;
use crate::{
config::core_config,
state::State,
state::{db_client, jwt_client},
};
const BCRYPT_COST: u32 = 10;
impl Resolve<CreateLocalUser, HeaderMap> for State {
#[instrument(name = "CreateLocalUser", skip(self))]
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"));
}
if username.is_empty() {
return Err(anyhow!("username cannot be empty string"));
}
if ObjectId::from_str(&username).is_ok() {
return Err(anyhow!("username cannot be valid ObjectId"));
}
let password = bcrypt::hash(password, BCRYPT_COST)
.context("failed to hash password")?;
let no_users_exist = db_client()
.await
.users
.find_one(Document::new())
.await?
.is_none();
let ts = unix_timestamp_ms() as i64;
let user = User {
id: Default::default(),
username,
enabled: no_users_exist,
admin: no_users_exist,
create_server_permissions: no_users_exist,
create_build_permissions: no_users_exist,
updated_at: ts,
last_update_view: 0,
recents: Default::default(),
all: Default::default(),
config: UserConfig::Local { password },
};
let user_id = db_client()
.await
.users
.insert_one(user)
.await
.context("failed to create user")?
.inserted_id
.as_object_id()
.context("inserted_id is not ObjectId")?
.to_string();
let jwt = jwt_client()
.generate(user_id)
.context("failed to generate jwt for user")?;
Ok(CreateLocalUserResponse { jwt })
}
}
impl Resolve<LoginLocalUser, HeaderMap> for State {
#[instrument(name = "LoginLocalUser", level = "debug", skip(self))]
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"));
}
let user = db_client()
.await
.users
.find_one(doc! { "username": &username })
.await
.context("failed at db query for users")?
.with_context(|| {
format!("did not find user with username {username}")
})?;
let UserConfig::Local {
password: user_pw_hash,
} = user.config
else {
return Err(anyhow!(
"non-local auth users can not log in with a password"
));
};
let verified = bcrypt::verify(password, &user_pw_hash)
.context("failed at verify password")?;
if !verified {
return Err(anyhow!("invalid credentials"));
}
let jwt = jwt_client()
.generate(user.id)
.context("failed at generating jwt for user")?;
Ok(LoginLocalUserResponse { jwt })
}
}