Compare commits

...

31 Commits

Author SHA1 Message Date
75c91821e9 Allow Docker build job to push image to registry
Some checks failed
Push Workflows / rustfmt (push) Successful in 4s
Push Workflows / tailwind-build (push) Successful in 5s
Push Workflows / docker-build (push) Failing after 25s
Push Workflows / clippy (push) Successful in 53s
Push Workflows / test (push) Successful in 1m1s
Push Workflows / docs (push) Successful in 1m0s
Push Workflows / build (push) Successful in 1m51s
Push Workflows / nix-build (push) Successful in 5m17s
2026-08-11 20:35:30 -04:00
b468c79056 Add CICD job to build Docker image
Some checks failed
Push Workflows / rustfmt (push) Successful in 4s
Push Workflows / tailwind-build (push) Successful in 5s
Push Workflows / docs (push) Successful in 53s
Push Workflows / clippy (push) Successful in 53s
Push Workflows / test (push) Successful in 1m2s
Push Workflows / build (push) Successful in 1m55s
Push Workflows / nix-build (push) Successful in 5m27s
Push Workflows / docker-build (push) Failing after 14s
2026-08-11 20:02:38 -04:00
5475ee8878 Add Dockerfile 2026-08-11 20:02:19 -04:00
2a388f61e2 Update flake
All checks were successful
Push Workflows / rustfmt (push) Successful in 4s
Push Workflows / tailwind-build (push) Successful in 6s
Push Workflows / clippy (push) Successful in 52s
Push Workflows / docs (push) Successful in 59s
Push Workflows / test (push) Successful in 1m2s
Push Workflows / build (push) Successful in 1m52s
Push Workflows / nix-build (push) Successful in 5m21s
2026-08-04 21:04:55 -04:00
ea2bc49600 Use buildDioxusWebApp builder
All checks were successful
Push Workflows / rustfmt (push) Successful in 5s
Push Workflows / tailwind-build (push) Successful in 5s
Push Workflows / clippy (push) Successful in 51s
Push Workflows / test (push) Successful in 58s
Push Workflows / docs (push) Successful in 58s
Push Workflows / build (push) Successful in 1m54s
Push Workflows / nix-build (push) Successful in 5m32s
2026-08-04 20:58:25 -04:00
c41a2474e6 Update dioxus library
All checks were successful
Push Workflows / rustfmt (push) Successful in 4s
Push Workflows / tailwind-build (push) Successful in 4s
Push Workflows / clippy (push) Successful in 1m13s
Push Workflows / test (push) Successful in 1m26s
Push Workflows / docs (push) Successful in 1m25s
Push Workflows / build (push) Successful in 2m8s
Push Workflows / nix-build (push) Successful in 5m53s
2026-08-04 20:44:33 -04:00
e95f335d76 Remove daisyui wrapper modules
Some checks failed
Push Workflows / test (push) Failing after 5s
Push Workflows / docs (push) Failing after 5s
Push Workflows / clippy (push) Failing after 6s
Push Workflows / rustfmt (push) Successful in 4s
Push Workflows / tailwind-build (push) Failing after 4s
Push Workflows / nix-build (push) Successful in 4m51s
Push Workflows / build (push) Failing after 1m15s
2026-08-04 20:12:39 -04:00
55db6c317c Use tailwindcss-with-pkgs flake 2026-08-04 20:12:00 -04:00
aff6b9a760 Use daisyui flake 2026-08-04 20:09:35 -04:00
b66bc54b3f Add health check endpoint
All checks were successful
Push Workflows / rustfmt (push) Successful in 5s
Push Workflows / tailwind-build (push) Successful in 6s
Push Workflows / docs (push) Successful in 19s
Push Workflows / clippy (push) Successful in 17s
Push Workflows / test (push) Successful in 24s
Push Workflows / build (push) Successful in 46s
Push Workflows / nix-build (push) Successful in 5m19s
2026-07-19 12:56:32 -04:00
c4cba925a7 Manually serve static files
All checks were successful
Push Workflows / rustfmt (push) Successful in 6s
Push Workflows / tailwind-build (push) Successful in 6s
Push Workflows / docs (push) Successful in 20s
Push Workflows / test (push) Successful in 24s
Push Workflows / clippy (push) Successful in 18s
Push Workflows / build (push) Successful in 46s
Push Workflows / nix-build (push) Successful in 5m11s
Remove unnecessary paths from require_auth_mw due to layer ordering
2026-07-19 10:53:34 -04:00
dcbac8a9ba Enable precompression 2026-07-19 10:53:34 -04:00
5e498195ce Move router setup to build_router 2026-07-19 10:53:28 -04:00
a2c754de2c Add function to Config to output warnings
All checks were successful
Push Workflows / tailwind-build (push) Successful in 7s
Push Workflows / rustfmt (push) Successful in 9s
Push Workflows / docs (push) Successful in 20s
Push Workflows / test (push) Successful in 24s
Push Workflows / clippy (push) Successful in 19s
Push Workflows / build (push) Successful in 47s
Push Workflows / nix-build (push) Successful in 5m18s
2026-07-19 10:13:05 -04:00
771b8e5ce4 Initialize config defaults in separate function
All checks were successful
Push Workflows / rustfmt (push) Successful in 6s
Push Workflows / tailwind-build (push) Successful in 6s
Push Workflows / docs (push) Successful in 20s
Push Workflows / test (push) Successful in 24s
Push Workflows / clippy (push) Successful in 18s
Push Workflows / build (push) Successful in 47s
Push Workflows / nix-build (push) Successful in 5m16s
2026-07-19 10:06:33 -04:00
b15b8b2ae1 Move load_config to Config::from_env 2026-07-19 10:00:07 -04:00
a0931eb420 Allow equality comparison of Error 2026-07-19 09:55:08 -04:00
3816f3ef28 Use less secure hashing in test mode
All checks were successful
Push Workflows / rustfmt (push) Successful in 6s
Push Workflows / tailwind-build (push) Successful in 7s
Push Workflows / docs (push) Successful in 21s
Push Workflows / clippy (push) Successful in 18s
Push Workflows / test (push) Successful in 25s
Push Workflows / build (push) Successful in 47s
Push Workflows / nix-build (push) Successful in 5m15s
2026-07-19 09:52:58 -04:00
af1aafbde6 Write config to log
All checks were successful
Push Workflows / rustfmt (push) Successful in 6s
Push Workflows / tailwind-build (push) Successful in 6s
Push Workflows / docs (push) Successful in 19s
Push Workflows / clippy (push) Successful in 17s
Push Workflows / test (push) Successful in 24s
Push Workflows / build (push) Successful in 45s
Push Workflows / nix-build (push) Successful in 5m11s
2026-07-14 20:32:46 -04:00
b4da87231d Use SecretString in config where appropriate 2026-07-14 20:32:25 -04:00
a976f04f7d Add SecretString type to config 2026-07-14 20:32:04 -04:00
738da0aac6 Create axum router directly
All checks were successful
Push Workflows / rustfmt (push) Successful in 5s
Push Workflows / tailwind-build (push) Successful in 6s
Push Workflows / docs (push) Successful in 25s
Push Workflows / clippy (push) Successful in 23s
Push Workflows / test (push) Successful in 31s
Push Workflows / build (push) Successful in 57s
Push Workflows / nix-build (push) Successful in 5m11s
Bind to configured host/port
Warn if configured host/port differs from dx-specified host/port
Make main async
2026-07-14 20:13:07 -04:00
76fac9b4cb Add tokio on server 2026-07-14 20:11:19 -04:00
4836108926 Add HTTP server error variant 2026-07-14 20:11:04 -04:00
d66871f2f5 Add server host/port config 2026-07-14 20:10:04 -04:00
d5eba9620c Link to favicon
All checks were successful
Push Workflows / rustfmt (push) Successful in 5s
Push Workflows / tailwind-build (push) Successful in 6s
Push Workflows / docs (push) Successful in 20s
Push Workflows / test (push) Successful in 23s
Push Workflows / clippy (push) Successful in 18s
Push Workflows / build (push) Successful in 47s
Push Workflows / nix-build (push) Successful in 6m24s
2026-07-11 12:41:03 -04:00
39afdd83a7 Add username and password form input presets
All checks were successful
Push Workflows / rustfmt (push) Successful in 5s
Push Workflows / tailwind-build (push) Successful in 6s
Push Workflows / docs (push) Successful in 31s
Push Workflows / clippy (push) Successful in 28s
Push Workflows / test (push) Successful in 35s
Push Workflows / build (push) Successful in 1m12s
Push Workflows / nix-build (push) Successful in 6m24s
2026-07-11 12:37:30 -04:00
34b887730a Enable account icons in Lucide 2026-07-11 12:37:30 -04:00
f2dc4c908e Export individual components from crate::components 2026-07-11 12:28:35 -04:00
219f1f5e7d Add open-signup to list of allowed endpoints 2026-07-11 12:26:28 -04:00
102387fdad Initialize tracing to INFO in release mode 2026-07-11 12:21:37 -04:00
21 changed files with 614 additions and 279 deletions

