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

10
.gitignore vendored
View File

@@ -1,4 +1,6 @@
target
node_modules
dist
.env
target
node_modules
dist
.env
config.json

110
.vscode/tasks.json vendored
View File

@@ -1,56 +1,56 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "cargo",
"command": "build",
"group": {
"kind": "build",
"isDefault": true
},
"label": "rust: cargo build"
},
{
"type": "cargo",
"command": "fmt",
"label": "rust: cargo fmt"
},
{
"type": "cargo",
"command": "check",
"label": "rust: cargo check"
},
{
"type": "cargo",
"command": "run",
"label": "rust: cargo run",
"options": {
"cwd": "${workspaceFolder}/core"
},
},
{
"type": "cargo",
"command": "run",
"label": "run periphery",
"options": {
"cwd": "${workspaceFolder}/periphery"
},
},
{
"type": "cargo",
"command": "run",
"label": "run core",
"options": {
"cwd": "${workspaceFolder}/core"
},
},
{
"type": "cargo",
"command": "run",
"label": "run cli",
"options": {
"cwd": "${workspaceFolder}/cli"
},
}
]
{
"version": "2.0.0",
"tasks": [
{
"type": "cargo",
"command": "build",
"group": {
"kind": "build",
"isDefault": true
},
"label": "rust: cargo build"
},
{
"type": "cargo",
"command": "fmt",
"label": "rust: cargo fmt"
},
{
"type": "cargo",
"command": "check",
"label": "rust: cargo check"
},
{
"type": "cargo",
"command": "run",
"label": "rust: cargo run",
"options": {
"cwd": "${workspaceFolder}/core"
},
},
{
"type": "cargo",
"command": "run",
"label": "run periphery",
"options": {
"cwd": "${workspaceFolder}/periphery"
},
},
{
"type": "cargo",
"command": "run",
"label": "run core",
"options": {
"cwd": "${workspaceFolder}/core"
},
},
{
"type": "cargo",
"command": "run",
"label": "run cli",
"options": {
"cwd": "${workspaceFolder}/cli"
},
}
]
}

5308
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
[workspace]
members = [
"cli",
"core",
"periphery",
"builder"
[workspace]
members = [
"cli",
"core",
"periphery",
"builder"
]

View File

@@ -1,20 +1,20 @@
[package]
name = "builder"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
docker = { package = "docker_client", path = "../lib/docker_client" }
git = { package = "git_client", path = "../lib/git_client" }
types = { path = "../lib/types" }
tokio = { version = "1.21", features = ["full"] }
axum = { version = "0.5", features = ["ws"] }
serde = "1.0"
serde_derive = "1.0"
mungos = "0.2.24"
anyhow = "1.0"
thiserror = "1.0"
envy = "0.4"
[package]
name = "builder"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
docker = { package = "docker_client", path = "../lib/docker_client" }
git = { package = "git_client", path = "../lib/git_client" }
types = { path = "../lib/types" }
tokio = { version = "1.21", features = ["full"] }
axum = { version = "0.5", features = ["ws"] }
serde = "1.0"
serde_derive = "1.0"
mungos = "0.2.24"
anyhow = "1.0"
thiserror = "1.0"
envy = "0.4"
dotenv = "0.15"

View File

@@ -1,22 +1,22 @@
use std::{net::SocketAddr, str::FromStr};
use dotenv::dotenv;
use mungos::Deserialize;
#[derive(Deserialize)]
struct Env {
port: u16,
}
pub fn load() {
pub fn load() -> (SocketAddr) {
dotenv().ok();
let env = envy::from_env::<Env>().unwrap();
let socket_addr = SocketAddr::from_str(&format!("0.0.0.0:{}", env.port))
.expect("failed to parse socket addr");
(socket_addr)
}
}
use std::{net::SocketAddr, str::FromStr};
use dotenv::dotenv;
use mungos::Deserialize;
#[derive(Deserialize)]
struct Env {
port: u16,
}
pub fn load() {
pub fn load() -> (SocketAddr) {
dotenv().ok();
let env = envy::from_env::<Env>().unwrap();
let socket_addr = SocketAddr::from_str(&format!("0.0.0.0:{}", env.port))
.expect("failed to parse socket addr");
(socket_addr)
}
}

View File

@@ -1,6 +1,6 @@
mod config;
#[tokio::main]
async fn main() {
let (socket_addr) = config::load();
}
mod config;
#[tokio::main]
async fn main() {
let (socket_addr) = config::load();
}

View File

@@ -1,10 +1,10 @@
[package]
name = "cli"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version="4.0", features=["derive"] }
[package]
name = "cli"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version="4.0", features=["derive"] }
bollard = "0.13"

