mirror of
https://github.com/warp-tech/warpgate.git
synced 2024-09-20 06:46:17 +08:00
wip
This commit is contained in:
parent
3fdd33c96f
commit
c8f92cda9f
|
@ -12,19 +12,17 @@ mod ssh_keys;
|
|||
mod targets;
|
||||
mod tickets_detail;
|
||||
mod tickets_list;
|
||||
mod users_list;
|
||||
mod users;
|
||||
|
||||
pub fn get() -> impl OpenApi {
|
||||
(
|
||||
sessions_list::Api,
|
||||
sessions_detail::Api,
|
||||
recordings_detail::Api,
|
||||
users_list::Api,
|
||||
roles::ListApi,
|
||||
roles::DetailApi,
|
||||
targets::ListApi,
|
||||
targets::DetailApi,
|
||||
targets::RolesApi,
|
||||
(targets::ListApi, targets::DetailApi, targets::RolesApi),
|
||||
(users::ListApi, users::DetailApi, users::RolesApi),
|
||||
tickets_list::Api,
|
||||
tickets_detail::Api,
|
||||
known_hosts_list::Api,
|
||||
|
|
328
warpgate-admin/src/api/users.rs
Normal file
328
warpgate-admin/src/api/users.rs
Normal file
|
@ -0,0 +1,328 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use poem::web::Data;
|
||||
use poem_openapi::param::Path;
|
||||
use poem_openapi::payload::Json;
|
||||
use poem_openapi::{ApiResponse, Object, OpenApi};
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, QueryFilter,
|
||||
QueryOrder, Set,
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
use uuid::Uuid;
|
||||
use warpgate_common::{
|
||||
Role as RoleConfig, User as UserConfig, UserAuthCredential, UserRequireCredentialsPolicy,
|
||||
WarpgateError,
|
||||
};
|
||||
use warpgate_db_entities::{Role, User, UserRoleAssignment};
|
||||
|
||||
#[derive(Object)]
|
||||
struct UserDataRequest {
|
||||
username: String,
|
||||
credentials: Vec<UserAuthCredential>,
|
||||
credential_policy: Option<UserRequireCredentialsPolicy>,
|
||||
}
|
||||
|
||||
#[derive(ApiResponse)]
|
||||
enum GetUsersResponse {
|
||||
#[oai(status = 200)]
|
||||
Ok(Json<Vec<UserConfig>>),
|
||||
}
|
||||
#[derive(ApiResponse)]
|
||||
enum CreateUserResponse {
|
||||
#[oai(status = 201)]
|
||||
Created(Json<UserConfig>),
|
||||
|
||||
#[oai(status = 400)]
|
||||
BadRequest(Json<String>),
|
||||
}
|
||||
|
||||
pub struct ListApi;
|
||||
|
||||
#[OpenApi]
|
||||
impl ListApi {
|
||||
#[oai(path = "/users", method = "get", operation_id = "get_users")]
|
||||
async fn api_get_all_users(
|
||||
&self,
|
||||
db: Data<&Arc<Mutex<DatabaseConnection>>>,
|
||||
) -> poem::Result<GetUsersResponse> {
|
||||
let db = db.lock().await;
|
||||
|
||||
let users = User::Entity::find()
|
||||
.order_by_asc(User::Column::Username)
|
||||
.all(&*db)
|
||||
.await
|
||||
.map_err(WarpgateError::from)?;
|
||||
|
||||
let users: Result<Vec<UserConfig>, _> = users.into_iter().map(|t| t.try_into()).collect();
|
||||
let users = users.map_err(WarpgateError::from)?;
|
||||
|
||||
Ok(GetUsersResponse::Ok(Json(users)))
|
||||
}
|
||||
|
||||
#[oai(path = "/users", method = "post", operation_id = "create_user")]
|
||||
async fn api_create_user(
|
||||
&self,
|
||||
db: Data<&Arc<Mutex<DatabaseConnection>>>,
|
||||
body: Json<UserDataRequest>,
|
||||
) -> poem::Result<CreateUserResponse> {
|
||||
if body.username.is_empty() {
|
||||
return Ok(CreateUserResponse::BadRequest(Json("name".into())));
|
||||
}
|
||||
|
||||
let db = db.lock().await;
|
||||
|
||||
let values = User::ActiveModel {
|
||||
id: Set(Uuid::new_v4()),
|
||||
username: Set(body.username.clone()),
|
||||
credentials: Set(
|
||||
serde_json::to_value(body.credentials.clone()).map_err(WarpgateError::from)?
|
||||
),
|
||||
credential_policy: Set(serde_json::to_value(body.credential_policy.clone())
|
||||
.map_err(WarpgateError::from)?),
|
||||
};
|
||||
|
||||
let user = values.insert(&*db).await.map_err(WarpgateError::from)?;
|
||||
|
||||
Ok(CreateUserResponse::Created(Json(
|
||||
user.try_into().map_err(WarpgateError::from)?,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ApiResponse)]
|
||||
enum GetUserResponse {
|
||||
#[oai(status = 200)]
|
||||
Ok(Json<UserConfig>),
|
||||
#[oai(status = 404)]
|
||||
NotFound,
|
||||
}
|
||||
|
||||
#[derive(ApiResponse)]
|
||||
enum UpdateUserResponse {
|
||||
#[oai(status = 200)]
|
||||
Ok(Json<UserConfig>),
|
||||
#[oai(status = 400)]
|
||||
BadRequest,
|
||||
#[oai(status = 404)]
|
||||
NotFound,
|
||||
}
|
||||
|
||||
#[derive(ApiResponse)]
|
||||
enum DeleteUserResponse {
|
||||
#[oai(status = 204)]
|
||||
Deleted,
|
||||
|
||||
#[oai(status = 404)]
|
||||
NotFound,
|
||||
}
|
||||
|
||||
pub struct DetailApi;
|
||||
|
||||
#[OpenApi]
|
||||
impl DetailApi {
|
||||
#[oai(path = "/users/:id", method = "get", operation_id = "get_user")]
|
||||
async fn api_get_user(
|
||||
&self,
|
||||
db: Data<&Arc<Mutex<DatabaseConnection>>>,
|
||||
id: Path<Uuid>,
|
||||
) -> poem::Result<GetUserResponse> {
|
||||
let db = db.lock().await;
|
||||
|
||||
let Some(user) = User::Entity::find_by_id(id.0)
|
||||
.one(&*db)
|
||||
.await
|
||||
.map_err(poem::error::InternalServerError)? else {
|
||||
return Ok(GetUserResponse::NotFound);
|
||||
};
|
||||
|
||||
Ok(GetUserResponse::Ok(Json(
|
||||
user.try_into().map_err(poem::error::InternalServerError)?,
|
||||
)))
|
||||
}
|
||||
|
||||
#[oai(path = "/users/:id", method = "put", operation_id = "update_user")]
|
||||
async fn api_update_user(
|
||||
&self,
|
||||
db: Data<&Arc<Mutex<DatabaseConnection>>>,
|
||||
body: Json<UserDataRequest>,
|
||||
id: Path<Uuid>,
|
||||
) -> poem::Result<UpdateUserResponse> {
|
||||
let db = db.lock().await;
|
||||
|
||||
let Some(user) = User::Entity::find_by_id(id.0)
|
||||
.one(&*db)
|
||||
.await
|
||||
.map_err(poem::error::InternalServerError)? else {
|
||||
return Ok(UpdateUserResponse::NotFound);
|
||||
};
|
||||
|
||||
let mut model: User::ActiveModel = user.into();
|
||||
model.username = Set(body.username.clone());
|
||||
model.credentials =
|
||||
Set(serde_json::to_value(body.credentials.clone()).map_err(WarpgateError::from)?);
|
||||
model.credential_policy =
|
||||
Set(serde_json::to_value(body.credential_policy.clone())
|
||||
.map_err(WarpgateError::from)?);
|
||||
let user = model
|
||||
.update(&*db)
|
||||
.await
|
||||
.map_err(poem::error::InternalServerError)?;
|
||||
|
||||
Ok(UpdateUserResponse::Ok(Json(
|
||||
user.try_into().map_err(WarpgateError::from)?,
|
||||
)))
|
||||
}
|
||||
|
||||
#[oai(path = "/users/:id", method = "delete", operation_id = "delete_user")]
|
||||
async fn api_delete_user(
|
||||
&self,
|
||||
db: Data<&Arc<Mutex<DatabaseConnection>>>,
|
||||
id: Path<Uuid>,
|
||||
) -> poem::Result<DeleteUserResponse> {
|
||||
let db = db.lock().await;
|
||||
|
||||
let Some(user) = User::Entity::find_by_id(id.0)
|
||||
.one(&*db)
|
||||
.await
|
||||
.map_err(poem::error::InternalServerError)? else {
|
||||
return Ok(DeleteUserResponse::NotFound);
|
||||
};
|
||||
|
||||
user.delete(&*db)
|
||||
.await
|
||||
.map_err(poem::error::InternalServerError)?;
|
||||
Ok(DeleteUserResponse::Deleted)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ApiResponse)]
|
||||
enum GetUserRolesResponse {
|
||||
#[oai(status = 200)]
|
||||
Ok(Json<Vec<RoleConfig>>),
|
||||
#[oai(status = 404)]
|
||||
NotFound,
|
||||
}
|
||||
|
||||
#[derive(ApiResponse)]
|
||||
enum AddUserRoleResponse {
|
||||
#[oai(status = 201)]
|
||||
Created,
|
||||
#[oai(status = 409)]
|
||||
AlreadyExists,
|
||||
}
|
||||
|
||||
#[derive(ApiResponse)]
|
||||
enum DeleteUserRoleResponse {
|
||||
#[oai(status = 204)]
|
||||
Deleted,
|
||||
#[oai(status = 404)]
|
||||
NotFound,
|
||||
}
|
||||
|
||||
pub struct RolesApi;
|
||||
|
||||
#[OpenApi]
|
||||
impl RolesApi {
|
||||
#[oai(
|
||||
path = "/users/:id/roles",
|
||||
method = "get",
|
||||
operation_id = "get_user_roles"
|
||||
)]
|
||||
async fn api_get_user_roles(
|
||||
&self,
|
||||
db: Data<&Arc<Mutex<DatabaseConnection>>>,
|
||||
id: Path<Uuid>,
|
||||
) -> poem::Result<GetUserRolesResponse> {
|
||||
let db = db.lock().await;
|
||||
|
||||
let Some((_, roles)) = User::Entity::find_by_id(*id)
|
||||
.find_with_related(Role::Entity)
|
||||
.all(&*db)
|
||||
.await
|
||||
.map(|x| x.into_iter().next())
|
||||
.map_err(WarpgateError::from)? else {
|
||||
return Ok(GetUserRolesResponse::NotFound)
|
||||
};
|
||||
|
||||
Ok(GetUserRolesResponse::Ok(Json(
|
||||
roles.into_iter().map(|x| x.into()).collect(),
|
||||
)))
|
||||
}
|
||||
|
||||
#[oai(
|
||||
path = "/users/:id/roles/:role_id",
|
||||
method = "post",
|
||||
operation_id = "add_user_role"
|
||||
)]
|
||||
async fn api_add_user_role(
|
||||
&self,
|
||||
db: Data<&Arc<Mutex<DatabaseConnection>>>,
|
||||
id: Path<Uuid>,
|
||||
role_id: Path<Uuid>,
|
||||
) -> poem::Result<AddUserRoleResponse> {
|
||||
let db = db.lock().await;
|
||||
|
||||
if !UserRoleAssignment::Entity::find()
|
||||
.filter(UserRoleAssignment::Column::UserId.eq(id.0.clone()))
|
||||
.filter(UserRoleAssignment::Column::RoleId.eq(role_id.0.clone()))
|
||||
.all(&*db)
|
||||
.await
|
||||
.map_err(WarpgateError::from)?
|
||||
.is_empty()
|
||||
{
|
||||
return Ok(AddUserRoleResponse::AlreadyExists);
|
||||
}
|
||||
|
||||
let values = UserRoleAssignment::ActiveModel {
|
||||
user_id: Set(id.0),
|
||||
role_id: Set(role_id.0),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
values.insert(&*db).await.map_err(WarpgateError::from)?;
|
||||
|
||||
Ok(AddUserRoleResponse::Created)
|
||||
}
|
||||
|
||||
#[oai(
|
||||
path = "/users/:id/roles/:role_id",
|
||||
method = "delete",
|
||||
operation_id = "delete_user_role"
|
||||
)]
|
||||
async fn api_delete_user_role(
|
||||
&self,
|
||||
db: Data<&Arc<Mutex<DatabaseConnection>>>,
|
||||
id: Path<Uuid>,
|
||||
role_id: Path<Uuid>,
|
||||
) -> poem::Result<DeleteUserRoleResponse> {
|
||||
let db = db.lock().await;
|
||||
|
||||
let Some(user) = User::Entity::find_by_id(id.0)
|
||||
.one(&*db)
|
||||
.await
|
||||
.map_err(poem::error::InternalServerError)? else {
|
||||
return Ok(DeleteUserRoleResponse::NotFound);
|
||||
};
|
||||
|
||||
let Some(role) = Role::Entity::find_by_id(role_id.0)
|
||||
.one(&*db)
|
||||
.await
|
||||
.map_err(poem::error::InternalServerError)? else {
|
||||
return Ok(DeleteUserRoleResponse::NotFound);
|
||||
};
|
||||
|
||||
let Some(model) = UserRoleAssignment::Entity::find()
|
||||
.filter(UserRoleAssignment::Column::UserId.eq(id.0))
|
||||
.filter(UserRoleAssignment::Column::RoleId.eq(role_id.0))
|
||||
.one(&*db)
|
||||
.await
|
||||
.map_err(WarpgateError::from)? else {
|
||||
return Ok(DeleteUserRoleResponse::NotFound);
|
||||
};
|
||||
|
||||
model.delete(&*db).await.map_err(WarpgateError::from)?;
|
||||
|
||||
Ok(DeleteUserRoleResponse::Deleted)
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use poem::web::Data;
|
||||
use poem_openapi::payload::Json;
|
||||
use poem_openapi::{ApiResponse, OpenApi};
|
||||
use tokio::sync::Mutex;
|
||||
use warpgate_core::{ConfigProvider, UserSnapshot};
|
||||
|
||||
pub struct Api;
|
||||
|
||||
#[derive(ApiResponse)]
|
||||
enum GetUsersResponse {
|
||||
#[oai(status = 200)]
|
||||
Ok(Json<Vec<UserSnapshot>>),
|
||||
}
|
||||
|
||||
#[OpenApi]
|
||||
impl Api {
|
||||
#[oai(path = "/users", method = "get", operation_id = "get_users")]
|
||||
async fn api_get_all_users(
|
||||
&self,
|
||||
config_provider: Data<&Arc<Mutex<dyn ConfigProvider + Send>>>,
|
||||
) -> poem::Result<GetUsersResponse> {
|
||||
let mut users = config_provider.lock().await.list_users().await?;
|
||||
users.sort_by(|a, b| a.username.cmp(&b.username));
|
||||
Ok(GetUsersResponse::Ok(Json(users)))
|
||||
}
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
use bytes::Bytes;
|
||||
use poem_openapi::Enum;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::Secret;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, Hash, Enum)]
|
||||
pub enum CredentialKind {
|
||||
#[serde(rename = "password")]
|
||||
Password,
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::path::PathBuf;
|
|||
use std::time::Duration;
|
||||
|
||||
use defaults::*;
|
||||
use poem_openapi::Object;
|
||||
use poem_openapi::{Object, Union};
|
||||
use serde::{Deserialize, Serialize};
|
||||
pub use target::*;
|
||||
use url::Url;
|
||||
|
@ -16,37 +16,51 @@ use crate::auth::CredentialKind;
|
|||
use crate::helpers::otp::OtpSecretKey;
|
||||
use crate::{ListenEndpoint, Secret, WarpgateError};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Union)]
|
||||
#[serde(tag = "type")]
|
||||
#[oai(discriminator_name = "kind", one_of)]
|
||||
pub enum UserAuthCredential {
|
||||
#[serde(rename = "password")]
|
||||
Password { hash: Secret<String> },
|
||||
Password(UserPasswordCredential),
|
||||
#[serde(rename = "publickey")]
|
||||
PublicKey { key: Secret<String> },
|
||||
PublicKey(UserPublicKeyCredential),
|
||||
#[serde(rename = "otp")]
|
||||
Totp {
|
||||
#[serde(with = "crate::helpers::serde_base64_secret")]
|
||||
key: OtpSecretKey,
|
||||
},
|
||||
Totp(UserTotpCredential),
|
||||
#[serde(rename = "sso")]
|
||||
Sso {
|
||||
provider: Option<String>,
|
||||
email: String,
|
||||
},
|
||||
Sso(UserSsoCredential),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Object)]
|
||||
pub struct UserPasswordCredential {
|
||||
pub hash: Secret<String>,
|
||||
}
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Object)]
|
||||
pub struct UserPublicKeyCredential {
|
||||
pub key: Secret<String>,
|
||||
}
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Object)]
|
||||
pub struct UserTotpCredential {
|
||||
#[serde(with = "crate::helpers::serde_base64_secret")]
|
||||
pub key: OtpSecretKey,
|
||||
}
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, Object)]
|
||||
pub struct UserSsoCredential {
|
||||
pub provider: Option<String>,
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
impl UserAuthCredential {
|
||||
pub fn kind(&self) -> CredentialKind {
|
||||
match self {
|
||||
Self::Password { .. } => CredentialKind::Password,
|
||||
Self::PublicKey { .. } => CredentialKind::PublicKey,
|
||||
Self::Totp { .. } => CredentialKind::Otp,
|
||||
Self::Sso { .. } => CredentialKind::Sso,
|
||||
Self::Password(_)=> CredentialKind::Password,
|
||||
Self::PublicKey(_)=> CredentialKind::PublicKey,
|
||||
Self::Totp(_)=> CredentialKind::Otp,
|
||||
Self::Sso(_)=> CredentialKind::Sso,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Object)]
|
||||
pub struct UserRequireCredentialsPolicy {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub http: Option<Vec<CredentialKind>>,
|
||||
|
@ -56,8 +70,10 @@ pub struct UserRequireCredentialsPolicy {
|
|||
pub mysql: Option<Vec<CredentialKind>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Object)]
|
||||
pub struct User {
|
||||
#[serde(default)]
|
||||
pub id: Uuid,
|
||||
pub username: String,
|
||||
pub credentials: Vec<UserAuthCredential>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
use std::time::SystemTime;
|
||||
|
||||
use bytes::Bytes;
|
||||
use rand::Rng;
|
||||
use totp_rs::{Algorithm, TOTP};
|
||||
|
||||
use super::rng::get_crypto_rng;
|
||||
use crate::types::Secret;
|
||||
|
||||
pub type OtpExposedSecretKey = Bytes;
|
||||
pub type OtpExposedSecretKey = Vec<u8>;
|
||||
pub type OtpSecretKey = Secret<OtpExposedSecretKey>;
|
||||
|
||||
pub fn generate_key() -> OtpSecretKey {
|
||||
Secret::new(Bytes::from_iter(get_crypto_rng().gen::<[u8; 32]>()))
|
||||
Secret::new(get_crypto_rng().gen::<[u8; 32]>().into())
|
||||
}
|
||||
|
||||
pub fn generate_setup_url(key: &OtpSecretKey, label: &str) -> Secret<String> {
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
use bytes::Bytes;
|
||||
use serde::Serializer;
|
||||
|
||||
use super::serde_base64;
|
||||
use crate::Secret;
|
||||
|
||||
pub fn serialize<S: Serializer>(secret: &Secret<Bytes>, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serde_base64::serialize(secret.expose_secret().as_ref(), serializer)
|
||||
pub fn serialize<S: Serializer>(secret: &Secret<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serde_base64::serialize(secret.expose_secret(), serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D: serde::Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> Result<Secret<Bytes>, D::Error> {
|
||||
) -> Result<Secret<Vec<u8>>, D::Error> {
|
||||
let inner = serde_base64::deserialize(deserializer)?;
|
||||
Ok(Secret::new(inner))
|
||||
}
|
||||
|
|
|
@ -94,6 +94,7 @@ impl<T: poem_openapi::types::Type> poem_openapi::types::Type for Secret<T> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
impl<T: ParseFromJSON> ParseFromJSON for Secret<T> {
|
||||
fn parse_from_json(value: Option<serde_json::Value>) -> poem_openapi::types::ParseResult<Self> {
|
||||
T::parse_from_json(value)
|
||||
|
|
|
@ -13,43 +13,37 @@ use warpgate_common::auth::{
|
|||
use warpgate_common::helpers::hash::verify_password_hash;
|
||||
use warpgate_common::helpers::otp::verify_totp;
|
||||
use warpgate_common::{
|
||||
Role as RoleConfig, Target as TargetConfig, User, UserAuthCredential, WarpgateConfig,
|
||||
Role as RoleConfig, Target as TargetConfig, User as UserConfig, UserAuthCredential,
|
||||
UserPasswordCredential, UserPublicKeyCredential, UserSsoCredential, UserTotpCredential,
|
||||
WarpgateError,
|
||||
};
|
||||
use warpgate_db_entities::{Role, Target};
|
||||
use warpgate_db_entities::{Role, Target, User};
|
||||
|
||||
use super::ConfigProvider;
|
||||
use crate::UserSnapshot;
|
||||
|
||||
pub struct DatabaseConfigProvider {
|
||||
db: Arc<Mutex<DatabaseConnection>>,
|
||||
config: Arc<Mutex<WarpgateConfig>>,
|
||||
}
|
||||
|
||||
impl DatabaseConfigProvider {
|
||||
pub async fn new(
|
||||
db: &Arc<Mutex<DatabaseConnection>>,
|
||||
config: &Arc<Mutex<WarpgateConfig>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
db: db.clone(),
|
||||
config: config.clone(),
|
||||
}
|
||||
pub async fn new(db: &Arc<Mutex<DatabaseConnection>>) -> Self {
|
||||
Self { db: db.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ConfigProvider for DatabaseConfigProvider {
|
||||
async fn list_users(&mut self) -> Result<Vec<UserSnapshot>, WarpgateError> {
|
||||
Ok(self
|
||||
.config
|
||||
.lock()
|
||||
.await
|
||||
.store
|
||||
.users
|
||||
.iter()
|
||||
.map(UserSnapshot::new)
|
||||
.collect::<Vec<_>>())
|
||||
async fn list_users(&mut self) -> Result<Vec<UserConfig>, WarpgateError> {
|
||||
let db = self.db.lock().await;
|
||||
|
||||
let users = User::Entity::find()
|
||||
.order_by_asc(User::Column::Username)
|
||||
.all(&*db)
|
||||
.await?;
|
||||
|
||||
let users: Result<Vec<UserConfig>, _> = users.into_iter().map(|t| t.try_into()).collect();
|
||||
|
||||
Ok(users?)
|
||||
}
|
||||
|
||||
async fn list_targets(&mut self) -> Result<Vec<TargetConfig>, WarpgateError> {
|
||||
|
@ -70,21 +64,20 @@ impl ConfigProvider for DatabaseConfigProvider {
|
|||
&mut self,
|
||||
username: &str,
|
||||
) -> Result<Option<Box<dyn CredentialPolicy + Sync + Send>>, WarpgateError> {
|
||||
let user = {
|
||||
self.config
|
||||
.lock()
|
||||
.await
|
||||
.store
|
||||
.users
|
||||
.iter()
|
||||
.find(|x| x.username == username)
|
||||
.map(User::to_owned)
|
||||
};
|
||||
let Some(user) = user else {
|
||||
let db = self.db.lock().await;
|
||||
|
||||
let user_model = User::Entity::find()
|
||||
.filter(User::Column::Username.eq(username))
|
||||
.one(&*db)
|
||||
.await?;
|
||||
|
||||
let Some(user_model) = user_model else {
|
||||
error!("Selected user not found: {}", username);
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let user: UserConfig = user_model.try_into()?;
|
||||
|
||||
let supported_credential_types: HashSet<CredentialKind> =
|
||||
user.credentials.iter().map(|x| x.kind()).collect();
|
||||
let default_policy = Box::new(AnySingleCredentialPolicy {
|
||||
|
@ -142,15 +135,12 @@ impl ConfigProvider for DatabaseConfigProvider {
|
|||
};
|
||||
|
||||
Ok(self
|
||||
.config
|
||||
.lock()
|
||||
.await
|
||||
.store
|
||||
.users
|
||||
.list_users()
|
||||
.await?
|
||||
.iter()
|
||||
.find(|x| {
|
||||
for cred in x.credentials.iter() {
|
||||
if let UserAuthCredential::Sso { provider, email } = cred {
|
||||
if let UserAuthCredential::Sso(UserSsoCredential { provider, email }) = cred {
|
||||
if provider.as_ref().unwrap_or(client_provider) == client_provider
|
||||
&& email == client_email
|
||||
{
|
||||
|
@ -168,20 +158,19 @@ impl ConfigProvider for DatabaseConfigProvider {
|
|||
username: &str,
|
||||
client_credential: &AuthCredential,
|
||||
) -> Result<bool, WarpgateError> {
|
||||
let user = {
|
||||
self.config
|
||||
.lock()
|
||||
.await
|
||||
.store
|
||||
.users
|
||||
.iter()
|
||||
.find(|x| x.username == username)
|
||||
.map(User::to_owned)
|
||||
};
|
||||
let Some(user) = user else {
|
||||
error!("Selected user not found: {}", username);
|
||||
return Ok(false);
|
||||
};
|
||||
let db = self.db.lock().await;
|
||||
|
||||
let user_model = User::Entity::find()
|
||||
.filter(User::Column::Username.eq(username))
|
||||
.one(&*db)
|
||||
.await?;
|
||||
|
||||
let Some(user_model) = user_model else {
|
||||
error!("Selected user not found: {}", username);
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
let user: UserConfig = user_model.try_into()?;
|
||||
|
||||
match client_credential {
|
||||
AuthCredential::PublicKey {
|
||||
|
@ -194,17 +183,17 @@ impl ConfigProvider for DatabaseConfigProvider {
|
|||
debug!(username = &user.username[..], "Client key: {}", client_key);
|
||||
|
||||
return Ok(user.credentials.iter().any(|credential| match credential {
|
||||
UserAuthCredential::PublicKey { key: ref user_key } => {
|
||||
&client_key == user_key.expose_secret()
|
||||
}
|
||||
UserAuthCredential::PublicKey(UserPublicKeyCredential {
|
||||
key: ref user_key,
|
||||
}) => &client_key == user_key.expose_secret(),
|
||||
_ => false,
|
||||
}));
|
||||
}
|
||||
AuthCredential::Password(client_password) => {
|
||||
return Ok(user.credentials.iter().any(|credential| match credential {
|
||||
UserAuthCredential::Password {
|
||||
UserAuthCredential::Password(UserPasswordCredential {
|
||||
hash: ref user_password_hash,
|
||||
} => verify_password_hash(
|
||||
}) => verify_password_hash(
|
||||
client_password.expose_secret(),
|
||||
user_password_hash.expose_secret(),
|
||||
)
|
||||
|
@ -220,9 +209,9 @@ impl ConfigProvider for DatabaseConfigProvider {
|
|||
}
|
||||
AuthCredential::Otp(client_otp) => {
|
||||
return Ok(user.credentials.iter().any(|credential| match credential {
|
||||
UserAuthCredential::Totp {
|
||||
UserAuthCredential::Totp(UserTotpCredential {
|
||||
key: ref user_otp_key,
|
||||
} => verify_totp(client_otp.expose_secret(), user_otp_key),
|
||||
}) => verify_totp(client_otp.expose_secret(), user_otp_key),
|
||||
_ => false,
|
||||
}))
|
||||
}
|
||||
|
@ -231,10 +220,10 @@ impl ConfigProvider for DatabaseConfigProvider {
|
|||
email: client_email,
|
||||
} => {
|
||||
for credential in user.credentials.iter() {
|
||||
if let UserAuthCredential::Sso {
|
||||
if let UserAuthCredential::Sso(UserSsoCredential {
|
||||
ref provider,
|
||||
ref email,
|
||||
} = credential
|
||||
}) = credential
|
||||
{
|
||||
if provider.as_ref().unwrap_or(client_provider) == client_provider {
|
||||
return Ok(email == client_email);
|
||||
|
@ -252,23 +241,19 @@ impl ConfigProvider for DatabaseConfigProvider {
|
|||
username: &str,
|
||||
target_name: &str,
|
||||
) -> Result<bool, WarpgateError> {
|
||||
let config = self.config.lock().await;
|
||||
let user = config
|
||||
.store
|
||||
.users
|
||||
.iter()
|
||||
.find(|x| x.username == username)
|
||||
.map(User::to_owned);
|
||||
|
||||
let db = self.db.lock().await;
|
||||
|
||||
let target_model: Option<Target::Model> = Target::Entity::find()
|
||||
.order_by_desc(Target::Column::Name)
|
||||
.filter(Target::Column::Name.eq(target_name))
|
||||
.one(&*db)
|
||||
.await?;
|
||||
|
||||
let Some(user) = user else {
|
||||
let user_model = User::Entity::find()
|
||||
.filter(User::Column::Username.eq(username))
|
||||
.one(&*db)
|
||||
.await?;
|
||||
|
||||
let Some(user_model) = user_model else {
|
||||
error!("Selected user not found: {}", username);
|
||||
return Ok(false);
|
||||
};
|
||||
|
@ -287,8 +272,8 @@ impl ConfigProvider for DatabaseConfigProvider {
|
|||
.map(|x| x.name)
|
||||
.collect();
|
||||
|
||||
let user_roles: HashSet<String> = Role::Entity::find()
|
||||
.filter(Role::Column::Name.is_in(user.roles))
|
||||
let user_roles: HashSet<String> = user_model
|
||||
.find_related(Role::Entity)
|
||||
.all(&*db)
|
||||
.await?
|
||||
.into_iter()
|
||||
|
|
|
@ -11,10 +11,12 @@ use warpgate_common::auth::{
|
|||
};
|
||||
use warpgate_common::helpers::hash::verify_password_hash;
|
||||
use warpgate_common::helpers::otp::verify_totp;
|
||||
use warpgate_common::{Target, User, UserAuthCredential, WarpgateConfig, WarpgateError};
|
||||
use warpgate_common::{
|
||||
Target, User, UserAuthCredential, UserPasswordCredential, UserPublicKeyCredential,
|
||||
UserSsoCredential, UserTotpCredential, WarpgateConfig, WarpgateError,
|
||||
};
|
||||
|
||||
use super::ConfigProvider;
|
||||
use crate::UserSnapshot;
|
||||
|
||||
pub struct FileConfigProvider {
|
||||
config: Arc<Mutex<WarpgateConfig>>,
|
||||
|
@ -30,7 +32,7 @@ impl FileConfigProvider {
|
|||
|
||||
#[async_trait]
|
||||
impl ConfigProvider for FileConfigProvider {
|
||||
async fn list_users(&mut self) -> Result<Vec<UserSnapshot>, WarpgateError> {
|
||||
async fn list_users(&mut self) -> Result<Vec<User>, WarpgateError> {
|
||||
Ok(self
|
||||
.config
|
||||
.lock()
|
||||
|
@ -38,7 +40,7 @@ impl ConfigProvider for FileConfigProvider {
|
|||
.store
|
||||
.users
|
||||
.iter()
|
||||
.map(UserSnapshot::new)
|
||||
.map(Clone::clone)
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
|
@ -138,7 +140,7 @@ impl ConfigProvider for FileConfigProvider {
|
|||
.iter()
|
||||
.find(|x| {
|
||||
for cred in x.credentials.iter() {
|
||||
if let UserAuthCredential::Sso { provider, email } = cred {
|
||||
if let UserAuthCredential::Sso(UserSsoCredential { provider, email }) = cred {
|
||||
if provider.as_ref().unwrap_or(client_provider) == client_provider
|
||||
&& email == client_email
|
||||
{
|
||||
|
@ -182,17 +184,17 @@ impl ConfigProvider for FileConfigProvider {
|
|||
debug!(username = &user.username[..], "Client key: {}", client_key);
|
||||
|
||||
return Ok(user.credentials.iter().any(|credential| match credential {
|
||||
UserAuthCredential::PublicKey { key: ref user_key } => {
|
||||
&client_key == user_key.expose_secret()
|
||||
}
|
||||
UserAuthCredential::PublicKey(UserPublicKeyCredential {
|
||||
key: ref user_key,
|
||||
}) => &client_key == user_key.expose_secret(),
|
||||
_ => false,
|
||||
}));
|
||||
}
|
||||
AuthCredential::Password(client_password) => {
|
||||
return Ok(user.credentials.iter().any(|credential| match credential {
|
||||
UserAuthCredential::Password {
|
||||
UserAuthCredential::Password(UserPasswordCredential {
|
||||
hash: ref user_password_hash,
|
||||
} => verify_password_hash(
|
||||
}) => verify_password_hash(
|
||||
client_password.expose_secret(),
|
||||
user_password_hash.expose_secret(),
|
||||
)
|
||||
|
@ -208,9 +210,9 @@ impl ConfigProvider for FileConfigProvider {
|
|||
}
|
||||
AuthCredential::Otp(client_otp) => {
|
||||
return Ok(user.credentials.iter().any(|credential| match credential {
|
||||
UserAuthCredential::Totp {
|
||||
UserAuthCredential::Totp(UserTotpCredential {
|
||||
key: ref user_otp_key,
|
||||
} => verify_totp(client_otp.expose_secret(), user_otp_key),
|
||||
}) => verify_totp(client_otp.expose_secret(), user_otp_key),
|
||||
_ => false,
|
||||
}))
|
||||
}
|
||||
|
@ -219,10 +221,10 @@ impl ConfigProvider for FileConfigProvider {
|
|||
email: client_email,
|
||||
} => {
|
||||
for credential in user.credentials.iter() {
|
||||
if let UserAuthCredential::Sso {
|
||||
if let UserAuthCredential::Sso(UserSsoCredential {
|
||||
ref provider,
|
||||
ref email,
|
||||
} = credential
|
||||
}) = credential
|
||||
{
|
||||
if provider.as_ref().unwrap_or(client_provider) == client_provider {
|
||||
return Ok(email == client_email);
|
||||
|
|
|
@ -11,14 +11,12 @@ use tokio::sync::Mutex;
|
|||
use tracing::*;
|
||||
use uuid::Uuid;
|
||||
use warpgate_common::auth::{AuthCredential, CredentialPolicy};
|
||||
use warpgate_common::{Secret, Target, WarpgateError};
|
||||
use warpgate_common::{Secret, Target, WarpgateError, User};
|
||||
use warpgate_db_entities::Ticket;
|
||||
|
||||
use crate::UserSnapshot;
|
||||
|
||||
#[async_trait]
|
||||
pub trait ConfigProvider {
|
||||
async fn list_users(&mut self) -> Result<Vec<UserSnapshot>, WarpgateError>;
|
||||
async fn list_users(&mut self) -> Result<Vec<User>, WarpgateError>;
|
||||
|
||||
async fn list_targets(&mut self) -> Result<Vec<Target>, WarpgateError>;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ use chrono::{DateTime, Utc};
|
|||
use poem_openapi::Object;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
use warpgate_common::{SessionId, Target, User};
|
||||
use warpgate_common::{SessionId, Target};
|
||||
use warpgate_db_entities::Session;
|
||||
|
||||
#[derive(Serialize, Deserialize, Object)]
|
||||
|
@ -31,16 +31,3 @@ impl From<Session::Model> for SessionSnapshot {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Object)]
|
||||
pub struct UserSnapshot {
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
impl UserSnapshot {
|
||||
pub fn new(user: &User) -> Self {
|
||||
Self {
|
||||
username: user.username.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,20 @@
|
|||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use tracing::*;
|
||||
|
||||
use anyhow::Result;
|
||||
use sea_orm::sea_query::Expr;
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ColumnTrait, ConnectOptions, Database, DatabaseConnection, EntityTrait,
|
||||
QueryFilter, TransactionTrait,
|
||||
};
|
||||
use tracing::*;
|
||||
use uuid::Uuid;
|
||||
use warpgate_common::helpers::fs::secure_file;
|
||||
use warpgate_common::{TargetOptions, TargetWebAdminOptions, WarpgateConfig, WarpgateError};
|
||||
use warpgate_db_entities::Target::TargetKind;
|
||||
use warpgate_db_entities::{LogEntry, Role, Target, TargetRoleAssignment};
|
||||
use warpgate_db_entities::{
|
||||
LogEntry, Role, Target, TargetRoleAssignment, User, UserRoleAssignment,
|
||||
};
|
||||
use warpgate_db_migrations::migrate_database;
|
||||
|
||||
use crate::consts::{BUILTIN_ADMIN_ROLE_NAME, BUILTIN_ADMIN_TARGET_NAME};
|
||||
|
@ -158,7 +161,6 @@ async fn migrate_config_into_db(
|
|||
info!("Migrating config file into the database");
|
||||
|
||||
let mut role_lookup = HashMap::new();
|
||||
let mut target_lookup = HashMap::new();
|
||||
|
||||
for role_config in config.store.roles.iter() {
|
||||
let role = match Role::Entity::find()
|
||||
|
@ -206,7 +208,6 @@ async fn migrate_config_into_db(
|
|||
values.insert(&*db).await.map_err(WarpgateError::from)?
|
||||
}
|
||||
};
|
||||
target_lookup.insert(target_config.name.clone(), target.id);
|
||||
|
||||
for role_name in target_config.allow_roles.iter() {
|
||||
if let Some(role_id) = role_lookup.get(role_name) {
|
||||
|
@ -229,6 +230,50 @@ async fn migrate_config_into_db(
|
|||
}
|
||||
config.store.targets = vec![];
|
||||
|
||||
for user_config in config.store.users.iter() {
|
||||
let user = match User::Entity::find()
|
||||
.filter(User::Column::Username.eq(user_config.username.clone()))
|
||||
.all(db)
|
||||
.await?
|
||||
.first()
|
||||
{
|
||||
Some(x) => x.to_owned(),
|
||||
None => {
|
||||
let values = User::ActiveModel {
|
||||
id: Set(Uuid::new_v4()),
|
||||
username: Set(user_config.username.clone()),
|
||||
credentials: Set(serde_json::to_value(user_config.credentials.clone())
|
||||
.map_err(WarpgateError::from)?),
|
||||
credential_policy: Set(serde_json::to_value(user_config.require.clone())
|
||||
.map_err(WarpgateError::from)?),
|
||||
};
|
||||
|
||||
info!("Migrating user {}", user_config.username);
|
||||
values.insert(&*db).await.map_err(WarpgateError::from)?
|
||||
}
|
||||
};
|
||||
|
||||
for role_name in user_config.roles.iter() {
|
||||
if let Some(role_id) = role_lookup.get(role_name) {
|
||||
if UserRoleAssignment::Entity::find()
|
||||
.filter(UserRoleAssignment::Column::UserId.eq(user.id))
|
||||
.filter(UserRoleAssignment::Column::RoleId.eq(*role_id))
|
||||
.all(db)
|
||||
.await?
|
||||
.is_empty()
|
||||
{
|
||||
let values = UserRoleAssignment::ActiveModel {
|
||||
user_id: Set(user.id),
|
||||
role_id: Set(*role_id),
|
||||
..Default::default()
|
||||
};
|
||||
values.insert(&*db).await.map_err(WarpgateError::from)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
config.store.users = vec![];
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
use std::io::Write;
|
||||
|
||||
use crate::UUID;
|
||||
|
||||
impl<B: Backend> FromSql<Binary, B> for UUID
|
||||
where
|
||||
Vec<u8>: FromSql<Binary, B>,
|
||||
{
|
||||
fn from_sql(bytes: Option<&B::RawValue>) -> diesel::deserialize::Result<Self> {
|
||||
let value = <Vec<u8>>::from_sql(bytes)?;
|
||||
Ok(UUID::from_bytes(&value)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Backend> ToSql<Binary, B> for UUID
|
||||
where
|
||||
[u8]: ToSql<Binary, B>,
|
||||
{
|
||||
fn to_sql<W: Write>(
|
||||
&self,
|
||||
out: &mut diesel::serialize::Output<W, B>,
|
||||
) -> diesel::serialize::Result {
|
||||
let bytes = self.0.as_bytes();
|
||||
<[u8] as ToSql<Binary, B>>::to_sql(bytes, out)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsExpression<Binary> for UUID {
|
||||
type Expression = Bound<Binary, UUID>;
|
||||
|
||||
fn as_expression(self) -> Self::Expression {
|
||||
Bound::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsExpression<Binary> for &'a UUID {
|
||||
type Expression = Bound<Binary, &'a UUID>;
|
||||
|
||||
fn as_expression(self) -> Self::Expression {
|
||||
Bound::new(self)
|
||||
}
|
||||
}
|
||||
// impl Expression for UUID {
|
||||
// type SqlType = diesel::sql_types::Binary;
|
||||
// }
|
|
@ -39,8 +39,7 @@ impl Services {
|
|||
Arc::new(Mutex::new(FileConfigProvider::new(&config).await)) as ConfigProviderArc
|
||||
}
|
||||
ConfigProviderKind::Database => {
|
||||
Arc::new(Mutex::new(DatabaseConfigProvider::new(&db, &config).await))
|
||||
as ConfigProviderArc
|
||||
Arc::new(Mutex::new(DatabaseConfigProvider::new(&db).await)) as ConfigProviderArc
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ use uuid::Uuid;
|
|||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Object)]
|
||||
#[sea_orm(table_name = "target_roles")]
|
||||
#[oai(rename = "Target")]
|
||||
#[oai(rename = "TargetRoleAssignment")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: u32,
|
||||
|
|
48
warpgate-db-entities/src/User.rs
Normal file
48
warpgate-db-entities/src/User.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
use poem_openapi::Object;
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
use warpgate_common::{User, UserAuthCredential, UserRequireCredentialsPolicy};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Object)]
|
||||
#[sea_orm(table_name = "users")]
|
||||
#[oai(rename = "User")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub username: String,
|
||||
pub credentials: serde_json::Value,
|
||||
pub credential_policy: serde_json::Value,
|
||||
}
|
||||
|
||||
impl Related<super::Role::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
super::UserRoleAssignment::Relation::Role.def()
|
||||
}
|
||||
|
||||
fn via() -> Option<RelationDef> {
|
||||
Some(super::UserRoleAssignment::Relation::User.def().rev())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
impl TryFrom<Model> for User {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(model: Model) -> Result<Self, Self::Error> {
|
||||
let credentials: Vec<UserAuthCredential> = serde_json::from_value(model.credentials)?;
|
||||
let credential_policy: Option<UserRequireCredentialsPolicy> =
|
||||
serde_json::from_value(model.credential_policy)?;
|
||||
Ok(Self {
|
||||
id: model.id,
|
||||
username: model.username,
|
||||
roles: vec![],
|
||||
credentials,
|
||||
require: credential_policy,
|
||||
})
|
||||
}
|
||||
}
|
37
warpgate-db-entities/src/UserRoleAssignment.rs
Normal file
37
warpgate-db-entities/src/UserRoleAssignment.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
use poem_openapi::Object;
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Object)]
|
||||
#[sea_orm(table_name = "user_roles")]
|
||||
#[oai(rename = "UserRoleAssignment")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: u32,
|
||||
pub user_id: Uuid,
|
||||
pub role_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||
pub enum Relation {
|
||||
User,
|
||||
Role,
|
||||
}
|
||||
|
||||
impl RelationTrait for Relation {
|
||||
fn def(&self) -> RelationDef {
|
||||
match self {
|
||||
Self::User => Entity::belongs_to(super::User::Entity)
|
||||
.from(Column::UserId)
|
||||
.to(super::User::Column::Id)
|
||||
.into(),
|
||||
Self::Role => Entity::belongs_to(super::Role::Entity)
|
||||
.from(Column::RoleId)
|
||||
.to(super::Role::Column::Id)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
|
@ -8,3 +8,5 @@ pub mod Session;
|
|||
pub mod Target;
|
||||
pub mod TargetRoleAssignment;
|
||||
pub mod Ticket;
|
||||
pub mod User;
|
||||
pub mod UserRoleAssignment;
|
||||
|
|
|
@ -9,6 +9,7 @@ mod m00004_create_known_host;
|
|||
mod m00005_create_log_entry;
|
||||
mod m00006_add_session_protocol;
|
||||
mod m00007_targets_and_roles;
|
||||
mod m00008_users;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
|
@ -23,6 +24,7 @@ impl MigratorTrait for Migrator {
|
|||
Box::new(m00005_create_log_entry::Migration),
|
||||
Box::new(m00006_add_session_protocol::Migration),
|
||||
Box::new(m00007_targets_and_roles::Migration),
|
||||
Box::new(m00008_users::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use sea_orm::Schema;
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
mod role {
|
||||
pub(crate) mod role {
|
||||
use sea_orm::entity::prelude::*;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
|
102
warpgate-db-migrations/src/m00008_users.rs
Normal file
102
warpgate-db-migrations/src/m00008_users.rs
Normal file
|
@ -0,0 +1,102 @@
|
|||
use sea_orm::Schema;
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
mod user {
|
||||
use sea_orm::entity::prelude::*;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "users")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub username: String,
|
||||
pub credentials: serde_json::Value,
|
||||
pub credential_policy: serde_json::Value,
|
||||
}
|
||||
|
||||
impl Related<crate::m00007_targets_and_roles::role::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
super::user_role_assignment::Relation::User.def()
|
||||
}
|
||||
|
||||
fn via() -> Option<RelationDef> {
|
||||
Some(super::user_role_assignment::Relation::Role.def().rev())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
}
|
||||
|
||||
mod user_role_assignment {
|
||||
use sea_orm::entity::prelude::*;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "user_roles")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: u32,
|
||||
pub user_id: Uuid,
|
||||
pub role_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||
pub enum Relation {
|
||||
User,
|
||||
Role,
|
||||
}
|
||||
|
||||
impl RelationTrait for Relation {
|
||||
fn def(&self) -> RelationDef {
|
||||
match self {
|
||||
Self::User => Entity::belongs_to(super::user::Entity)
|
||||
.from(Column::UserId)
|
||||
.to(super::user::Column::Id)
|
||||
.into(),
|
||||
Self::Role => Entity::belongs_to(crate::m00007_targets_and_roles::role::Entity)
|
||||
.from(Column::RoleId)
|
||||
.to(crate::m00007_targets_and_roles::role::Column::Id)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
}
|
||||
|
||||
pub struct Migration;
|
||||
|
||||
impl MigrationName for Migration {
|
||||
fn name(&self) -> &str {
|
||||
"m00008_users"
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let builder = manager.get_database_backend();
|
||||
let schema = Schema::new(builder);
|
||||
manager
|
||||
.create_table(schema.create_table_from_entity(user::Entity))
|
||||
.await?;
|
||||
manager
|
||||
.create_table(schema.create_table_from_entity(user_role_assignment::Entity))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(user_role_assignment::Entity).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(user::Entity).to_owned())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -54,6 +54,12 @@ const routes = {
|
|||
'/roles/:id': wrap({
|
||||
asyncComponent: () => import('./Role.svelte'),
|
||||
}),
|
||||
'/users/create': wrap({
|
||||
asyncComponent: () => import('./CreateUser.svelte'),
|
||||
}),
|
||||
// '/users/:id': wrap({
|
||||
// asyncComponent: () => import('./User.svelte'),
|
||||
// }),
|
||||
'/ssh': wrap({
|
||||
asyncComponent: () => import('./SSH.svelte'),
|
||||
}),
|
||||
|
|
|
@ -4,79 +4,115 @@ import { link } from 'svelte-spa-router'
|
|||
import { Alert, Spinner } from 'sveltestrap'
|
||||
</script>
|
||||
|
||||
<div class="page-summary-bar">
|
||||
<h1>Targets</h1>
|
||||
<a
|
||||
class="btn btn-outline-secondary ms-auto"
|
||||
href="/targets/create"
|
||||
use:link>
|
||||
Add a target
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{#await api.getTargets()}
|
||||
<Spinner />
|
||||
{:then targets}
|
||||
<div class="list-group list-group-flush">
|
||||
{#each targets as target}
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-6 mb-4 pe-4">
|
||||
<div class="page-summary-bar">
|
||||
<h1>Targets</h1>
|
||||
<a
|
||||
class="list-group-item list-group-item-action"
|
||||
href="/targets/{target.id}"
|
||||
class="btn btn-outline-secondary ms-auto"
|
||||
href="/targets/create"
|
||||
use:link>
|
||||
<strong class="me-auto">
|
||||
{target.name}
|
||||
</strong>
|
||||
<small class="text-muted ms-auto">
|
||||
{#if target.options.kind === 'Http'}
|
||||
HTTP
|
||||
{/if}
|
||||
{#if target.options.kind === 'MySql'}
|
||||
MySQL
|
||||
{/if}
|
||||
{#if target.options.kind === 'Ssh'}
|
||||
SSH
|
||||
{/if}
|
||||
{#if target.options.kind === 'WebAdmin'}
|
||||
This web admin interface
|
||||
{/if}
|
||||
</small>
|
||||
Add a target
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#await api.getTargets()}
|
||||
<Spinner />
|
||||
{:then targets}
|
||||
<div class="list-group list-group-flush">
|
||||
{#each targets as target}
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
<a
|
||||
class="list-group-item list-group-item-action"
|
||||
href="/targets/{target.id}"
|
||||
use:link>
|
||||
<strong class="me-auto">
|
||||
{target.name}
|
||||
</strong>
|
||||
<small class="text-muted ms-auto">
|
||||
{#if target.options.kind === 'Http'}
|
||||
HTTP
|
||||
{/if}
|
||||
{#if target.options.kind === 'MySql'}
|
||||
MySQL
|
||||
{/if}
|
||||
{#if target.options.kind === 'Ssh'}
|
||||
SSH
|
||||
{/if}
|
||||
{#if target.options.kind === 'WebAdmin'}
|
||||
This web admin interface
|
||||
{/if}
|
||||
</small>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{:catch error}
|
||||
<Alert color="danger">{error}</Alert>
|
||||
{/await}
|
||||
</div>
|
||||
{:catch error}
|
||||
<Alert color="danger">{error}</Alert>
|
||||
{/await}
|
||||
|
||||
<div class="page-summary-bar mt-4">
|
||||
<h1>Roles</h1>
|
||||
<a
|
||||
class="btn btn-outline-secondary ms-auto"
|
||||
href="/roles/create"
|
||||
use:link>
|
||||
Add a role
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{#await api.getRoles()}
|
||||
<Spinner />
|
||||
{:then roles}
|
||||
<div class="list-group list-group-flush">
|
||||
{#each roles as role}
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
<div class="col-12 col-lg-6 pe-4">
|
||||
<div class="page-summary-bar">
|
||||
<h1>Users</h1>
|
||||
<a
|
||||
class="list-group-item list-group-item-action"
|
||||
href="/roles/{role.id}"
|
||||
class="btn btn-outline-secondary ms-auto"
|
||||
href="/users/create"
|
||||
use:link>
|
||||
<strong class="me-auto">
|
||||
{role.name}
|
||||
</strong>
|
||||
Add a user
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#await api.getUsers()}
|
||||
<Spinner />
|
||||
{:then users}
|
||||
<div class="list-group list-group-flush">
|
||||
{#each users as user}
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
<a
|
||||
class="list-group-item list-group-item-action"
|
||||
href="/users/{user.id}"
|
||||
use:link>
|
||||
<strong class="me-auto">
|
||||
{user.username}
|
||||
</strong>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{:catch error}
|
||||
<Alert color="danger">{error}</Alert>
|
||||
{/await}
|
||||
|
||||
<div class="page-summary-bar mt-4">
|
||||
<h1>Roles</h1>
|
||||
<a
|
||||
class="btn btn-outline-secondary ms-auto"
|
||||
href="/roles/create"
|
||||
use:link>
|
||||
Add a role
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{#await api.getRoles()}
|
||||
<Spinner />
|
||||
{:then roles}
|
||||
<div class="list-group list-group-flush">
|
||||
{#each roles as role}
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
<a
|
||||
class="list-group-item list-group-item-action"
|
||||
href="/roles/{role.id}"
|
||||
use:link>
|
||||
<strong class="me-auto">
|
||||
{role.name}
|
||||
</strong>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{:catch error}
|
||||
<Alert color="danger">{error}</Alert>
|
||||
{/await}
|
||||
</div>
|
||||
{:catch error}
|
||||
<Alert color="danger">{error}</Alert>
|
||||
{/await}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.list-group-item {
|
||||
|
|
46
warpgate-web/src/admin/CreateUser.svelte
Normal file
46
warpgate-web/src/admin/CreateUser.svelte
Normal file
|
@ -0,0 +1,46 @@
|
|||
<script lang="ts">
|
||||
import { api } from 'admin/lib/api'
|
||||
import AsyncButton from 'common/AsyncButton.svelte'
|
||||
import { replace } from 'svelte-spa-router'
|
||||
import { Alert, FormGroup } from 'sveltestrap'
|
||||
|
||||
let error: Error|null = null
|
||||
let username = ''
|
||||
|
||||
async function create () {
|
||||
if (!username) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const user = await api.createUser({
|
||||
userDataRequest: {
|
||||
username,
|
||||
credentials: [],
|
||||
credentialPolicy: {},
|
||||
},
|
||||
})
|
||||
replace(`/users/${user.id}`)
|
||||
} catch (err) {
|
||||
error = err
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#if error}
|
||||
<Alert color="danger">{error}</Alert>
|
||||
{/if}
|
||||
|
||||
|
||||
<div class="page-summary-bar">
|
||||
<h1>Add a user</h1>
|
||||
</div>
|
||||
|
||||
<FormGroup floating label="Username">
|
||||
<input class="form-control" bind:value={username} />
|
||||
</FormGroup>
|
||||
|
||||
<AsyncButton
|
||||
outline
|
||||
click={create}
|
||||
>Create user</AsyncButton>
|
|
@ -197,26 +197,6 @@
|
|||
"operationId": "get_recording"
|
||||
}
|
||||
},
|
||||
"/users": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/UserSnapshot"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"operationId": "get_users"
|
||||
}
|
||||
},
|
||||
"/roles": {
|
||||
"get": {
|
||||
"responses": {
|
||||
|
@ -635,6 +615,262 @@
|
|||
"operationId": "delete_target_role"
|
||||
}
|
||||
},
|
||||
"/users": {
|
||||
"get": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/User"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"operationId": "get_users"
|
||||
},
|
||||
"post": {
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UserDataRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/User"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"operationId": "create_user"
|
||||
}
|
||||
},
|
||||
"/users/{id}": {
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"deprecated": false
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/User"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"operationId": "get_user"
|
||||
},
|
||||
"put": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"deprecated": false
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UserDataRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/User"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": ""
|
||||
},
|
||||
"404": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"operationId": "update_user"
|
||||
},
|
||||
"delete": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"deprecated": false
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": ""
|
||||
},
|
||||
"404": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"operationId": "delete_user"
|
||||
}
|
||||
},
|
||||
"/users/{id}/roles": {
|
||||
"get": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"deprecated": false
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Role"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"operationId": "get_user_roles"
|
||||
}
|
||||
},
|
||||
"/users/{id}/roles/{role_id}": {
|
||||
"post": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "role_id",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"deprecated": false
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": ""
|
||||
},
|
||||
"409": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"operationId": "add_user_role"
|
||||
},
|
||||
"delete": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "role_id",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"deprecated": false
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": ""
|
||||
},
|
||||
"404": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"operationId": "delete_user_role"
|
||||
}
|
||||
},
|
||||
"/tickets": {
|
||||
"get": {
|
||||
"responses": {
|
||||
|
@ -828,6 +1064,16 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"CredentialKind": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Password",
|
||||
"PublicKey",
|
||||
"Otp",
|
||||
"Sso",
|
||||
"WebUserApproval"
|
||||
]
|
||||
},
|
||||
"GetLogsRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -1420,14 +1666,233 @@
|
|||
"Required"
|
||||
]
|
||||
},
|
||||
"UserSnapshot": {
|
||||
"User": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"username"
|
||||
"id",
|
||||
"username",
|
||||
"credentials",
|
||||
"roles"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
},
|
||||
"credentials": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/UserAuthCredential"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"$ref": "#/components/schemas/UserRequireCredentialsPolicy"
|
||||
},
|
||||
"roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"UserAuthCredential": {
|
||||
"type": "object",
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/UserAuthCredential_UserPasswordCredential"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/UserAuthCredential_UserPublicKeyCredential"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/UserAuthCredential_UserTotpCredential"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/UserAuthCredential_UserSsoCredential"
|
||||
}
|
||||
],
|
||||
"discriminator": {
|
||||
"propertyName": "kind",
|
||||
"mapping": {
|
||||
"Password": "#/components/schemas/UserAuthCredential_UserPasswordCredential",
|
||||
"PublicKey": "#/components/schemas/UserAuthCredential_UserPublicKeyCredential",
|
||||
"Totp": "#/components/schemas/UserAuthCredential_UserTotpCredential",
|
||||
"Sso": "#/components/schemas/UserAuthCredential_UserSsoCredential"
|
||||
}
|
||||
}
|
||||
},
|
||||
"UserAuthCredential_UserPasswordCredential": {
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"example": "Password"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/UserPasswordCredential"
|
||||
}
|
||||
]
|
||||
},
|
||||
"UserAuthCredential_UserPublicKeyCredential": {
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"example": "PublicKey"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/UserPublicKeyCredential"
|
||||
}
|
||||
]
|
||||
},
|
||||
"UserAuthCredential_UserSsoCredential": {
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"example": "Sso"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/UserSsoCredential"
|
||||
}
|
||||
]
|
||||
},
|
||||
"UserAuthCredential_UserTotpCredential": {
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"example": "Totp"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/UserTotpCredential"
|
||||
}
|
||||
]
|
||||
},
|
||||
"UserDataRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"username",
|
||||
"credentials"
|
||||
],
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string"
|
||||
},
|
||||
"credentials": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/UserAuthCredential"
|
||||
}
|
||||
},
|
||||
"credential_policy": {
|
||||
"$ref": "#/components/schemas/UserRequireCredentialsPolicy"
|
||||
}
|
||||
}
|
||||
},
|
||||
"UserPasswordCredential": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"hash"
|
||||
],
|
||||
"properties": {
|
||||
"hash": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"UserPublicKeyCredential": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"key"
|
||||
],
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"UserRequireCredentialsPolicy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"http": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/CredentialKind"
|
||||
}
|
||||
},
|
||||
"ssh": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/CredentialKind"
|
||||
}
|
||||
},
|
||||
"mysql": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/CredentialKind"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"UserSsoCredential": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"email"
|
||||
],
|
||||
"properties": {
|
||||
"provider": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"UserTotpCredential": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"key"
|
||||
],
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@ use uuid::Uuid;
|
|||
use warpgate_common::helpers::fs::{secure_directory, secure_file};
|
||||
use warpgate_common::helpers::hash::hash_password;
|
||||
use warpgate_common::{
|
||||
HTTPConfig, ListenEndpoint, MySQLConfig, Role, SSHConfig, Secret, Target, TargetOptions,
|
||||
TargetWebAdminOptions, User, UserAuthCredential, WarpgateConfigStore,
|
||||
HTTPConfig, ListenEndpoint, MySQLConfig, SSHConfig, Secret, User, UserAuthCredential,
|
||||
WarpgateConfigStore, UserPasswordCredential,
|
||||
};
|
||||
use warpgate_core::consts::BUILTIN_ADMIN_ROLE_NAME;
|
||||
use warpgate_core::Services;
|
||||
|
@ -74,10 +74,6 @@ pub(crate) async fn command(cli: &crate::Cli) -> Result<()> {
|
|||
|
||||
let theme = ColorfulTheme::default();
|
||||
let mut store = WarpgateConfigStore {
|
||||
roles: vec![Role {
|
||||
id: Uuid::new_v4(),
|
||||
name: BUILTIN_ADMIN_ROLE_NAME.to_owned(),
|
||||
}],
|
||||
http: HTTPConfig {
|
||||
enable: true,
|
||||
..Default::default()
|
||||
|
@ -158,15 +154,6 @@ pub(crate) async fn command(cli: &crate::Cli) -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
if store.http.enable {
|
||||
store.targets.push(Target {
|
||||
id: Uuid::new_v4(),
|
||||
name: "Web admin".to_owned(),
|
||||
allow_roles: vec![BUILTIN_ADMIN_ROLE_NAME.to_owned()],
|
||||
options: TargetOptions::WebAdmin(TargetWebAdminOptions {}),
|
||||
});
|
||||
}
|
||||
|
||||
store.http.certificate = PathBuf::from(&data_path)
|
||||
.join("tls.certificate.pem")
|
||||
.to_string_lossy()
|
||||
|
@ -205,10 +192,11 @@ pub(crate) async fn command(cli: &crate::Cli) -> Result<()> {
|
|||
.interact()?;
|
||||
|
||||
store.users.push(User {
|
||||
id: Uuid::new_v4(),
|
||||
username: "admin".into(),
|
||||
credentials: vec![UserAuthCredential::Password {
|
||||
credentials: vec![UserAuthCredential::Password(UserPasswordCredential {
|
||||
hash: Secret::new(hash_password(&password)),
|
||||
}],
|
||||
})],
|
||||
require: None,
|
||||
roles: vec![BUILTIN_ADMIN_ROLE_NAME.into()],
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue