Compare commits

...

3 Commits

Author SHA1 Message Date
Félix Baylac Jacqué b9d09e1c99 Frontend: redirect to homepage after login
Build nomnom / Build-NomNom (push) Failing after 1m33s Details
2024-01-25 13:13:17 +01:00
Félix Baylac Jacqué 6ec67b1993 Templates: actual design 2024-01-25 12:14:22 +01:00
Félix Baylac Jacqué 627906e6e6 S3: c'est oui 2024-01-23 15:21:04 +01:00
22 changed files with 562 additions and 113 deletions

301
Cargo.lock generated
View File

@ -8,7 +8,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"bytes",
"futures-core",
"futures-sink",
@ -19,6 +19,29 @@ dependencies = [
"tracing",
]
[[package]]
name = "actix-files"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf0bdd6ff79de7c9a021f5d9ea79ce23e108d8bfc9b49b5b4a2cf6fad5a35212"
dependencies = [
"actix-http",
"actix-service",
"actix-utils",
"actix-web",
"bitflags 2.4.2",
"bytes",
"derive_more",
"futures-core",
"http-range",
"log",
"mime",
"mime_guess",
"percent-encoding",
"pin-project-lite",
"v_htmlescape",
]
[[package]]
name = "actix-http"
version = "3.3.1"
@ -31,7 +54,7 @@ dependencies = [
"actix-utils",
"ahash 0.8.3",
"base64 0.21.2",
"bitflags",
"bitflags 1.3.2",
"brotli",
"bytes",
"bytestring",
@ -322,6 +345,37 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "aws-config"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "004dc45f6b869e6a70725df448004a720b7f52f6607d55d8815cbd5448f86def"
dependencies = [
"aws-credential-types",
"aws-http 0.60.0",
"aws-runtime",
"aws-sdk-sso",
"aws-sdk-ssooidc",
"aws-sdk-sts",
"aws-smithy-async",
"aws-smithy-http 0.60.0",
"aws-smithy-json 0.60.0",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
"aws-smithy-types",
"aws-types",
"bytes",
"fastrand",
"hex",
"http",
"hyper",
"ring",
"time",
"tokio",
"tracing",
"zeroize",
]
[[package]]
name = "aws-credential-types"
version = "1.1.0"
@ -334,6 +388,22 @@ dependencies = [
"zeroize",
]
[[package]]
name = "aws-http"
version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "361c4310fdce94328cc2d1ca0c8a48c13f43009c61d3367585685a50ca8c66b6"
dependencies = [
"aws-smithy-runtime-api",
"aws-smithy-types",
"aws-types",
"bytes",
"http",
"http-body",
"pin-project-lite",
"tracing",
]
[[package]]
name = "aws-http"
version = "0.61.0"
@ -357,11 +427,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c6d61ac3425f2bd1d69393b96569a7408467f8927a5cfeba597b19f78ebb185"
dependencies = [
"aws-credential-types",
"aws-http",
"aws-http 0.61.0",
"aws-sigv4",
"aws-smithy-async",
"aws-smithy-eventstream",
"aws-smithy-http",
"aws-smithy-http 0.61.0",
"aws-smithy-runtime-api",
"aws-smithy-types",
"aws-types",
@ -379,18 +449,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "693ff3ba604fa0db18799fb770c7cde5c3e9302a7b7644647c4e46a615b96e23"
dependencies = [
"aws-credential-types",
"aws-http",
"aws-http 0.61.0",
"aws-runtime",
"aws-sigv4",
"aws-smithy-async",
"aws-smithy-checksums",
"aws-smithy-eventstream",
"aws-smithy-http",
"aws-smithy-json",
"aws-smithy-http 0.61.0",
"aws-smithy-json 0.61.0",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
"aws-smithy-types",
"aws-smithy-xml",
"aws-smithy-xml 0.61.0",
"aws-types",
"bytes",
"http",
@ -402,6 +472,73 @@ dependencies = [
"url",
]
[[package]]
name = "aws-sdk-sso"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86575c7604dcdb583aba3390200e5333d8e4fe597bad54f57b190aaf4fac9771"
dependencies = [
"aws-credential-types",
"aws-http 0.60.0",
"aws-runtime",
"aws-smithy-async",
"aws-smithy-http 0.60.0",
"aws-smithy-json 0.60.0",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
"aws-smithy-types",
"aws-types",
"bytes",
"http",
"regex",
"tracing",
]
[[package]]
name = "aws-sdk-ssooidc"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef0d7c1d0730adb5e85407174483a579e39576e0f4350ecd0fac69ec1217b1b"
dependencies = [
"aws-credential-types",
"aws-http 0.60.0",
"aws-runtime",
"aws-smithy-async",
"aws-smithy-http 0.60.0",
"aws-smithy-json 0.60.0",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
"aws-smithy-types",
"aws-types",
"bytes",
"http",
"regex",
"tracing",
]
[[package]]
name = "aws-sdk-sts"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f45778089751d5aa8645a02dd60865fa0eea39f00be5db2c7779bc50b83db19a"
dependencies = [
"aws-credential-types",
"aws-http 0.60.0",
"aws-runtime",
"aws-smithy-async",
"aws-smithy-http 0.60.0",
"aws-smithy-json 0.60.0",
"aws-smithy-query",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
"aws-smithy-types",
"aws-smithy-xml 0.60.3",
"aws-types",
"http",
"regex",
"tracing",
]
[[package]]
name = "aws-sigv4"
version = "1.1.0"
@ -410,7 +547,7 @@ checksum = "82f39bf5bfa061fd1487a7ba274927dd6d70feed5cecaf3367932bcc83148d8f"
dependencies = [
"aws-credential-types",
"aws-smithy-eventstream",
"aws-smithy-http",
"aws-smithy-http 0.61.0",
"aws-smithy-runtime-api",
"aws-smithy-types",
"bytes",
@ -447,7 +584,7 @@ version = "0.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3c4fd37c10269ad70de25cfbe29f52c1ae6fc48606a2b1ed2c4bdeb624d5da9"
dependencies = [
"aws-smithy-http",
"aws-smithy-http 0.61.0",
"aws-smithy-types",
"bytes",
"crc32c",
@ -473,6 +610,26 @@ dependencies = [
"crc32fast",
]
[[package]]
name = "aws-smithy-http"
version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b1de8aee22f67de467b2e3d0dd0fb30859dc53f579a63bd5381766b987db644"
dependencies = [
"aws-smithy-runtime-api",
"aws-smithy-types",
"bytes",
"bytes-utils",
"futures-core",
"http",
"http-body",
"once_cell",
"percent-encoding",
"pin-project-lite",
"pin-utils",
"tracing",
]
[[package]]
name = "aws-smithy-http"
version = "0.61.0"
@ -494,6 +651,15 @@ dependencies = [
"tracing",
]
[[package]]
name = "aws-smithy-json"
version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a46dd338dc9576d6a6a5b5a19bd678dcad018ececee11cf28ecd7588bd1a55c"
dependencies = [
"aws-smithy-types",
]
[[package]]
name = "aws-smithy-json"
version = "0.61.0"
@ -503,6 +669,16 @@ dependencies = [
"aws-smithy-types",
]
[[package]]
name = "aws-smithy-query"
version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feb5b8c7a86d4b6399169670723b7e6f21a39fc833a30f5c5a2f997608178129"
dependencies = [
"aws-smithy-types",
"urlencoding",
]
[[package]]
name = "aws-smithy-runtime"
version = "1.1.0"
@ -510,7 +686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de8c54dd9c5a159013f1e6885cb7c1ae8fc98dc286d2aebe71737effef28e37"
dependencies = [
"aws-smithy-async",
"aws-smithy-http",
"aws-smithy-http 0.61.0",
"aws-smithy-runtime-api",
"aws-smithy-types",
"bytes",
@ -541,6 +717,7 @@ dependencies = [
"pin-project-lite",
"tokio",
"tracing",
"zeroize",
]
[[package]]
@ -566,6 +743,15 @@ dependencies = [
"tokio-util",
]
[[package]]
name = "aws-smithy-xml"
version = "0.60.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef796feaf894d7fd03869235237aeffe73ed1b29a3927cceeee2eecadf876eba"
dependencies = [
"xmlparser",
]
[[package]]
name = "aws-smithy-xml"
version = "0.61.0"
@ -656,6 +842,12 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "block-buffer"
version = "0.10.3"
@ -760,7 +952,7 @@ version = "4.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"clap_derive",
"clap_lex",
"is-terminal",
@ -1398,6 +1590,12 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "http-range"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
[[package]]
name = "httparse"
version = "1.8.0"
@ -1631,6 +1829,16 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mime_guess"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@ -1672,16 +1880,22 @@ dependencies = [
name = "nom-nom-gc"
version = "0.1.0"
dependencies = [
"actix-files",
"actix-web",
"anyhow",
"aws-config",
"aws-sdk-s3",
"aws-types",
"chrono",
"clap",
"deadpool-postgres",
"handlebars",
"heck",
"mime",
"mime_guess",
"postgres-types",
"refinery",
"rust-embed",
"serde",
"serde_json",
"tokio",
@ -1761,7 +1975,7 @@ version = "0.10.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"cfg-if",
"foreign-types",
"libc",
@ -2078,7 +2292,7 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
"bitflags 1.3.2",
]
[[package]]
@ -2186,6 +2400,40 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "rust-embed"
version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 2.0.37",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665"
dependencies = [
"sha2",
"walkdir",
]
[[package]]
name = "rustc-demangle"
version = "0.1.23"
@ -2216,7 +2464,7 @@ version = "0.36.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"errno",
"io-lifetimes",
"libc",
@ -2333,7 +2581,7 @@ version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"core-foundation",
"core-foundation-sys",
"libc",
@ -2822,6 +3070,15 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
[[package]]
name = "unicase"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-bidi"
version = "0.3.8"
@ -2873,6 +3130,12 @@ dependencies = [
"serde",
]
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "uuid"
version = "1.4.1"
@ -2883,6 +3146,12 @@ dependencies = [
"serde",
]
[[package]]
name = "v_htmlescape"
version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c"
[[package]]
name = "vcpkg"
version = "0.2.15"

View File

@ -7,6 +7,10 @@ edition = "2021"
[dependencies]
actix-web = "4"
actix-files = "*"
mime = "*"
mime_guess = "*"
rust-embed = "*"
clap = { version = "4.0.29", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
url = "*"
@ -22,6 +26,8 @@ refinery = { version = "0.8.11", features = ["tokio-postgres"] }
uuid = { version = "1.4.1", features = ["v4"] }
chrono = { version = "*", features = ["serde"] }
aws-sdk-s3 = "1.6.0"
aws-config = "1.0.3"
aws-types = "*"
[dependencies.heck]
version = "0.4.1"

View File

@ -30,6 +30,7 @@ CREATE TABLE Projects (
id SERIAL PRIMARY KEY NOT NULL,
name text NOT NULL UNIQUE,
binary_cache_id integer NOT NULL,
closure_generation_nb integer NOT NULL,
-- TODO: figure out rules
CONSTRAINT fk_project_binary_cache FOREIGN KEY (binary_cache_id) REFERENCES BinaryCaches(id)
);
@ -43,13 +44,28 @@ CREATE TABLE ProjectTokens (
CREATE TABLE Closures (
id SERIAL PRIMARY KEY NOT NULL,
project_id integer NOT NULL,
project_id INTEGER NOT NULL,
objects text[] NOT NULL,
date timestamp NOT NULL,
CONSTRAINT fk_project_closure FOREIGN KEY (project_id) REFERENCES Projects(id)
);
CREATE TABLE Objects (
id SERIAL PRIMARY KEY NOT NULL,
key text NOT NULL
);
CREATE TABLE ObjectClosure (
object_id INTEGER NOT NULL,
closure_id INTEGER NOT NULL,
CONSTRAINT fk_objectclosure_object FOREIGN KEY (object_id) REFERENCES Objects(id),
CONSTRAINT fk_objectclosure_closure FOREIGN KEY (closure_id) REFERENCES Closures(id),
PRIMARY KEY (object_id, closure_id)
);
-- We'll mostly querying the Keys using the associated uid.
CREATE INDEX idx_keys_uid ON Keys USING HASH (user_id);
-- We'll be often sorting Closures through their datetime.
CREATE INDEX idx_date_closures ON Closures USING HASH (date);
-- We'll be querying objects through their names.
CREATE INDEX idx_objects_key ON Objects USING HASH (key);

View File

@ -6,11 +6,14 @@ dbname="nomnomdev"
port="12345"
dbdir="$(mktemp -d)"
garagedir="$(mktemp -d)"
garageaddr="[::1]:3900"
garagebucket="nix-cache"
cfgfile="${dbdir}/config.json"
trap 'rm -rf ${dbdir}' EXIT
initdb "$dbdir"
postgres -D "${dbdir}" -c unix_socket_directories="${dbdir}" -c listen_addresses= -c port="${port}" &
postgres -D "${dbdir}" -c unix_socket_directories="${dbdir}" -c listen_addresses= -c port="${port}" > /dev/null &
pgpid=$!
# Trick to help the "./psql" script to find the DB dir & co
@ -21,19 +24,79 @@ export dbname="$dbname"
export cfgfile="$cfgfile"
EOF
trap 'rm -rf ${dbdir} && rm /tmp/nom-nom-dev-args && kill ${pgpid}' EXIT
# Garage Directory
cat <<EOF > "$garagedir/config.toml"
metadata_dir = "$garagedir/meta"
data_dir = "$garagedir/data"
block_size = 1048576
replication_mode = "1"
compression_level = 1
rpc_secret = "4425f5c26c5e11581d3223904324dcb5b5d5dfb14e5e7f35e38c595424f5f1e6"
rpc_bind_addr = "[::]:3901"
rpc_public_addr = "[::]:3901"
bootstrap_peers = [
]
consul_host = "consul.service"
consul_service_name = "garage-daemon"
sled_cache_capacity = 134217728
sled_flush_every_ms = 2000
[s3_api]
api_bind_addr = "${garageaddr}"
s3_region = "garage"
root_domain = ".s3.garage"
[s3_web]
bind_addr = "[::]:3902"
root_domain = ".web.garage"
index = "index.html"
EOF
garage -c "$garagedir/config.toml" server &
garagepid=$!
trap 'rm -rf ${dbdir} && rm -rf ${garagedir} && rm /tmp/nom-nom-dev-args && kill ${pgpid} && kill ${garagepid}' EXIT
# Yeah, this is very meh. We need to wait for the server to be ready
# to receive requests to create the DB.
sleep 2
createdb -h "${dbdir}" -p "${port}" "${dbname}"
garage -c "$garagedir/config.toml" status
garagenodeid=$(garage -c "$garagedir/config.toml" node id | cut -f 1 -d '@')
garage -c "$garagedir/config.toml" layout assign "$garagenodeid" -c 500MB -z zone
garage -c "$garagedir/config.toml" layout show
garage -c "$garagedir/config.toml" layout apply --version 1
garage -c "$garagedir/config.toml" status
garage -c "$garagedir/config.toml" bucket create "${garagebucket}"
garage -c "$garagedir/config.toml" key create nomnom-key
garage -c "$garagedir/config.toml" bucket allow --read --write --owner "${garagebucket}" --key nomnom-key
garagekeyid=$(garage -c "$garagedir/config.toml" key info nomnom-key | grep "Key ID" | cut -f3 -d " ")
garagekeysecret=$(garage -c "$garagedir/config.toml" key info --show-secret nomnom-key | grep "Secret key" | cut -f3 -d " ")
access_key_filepath=$(mktemp)
echo "${garagekeyid}" > "${access_key_filepath}"
secret_key_filepath=$(mktemp)
echo "${garagekeysecret}" > "${secret_key_filepath}"
cat <<EOF > "${cfgfile}"
{
"url": "http://localhost:8001",
"db_host": "${dbdir}",
"db_port": ${port},
"db_name": "${dbname}"
"db_name": "${dbname}",
"s3_endpoint": "http://${garageaddr}",
"s3_region": "garage",
"s3_bucket": "${garagebucket}",
"s3_access_key_filepath": "${access_key_filepath}",
"s3_secret_key_filepath": "${secret_key_filepath}"
}
EOF
@ -45,4 +108,4 @@ if [ -f dump.sql ]; then
./psql -f dump.sql
fi
cargo run --bin nom-nom-gc-server -- --bind "[::1]:8001" --config "${cfgfile}"
RUST_BACKTRACE=1 cargo run --bin nom-nom-gc-server -- --bind "[::1]:8001" --config "${cfgfile}"

View File

@ -3,10 +3,12 @@
pkgs.mkShell {
nativeBuildInputs = [
pkgs.rustc
pkgs.rustfmt
pkgs.cargo
pkgs.rust-analyzer
pkgs.pkg-config
pkgs.postgresql
pkgs.garage
];
buildInputs = [
pkgs.openssl

View File

@ -29,7 +29,7 @@ async fn main() -> Result<()> {
let config = read_config(&args.config.unwrap_or("/etc/nom-nom-gc/config.json".to_owned()))
.unwrap_or_else(|e| panic!("Cannot read config file: {}", e));
// todo: don't consume config in appstate new
let state = models::AppState::new(config.clone());
let state = models::AppState::new(config.clone()).await;
match args.command {
Command::RegisterUser(args) => register_user(args.username, state, config).await,
}

View File

@ -31,3 +31,7 @@ pub async fn new_binary_cache_post(app_state: web::Data<AppState<'_>>, req: Http
.finish()
}
}
pub async fn get_binary_cache(_app_state: web::Data<AppState<'_>>, _req: HttpRequest, _path: web::Path<String>) -> impl Responder {
HttpResponse::NotImplemented().finish()
}

View File

@ -1,8 +1,7 @@
use actix_web::{HttpResponse, http::header::{ContentType, self}, web, HttpRequest, cookie::{Cookie, SameSite}};
use chrono::Local;
use uuid::Uuid;
use crate::{models::{AppState, SessionUuid, User, ProjectSummary}, templates};
use crate::{models::{AppState, SessionUuid, User}, templates};
pub mod authentication;
pub mod binary_cache;
@ -11,14 +10,14 @@ pub use authentication::*;
pub use binary_cache::*;
pub async fn landing_page (app_state: web::Data<AppState<'_>>) -> HttpResponse {
let summaries: Vec<ProjectSummary> = vec![
/* let summaries: Vec<ProjectSummary> = vec![
ProjectSummary {
name: "Test Project".to_string(),
latest_closure: "/nix/store/blabla".to_string(),
latest_closure_datetime: Local::now(),
}
];
let content: String = templates::landing_page(app_state.hbs.clone(), true, summaries).unwrap();
];*/
let content: String = templates::landing_page(app_state.hbs.clone(), true).unwrap();
HttpResponse::Ok()
.content_type(ContentType::html())
.body(content)
@ -64,7 +63,7 @@ async fn check_authentication(app_state: &web::Data<AppState<'_>>, req: HttpRequ
let auth_session = app_state.session.user_sessions.read().await;
let cookie = req.cookie("auth-uuid").ok_or_else(|| redirect_to_login("missing cookie in request", &req))?;
let cookie = cookie.value();
let client_uuid = Uuid::parse_str(&cookie).map_err(|e| redirect_to_login(e, &req))?;
let client_uuid = Uuid::parse_str(cookie).map_err(|e| redirect_to_login(e, &req))?;
let user = auth_session.get(&SessionUuid(client_uuid)).ok_or_else(|| redirect_to_login("cannot find UUID in session", &req))?;
Ok(user.clone())
}

View File

@ -2,3 +2,4 @@ pub mod app;
pub mod handlers;
pub mod models;
pub mod templates;
pub mod s3;

View File

@ -2,7 +2,10 @@ use std::collections::HashMap;
use std::fs;
use std::ops::DerefMut;
use std::sync::Arc;
use chrono::{DateTime, Local};
use aws_config::{BehaviorVersion, Region};
use aws_sdk_s3::config::{Credentials, SharedCredentialsProvider};
use postgres_types::{FromSql, ToSql};
use url::Url;
@ -23,7 +26,12 @@ pub struct Configuration {
pub url: String,
pub db_host: Option<String>,
pub db_port: Option<u16>,
pub db_name: String
pub db_name: String,
pub s3_endpoint: String,
pub s3_region: String,
pub s3_bucket: String,
pub s3_access_key_filepath: String,
pub s3_secret_key_filepath: String
}
@ -71,7 +79,8 @@ pub struct AppState<'a>{
pub webauthn: Arc<Webauthn>,
pub db: Pool,
pub hbs: Arc<Handlebars<'a>>,
pub session: TempSession
pub session: TempSession,
pub s3_client: aws_sdk_s3::Client
}
mod embedded {
@ -91,23 +100,17 @@ pub struct BinaryCache {
pub access_key: String,
pub secret_key: String,
pub region: String,
pub endpoint: String
pub endpoint_url: String
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Project {
pub name: String,
}
#[derive(Serialize, Clone, Debug, Eq, PartialEq)]
pub struct ProjectSummary {
pub name: String,
pub latest_closure: String,
pub latest_closure_datetime: DateTime<Local>
pub latest_closure_generation: u32
}
impl AppState<'_> {
pub fn new(conf: Configuration) -> Self {
pub async fn new(conf: Configuration) -> Self {
let rp = "localhost";
let rp_origin = Url::parse(&conf.url).expect("Invalid URL");
let builder = WebauthnBuilder::new(rp, &rp_origin).expect("Invalid configuration");
@ -128,12 +131,26 @@ impl AppState<'_> {
};
let mgr = Manager::from_config(pg_config, NoTls, mgr_config);
let pool = Pool::builder(mgr).max_size(16).build().unwrap();
let access_key = fs::read_to_string(&conf.s3_access_key_filepath)
.unwrap_or_else(|_| format!("Cannot read the S3 access key from {}", &conf.s3_access_key_filepath.clone()));
let secret_key = fs::read_to_string(&conf.s3_secret_key_filepath)
.unwrap_or_else(|_| format!("Cannot read the S3 secret key from {}", &conf.s3_secret_key_filepath.clone()));
let access_key = access_key.strip_suffix('\n').unwrap_or(&access_key);
let secret_key = secret_key.strip_suffix('\n').unwrap_or(&secret_key);
let credentials = Credentials::new(access_key, secret_key, None, None, "nom-nom-provider");
let s3_client_config = aws_config::SdkConfig::builder()
.endpoint_url(conf.s3_endpoint)
.region(Some(Region::new(conf.s3_region)))
.credentials_provider(SharedCredentialsProvider::new(credentials))
.behavior_version(BehaviorVersion::latest())
.build();
let s3_client = aws_sdk_s3::Client::new(&s3_client_config);
AppState {
webauthn,
db: pool,
hbs,
session
session,
s3_client
}
}
@ -198,13 +215,13 @@ impl AppState<'_> {
pub async fn create_binary_cache(&self, binary_cache: &BinaryCache) -> Result<()> {
let conn = self.db.get().await?;
let stmt = conn.prepare_cached("INSERT INTO BinaryCaches (name, access_key, secret_key, region, endpoint) VALUES ($1, $2, $3, $4, $5)").await?;
let _ = conn.execute(&stmt, &[&binary_cache.name, &binary_cache.access_key, &binary_cache.secret_key, &binary_cache.region, &binary_cache.endpoint]).await?;
let _ = conn.execute(&stmt, &[&binary_cache.name, &binary_cache.access_key, &binary_cache.secret_key, &binary_cache.region, &binary_cache.endpoint_url]).await?;
Ok(())
}
pub async fn create_project(&self, binary_cache: &BinaryCache, project: &Project) -> Result<()> {
let conn = self.db.get().await?;
let stmt = conn.prepare_cached("INSERT INTO Projects (name, binary_cache_id) \
let stmt = conn.prepare_cached("INSERT INTO Projects (name, binary_cache_id, 0) \
SELECT $1, b.id FROM BinaryCaches b \
WHERE b.name = $2").await?;
let _ = conn.execute(&stmt, &[&project.name, &binary_cache.name]).await?;
@ -223,16 +240,17 @@ impl AppState<'_> {
pub async fn get_project(&self, token: &ProjectUuid) -> Result<Project> {
let conn = self.db.get().await?;
let stmt = conn.prepare_cached("SELECT name FROM Projects p \
let stmt = conn.prepare_cached("SELECT name, closure_generation FROM Projects p \
INNER JOIN ProjectTokens t ON p.id = t.project_id \
WHERE t.token = $1").await?;
let row = conn.query_one(&stmt, &[&token.0]).await?;
Ok(Project {
name: row.get(0)
name: row.get(0),
latest_closure_generation: row.get(1)
})
}
pub async fn get_project_summaries(&self) -> Result<Vec<ProjectSummary>> {
/* pub async fn get_project_summaries(&self) -> Result<Vec<ProjectSummary>> {
let conn = self.db.get().await?;
let stmt = conn.prepare_cached("SELECT p.name, p FROM Projects p \
INNER JOIN Closures c ON c.project_id = p.id").await?;
@ -244,5 +262,5 @@ impl AppState<'_> {
latest_closure_datetime: r.get(2)
}).collect()
)
}
}*/
}

10
src/s3/mod.rs Normal file
View File

@ -0,0 +1,10 @@
use anyhow::{anyhow, Result};
use aws_sdk_s3::{operation::head_bucket::HeadBucketOutput, Client};
use crate::models::Configuration;
pub async fn check_bucket(client: &Client, config: &Configuration) -> Result<HeadBucketOutput> {
println!("{}",&config.s3_bucket);
let res = client.head_bucket().bucket(&config.s3_bucket).send().await;
res.map_err(|e| anyhow!("Cannot access the binary cache bucket: {}", e))
}

View File

@ -1,11 +1,14 @@
use std::net::SocketAddr;
use actix_web::{App, web, HttpServer};
use actix_web::{App, web, HttpServer, HttpResponse};
use clap::Parser;
use mime_guess::from_path;
use nom_nom_gc::handlers;
use nom_nom_gc::models::read_config;
use nom_nom_gc::models;
use nom_nom_gc::s3::check_bucket;
use rust_embed::RustEmbed;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
@ -16,6 +19,19 @@ struct CLIArgs {
config: Option<String>
}
#[derive(RustEmbed)]
#[folder = "src/static"]
struct Static;
async fn handle_embedded_file(path: web::Path<String>) -> HttpResponse {
match Static::get(&path) {
Some(content) => HttpResponse::Ok()
.content_type(from_path(&*path).first_or_octet_stream().as_ref())
.body(content.data.into_owned()),
None => HttpResponse::NotFound().body("404 Not Found"),
}
}
#[tokio::main]
async fn main() -> std::io::Result<()> {
let args = CLIArgs::parse();
@ -23,14 +39,20 @@ async fn main() -> std::io::Result<()> {
let config_path = args.config.unwrap_or("/etc/nom-nom-gc/config.json".to_owned());
let config = read_config(&config_path)
.unwrap_or_else(|e| panic!("Cannot read config file: {}", e));
let state = models::AppState::new(config);
let state = models::AppState::new(config.clone()).await;
println!("Running DB migrations");
state.run_migrations().await.unwrap_or_else(|e| panic!("Db migration error: {}", e));
println!("Checking binary cache bucket");
let bucket = check_bucket(&state.s3_client, &config).await;
match bucket {
Ok(_) => println!("Connection to the bucket successful"),
Err(e) => panic!("Cannot connect to the binary cache bucket: {}", e)
}
println!("Server listening to {}", &args.bind);
HttpServer::new(
move || {
App::new().app_data(web::Data::new(state.clone()))
App::new()
.app_data(web::Data::new(state.clone()))
.route("/", web::get().to(handlers::landing_page))
.route("/account/register/{uuid}", web::get().to(handlers::webauthn_registration))
.route("/account/register-init", web::post().to(handlers::start_webauthn_registration))
@ -40,6 +62,8 @@ async fn main() -> std::io::Result<()> {
.route("/login/finish", web::post().to(handlers::webauthn_login_finish))
.route("/binary-cache/new", web::get().to(handlers::new_binary_cache))
.route("/binary-cache/new", web::post().to(handlers::new_binary_cache_post))
.route("/binary-cache/{id}", web::get().to(handlers::get_binary_cache))
.route("/static/{_:.*}", web::get().to(handle_embedded_file))
})
.bind(addr)
.unwrap()

BIN
src/static/malix.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -3,21 +3,7 @@
<a href="/binary-cache/new">New Binary Cache</a>
{{#each binaryCaches}}
<div class="binary-cache">
<h3>{{this.name}}</h3>
<table>
<tr>
<th>Project Name</th>
<th>Latest Closure</th>
<th>Datetime</th>
</tr>
{{#each this.projects}}
<tr>
<td>{{this.name}}</td>
<td>{{this.latestClosure}}</td>
<td>{{this.datetime}}</td>
</tr>
{{/each}}
</table>
<h3><a href="/binary-cache/{{this.id}}">{{this.name}}</a></h3>
</div>
{{/each}}
{{ /template }}

View File

@ -1,4 +1,4 @@
{{#> template js=js }}
<h1>Login</h1>
<form id="key-form"><label>Username</label><input id="user-name"/><input type="submit" value="Login" id="login-btn"/></form>
<form id="key-form"><label>Username</label><input id="user-name"/><input type="submit" value="Login" id="submit"/></form>
{{ /template }}

View File

@ -1,5 +1,5 @@
body {
background-color: #F9FA57;
background-color: #FFEE00;
color: black;
font-family: sans-serif;
}
@ -19,17 +19,22 @@ main {
}
nav {
padding: 1em;
padding: .5em;
margin: 1em;
background-color: white;
box-shadow: 10px 15px #C800E4;
border: 2px solid black;
}
nav > ul {
margin: 0;
padding-left: 0;
font-size: 2em;
display: flex;
justify-content: space-around;
list-style-type: none;
align-items: center;
flex-basis: 90%;
}
#main-container {
@ -38,4 +43,50 @@ nav > ul {
width: 85%;
padding: 2em;
margin: 1em;
border: 2px solid black;
}
form {
display: flex;
flex-direction: column;
width: 20em;
margin: auto;
padding: 1em;
border: 3px solid;
border-radius: 9px;
}
form > label {
margin-top: .5em;
font-weight: bold;
}
form > input {
border: 2px solid black;
border-radius: 10px;
padding-left: 1em;
font-weight: bold;
}
form > #submit {
margin-top: 3em;
font-weight: bold;
}
#logo-txt {
font-size: .4em;
color: #C800E4;
}
#nom-nom-gc-txt {
writing-mode: vertical-rl;
color: #C800E4;
font-weight: bold;
font-size: 2em;
margin-left: 0;
text-align: center;
}
nav {
display: flex;
}

View File

@ -1,46 +1,44 @@
use handlebars::RenderError;
use rust_embed::RustEmbed;
use serde_json::json;
use handlebars::Handlebars;
use std::{path::PathBuf, sync::Arc};
use std::{sync::Arc, str::from_utf8};
use crate::models::{RegistrationUuid, ProjectSummary};
use crate::models::RegistrationUuid;
#[derive(RustEmbed)]
#[folder = "src/templates"]
struct Templates;
pub fn new<'a>() -> Result<Handlebars<'a>, RenderError> {
let rootpath = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let landing_path = rootpath.join("src/templates/landing.hbs");
let template_path = rootpath.join("src/templates/template.hbs");
let register_user = rootpath.join("src/templates/register-user.hbs");
let landing_path = Templates::get("landing.hbs").unwrap();
let template_path = Templates::get("template.hbs").unwrap();
let register_user = Templates::get("register-user.hbs").unwrap();
let mut hbs = handlebars::Handlebars::new();
let css = rootpath.join("src/templates/main.css");
let webauthn_register_js = rootpath.join("src/templates/webauthn-register.js");
let webauthn_login_js = rootpath.join("src/templates/webauthn-login.js");
let login = rootpath.join("src/templates/login.hbs");
let new_binary_cache_form = rootpath.join("src/templates/new-binary-cache.hbs");
let css = Templates::get("main.css").unwrap();
let webauthn_register_js =Templates::get("webauthn-register.js").unwrap();
let webauthn_login_js = Templates::get("webauthn-login.js").unwrap();
let login = Templates::get("login.hbs").unwrap();
let new_binary_cache_form = Templates::get("new-binary-cache.hbs").unwrap();
hbs.register_template_file("landing", landing_path.to_str().unwrap())?;
hbs.register_template_file("template", template_path.to_str().unwrap())?;
hbs.register_template_file("css", css.to_str().unwrap())?;
hbs.register_template_file("webauthn-register-js", webauthn_register_js.to_str().unwrap())?;
hbs.register_template_file("webauthn-login-js", webauthn_login_js.to_str().unwrap())?;
hbs.register_template_file("register-user", register_user.to_str().unwrap())?;
hbs.register_template_file("login", login.to_str().unwrap())?;
hbs.register_template_file("new-binary-cache-form", new_binary_cache_form.to_str().unwrap())?;
hbs.register_template_string("landing", from_utf8(&landing_path.data).unwrap())?;
hbs.register_template_string("template", from_utf8(&template_path.data).unwrap())?;
hbs.register_template_string("css", from_utf8(&css.data).unwrap())?;
hbs.register_template_string("webauthn-register-js", from_utf8(&webauthn_register_js.data).unwrap())?;
hbs.register_template_string("webauthn-login-js", from_utf8(&webauthn_login_js.data).unwrap())?;
hbs.register_template_string("register-user", from_utf8(&register_user.data).unwrap())?;
hbs.register_template_string("login", from_utf8(&login.data).unwrap())?;
hbs.register_template_string("new-binary-cache-form", from_utf8(&new_binary_cache_form.data).unwrap())?;
Ok(hbs)
}
pub fn landing_page(hb: Arc<Handlebars<'_>>, logged: bool, project_summaries: Vec<ProjectSummary>) -> Result<String, RenderError> {
pub fn landing_page(hb: Arc<Handlebars<'_>>, _logged: bool) -> Result<String, RenderError> {
let data = json!({
"binaryCaches": [{
"name": "NixOS Binary Cache",
"projects": project_summaries.into_iter().map(|p| json!({
"name": p.name,
"latestClosure": p.latest_closure,
"datetime": p.latest_closure_datetime.to_string()
}
)).collect::<Vec<_>>()
}]});
}]
});
hb.render("landing", &data)
}

View File

@ -1,16 +1,16 @@
{{#> template }}
<h1>New Binary Cache</h1>
<form name="binary-cache" method="post" action="/binary-cache/new">
<form id="binary-cache-form" name="binary-cache" method="post" action="/binary-cache/new">
<label>Bucket Name</label>
<input name="name"/>
<input name="name" placeholder="Nix Cache"/>
<label>Bucket Access Key</label>
<input name="access_key"/>
<input name="access_key" placeholder="AKIAIOSFODNN7EXAMPLE"/>
<label>Bucket Secret Key</label>
<input name="secret_key"/>
<input name="secret_key" placeholder="JalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"/>
<label>Bucket Region</label>
<input name="region"/>
<input name="region" placeholder="garage"/>
<label>Bucket Endpoint</label>
<input name="endpoint"/>
<input type="submit" value="Create binary cache"/>
<input name="endpoint_url" placeholder="https://cache.yourdomain.com"/>
<input id="submit" type="submit" value="Create binary cache"/>
</form>
{{ /template }}

View File

@ -1,6 +1,6 @@
{{#> template js=js }}
<h1>New user: {{ username }}</h1>
<form id="key-form"><label>Key Name</label><input id="key-name"/><input type="submit" value="Enroll FIDO key"/></form>
<form id="key-form"><label>Key Name</label><input id="key-name"/><input type="submit" id="submit" value="Enroll FIDO key"/></form>
<table>
<tr><th>KeyID</th></tr>
{{#each keyids}}

View File

@ -11,11 +11,12 @@
</head>
<body>
<nav>
<p id="nom-nom-gc-txt">NOM NOM GC</p>
<ul>
<li><h1>Nom Nom GC</h1></li>
<li><a href="/">Home</a></li>
<li><a href="/projects">Projects</a></li>
<li><a href="/login">Login</a></li>
<li><img id="logo" src="/static/malix.webp" alt="logo"/><p id="logo-txt">Malix is eating your cache</p></li>
<li><a href="/">HOME</a></li>
<li><a href="/projects">PROJECTS</a></li>
<li><a href="/login">LOGIN</a></li>
</nav>
<main>
<div id="main-container">

View File

@ -41,9 +41,7 @@ function encodeSolvedChallenge(solvedChallenge) {
async function finish_auth (challengeUuid, solvedChallenge) {
// Encode challenge response
console.log(solvedChallenge);
const encodedSolvedChallenge = encodeSolvedChallenge(solvedChallenge);
console.log(encodedSolvedChallenge);
const resp = await fetch("/login/finish", {
method: 'POST',
body: JSON.stringify({uuid: challengeUuid, challenge: encodedSolvedChallenge}),
@ -64,5 +62,6 @@ async function perform_webauthn_dance () {
button.addEventListener("submit", async (e) => {
e.preventDefault();
await perform_webauthn_dance();
window.location = "/";
});
}) ();

View File

@ -74,10 +74,12 @@ async fn run_test_db(db: &TestDB) -> Result<()> {
access_key: "access key".to_string(),
secret_key: "secret key".to_string(),
region: "reg-01".to_string(),
endpoint_url: "localhost:"
};
state.create_binary_cache(&binary_cache).await?;
let project = Project {
name: "super-duper-project".to_string()
name: "super-duper-project".to_string(),
latest_closure_generation: 0
};
state.create_project(&binary_cache, &project).await?;
let token = state.create_project_token(&project).await?;