View File

@@ -1,3 +1,3 @@
fn main() {
println!("Hello, world!");
}
fn main() {
println!("Hello, world!");
}

View File

@@ -1,31 +1,31 @@
[package]
name = "monitor-core"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
docker = { package = "docker_client", path = "../lib/docker_client" }
git = { package = "git_client", path = "../lib/git_client" }
db = { package = "db_client", path = "../lib/db_client" }
types = { path = "../lib/types" }
tokio = { version = "1.21", features = ["full"] }
axum = { version = "0.5", features = ["ws", "json"] }
axum-extra = { version = "0.3", features = ["spa"] }
tower = { version = "0.4", features = ["full"] }
tower-http = { version = "0.3", features = ["cors"] }
slack = { package = "slack_client_rs", version = "0.0.7" }
mungos = "0.2.24"
dotenv = "0.15"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
envy = "0.4"
oauth2 = "4.2.3"
anyhow = "1.0"
bcrypt = "0.13"
jwt = "0.16"
hmac = "0.12"
sha2 = "0.10"
[package]
name = "monitor-core"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
docker = { package = "docker_client", path = "../lib/docker_client" }
git = { package = "git_client", path = "../lib/git_client" }
db = { package = "db_client", path = "../lib/db_client" }
types = { path = "../lib/types" }
tokio = { version = "1.21", features = ["full"] }
axum = { version = "0.5", features = ["ws", "json"] }
axum-extra = { version = "0.3", features = ["spa"] }
tower = { version = "0.4", features = ["full"] }
tower-http = { version = "0.3", features = ["cors"] }
slack = { package = "slack_client_rs", version = "0.0.7" }
mungos = "0.2.24"
dotenv = "0.15"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
envy = "0.4"
oauth2 = "4.2.3"
anyhow = "1.0"
bcrypt = "0.13"
jwt = "0.16"
hmac = "0.12"
sha2 = "0.10"
async_timing_util = "0.1.11"

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,88 +1,88 @@
use anyhow::{anyhow, Context};
use axum::{extract::Json, routing::post, Extension, Router};
use db::{DbClient, DbExtension};
use mungos::{doc, Deserialize};
use types::{User, UserCredentials};
use crate::helpers::handle_anyhow_error;
use super::jwt::JwtExtension;
const BCRYPT_COST: u32 = 10;
pub fn router() -> Router {
Router::new()
.route(
"/create_user",
post(|db, jwt, body| async {
create_user_handler(db, jwt, body)
.await
.map_err(handle_anyhow_error)
}),
)
.route(
"/login",
post(|db, jwt, body| async {
login_handler(db, jwt, body)
.await
.map_err(handle_anyhow_error)
}),
)
}
async fn create_user_handler(
Extension(db): DbExtension,
Extension(jwt): JwtExtension,
Json(UserCredentials { username, password }): Json<UserCredentials>,
) -> anyhow::Result<String> {
let password = bcrypt::hash(password, BCRYPT_COST).context("failed to hash password")?;
let user = User {
username,
password: Some(password),
..Default::default()
};
let user_id = db
.users
.create_one(user)
.await
.context("failed to create user")?;
let jwt = jwt
.generate(user_id)
.context("failed to generate jwt for user")?;
Ok(jwt)
}
async fn login_handler(
Extension(db): DbExtension,
Extension(jwt): JwtExtension,
Json(UserCredentials { username, password }): Json<UserCredentials>,
) -> anyhow::Result<String> {
let user = db
.users
.find_one(doc! { "username": &username }, None)
.await
.context("failed at mongo query")?
.ok_or(anyhow!("did not find user with username {username}"))?;
let user_pw_hash = user
.password
.ok_or(anyhow!("invalid login, user does not have password login"))?;
let verified = bcrypt::verify(password, &user_pw_hash).context("failed at verify password")?;
if !verified {
return Err(anyhow!("invalid credentials"));
}
let user_id = user.id.ok_or(anyhow!("user does not have id"))?.to_string();
let jwt = jwt
.generate(user_id)
.context("failed at generating jwt for user")?;
Ok(jwt)
}
use anyhow::{anyhow, Context};
use axum::{extract::Json, routing::post, Extension, Router};
use db::{DbClient, DbExtension};
use mungos::{doc, Deserialize};
use types::{User, UserCredentials};
use crate::helpers::handle_anyhow_error;
use super::jwt::JwtExtension;
const BCRYPT_COST: u32 = 10;
pub fn router() -> Router {
Router::new()
.route(
"/create_user",
post(|db, jwt, body| async {
create_user_handler(db, jwt, body)
.await
.map_err(handle_anyhow_error)
}),
)
.route(
"/login",
post(|db, jwt, body| async {
login_handler(db, jwt, body)
.await
.map_err(handle_anyhow_error)
}),
)
}
async fn create_user_handler(
Extension(db): DbExtension,
Extension(jwt): JwtExtension,
Json(UserCredentials { username, password }): Json<UserCredentials>,
) -> anyhow::Result<String> {
let password = bcrypt::hash(password, BCRYPT_COST).context("failed to hash password")?;
let user = User {
username,
password: Some(password),
..Default::default()
};
let user_id = db
.users
.create_one(user)
.await
.context("failed to create user")?;
let jwt = jwt
.generate(user_id)
.context("failed to generate jwt for user")?;
Ok(jwt)
}
async fn login_handler(
Extension(db): DbExtension,
Extension(jwt): JwtExtension,
Json(UserCredentials { username, password }): Json<UserCredentials>,
) -> anyhow::Result<String> {
let user = db
.users
.find_one(doc! { "username": &username }, None)
.await
.context("failed at mongo query")?
.ok_or(anyhow!("did not find user with username {username}"))?;
let user_pw_hash = user
.password
.ok_or(anyhow!("invalid login, user does not have password login"))?;
let verified = bcrypt::verify(password, &user_pw_hash).context("failed at verify password")?;
if !verified {
return Err(anyhow!("invalid credentials"));
}
let user_id = user.id.ok_or(anyhow!("user does not have id"))?.to_string();
let jwt = jwt
.generate(user_id)
.context("failed at generating jwt for user")?;
Ok(jwt)
}

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,15 +1,15 @@
use std::{net::SocketAddr, str::FromStr};
use axum::http::StatusCode;
use types::CoreConfig;
pub fn handle_anyhow_error(err: anyhow::Error) -> (StatusCode, String) {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Internal Error: {err:#?}"),
)
}
pub fn get_socket_addr(config: &CoreConfig) -> SocketAddr {
SocketAddr::from_str(&format!("0.0.0.0:{}", config.port)).expect("failed to parse socket addr")
}
use std::{net::SocketAddr, str::FromStr};
use axum::http::StatusCode;
use types::CoreConfig;
pub fn handle_anyhow_error(err: anyhow::Error) -> (StatusCode, String) {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Internal Error: {err:#?}"),
)
}
pub fn get_socket_addr(config: &CoreConfig) -> SocketAddr {
SocketAddr::from_str(&format!("0.0.0.0:{}", config.port)).expect("failed to parse socket addr")
}

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