View File

@@ -84,3 +84,40 @@ jobs:
uses: actions/checkout@v4
- name: Run tailwindcss
run: tailwindcss --input style/tailwind.css
docker-build:
runs-on: ubuntu-latest-docker
permissions:
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Gitea container registry
uses: docker/login-action@v4
with:
registry: ${{ env.registry }}
username: ${{ env.actions_user }}
password: ${{ secrets.CONTAINER_REGISTRY_TOKEN }}
- name: Get Image Name
id: get-image-name
run: |
echo "IMAGE_NAME=$(echo ${{ env.registry }}/${{ gitea.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT
- name: Docker meta
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ steps.get-image-name.outputs.IMAGE_NAME }}
tags: |
type=sha
type=ref,event=branch
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v7
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

155
Cargo.lock generated
View File

@@ -875,9 +875,9 @@ dependencies = [
[[package]]
name = "dioxus"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c01ecf7ddbae18a419ad3d83c486101a85ffc5740ea09cdd0f09a30dc12170d"
checksum = "9320593eda4f01858f046698b42441603d2af5fcb62a6c1f9046a0c2eebd6d9b"
dependencies = [
"dioxus-asset-resolver",
"dioxus-cli-config",
@@ -908,9 +908,9 @@ dependencies = [
[[package]]
name = "dioxus-asset-resolver"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69387edbbc60c7cb93ad96d8cc7a22b49a76e21643380b89b1c49a78d347ff60"
checksum = "84235ff0e272f15d4537603cdd50df6d45d50e51bb96326dbc0bdfd01d825226"
dependencies = [
"dioxus-cli-config",
"http",
@@ -929,18 +929,18 @@ dependencies = [
[[package]]
name = "dioxus-cli-config"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c000f584ddf608e2b272b3074bf11512a474eeeb2eb85a1915f276ce5c4a8615"
checksum = "322d10f47effebd85ee0662a2cb083ff920d40dd6295a4afd8cfa4f9bc2e05c6"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "dioxus-config-macro"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7637091592978fbfdb45a16b26bd99fd97fb1bd7e31c6a963530e00c022af321"
checksum = "9f3a906c8219e94fa48189a793c760a4e3193cbb7791f799e9d37633fe41161d"
dependencies = [
"proc-macro2",
"quote",
@@ -948,15 +948,15 @@ dependencies = [
[[package]]
name = "dioxus-config-macros"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54f9ed8fc1a215ad34bb8dbae42a4ea54efbcd26ca9006bbe5cca78e511bf25f"
checksum = "20de015bc89c20f4ffc7b4f849943420308365fa66f991250b5c075261990a21"
[[package]]
name = "dioxus-core"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45887100ff0cf89abeb8b659808294fda48cd53f3b424e36407dedffcfea830b"
checksum = "1b232228ada232c0adc151e41ebd9ef70cc04683db097fa0dc6015979fa01722"
dependencies = [
"anyhow",
"const_format",
@@ -976,9 +976,9 @@ dependencies = [
[[package]]
name = "dioxus-core-macro"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "370c63663dff0f24df5dfea643ca239283542c6b228a302f69b32e1d36762b7f"
checksum = "6256a68462222f0600f5fd7d9542cbd9f299bc19355d900f7827dd3fc9e09fdb"
dependencies = [
"convert_case 0.8.0",
"dioxus-rsx",
@@ -989,15 +989,15 @@ dependencies = [
[[package]]
name = "dioxus-core-types"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36963eab106b169737762f9cd5ee5fd97f585989dcb2d8e30a596e97a6999009"
checksum = "96007ed6cbad951dfed82d83c581aabd95bc591f351f5666d454ddbe7845b324"
[[package]]
name = "dioxus-devtools"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2349cedbdf1b429df1f1bea61fdee0ad3dae7b2548eedfbeca82710122a57da0"
checksum = "434cebb282b3f820a341582ebfd37dab907d5a25c3eeafd87802468afcd2a959"
dependencies = [
"dioxus-cli-config",
"dioxus-core",
@@ -1015,9 +1015,9 @@ dependencies = [
[[package]]
name = "dioxus-devtools-types"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab9b0f7565d1916b70915f59b89ea8054ef0a9d67a364a32bbee68ef5f3818d"
checksum = "dfd6f3475e38c93be245a664a9ce11679a8f2b99832620705cf7837f42aa39ce"
dependencies = [
"dioxus-core",
"serde",
@@ -1026,9 +1026,9 @@ dependencies = [
[[package]]
name = "dioxus-document"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e3a5bec7ffc999ff23446a487eb5cd86111d1574a23533dd3f8b3c69a53a22"
checksum = "2f6fadd5c6d886b48b762820abae1a08142a2159f8a1136c0466b4b4ed9f547e"
dependencies = [
"dioxus-core",
"dioxus-core-macro",
@@ -1045,9 +1045,9 @@ dependencies = [
[[package]]
name = "dioxus-fullstack"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37f0558edb88af5ad47275ae36a7f06317163ba482db377c26d7d8590b5cd0f6"
checksum = "a91d5acabd1470145bcf164cab9fd2edcf2e4fba0fa6fb0fd6dca23079fcc169"
dependencies = [
"anyhow",
"async-stream",
@@ -1110,9 +1110,9 @@ dependencies = [
[[package]]
name = "dioxus-fullstack-core"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc634b28b4b1e3eab1e8df4f98510e2d2fa39d686321467f977213155e86ed2b"
checksum = "0011e9bca8da2ae6bf02ba9ef93ba6c5fa539e402de3aeb316ad86ca77c59079"
dependencies = [
"anyhow",
"axum-core",
@@ -1138,9 +1138,9 @@ dependencies = [
[[package]]
name = "dioxus-fullstack-macro"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85a8fe7da549859fae00c7f4bf11a2aab734ae7ef6f98f280dce9bea1f3326ec"
checksum = "681f39665de36af7bb724b1cc0ca9c2b7c36a2b93febf1b2911716479eac3db0"
dependencies = [
"const_format",
"convert_case 0.8.0",
@@ -1152,9 +1152,9 @@ dependencies = [
[[package]]
name = "dioxus-history"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a15232302d1933015fcf2d6fe9e286ad36f6e9c205a546089a0f326023bb0d2"
checksum = "2404b77d4441e694a5e93afb5c9a729efb26a3f6920f769cfe6cfedf5c7ac210"
dependencies = [
"dioxus-core",
"tracing",
@@ -1162,9 +1162,9 @@ dependencies = [
[[package]]
name = "dioxus-hooks"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4534f91cf6305204b948bdec130076ac9ecc7c22faab29475b76870558bf73ea"
checksum = "9d3807a1bc039299cd37ad85f1c56ae7088210e8a38e3e877f01034d68c15fbb"
dependencies = [
"dioxus-core",
"dioxus-signals",
@@ -1178,9 +1178,9 @@ dependencies = [
[[package]]
name = "dioxus-html"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e03d6ad4040b667f2b2eefcb678840e630938c09bf9ec39b04ea4d1d96d90d44"
checksum = "40b9a85679c4dfd8699407a9ad5041a6a4fe6be0ee386353951cd03b57a12030"
dependencies = [
"async-trait",
"bytes",
@@ -1205,9 +1205,9 @@ dependencies = [
[[package]]
name = "dioxus-html-internal-macro"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "584e2772127ab00f0d5e1d4d9795f39fecebc828ece0b7a02349d438bc1b1ce7"
checksum = "500282dad5b62e91f93e127cbf41c587bf1c457d52514a75f3ff2a55fd0ea9f6"
dependencies = [
"convert_case 0.8.0",
"proc-macro2",
@@ -1217,9 +1217,9 @@ dependencies = [
[[package]]
name = "dioxus-interpreter-js"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11999d6eb5bb179a9512dad30e5de408aab66f2cb65de9098c9fbe02927e2978"
checksum = "faa22cc3431e7efdbae28069542a1763c977d5edbe4d4fbfb0b7667b9c9966e9"
dependencies = [
"dioxus-core",
"dioxus-core-types",
@@ -1236,9 +1236,9 @@ dependencies = [
[[package]]
name = "dioxus-liveview"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7344b8f174967c7d2f6ad0103d680ab57daea83ebe3368f7f011c402fd6aaf77"
checksum = "8b9cfecfc0037cb85b649739acc49a09dd303d1fa1daa7af609f5796b491b71c"
dependencies = [
"axum",
"dioxus-cli-config",
@@ -1264,9 +1264,9 @@ dependencies = [
[[package]]
name = "dioxus-logger"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ccdfe36d2cb830a2784e40f7e6f7199805a2c6da99bd65b1ca308f11aed28"
checksum = "0f36b2dac4df8c747431f77f2a6644dd033ea98e7f8ac80271ff47fd4ba8a548"
dependencies = [
"dioxus-cli-config",
"tracing",
@@ -1276,9 +1276,9 @@ dependencies = [
[[package]]
name = "dioxus-router"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38e47f62d680429badfcb99bf5dec17ee92b0cb9623f264e36bc003a1359bfdc"
checksum = "e9652a5bcead34f687d7dbb12d5c8f6a7ecb342786273395ca0078e51b24320f"
dependencies = [
"dioxus-cli-config",
"dioxus-core",
@@ -1297,9 +1297,9 @@ dependencies = [
[[package]]
name = "dioxus-router-macro"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f83fb667d27e256f8c9eca49963fbace66a8722cb64ee15a10ffc97d092357e"
checksum = "56d20cf2173183946fdf8ab069b606a840a9d06e949050619355fc8cb97aafb6"
dependencies = [
"base16",
"digest 0.10.7",
@@ -1312,9 +1312,9 @@ dependencies = [
[[package]]
name = "dioxus-rsx"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2106afda239a4c7c22ffa1ca19117011225fc1c735c139c0a5b765996aa8bb1d"
checksum = "aea662c9e13a91279d8acbc0d02a80430c7081704c20e8c791e4c4a151268596"
dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
@@ -1325,9 +1325,9 @@ dependencies = [
[[package]]
name = "dioxus-server"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5ba2095c16f847d3f680a94cc9b0637d190aace651ecfad0feda180da13634b"
checksum = "ad8d8cfcc78453d8f1eba4350126161844a0b10b4dc898bac7649b8615f92c48"
dependencies = [
"anyhow",
"async-trait",
@@ -1383,9 +1383,9 @@ dependencies = [
[[package]]
name = "dioxus-signals"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3705754f5e043deec9fc7af0d159f18e5b21c02c47d255c7e477f31368f0b6d2"
checksum = "95d85e36fcaf7abdc986836801bf248776da7d5961f5fac65fdbf5ed491afb4f"
dependencies = [
"dioxus-core",
"futures-channel",
@@ -1399,9 +1399,9 @@ dependencies = [
[[package]]
name = "dioxus-ssr"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d261c5c9907b84fb1ed52f59f46d68c84a4ae860a65cc5effd0cea740ee428af"
checksum = "4544ecf908962909de403bfdf94f771b036e9e1857e371cd3279841a15a892e6"
dependencies = [
"askama_escape",
"dioxus-core",
@@ -1411,9 +1411,9 @@ dependencies = [
[[package]]
name = "dioxus-stores"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64bec7b21c86b1360ec965a07a53a2c96b7caee3465049e1c299a45024e87614"
checksum = "15eaa9f2ccc5ce962d056515e61345c846a54b13a2b880cf7e8ac93faa53a178"
dependencies = [
"dioxus-core",
"dioxus-signals",
@@ -1423,9 +1423,9 @@ dependencies = [
[[package]]
name = "dioxus-stores-macro"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40a5875e9f890f27b1cc3e5b56c1e23601211470315a1fb8627c4ca4f3b2be9a"
checksum = "a13bd8a693d7042544fa3ac79698a08783bbc698d1ed257e88c5d4059e9ac476"
dependencies = [
"convert_case 0.8.0",
"proc-macro2",
@@ -1435,9 +1435,9 @@ dependencies = [
[[package]]
name = "dioxus-web"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc0a0be76b404e8242a597db0fb239d05f8dee4e7856bc1fc7144f7e244822fd"
checksum = "481093233d789c5ffbdde66e7970e803b01a199a7ab0c0f8ef84c65d30b7384f"
dependencies = [
"dioxus-cli-config",
"dioxus-core",
@@ -1770,9 +1770,9 @@ dependencies = [
[[package]]
name = "generational-box"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cd0d825b8d339701ad330dbcd6399519ced4d143484954daf6e3185dace4f77"
checksum = "b8b2dc9b873cd1a8adb80aa5dcef9a8205f781a8a313d5ffb867248cb3bcb764"
dependencies = [
"parking_lot",
"tracing",
@@ -2097,7 +2097,7 @@ dependencies = [
"libc",
"percent-encoding",
"pin-project-lite",
"socket2 0.6.4",
"socket2 0.5.10",
"system-configuration",
"tokio",
"tower-layer",
@@ -2372,9 +2372,9 @@ checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37"
[[package]]
name = "lazy-js-bundle"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccafada6c9541db44db758619236f2748f6e1bdaa84d04ded858567cd1e89321"
checksum = "51cdaa5abc885e2d606e6985281ace220778bde190be20aab1ec2346ed8bd1fa"
[[package]]
name = "lazy_static"
@@ -2428,6 +2428,7 @@ dependencies = [
"rand 0.10.1",
"serde",
"thiserror 2.0.18",
"tokio",
"tower-http 0.7.0",
"tower-sessions-redis-store",
"tracing",
@@ -2510,9 +2511,9 @@ dependencies = [
[[package]]
name = "manganis"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bfcf56309de35b48b8780ea097ace5c3b773a617b52edc49dfc9a63a7d9dc43"
checksum = "1ed2af894a724a0b6e521f9efe84f98cfdc74a4de6828efef77ffd3b1679fd52"
dependencies = [
"const-serialize 0.7.2",
"const-serialize 0.8.0-alpha.0",
@@ -2526,9 +2527,9 @@ dependencies = [
[[package]]
name = "manganis-core"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a24d6be68f594495aea60850a284029d585d7b7839b26096c1b6d758f8518648"
checksum = "e258c71b137bf48deaffb48b8a8a2f994af9e5c3ca201daa20d41e26b379da11"
dependencies = [
"const-serialize 0.7.2",
"const-serialize 0.8.0-alpha.0",
@@ -2540,9 +2541,9 @@ dependencies = [
[[package]]
name = "manganis-macro"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e782a10318d707c0833e31876ded8acf91287eee0010af8392559af614c7226"
checksum = "0abae8346bbf637e6d21ac3831863bd5a98657517f5cd2d69d651046b95e7ce9"
dependencies = [
"dunce",
"macro-string",
@@ -3076,7 +3077,7 @@ dependencies = [
"quinn-udp",
"rustc-hash 2.1.2",
"rustls",
"socket2 0.6.4",
"socket2 0.5.10",
"thiserror 2.0.18",
"tokio",
"tracing",
@@ -3113,7 +3114,7 @@ dependencies = [
"cfg_aliases",
"libc",
"once_cell",
"socket2 0.6.4",
"socket2 0.5.10",
"tracing",
"windows-sys 0.60.2",
]
@@ -3749,9 +3750,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subsecond"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cc79674bd55726e6b123204403389400229a95fe4a3b2c5453dada70b06ca95"
checksum = "d350d5788fa94d560d92269266a50efc77b036bfca6675e420b64df7b3211f37"
dependencies = [
"js-sys",
"libc",
@@ -3768,9 +3769,9 @@ dependencies = [
[[package]]
name = "subsecond-types"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9798bfed58797aed51c672aa99810aac30a50d3120ecfdcf28c13784e9a8f1c"
checksum = "dcf32d66269b5fbb8558334e8d22b657b2f7af0dd2ef56c231541099a862e464"
dependencies = [
"serde",
]

View File

@@ -16,16 +16,17 @@ config = { version = "0.15.24", optional = true }
diesel = { version = "2.3.10", optional = true, features = ["chrono"] }
diesel-async = { version = "0.9.1", optional = true, features = ["postgres", "deadpool", "migrations"] }
diesel_migrations = { version = "2.3.2", optional = true }
dioxus = { version = "0.7.9", features = ["router", "fullstack"] }
dioxus-html = "0.7.9"
dioxus = { version = "0.7.10", features = ["router", "fullstack"] }
dioxus-html = "0.7.10"
dotenvy = { version = "0.15.7", optional = true }
fred = { version = "10.1.0", optional = true }
lucide-dioxus = { version = "3.11.0", features = ["notifications"] }
lucide-dioxus = { version = "3.11.0", features = ["notifications", "account"] }
pbkdf2 = { version = "0.13.0", optional = true, features = ["getrandom", "phc"] }
rand = "0.10.1"
serde = { version = "1.0.228", features = ["derive"] }
thiserror = "2.0.18"
tower-http = { version = "0.7.0", optional = true, features = ["fs"] }
tokio = { version = "1.52.3", optional = true }
tower-sessions-redis-store = { version = "0.16.0", optional = true }
tracing = "0.1.44"
@@ -42,6 +43,7 @@ server = [
"dep:dotenvy",
"dep:fred",
"dep:pbkdf2",
"dep:tokio",
"dep:tower-http",
"dep:tower-sessions-redis-store",
]

View File

@@ -18,3 +18,6 @@ script = []
# Javascript code file
# serve: [dev-server] only
script = []
[web]
pre_compress = true

99
Dockerfile Normal file
View File

@@ -0,0 +1,99 @@
FROM rust:slim AS builder
WORKDIR /app
# Install a few dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \
git \
curl \
unzip && \
rm -rf /var/lib/apt/lists/*
RUN rustup target add wasm32-unknown-unknown
# Install dioxus-cli
RUN curl -sSL https://dioxus.dev/install.sh | bash
ENV DAISYUI_PATH=/opt/daisyui/daisyui/
ENV DAISYUI_THEME_PATH=/opt/daisyui/theme/
ENV NODE_PATH=$DAISYUI_PATH:$DAISYUI_THEME_PATH
# Install DaisyUI bundles
RUN mkdir -p $DAISYUI_PATH $DAISYUI_THEME_PATH && \
cd $DAISYUI_PATH && \
curl -sLO https://github.com/saadeghi/daisyui/releases/latest/download/daisyui.js && \
cd $DAISYUI_THEME_PATH && \
curl -sLO https://github.com/saadeghi/daisyui/releases/latest/download/daisyui-theme.js
# Install Tailwind CSS CLI
RUN curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 && \
install tailwindcss-linux-x64 /usr/local/bin/tailwindcss && \
rm tailwindcss-linux-x64
# Include Rust binaries in PATH
ENV PATH="/root/.cargo/bin:${PATH}"
# Copy project dependency manifests
COPY Cargo.toml Cargo.lock Dioxus.toml /app/
# Create dummy files to force cargo to build the dependencies
# main() content fixes "failed to find intrinsics to enable `clone_ref` function"
# See https://github.com/trunk-rs/trunk/issues/951#issuecomment-2816819963
RUN mkdir /app/src && \
echo "fn main() { dioxus::logger::initialize_default(); }" > /app/src/main.rs && \
echo "fn main() {}" > /app/src/build.rs
# Prebuild dependencies
RUN dx build --release --locked --web
# Remove assets from the initial build
RUN rm -rf /app/target/dx/libretunes/release/web/public
RUN rm -rf /app/src
COPY --exclude=assets/tailwind.css assets /app/assets
COPY style /app/style
COPY migrations /app/migrations
COPY src /app/src
# Copy necessary git files for getting build version
RUN mkdir /app/.git
COPY .git/HEAD .git/refs /app/.git
# dx build will run tailwindcss anyways, but doing it first here is faster and clearer if it fails
RUN tailwindcss --input style/tailwind.css --output assets/tailwind.css
# Touch files to force rebuild
RUN touch /app/src/main.rs && touch /app/src/build.rs
# Actually build
RUN dx build --release --locked --web
# Use ldd to list all dependencies of the server, then copy them to /app/libs
RUN mkdir /app/libs && ldd /app/target/dx/libretunes/release/web/server | grep "=> /" | \
awk '{print $3}' | xargs -I '{}' cp '{}' /app/libs
# Build the final image
FROM scratch
LABEL license="MIT"
LABEL description="LibreTunes, an open-source browser audio player and \
library manager built for collaborative listening."
# Copy the binary and assets to the final image
COPY --from=builder /app/target/dx/libretunes/release/web/server /libretunes
COPY --from=builder /app/target/dx/libretunes/release/web/public /public
ENV LIBRETUNES_SERVER_PUBLIC_PATH=/public
ENV DIOXUS_PUBLIC_PATH=/public
# Copy libraries to /lib64
COPY --from=builder /app/libs /lib64
COPY --from=builder /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2
ENV LD_LIBRARY_PATH=/lib64
EXPOSE 8080
ENTRYPOINT [ "/libretunes" ]

62
flake.lock generated
View File

@@ -1,5 +1,35 @@
{
"nodes": {
"daisyui": {
"locked": {
"lastModified": 1785801268,
"narHash": "sha256-dqE+kAW1Smsf+H1Yfzy6DbYHST/sWfz3Ghzw7M331k8=",
"ref": "refs/heads/main",
"rev": "fd3f4af492599cf639c4d5f52f9521f0054e5e9e",
"revCount": 3,
"type": "git",
"url": "https://git.mregirouard.com/nix/daisyui.git"
},
"original": {
"type": "git",
"url": "https://git.mregirouard.com/nix/daisyui.git"
}
},
"dx-build": {
"locked": {
"lastModified": 1785888038,
"narHash": "sha256-zhUWl+BXYBBIHuLsjlLh+8RL6ymrUCLCnBL7I6/BAGw=",
"ref": "refs/heads/main",
"rev": "c3fbf020a501226c0ef7c1f6a394d9799c3cf52f",
"revCount": 3,
"type": "git",
"url": "https://gitea.mregirouard.com/nix/dx-build.git"
},
"original": {
"type": "git",
"url": "https://gitea.mregirouard.com/nix/dx-build.git"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
@@ -20,11 +50,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1780930886,
"narHash": "sha256-rppURzHviaQN131F+nLiLdGfcb0uCd9gGP0E5+iw9MI=",
"lastModified": 1785747939,
"narHash": "sha256-D740uKsMbgsfK2oaDenJLLPIZfq7W0/g4KN/Fls8eKs=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "8c3cede7ddc26bd659d2d383b5610efbd2c7a16e",
"rev": "104240a772428cc2e20d8fd86c9ddbb886bbaff2",
"type": "github"
},
"original": {
@@ -36,9 +66,12 @@
},
"root": {
"inputs": {
"daisyui": "daisyui",
"dx-build": "dx-build",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
"rust-overlay": "rust-overlay",
"tailwindcss-with-pkgs": "tailwindcss-with-pkgs"
}
},
"rust-overlay": {
@@ -48,11 +81,11 @@
]
},
"locked": {
"lastModified": 1781061510,
"narHash": "sha256-tVuGHgt/TsWu1rUAqEL+eWRIJJHtiPE2+yQ63b+/WTU=",
"lastModified": 1785820796,
"narHash": "sha256-sZXy8mzUMi2cOGulhoW4HWAZB6JhXOAx1x8J4auZFWk=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "d286e9691bb03045febbf8304a658eab1487d1b5",
"rev": "b6916ba032e02122d6ed3064f40cabe937363d43",
"type": "github"
},
"original": {
@@ -75,6 +108,21 @@
"repo": "default",
"type": "github"
}
},
"tailwindcss-with-pkgs": {
"locked": {
"lastModified": 1785801205,
"narHash": "sha256-yfKa3/wK/m1Gwjc923Fvpa/k44MjQOOR3eUfjqLn+qI=",
"ref": "refs/heads/main",
"rev": "f28fd97c86f37a6078599338fa9c6a0327821b9d",
"revCount": 3,
"type": "git",
"url": "https://git.mregirouard.com/nix/tailwindcss-with-pkgs.git"
},
"original": {
"type": "git",
"url": "https://git.mregirouard.com/nix/tailwindcss-with-pkgs.git"
}
}
},
"root": "root",

View File

@@ -8,6 +8,12 @@
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
daisyui.url = "git+https://git.mregirouard.com/nix/daisyui.git";
tailwindcss-with-pkgs.url = "git+https://git.mregirouard.com/nix/tailwindcss-with-pkgs.git";
dx-build.url = "git+https://gitea.mregirouard.com/nix/dx-build.git";
};
outputs = { self, nixpkgs, rust-overlay, flake-utils, ... }@inputs:
@@ -35,43 +41,12 @@
overlays = [
(import rust-overlay)
wasm-bindgen-overlay
inputs.daisyui.overlays.default
inputs.tailwindcss-with-pkgs.overlays.default
inputs.dx-build.overlays.default
];
};
daisyui-version = "v5.5.23";
daisyui = pkgs.stdenvNoCC.mkDerivation {
name = "daisyui";
src = pkgs.fetchurl {
url = "https://github.com/saadeghi/daisyui/releases/download/${daisyui-version}/daisyui.js";
sha256 = "sha256-yu/8ebzKXMfrcHJw2FzcXNzwYOF1hC+nufrTaPOMWeA=";
};
unpackPhase = "true";
installPhase = ''
mkdir "$out"
cp "$src" "$out/daisyui.js"
'';
};
daisyui-theme = pkgs.stdenvNoCC.mkDerivation {
name = "daisyui-theme";
src = pkgs.fetchurl {
url = "https://github.com/saadeghi/daisyui/releases/download/${daisyui-version}/daisyui-theme.js";
sha256 = "sha256-p/ofznslFSKQ87UuP4XV4bvU7xmAkci/WRlAznJt9MY=";
};
unpackPhase = "true";
installPhase = ''
mkdir "$out"
cp "$src" "$out/daisyui-theme.js"
'';
};
rust = pkgs.rust-bin.stable.latest.default.override {
targets = [ "wasm32-unknown-unknown" ];
};
@@ -82,17 +57,16 @@
wasm-bindgen-cli_0_2_123
binaryen
lld
tailwindcss_4
(tailwindcss_4.withPackages [daisyui daisyui-theme])
postgresql
];
rev = if builtins.hasAttr "rev" self then self.rev else self.dirtyRev;
pkg-info = (pkgs.lib.trivial.importTOML ./Cargo.toml).package;
in
rec {
devShells.default = pkgs.mkShell {
DAISYUI_PATH = "${daisyui}";
DAISYUI_THEME_PATH = "${daisyui-theme}";
buildInputs = with pkgs; [
diesel-cli
] ++ build-pkgs;
@@ -100,35 +74,23 @@
packages.default = packages.web;
packages.web = pkgs.rustPlatform.buildRustPackage {
name = "libretunes";
packages.web = pkgs.rustPlatform.buildDioxusWebApp {
pname = pkg-info.name;
version = pkg-info.version;
src = ./.;
DAISYUI_PATH = "${daisyui}";
DAISYUI_THEME_PATH = "${daisyui-theme}";
GIT_REV = rev;
cargoLock.lockFile = ./Cargo.lock;
nativeBuildInputs = with pkgs; [
makeWrapper
] ++ build-pkgs;
(tailwindcss_4.withPackages [daisyui daisyui-theme])
wasm-bindgen-cli_0_2_123
];
buildPhase = ''
preBuild = ''
# dx build will run tailwindcss anyways, but doing it first here is faster and clearer if it fails
tailwindcss --input style/tailwind.css --output assets/tailwind.css
dx build --release --frozen --web
'';
installPhase = ''
mkdir -p "$out/bin"
install -t "$out" target/dx/libretunes/release/web/server
cp -r target/dx/libretunes/release/web/public "$out/public"
makeWrapper "$out/server" "$out/bin/libretunes" \
--set DIOXUS_PUBLIC_PATH "$out/public"
'';
};
}

55
src/api/health.rs Normal file
View File

@@ -0,0 +1,55 @@
use dioxus::prelude::*;
use crate::util::error::Result;
cfg_if::cfg_if! {
if #[cfg(feature = "server")] {
use dioxus::server::axum::Extension;
use diesel_async::{AsyncConnection, SimpleAsyncConnection};
use crate::{
server::{auth::AuthSession, config::Config, database::DbPool},
util::error::{Contextualize, Error, ErrorType},
};
}
}
#[get("/api/v1/health", db_pool: Option<Extension<DbPool>>, auth: Option<Extension<AuthSession>>, config: Option<Extension<Config>>)]
pub async fn health_check() -> Result<bool> {
if auth.is_none() {
return Err(Error::new_here(ErrorType::HttpServer(
"Unable to retrieve auth session middleware".to_owned(),
)));
}
if config.is_none() {
return Err(Error::new_here(ErrorType::HttpServer(
"Unable to retrieve server config".to_owned(),
)));
}
let Some(db_pool) = db_pool else {
return Err(Error::new_here(ErrorType::HttpServer(
"Unable to retrieve database connection pool".to_owned(),
)));
};
let mut db_conn = db_pool
.get()
.await
.err_context("Failed to get database pool connection")?;
db_conn
.begin_test_transaction()
.await
.err_context("Failed to create database transaction")?;
db_conn
.batch_execute("SELECT 1;")
.await
.err_context("Failed to execute database health check query")?;
Ok(true)
}

View File

@@ -1 +1,2 @@
pub mod auth;
pub mod health;

View File

@@ -14,6 +14,7 @@ enum Route {
#[component]
pub fn App() -> Element {
rsx! {
document::Link { rel: "icon", href: LOGO_ICO }
document::Link { rel: "stylesheet", href: TAILWIND_CSS }
Router::<Route> {}
}

View File

@@ -37,3 +37,32 @@ pub fn FormInput(
}
}
}
#[component]
pub fn UsernameInput() -> Element {
rsx! {
FormInput {
label: "Username",
required: true,
lucide_dioxus::UserRound {
class: "opacity-50",
}
}
}
}
#[component]
pub fn PasswordInput() -> Element {
rsx! {
FormInput {
label: "Password",
required: true,
r#type: "password",
lucide_dioxus::KeyRound {
class: "opacity-50",
}
}
}
}

View File

@@ -1,3 +1,7 @@
pub mod footer;
pub mod form;
pub mod form_input;
pub use footer::*;
pub use form::*;
pub use form_input::*;

View File

@@ -16,6 +16,9 @@ use crate::app::App;
fn tracing_setup() {
#[cfg(debug_assertions)]
dioxus::logger::init(tracing::Level::DEBUG).expect("Failed to initialize tracing logger");
#[cfg(not(debug_assertions))]
dioxus::logger::init(tracing::Level::INFO).expect("Failed to initialize tracing logger");
}
#[cfg(not(feature = "server"))]
@@ -25,10 +28,11 @@ fn main() {
}
#[cfg(feature = "server")]
fn main() -> std::process::ExitCode {
#[tokio::main]
async fn main() -> std::process::ExitCode {
tracing_setup();
let Err(e) = server::main();
let Err(e) = server::main().await;
tracing::error!("Server main failed:\n{e}");
std::process::ExitCode::FAILURE

View File

@@ -43,6 +43,20 @@ use crate::util::error::{Error, Result};
#[diesel(sql_type = sql_types::Text)]
pub struct HashedPassword(String);
/// Get a `Pbkdf2` instance for hashing
fn get_pbkdf2() -> Pbkdf2 {
use pbkdf2::{Algorithm, Params};
if cfg!(test) {
// Use lower security in testing mode so it doesn't take as long
// `Params::new` panics only if `rounds` < `MIN_ROUNDS`
Pbkdf2::new(Algorithm::default(), Params::new(Params::MIN_ROUNDS).expect("failed creating Pbkdf2 Params"))
} else {
// Default uses a sufficiently secure configuration
Pbkdf2::default()
}
}
impl HashedPassword {
/// Check a password attempt against this hashed password
///
@@ -55,7 +69,7 @@ impl HashedPassword {
let pw_hash = PasswordHash::new(&self.0)
.map_err(|e| Error::message_here(format!("Error parsing `HashedPassword`: {e}")))?;
match Pbkdf2::default().verify_password(password_attempt.as_bytes(), &pw_hash) {
match get_pbkdf2().verify_password(password_attempt.as_bytes(), &pw_hash) {
Ok(()) => Ok(true),
Err(PasswordInvalid) => Ok(false),
Err(e) => Err(Error::message_here(format!(
@@ -134,7 +148,7 @@ impl UserCredentials {
/// Attempt to convert into `HashedUserCredentials` by hashing the password. Yields a PBKDF2
/// error on failure.
pub fn try_hash(self) -> Result<HashedUserCredentials, pbkdf2::password_hash::Error> {
let hashed_password = Pbkdf2::default().hash_password(self.password.as_bytes())?;
let hashed_password = get_pbkdf2().hash_password(self.password.as_bytes())?;
Ok(HashedUserCredentials {
username: self.username,

View File

@@ -1,11 +1,41 @@
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::path::PathBuf;
use config::{ConfigBuilder, ConfigError, builder::DefaultState};
use serde::Deserialize;
/// Enable secure cookies by default only in release mode
/// (Secure cookies can't be set over HTTP. Rough assumption: development is done over HTTP)
const DEFAULT_COOKIES_SECURE: bool = cfg!(not(debug_assertions));
// Simple newtype to avoid showing secrets with `Debug` / `Display`
#[derive(Clone, Deserialize)]
pub struct SecretString(String);
impl SecretString {
pub fn expose(&self) -> &String {
&self.0
}
}
impl From<String> for SecretString {
fn from(s: String) -> Self {
Self(s)
}
}
impl std::fmt::Debug for SecretString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "*****")
}
}
impl std::fmt::Display for SecretString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "*****")
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct AuthConfig {
pub open_signup: bool,
@@ -15,11 +45,11 @@ pub struct AuthConfig {
/// Build a connection URI from parts
fn format_uri(
scheme: &str,
username: &Option<String>,
password: &Option<String>,
username: Option<&String>,
password: Option<&String>,
host: &str,
port: &Option<u16>,
path: &Option<String>,
port: Option<u16>,
path: Option<&String>,
) -> String {
let mut url = format!("{scheme}://");
@@ -63,14 +93,14 @@ impl DatabaseConfig {
#[serde(untagged)]
enum DatabaseConnectionConfig {
FromUrl {
url: String,
url: SecretString,
},
FromParts {
host: String,
port: Option<u16>,
database: Option<String>,
username: Option<String>,
password: Option<String>,
password: Option<SecretString>,
},
}
@@ -78,14 +108,21 @@ impl DatabaseConnectionConfig {
/// Convert this configuration into the Postgres connection URI
pub fn as_uri(&self) -> String {
match self {
Self::FromUrl { url } => url.clone(),
Self::FromUrl { url } => url.expose().clone(),
Self::FromParts {
host,
port,
database,
username,
password,
} => format_uri("postgres", username, password, host, port, database),
} => format_uri(
"postgres",
username.as_ref(),
password.as_ref().map(|s| s.expose()),
host,
*port,
database.as_ref(),
),
}
}
}
@@ -94,7 +131,7 @@ impl DatabaseConnectionConfig {
#[serde(untagged)]
enum KeyValStoreConnectionConfig {
FromUrl {
url: String,
url: SecretString,
},
FromParts {
scheme: Option<String>,
@@ -102,7 +139,7 @@ enum KeyValStoreConnectionConfig {
port: Option<u16>,
database: Option<String>,
username: Option<String>,
password: Option<String>,
password: Option<SecretString>,
},
}
@@ -110,7 +147,7 @@ impl KeyValStoreConnectionConfig {
/// Convert this configuration into the Redis connection URI
pub fn as_uri(&self) -> String {
match self {
Self::FromUrl { url } => url.clone(),
Self::FromUrl { url } => url.expose().clone(),
Self::FromParts {
scheme,
host,
@@ -120,11 +157,11 @@ impl KeyValStoreConnectionConfig {
password,
} => format_uri(
scheme.as_deref().unwrap_or("redis"),
username,
password,
username.as_ref(),
password.as_ref().map(|s| s.expose().clone()).as_ref(),
host,
port,
database,
*port,
database.as_ref(),
),
}
}
@@ -146,6 +183,14 @@ impl KeyValStoreConfig {
#[derive(Debug, Clone, Deserialize)]
pub struct ServerConfig {
pub public_path: PathBuf,
pub host: IpAddr,
pub port: u16,
}
impl ServerConfig {
pub fn serve_addr(&self) -> SocketAddr {
SocketAddr::new(self.host, self.port)
}
}
#[derive(Debug, Clone, Deserialize)]
@@ -157,23 +202,65 @@ pub struct Config {
pub server: ServerConfig,
}
/// Parse configuration from the expected files and environment variables
pub fn load_config() -> Result<Config, config::ConfigError> {
use config::{Environment, File};
impl Config {
/// Parse configuration from the expected files and environment variables
pub fn from_env() -> Result<Self, ConfigError> {
use config::{Environment, File};
let pkg_name = env!("CARGO_PKG_NAME");
let pkg_name = env!("CARGO_PKG_NAME");
config::Config::builder()
.set_default("server.port", 8080)?
.set_default("auth.open_signup", false)?
.set_default("auth.cookies_secure", DEFAULT_COOKIES_SECURE)?
.set_default("server.public_path", default_public_dir())?
.add_source(File::with_name(&format!("/etc/{pkg_name}/config")).required(false))
.add_source(File::with_name(&format!("/etc/{pkg_name}")).required(false))
.add_source(File::with_name("config").required(false))
.add_source(Environment::with_prefix(pkg_name).separator("_"))
.build()?
.try_deserialize()
Self::defaults()?
.add_source(File::with_name(&format!("/etc/{pkg_name}/config")).required(false))
.add_source(File::with_name(&format!("/etc/{pkg_name}")).required(false))
.add_source(File::with_name("config").required(false))
.add_source(Environment::with_prefix(pkg_name).separator("_"))
.build()?
.try_deserialize()
}
/// Generate a `config::ConfigBuilder` from default values
fn defaults() -> Result<ConfigBuilder<DefaultState>, ConfigError> {
config::Config::builder()
.set_default(
"server.port",
dioxus::cli_config::server_port().unwrap_or(8080),
)?
.set_default(
"server.host",
dioxus::cli_config::server_ip()
.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED))
.to_string(),
)?
.set_default("auth.open_signup", false)?
.set_default("auth.cookies_secure", DEFAULT_COOKIES_SECURE)?
.set_default("server.public_path", default_public_dir())
}
/// Log any configuration-related warning messages, such as differences between user-configured
/// values and those supplied by the Dioxus CLI
pub fn log_warnings(&self) {
if let Some(dx_port) = dioxus::cli_config::server_port()
&& dx_port != self.server.port
{
tracing::warn!(
"Your configured server port ({}) doesn't match the one specified from environment \
variables set by the Dioxus CLI ({dx_port}). If you are intending to use the dx tool, \
please do not specify a port in the configuration.",
self.server.port
);
}
if let Ok(dx_public_path) = std::env::var("DIOXUS_PUBLIC_PATH")
&& dx_public_path != self.server.public_path
{
tracing::warn!(
"Your configured server public path ({}) doesn't match the one specified from environment \
variables set by the Dioxus CLI ({dx_public_path}). If you are intending to use the dx tool, \
please do not specify a public_path in the configuration.",
self.server.public_path.display()
);
}
}
}
/// Provide a sane default for the public path, using the same sources as Dioxus does internally.

View File

@@ -1,54 +1,24 @@
use dioxus::{
fullstack::axum::{Router, middleware::from_fn},
fullstack::axum::{self, Router, middleware::from_fn},
prelude::{DioxusRouterExt, ServeConfig},
server::axum::Extension,
};
use tower_http::services::ServeFile;
use tokio::net::TcpListener;
use tower_http::services::{ServeDir, ServeFile};
use crate::App;
use crate::app::LOGO_ICO;
use crate::server::{
auth::build_auth_layer,
config::{self, Config},
database, key_val_store,
config::Config,
database::{self, DbPool},
key_val_store::{self, KeyValPool},
require_auth_mw::require_auth_middleware,
};
use crate::util::error::{Contextualize, Error, Result};
pub fn main() -> Result<std::convert::Infallible> {
if let Err(e) = dotenvy::dotenv() {
tracing::warn!("Error reading .env: {e}");
}
tracing::debug!("Loading configuration...");
let config = config::load_config()
.map_err(|e| Error::message_here(e.to_string()))
.err_context("Failed to load config")?;
// Dioxus doesn't expose a way to configure the public path other than this environment
// variable, and also doesn't provide a way to read what the public path is. As a workaround we
// expose a config option and set this variable that Dioxus expects.
// SAFETY: "This function is safe to call in a single-threaded program."
unsafe {
std::env::set_var("DIOXUS_PUBLIC_PATH", config.server.public_path.clone());
}
// `Ok(...?)` is because `dioxus::serve` expects an `anyhow::Result`
dioxus::serve(move || {
let config = config.clone();
async move { Ok(router_setup(config).await?) }
});
}
/// Set up the axum Router
async fn router_setup(config: Config) -> Result<Router> {
let db_pool = database::setup(config.database.connection_uri())
.await
.err_context("Failed database setup")?;
let key_val_pool = key_val_store::setup(&config.key_val_store.connection_uri())
.await
.err_context("Failed key-value store setup")?;
use crate::util::error::{Contextualize, Error, ErrorType, Result};
// Build the `axum::Router` and attach config, database, and key/val store as extensions
pub fn build_router(config: Config, db_pool: DbPool, key_val_pool: KeyValPool) -> Router {
let favicon_path = {
// Resolve the favicon path
let asset_path = LOGO_ICO.resolve();
@@ -61,17 +31,76 @@ async fn router_setup(config: Config) -> Result<Router> {
config.server.public_path.join(asset_path_rel)
};
tracing::debug!("Favicon path: {}", favicon_path.display());
let auth_layer = build_auth_layer(db_pool.clone(), key_val_pool, config.auth.cookies_secure);
let router = dioxus::server::router(App)
let public_path = config.server.public_path.clone();
let serve_assets = ServeDir::new(public_path.join("assets"));
let serve_index = ServeFile::new(public_path.join("index.html"));
let serve_favicon = ServeFile::new(favicon_path);
let router = Router::new()
.serve_api_application(ServeConfig::new(), App)
.layer(from_fn(require_auth_middleware))
.layer(Extension(config))
.layer(Extension(db_pool))
.layer(auth_layer)
.nest_service("/favicon.ico", ServeFile::new(favicon_path));
.layer(auth_layer);
tracing::info!("Setup complete, returning Router...");
Ok(router)
// In release mode, serve precompressed assets. In debug mode, serve WASM patch files.
if cfg!(debug_assertions) {
let serve_wasm = ServeDir::new(public_path.join("wasm"));
router
.nest_service("/wasm", serve_wasm)
.nest_service("/assets", serve_assets)
.nest_service("/index.html", serve_index)
.nest_service("/favicon.ico", serve_favicon)
} else {
router
.nest_service("/assets", serve_assets.precompressed_br())
.nest_service("/index.html", serve_index)
.nest_service("/favicon.ico", serve_favicon.precompressed_br())
}
}
pub async fn main() -> Result<std::convert::Infallible> {
if let Err(e) = dotenvy::dotenv() {
tracing::warn!("Error reading .env: {e}");
}
tracing::debug!("Loading configuration...");
let config = Config::from_env()
.map_err(|e| Error::message_here(e.to_string()))
.err_context("Failed to load config")?;
tracing::debug!("Loaded configuration: {config:#?}");
config.log_warnings();
let db_pool = database::setup(config.database.connection_uri())
.await
.err_context("Failed database setup")?;
let key_val_pool = key_val_store::setup(&config.key_val_store.connection_uri())
.await
.err_context("Failed key-value store setup")?;
let addr = config.server.serve_addr();
tracing::info!("Setup complete, building router...");
let router = build_router(config, db_pool, key_val_pool);
tracing::info!("Listening on {addr}...");
let listener = TcpListener::bind(addr)
.await
.map_err(|e| ErrorType::HttpServer(e.to_string()))
.err_context(format!("Failed to bind to {addr}"))?;
axum::serve(listener, router)
.await
.map_err(|e| ErrorType::HttpServer(e.to_string()))
.err_context("HTTP server error")?;
Err(Error::new_here(ErrorType::HttpServer(
"axum::serve should never return".to_owned(),
)))
}

View File

@@ -7,18 +7,14 @@ use dioxus::prelude::*;
use crate::server::auth::AuthSession;
#[cfg(not(debug_assertions))]
const ALLOWED_PATH_PREFIX: [&str; 1] = ["/assets/"];
#[cfg(debug_assertions)]
const ALLOWED_PATH_PREFIX: [&str; 2] = ["/assets/", "/wasm/"];
const ALLOWED_PATHS: [&str; 5] = [
const ALLOWED_PATHS: [&str; 7] = [
"/login",
"/signup",
"/api/v1/auth/login",
"/api/v1/auth/signup",
"/api/v1/auth/user",
"/api/v1/auth/open-signup",
"/api/v1/health",
];
/// Axum middleware to redirect an unauthenticated request to /login unless it matches one of the allowed paths
@@ -28,11 +24,7 @@ pub async fn require_auth_middleware(
) -> Result<Response<Body>, (StatusCode, &'static str)> {
let path = req.uri().path();
if ALLOWED_PATH_PREFIX
.iter()
.any(|prefix| path.starts_with(prefix))
|| ALLOWED_PATHS.contains(&path)
{
if ALLOWED_PATHS.contains(&path) {
let response = next.run(req).await;
return Ok(response);
}

View File

@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
/// A location in the source code
/// A thin wrapper over `std::panic::Location`, which isn't `Serialize` or `Deserialize`
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ErrorLocation {
file: String,
line: u32,
@@ -59,7 +59,7 @@ fn rand_modal_id() -> String {
format!("err-modal-{random_str}")
}
#[derive(Debug, Clone, Deserialize, Serialize, thiserror::Error)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, thiserror::Error)]
pub struct Error {
#[source]
/// The error type and data
@@ -307,7 +307,7 @@ impl<T, E: Into<Error>> Contextualize<Result<T>> for E {
}
}
#[derive(Debug, Clone, thiserror::Error, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, thiserror::Error, Deserialize, Serialize)]
pub enum ErrorType {
#[error("Authentication error: {0}")]
Auth(AuthError),
@@ -327,6 +327,9 @@ pub enum ErrorType {
// and Fred is only available on the server
#[error("Key-value store error: {0}")]
KeyValStore(String),
#[error("HTTP server error: {0}")]
HttpServer(String),
}
impl From<ErrorType> for Error {
@@ -362,7 +365,7 @@ impl From<fred::error::Error> for Error {
}
}
#[derive(Debug, Clone, thiserror::Error, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, thiserror::Error, Deserialize, Serialize)]
pub enum AuthError {
#[error("Invalid credentials")]
InvalidCredentials,

View File

@@ -1,18 +0,0 @@
let daisyui_theme = null;
try {
daisyui_theme = require('daisyui/theme');
} catch (e) { }
if (daisyui_theme == null) {
try {
daisyui_theme = require(`${process.env.DAISYUI_THEME_PATH}/daisyui-theme.js`);
} catch (e) { }
}
if (daisyui_theme == null) {
console.error('Could not find DaisyUI/theme');
throw new Error('Could not find DaisyUI/theme');
}
export default daisyui_theme.default || daisyui_theme;

View File

@@ -1,18 +0,0 @@
let daisyui = null;
try {
daisyui = require('daisyui');
} catch (e) { }
if (daisyui == null) {
try {
daisyui = require(`${process.env.DAISYUI_PATH}/daisyui.js`);
} catch (e) { }
}
if (daisyui == null) {
console.error('Could not find DaisyUI');
throw new Error('Could not find DaisyUI');
}
export default daisyui.default || daisyui;

View File

@@ -1,10 +1,10 @@
@import "tailwindcss";
@plugin "./daisyui-wrapper.js" {
@plugin "daisyui" {
themes: all;
};
@plugin "./daisyui-theme-wrapper.js" {
@plugin "daisyui-theme" {
};
@source not "*";