@@ -1,16 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="shortcut icon" type="image/ico" href="/src/assets/favicon.ico" />
<title>Solid App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="/src/index.tsx" type="module"></script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="shortcut icon" type="image/ico" href="/src/assets/favicon.ico" />
<title>Solid App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="/src/index.tsx" type="module"></script>
</body>
</html>

View File

@@ -1,20 +1,20 @@
{
"name": "vite-template-solid",
"version": "0.0.0",
"description": "",
"scripts": {
"start": "vite",
"dev": "vite",
"build": "vite build",
"serve": "vite preview"
},
"license": "MIT",
"devDependencies": {
"typescript": "^4.8.2",
"vite": "^3.0.9",
"vite-plugin-solid": "^2.3.0"
},
"dependencies": {
"solid-js": "^1.5.1"
}
}
{
"name": "vite-template-solid",
"version": "0.0.0",
"description": "",
"scripts": {
"start": "vite",
"dev": "vite",
"build": "vite build",
"serve": "vite preview"
},
"license": "MIT",
"devDependencies": {
"typescript": "^4.8.2",
"vite": "^3.0.9",
"vite-plugin-solid": "^2.3.0"
},
"dependencies": {
"solid-js": "^1.5.1"
}
}

View File

@@ -1,33 +1,33 @@
.App {
text-align: center;
}
.logo {
animation: logo-spin infinite 20s linear;
height: 40vmin;
pointer-events: none;
}
.header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.link {
color: #b318f0;
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.App {
text-align: center;
}
.logo {
animation: logo-spin infinite 20s linear;
height: 40vmin;
pointer-events: none;
}
.header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.link {
color: #b318f0;
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@@ -1,27 +1,27 @@
import type { Component } from 'solid-js';
import logo from './logo.svg';
import styles from './App.module.css';
const App: Component = () => {
return (
<div class={styles.App}>
<header class={styles.header}>
<img src={logo} class={styles.logo} alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
class={styles.link}
href="https://github.com/solidjs/solid"
target="_blank"
rel="noopener noreferrer"
>
Learn Solid
</a>
</header>
</div>
);
};
export default App;
import type { Component } from 'solid-js';
import logo from './logo.svg';
import styles from './App.module.css';
const App: Component = () => {
return (
<div class={styles.App}>
<header class={styles.header}>
<img src={logo} class={styles.logo} alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
class={styles.link}
href="https://github.com/solidjs/solid"
target="_blank"
rel="noopener noreferrer"
>
Learn Solid
</a>
</header>
</div>
);
};
export default App;

View File

@@ -1,13 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@@ -1,7 +1,7 @@
/* @refresh reload */
import { render } from 'solid-js/web';
import './index.css';
import App from './App';
render(() => <App />, document.getElementById('root') as HTMLElement);
/* @refresh reload */
import { render } from 'solid-js/web';
import './index.css';
import App from './App';
render(() => <App />, document.getElementById('root') as HTMLElement);

View File

@@ -1,15 +1,15 @@
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"types": ["vite/client"],
"noEmit": true,
"isolatedModules": true
}
}
{
"compilerOptions": {
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"types": ["vite/client"],
"noEmit": true,
"isolatedModules": true
}
}

View File

@@ -1,12 +1,12 @@
import { defineConfig } from 'vite';
import solidPlugin from 'vite-plugin-solid';
export default defineConfig({
plugins: [solidPlugin()],
server: {
port: 3000,
},
build: {
target: 'esnext',
},
});
import { defineConfig } from 'vite';
import solidPlugin from 'vite-plugin-solid';
export default defineConfig({
plugins: [solidPlugin()],
server: {
port: 3000,
},
build: {
target: 'esnext',
},
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,12 @@
[package]
name = "db_client"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
types = { path = "../types" }
axum = "0.5"
mungos = "0.2.24"
[package]
name = "db_client"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
types = { path = "../types" }
axum = "0.5"
mungos = "0.2.24"
anyhow = "1.0"

View File

@@ -1,84 +1,84 @@
use anyhow::Context;
use mungos::{Collection, Mungos};
use types::{Build, Deployment, Procedure, Server, Update, User};
pub async fn users_collection(mungos: &Mungos, db_name: &str) -> anyhow::Result<Collection<User>> {
let coll = mungos.collection(db_name, "users");
coll.create_unique_index("username")
.await
.context("failed at creating username index")?;
Ok(coll)
}
pub async fn servers_collection(
mungos: &Mungos,
db_name: &str,
) -> anyhow::Result<Collection<Server>> {
let coll = mungos.collection(db_name, "servers");
coll.create_unique_index("name")
.await
.context("failed at creating name index")?;
coll.create_index("permissions")
.await
.context("failed at creating permissions index")?;
Ok(coll)
}
pub async fn deployments_collection(
mungos: &Mungos,
db_name: &str,
) -> anyhow::Result<Collection<Deployment>> {
let coll = mungos.collection(db_name, "deployments");
coll.create_unique_index("name")
.await
.context("failed at creating name index")?;
coll.create_index("permissions")
.await
.context("failed at creating permissions index")?;
Ok(coll)
}
pub async fn builds_collection(
mungos: &Mungos,
db_name: &str,
) -> anyhow::Result<Collection<Build>> {
let coll = mungos.collection(db_name, "builds");
coll.create_unique_index("name")
.await
.context("failed at creating name index")?;
coll.create_index("permissions")
.await
.context("failed at creating permissions index")?;
Ok(coll)
}
pub async fn updates_collection(
mungos: &Mungos,
db_name: &str,
) -> anyhow::Result<Collection<Update>> {
let coll = mungos.collection(db_name, "updates");
coll.create_index("entity_id")
.await
.context("failed at creating entity_id index")?;
coll.create_index("ts")
.await
.context("failed at creating ts index")?;
coll.create_index("operator")
.await
.context("failed at creating operator index")?;
Ok(coll)
}
pub async fn procedures_collection(
mungos: &Mungos,
db_name: &str,
) -> anyhow::Result<Collection<Procedure>> {
let coll = mungos.collection(db_name, "procedures");
coll.create_index("name")
.await
.context("failed at creating entity_id index")?;
coll.create_index("permissions")
.await
.context("failed at creating permissions index")?;
Ok(coll)
}
use anyhow::Context;
use mungos::{Collection, Mungos};
use types::{Build, Deployment, Procedure, Server, Update, User};
pub async fn users_collection(mungos: &Mungos, db_name: &str) -> anyhow::Result<Collection<User>> {
let coll = mungos.collection(db_name, "users");
coll.create_unique_index("username")
.await
.context("failed at creating username index")?;
Ok(coll)
}
pub async fn servers_collection(
mungos: &Mungos,
db_name: &str,
) -> anyhow::Result<Collection<Server>> {
let coll = mungos.collection(db_name, "servers");
coll.create_unique_index("name")
.await
.context("failed at creating name index")?;
coll.create_index("permissions")
.await
.context("failed at creating permissions index")?;
Ok(coll)
}
pub async fn deployments_collection(
mungos: &Mungos,
db_name: &str,
) -> anyhow::Result<Collection<Deployment>> {
let coll = mungos.collection(db_name, "deployments");
coll.create_unique_index("name")
.await
.context("failed at creating name index")?;
coll.create_index("permissions")
.await
.context("failed at creating permissions index")?;
Ok(coll)
}
pub async fn builds_collection(
mungos: &Mungos,
db_name: &str,
) -> anyhow::Result<Collection<Build>> {
let coll = mungos.collection(db_name, "builds");
coll.create_unique_index("name")
.await
.context("failed at creating name index")?;
coll.create_index("permissions")
.await
.context("failed at creating permissions index")?;
Ok(coll)
}
pub async fn updates_collection(
mungos: &Mungos,
db_name: &str,
) -> anyhow::Result<Collection<Update>> {
let coll = mungos.collection(db_name, "updates");
coll.create_index("entity_id")
.await
.context("failed at creating entity_id index")?;
coll.create_index("ts")
.await
.context("failed at creating ts index")?;
coll.create_index("operator")
.await
.context("failed at creating operator index")?;
Ok(coll)
}
pub async fn procedures_collection(
mungos: &Mungos,
db_name: &str,
) -> anyhow::Result<Collection<Procedure>> {
let coll = mungos.collection(db_name, "procedures");
coll.create_index("name")
.await
.context("failed at creating entity_id index")?;
coll.create_index("permissions")
.await
.context("failed at creating permissions index")?;
Ok(coll)
}

View File

@@ -1,73 +1,73 @@
use std::{sync::Arc, time::Duration};
use axum::Extension;
use collections::{
builds_collection, deployments_collection, procedures_collection, servers_collection,
updates_collection, users_collection,
};
use mungos::{Collection, Mungos};
use types::{Build, CoreConfig, Deployment, Procedure, Server, Update, User};
mod collections;
pub type DbExtension = Extension<Arc<DbClient>>;
pub struct DbClient {
pub users: Collection<User>,
pub servers: Collection<Server>,
pub deployments: Collection<Deployment>,
pub builds: Collection<Build>,
pub procedures: Collection<Procedure>,
pub updates: Collection<Update>,
}
pub struct DbConfig {
mongo_uri: String,
mongo_app_name: String,
mongo_db_name: String,
}
impl From<&CoreConfig> for DbConfig {
fn from(config: &CoreConfig) -> DbConfig {
DbConfig {
mongo_uri: config.mongo_uri.clone(),
mongo_app_name: config.mongo_app_name.clone(),
mongo_db_name: config.mongo_db_name.clone(),
}
}
}
impl DbClient {
pub async fn extension(config: DbConfig) -> DbExtension {
let db_name = &config.mongo_db_name;
let mungos = Mungos::new(
&config.mongo_uri,
&config.mongo_app_name,
Duration::from_secs(3),
None,
)
.await
.expect("failed to initialize mungos");
let client = DbClient {
users: users_collection(&mungos, db_name)
.await
.expect("failed to make users collection"),
servers: servers_collection(&mungos, db_name)
.await
.expect("failed to make servers collection"),
deployments: deployments_collection(&mungos, db_name)
.await
.expect("failed to make deployments collection"),
builds: builds_collection(&mungos, db_name)
.await
.expect("failed to make builds collection"),
updates: updates_collection(&mungos, db_name)
.await
.expect("failed to make updates collection"),
procedures: procedures_collection(&mungos, db_name)
.await
.expect("failed to make procedures collection"),
};
Extension(Arc::new(client))
}
}
use std::{sync::Arc, time::Duration};
use axum::Extension;
use collections::{
builds_collection, deployments_collection, procedures_collection, servers_collection,
updates_collection, users_collection,
};
use mungos::{Collection, Mungos};
use types::{Build, CoreConfig, Deployment, Procedure, Server, Update, User};
mod collections;
pub type DbExtension = Extension<Arc<DbClient>>;
pub struct DbClient {
pub users: Collection<User>,
pub servers: Collection<Server>,
pub deployments: Collection<Deployment>,
pub builds: Collection<Build>,
pub procedures: Collection<Procedure>,
pub updates: Collection<Update>,
}
pub struct DbConfig {
mongo_uri: String,
mongo_app_name: String,
mongo_db_name: String,
}
impl From<&CoreConfig> for DbConfig {
fn from(config: &CoreConfig) -> DbConfig {
DbConfig {
mongo_uri: config.mongo_uri.clone(),
mongo_app_name: config.mongo_app_name.clone(),
mongo_db_name: config.mongo_db_name.clone(),
}
}
}
impl DbClient {
pub async fn extension(config: DbConfig) -> DbExtension {
let db_name = &config.mongo_db_name;
let mungos = Mungos::new(
&config.mongo_uri,
&config.mongo_app_name,
Duration::from_secs(3),
None,
)
.await
.expect("failed to initialize mungos");
let client = DbClient {
users: users_collection(&mungos, db_name)
.await
.expect("failed to make users collection"),
servers: servers_collection(&mungos, db_name)
.await
.expect("failed to make servers collection"),
deployments: deployments_collection(&mungos, db_name)
.await
.expect("failed to make deployments collection"),
builds: builds_collection(&mungos, db_name)
.await
.expect("failed to make builds collection"),
updates: updates_collection(&mungos, db_name)
.await
.expect("failed to make updates collection"),
procedures: procedures_collection(&mungos, db_name)
.await
.expect("failed to make procedures collection"),
};
Extension(Arc::new(client))
}
}

View File

@@ -1,13 +1,13 @@
[package]
name = "docker_client"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
types = { path = "../types" }
run_command = { version = "0.0.5", features = ["async_tokio"] }
bollard = "0.13"
anyhow = "1.0"
[package]
name = "docker_client"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
types = { path = "../types" }
run_command = { version = "0.0.5", features = ["async_tokio"] }
bollard = "0.13"
anyhow = "1.0"
thiserror = "1.0"

View File

@@ -1,9 +1,9 @@
use types::Build;
use crate::DockerClient;
impl DockerClient {
pub async fn build(&self, build: Build) -> anyhow::Result<()> {
todo!()
}
}
use types::Build;
use crate::DockerClient;
impl DockerClient {
pub async fn build(&self, build: Build) -> anyhow::Result<()> {
todo!()
}
}

View File

@@ -1,45 +1,45 @@
use run_command::{async_run_command, CommandOutput};
use types::{Deployment, Log};
use crate::DockerClient;
impl DockerClient {
pub async fn deploy(&self, deployment: &Deployment) -> (bool, Log) {
let docker_run = docker_run_command(deployment);
let output = async_run_command(&docker_run).await;
output_into_log("docker run", output)
}
pub async fn docker_start_command(&self, container_name: &str) -> (bool, Log) {
let command = format!("start stop {container_name}");
let output = async_run_command(&command).await;
output_into_log("docker stop", output)
}
pub async fn docker_stop_command(&self, container_name: &str) -> (bool, Log) {
let command = format!("docker stop {container_name}");
let output = async_run_command(&command).await;
output_into_log("docker stop", output)
}
pub async fn docker_stop_and_remove(&self, container_name: &str) -> (bool, Log) {
let command =
format!("docker stop {container_name} && docker container rm {container_name}");
let output = async_run_command(&command).await;
output_into_log("docker stop and remove", output)
}
}
fn docker_run_command(deployment: &Deployment) -> String {
todo!()
}
fn output_into_log(stage: &str, output: CommandOutput) -> (bool, Log) {
let success = output.success();
let log = Log {
stage: stage.to_string(),
stdout: output.stdout,
stderr: output.stderr,
};
(success, log)
}
use run_command::{async_run_command, CommandOutput};
use types::{Deployment, Log};
use crate::DockerClient;
impl DockerClient {
pub async fn deploy(&self, deployment: &Deployment) -> (bool, Log) {
let docker_run = docker_run_command(deployment);
let output = async_run_command(&docker_run).await;
output_into_log("docker run", output)
}
pub async fn docker_start_command(&self, container_name: &str) -> (bool, Log) {
let command = format!("start stop {container_name}");
let output = async_run_command(&command).await;
output_into_log("docker stop", output)
}
pub async fn docker_stop_command(&self, container_name: &str) -> (bool, Log) {
let command = format!("docker stop {container_name}");
let output = async_run_command(&command).await;
output_into_log("docker stop", output)
}
pub async fn docker_stop_and_remove(&self, container_name: &str) -> (bool, Log) {
let command =
format!("docker stop {container_name} && docker container rm {container_name}");
let output = async_run_command(&command).await;
output_into_log("docker stop and remove", output)
}
}
fn docker_run_command(deployment: &Deployment) -> String {
todo!()
}
fn output_into_log(stage: &str, output: CommandOutput) -> (bool, Log) {
let success = output.success();
let log = Log {
stage: stage.to_string(),
stdout: output.stdout,
stderr: output.stderr,
};
(success, log)
}

View File

@@ -1,44 +1,44 @@
use anyhow::anyhow;
use bollard::{container::ListContainersOptions, Docker};
use types::BasicContainerInfo;
mod build;
mod deploy;
pub struct DockerClient {
client: Docker,
}
impl DockerClient {
pub fn new() -> anyhow::Result<DockerClient> {
Ok(DockerClient {
client: Docker::connect_with_local_defaults()?,
})
}
pub async fn list_containers(&self) -> anyhow::Result<Vec<BasicContainerInfo>> {
let res = self
.client
.list_containers(Some(ListContainersOptions::<String> {
all: true,
..Default::default()
}))
.await?
.into_iter()
.map(|s| {
let info = BasicContainerInfo {
name: s
.names
.ok_or(anyhow!("no names on container"))?
.pop()
.ok_or(anyhow!("no names on container (empty vec)"))?
.replace("/", ""),
state: s.state.unwrap().parse().unwrap(),
status: s.status,
};
Ok::<_, anyhow::Error>(info)
})
.collect::<anyhow::Result<Vec<BasicContainerInfo>>>()?;
Ok(res)
}
}
use anyhow::anyhow;
use bollard::{container::ListContainersOptions, Docker};
use types::BasicContainerInfo;
mod build;
mod deploy;
pub struct DockerClient {
client: Docker,
}
impl DockerClient {
pub fn new() -> anyhow::Result<DockerClient> {
Ok(DockerClient {
client: Docker::connect_with_local_defaults()?,
})
}
pub async fn list_containers(&self) -> anyhow::Result<Vec<BasicContainerInfo>> {
let res = self
.client
.list_containers(Some(ListContainersOptions::<String> {
all: true,
..Default::default()
}))
.await?
.into_iter()
.map(|s| {
let info = BasicContainerInfo {
name: s
.names
.ok_or(anyhow!("no names on container"))?
.pop()
.ok_or(anyhow!("no names on container (empty vec)"))?
.replace("/", ""),
state: s.state.unwrap().parse().unwrap(),
status: s.status,
};
Ok::<_, anyhow::Error>(info)
})
.collect::<anyhow::Result<Vec<BasicContainerInfo>>>()?;
Ok(res)
}
}

View File

@@ -1,8 +1,8 @@
[package]
name = "git_client"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
[package]
name = "git_client"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@@ -1,3 +1,3 @@
pub struct GitClient;
impl GitClient {}
pub struct GitClient;
impl GitClient {}

View File

@@ -1,14 +1,14 @@
[package]
name = "types"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = "1.0"
serde_derive = "1.0"
mungos = "0.2.24"
strum = "0.24"
strum_macros = "0.24"
[package]
name = "types"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = "1.0"
serde_derive = "1.0"
mungos = "0.2.24"
strum = "0.24"
strum_macros = "0.24"
async_timing_util = "0.1.11"

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,

View File

@@ -1,8 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
{
"folders": [
{
"path": "."
}
],
"settings": {}
}

View File

@@ -1,16 +1,16 @@
[package]
name = "monitor-periphery"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tokio = { version = "1.21", features = ["full"] }
axum = { version = "0.5" }
tower = { version = "0.4", features = ["full"] }
dotenv = "0.15"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
[package]
name = "monitor-periphery"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tokio = { version = "1.21", features = ["full"] }
axum = { version = "0.5" }
tower = { version = "0.4", features = ["full"] }
dotenv = "0.15"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
bollard = "0.13"

View File

@@ -1,2 +1,2 @@
#[tokio::main]
async fn main() {}
#[tokio::main]
async fn main() {}