| 1 |
[package] |
| 2 |
name = "makenotwork" |
| 3 |
version = "0.11.19" |
| 4 |
edition = "2024" |
| 5 |
license = "LicenseRef-PolyForm-Noncommercial-1.0.0" |
| 6 |
# Server binary: never published to a registry. Marks the crate private so |
| 7 |
# supply-chain tooling (cargo-deny) treats its first-party path deps correctly. |
| 8 |
publish = false |
| 9 |
|
| 10 |
[features] |
| 11 |
# Cheap Argon2id parameters (8 MiB, 1 iteration) so a suite that seeds hundreds |
| 12 |
# of password hashes is not spending ~600ms on each. Scoped to `auth::hash_password` |
| 13 |
# and nothing else: verification reads its parameters from the hash string, so |
| 14 |
# the production verifier is still what runs. |
| 15 |
# |
| 16 |
# It used to swap the rate-limit constants too, which meant the limiter under |
| 17 |
# test was never the limiter that ships. Those are runtime config now |
| 18 |
# (`constants::RateLimits`), so this feature can no longer change how the server |
| 19 |
# behaves under load. Do not add anything to it that can. |
| 20 |
fast-tests = [] |
| 21 |
|
| 22 |
[dependencies] |
| 23 |
# Async trait (for StorageBackend trait object) |
| 24 |
async-trait = "0.1" |
| 25 |
|
| 26 |
# Web framework |
| 27 |
axum = { version = "0.8.8", features = ["macros"] } |
| 28 |
axum-extra = { version = "0.12.6", features = ["cookie", "form", "query", "typed-header"] } |
| 29 |
|
| 30 |
# OpenAPI spec generation |
| 31 |
utoipa = { version = "5", features = ["axum_extras", "chrono", "uuid"] } |
| 32 |
utoipa-axum = "0.2" |
| 33 |
utoipa-swagger-ui = { version = "9", features = ["axum"] } |
| 34 |
serde = { version = "1.0.228", features = ["derive"] } |
| 35 |
serde_json = "1.0.149" |
| 36 |
toml = "1.1" |
| 37 |
# `process` is used directly (git_ssh, build_runner). It was previously arriving |
| 38 |
# only by feature unification through an S3 dependency, so it belongs here. |
| 39 |
tokio = { version = "1.50.0", features = ["macros", "rt-multi-thread", "net", "signal", "process"] } |
| 40 |
tokio-stream = { version = "0.1", features = ["sync"] } |
| 41 |
tokio-util = { version = "0.7", features = ["io"] } |
| 42 |
tower = "0.5.3" |
| 43 |
tower-http = { version = "0.7.0", features = ["trace", "fs", "limit", "request-id", "propagate-header", "set-header"] } |
| 44 |
tracing = "0.1.44" |
| 45 |
tracing-subscriber = { version = "0.3.22", features = ["env-filter", "json"] } |
| 46 |
|
| 47 |
# Templates |
| 48 |
askama = "0.16.0" |
| 49 |
|
| 50 |
# Environment & Configuration |
| 51 |
dotenvy = "0.15.7" |
| 52 |
|
| 53 |
# Database |
| 54 |
sqlx = { version = "0.8.6", features = ["runtime-tokio", "postgres", "uuid", "chrono", "migrate", "macros"] } |
| 55 |
uuid = { version = "1.22.0", features = ["v4", "serde"] } |
| 56 |
chrono = { version = "0.4.44", features = ["serde"] } |
| 57 |
|
| 58 |
# Authentication |
| 59 |
argon2 = "0.5.3" |
| 60 |
tower-sessions = { version = "0.14.0", features = ["axum-core"] } |
| 61 |
tower-sessions-sqlx-store = { version = "0.15.0", features = ["postgres"] } |
| 62 |
|
| 63 |
# Concurrent hash map (session touch cache) |
| 64 |
dashmap = "6" |
| 65 |
|
| 66 |
# Rate Limiting |
| 67 |
tower_governor = "0.8.0" |
| 68 |
governor = "0.10.4" |
| 69 |
|
| 70 |
# JWT (SyncKit) |
| 71 |
jsonwebtoken = { version = "10.4.0", features = ["rust_crypto"] } |
| 72 |
|
| 73 |
# TOTP / 2FA |
| 74 |
totp-rs = { version = "5.7", features = ["qr"] } |
| 75 |
|
| 76 |
# WebAuthn / Passkeys |
| 77 |
# 0.6 is where webauthn-rs drops openssl for crypto-glue (RustCrypto), which is |
| 78 |
# what gets openssl-sys out of the tree and off cargo-deny's ban list. Still a |
| 79 |
# `-dev` prerelease upstream, so the version is pinned exactly rather than left |
| 80 |
# to float. The stored `user_passkeys.credential_json` format is unchanged: a |
| 81 |
# 0.5-written Passkey round-trips through 0.6 byte-identically, and 0.5 still |
| 82 |
# reads what 0.6 writes, so no migration and a rollback stays open. |
| 83 |
webauthn-rs = { version = "=0.6.1-dev", features = ["danger-allow-state-serialisation", "conditional-ui"] } |
| 84 |
webauthn-rs-proto = "=0.6.1-dev" |
| 85 |
|
| 86 |
# Security |
| 87 |
# Direct only so `crypto::install_default_crypto_provider` can name a provider; |
| 88 |
# every TLS client here reaches rustls transitively. |
| 89 |
rustls = { version = "0.23", default-features = false, features = ["ring"] } |
| 90 |
rand = "0.10" |
| 91 |
hmac = "0.13.0" |
| 92 |
sha1 = "0.11.0" |
| 93 |
sha2 = "0.11.0" |
| 94 |
subtle = "2.6" |
| 95 |
chacha20poly1305 = "0.11.0" |
| 96 |
hex = "0.4.3" |
| 97 |
base64 = "0.22.1" |
| 98 |
# SSHSIG verification for signed commits and tags (`git::signing`). Feature set |
| 99 |
# matches `validation::users::SSH_KEY_TYPES`, the algorithms MNW accepts as |
| 100 |
# account keys: there is no point verifying a signature by a key nobody could |
| 101 |
# have registered. 0.6 rather than the 0.7 release candidate, and RustCrypto |
| 102 |
# rather than a C library, per the crypto-provider preference. |
| 103 |
ssh-key = { version = "0.6", default-features = false, features = ["alloc", "std", "ed25519", "p256", "p384", "p521", "rsa"] } |
| 104 |
|
| 105 |
# Temp files (content export) |
| 106 |
tempfile = "3" |
| 107 |
|
| 108 |
# File scanning |
| 109 |
infer = "0.22" |
| 110 |
goblin = "0.10" |
| 111 |
zip = "8.2" |
| 112 |
# Single-stream decompressors for archive-bomb detection (already in the tree |
| 113 |
# transitively via `zip`; pinned to the resolved versions so no new build). |
| 114 |
flate2 = "1" |
| 115 |
bzip2 = "0.6" |
| 116 |
xz2 = "0.1" |
| 117 |
zstd = "0.13" |
| 118 |
yara-x = "1.16" |
| 119 |
fs2 = "0.4" |
| 120 |
memmap2 = "0.9" |
| 121 |
|
| 122 |
# CSV parsing (import system) |
| 123 |
csv = "1.3" |
| 124 |
|
| 125 |
# CLI |
| 126 |
clap = { version = "4", features = ["derive"] } |
| 127 |
|
| 128 |
# Logging (used by sqlx slow query config) |
| 129 |
log = "0.4" |
| 130 |
|
| 131 |
# Error handling |
| 132 |
thiserror = "2.0.18" |
| 133 |
anyhow = "1.0.102" |
| 134 |
bytes = "1" |
| 135 |
|
| 136 |
# Email validation (used at notify-me signup and guest-checkout entry points) |
| 137 |
email_address = "0.2" |
| 138 |
|
| 139 |
# Metrics |
| 140 |
metrics = "0.24" |
| 141 |
metrics-exporter-prometheus = { version = "0.18.1", default-features = false } |
| 142 |
|
| 143 |
# Markdown rendering + documentation engine |
| 144 |
docengine = { git = "https://makenot.work/git/max/docengine.git", version = "0.7", features = ["doc-loader", "directives", "frontmatter", "media-urls"] } |
| 145 |
mnw-assumptions = { path = "../shared/mnw-assumptions" } |
| 146 |
|
| 147 |
# Tag standard |
| 148 |
tagtree = { path = "../shared/tagtree" } |
| 149 |
|
| 150 |
# The git-over-SSH command grammar, shared with mnw-cli so the two SSH doors |
| 151 |
# cannot drift apart the way their hand-written parsers did. |
| 152 |
git-command = { path = "../shared/git-command" } |
| 153 |
|
| 154 |
# Shared theme palette + the bundled theme set (Tier 0 creator theming). |
| 155 |
makeover = "2.5.0" |
| 156 |
|
| 157 |
# The description layer, for the tier G1 spike only: one read-only screen served |
| 158 |
# through quasi beside its Askama equivalent, so the cost of describing a screen |
| 159 |
# here is measured rather than argued. Not load-bearing for any shipped route. |
| 160 |
# See wiki look-wave-2, tier G. |
| 161 |
quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.11" } |
| 162 |
# The description vocabulary quasi's screen types are built from. Pinned here |
| 163 |
# rather than reached through quasi-router's re-export because a described |
| 164 |
# screen names FieldKind and Tone directly; it has to track what quasi-router |
| 165 |
# resolves or the two `layout::` paths are different crates. |
| 166 |
makeover-layout = "0.27.0" |
| 167 |
# For the request head the per-viewer state factory reads. axum re-exports it, |
| 168 |
# but the factory's signature is quasi-axum's and names `http::request::Parts`. |
| 169 |
http = "1.3.1" |
| 170 |
quasi-axum = { git = "https://makenot.work/git/max/quasi.git", version = "0.11" } |
| 171 |
quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.11" } |
| 172 |
# The first-party widget set. The carousel was proved here against three pages |
| 173 |
# and then moved there; what is left in `quasi/widgets/carousel.rs` is the |
| 174 |
# Askama glue. Not `makeover-basics`: a widget is an assembly of `Node`s, so it |
| 175 |
# sits above quasi-router rather than inside the description suite. |
| 176 |
quasi-basics = { git = "https://makenot.work/git/max/quasi.git", version = "0.11" } |
| 177 |
|
| 178 |
# Git source browser |
| 179 |
gix = { version = "0.86", default-features = false, features = ["blame", "mailmap", "revision", "sha1", "max-performance-safe"] } |
| 180 |
syntect = { version = "5", default-features = false, features = ["default-syntaxes", "default-themes", "html", "regex-fancy"] } |
| 181 |
regex = "1" |
| 182 |
semver = "1" |
| 183 |
|
| 184 |
# S3 Storage |
| 185 |
s3-storage = { path = "../shared/s3-storage" } |
| 186 |
|
| 187 |
# Stripe Payments: async-stripe 1.0.0-rc.6 (split into sub-crates). |
| 188 |
# The umbrella `async-stripe` provides the HTTP client; resource types live in |
| 189 |
# per-domain sub-crates. The `deserialize` feature on each resource crate is |
| 190 |
# required to derive `serde::Deserialize` on Subscription, Invoice, etc: we |
| 191 |
# parse them from webhook payloads ourselves (no built-in webhook helper in the |
| 192 |
# rc line; see `payments::webhooks::verify_signature` for our HMAC check). |
| 193 |
# `default-tls` is native-tls, which drags openssl-sys in through hyper-tls; |
| 194 |
# cargo-deny bans it. rustls-tls-native keeps the system trust store (matching |
| 195 |
# reqwest's platform verifier elsewhere in the tree) and carries the `__hyper` |
| 196 |
# feature the client needs; the provider must be named separately or |
| 197 |
# async-stripe's connector.rs refuses to compile. |
| 198 |
async-stripe = { version = "1.0.0-rc.6", default-features = false, features = ["rustls-tls-native", "rustls-ring"] } |
| 199 |
async-stripe-shared = { version = "1.0.0-rc.6", features = ["deserialize"] } |
| 200 |
async-stripe-billing = { version = "1.0.0-rc.6", features = ["deserialize", "subscription", "billing_portal_session"] } |
| 201 |
async-stripe-checkout = { version = "1.0.0-rc.6", features = ["deserialize", "checkout_session"] } |
| 202 |
async-stripe-connect = { version = "1.0.0-rc.6", features = ["deserialize", "account", "account_link", "transfer", "transfer_reversal"] } |
| 203 |
async-stripe-core = { version = "1.0.0-rc.6", features = ["deserialize", "balance", "refund", "customer"] } |
| 204 |
async-stripe-payment = { version = "1.0.0-rc.6", features = ["deserialize"] } |
| 205 |
async-stripe-product = { version = "1.0.0-rc.6", features = ["deserialize", "product", "price"] } |
| 206 |
async-stripe-types = { version = "1.0.0-rc.6", features = ["deserialize"] } |
| 207 |
# reqwest 0.13's `rustls` feature is an alias for `__rustls-aws-lc-rs` and there |
| 208 |
# is no ring counterpart, so the default feature set is what would drag the C |
| 209 |
# backend back in. `rustls-no-provider` keeps the platform verifier and lets |
| 210 |
# `crypto::install_default_crypto_provider` supply ring; the rest of this list is |
| 211 |
# reqwest's own default set (`charset`, `http2`, `system-proxy`) restated, since |
| 212 |
# naming a TLS feature means turning defaults off. |
| 213 |
reqwest = { version = "0.13", default-features = false, features = [ |
| 214 |
"json", "cookies", "form", "query", |
| 215 |
"charset", "http2", "system-proxy", "rustls-no-provider", |
| 216 |
] } |
| 217 |
urlencoding = "2.1.3" |
| 218 |
|
| 219 |
# URL parsing |
| 220 |
url = "2.5.8" |
| 221 |
apple-codesign = { version = "0.29.0", default-features = false } |
| 222 |
authenticode = { version = "0.6.0", features = ["std", "object"] } |
| 223 |
x509-cert = "0.2.5" |
| 224 |
const-oid = { version = "0.9", features = ["db"] } |
| 225 |
object = { version = "0.39", features = ["pe"] } |
| 226 |
ammonia = "4" |
| 227 |
lightningcss = { version = "1.0.0-alpha.71", default-features = false, features = ["visitor"] } |
| 228 |
|
| 229 |
[[bin]] |
| 230 |
name = "mnw-admin" |
| 231 |
path = "src/bin/mnw-admin.rs" |
| 232 |
|
| 233 |
# Writes the OpenAPI spec to openapi.json. The spec is the SyncKit wire |
| 234 |
# contract and the client is in another repo, so it ships as a committed |
| 235 |
# artifact rather than only as a served endpoint. |
| 236 |
[[bin]] |
| 237 |
name = "export-openapi" |
| 238 |
path = "src/bin/export-openapi.rs" |
| 239 |
|
| 240 |
[[bin]] |
| 241 |
name = "export-operational-mail-doc" |
| 242 |
path = "src/bin/export-operational-mail-doc.rs" |
| 243 |
|
| 244 |
[build-dependencies] |
| 245 |
# Emits static/geometry.css (makeover-geometry) and static/layout.css |
| 246 |
# (makeover-webview). The same generator GO and BB run; only the output paths |
| 247 |
# differ, since the server serves its stylesheets rather than bundling them. |
| 248 |
makeover-build = "0.38.0" |
| 249 |
# Read directly for the embeds' own copy of the spacing layer, which they need |
| 250 |
# because an iframe cannot link a stylesheet. makeover-build does not re-export |
| 251 |
# it, so the pin lives here and has to track the one makeover-build resolves. |
| 252 |
makeover-geometry = "0.7" |
| 253 |
|
| 254 |
[dev-dependencies] |
| 255 |
tower = { version = "0.5.3", features = ["util"] } |
| 256 |
http-body-util = "0.1" |
| 257 |
webauthn-authenticator-rs = { version = "=0.6.1-dev", features = ["softpasskey"] } |
| 258 |
tempfile = "3" |
| 259 |
proptest = "1" |
| 260 |
wiremock = "0.6" |
| 261 |
pom-contract = { path = "../shared/pom-contract" } |
| 262 |
# The parity harness's normalizer. Already in the graph as ammonia's parser, so |
| 263 |
# this pins the same build rather than adding one; a dev-dependency because |
| 264 |
# nothing in the served binary parses HTML. |
| 265 |
html5ever = "0.39" |
| 266 |
|
| 267 |
[profile.release] |
| 268 |
# Drop the symbol table from the shipped binary. Release builds already carry no |
| 269 |
# debuginfo, so this is purely the symbol table: measured 161.3 MiB -> 120.7 MiB |
| 270 |
# on 0.10.19 (25.1%) for no runtime cost. Every tier rsyncs that difference on |
| 271 |
# every promote. Nothing here reads a symbolized backtrace (no `backtrace` crate, |
| 272 |
# no RUST_BACKTRACE in the unit file). If a panic ever does need symbolizing, |
| 273 |
# rebuild the sha with `--config profile.release.strip=false`: the build is |
| 274 |
# reproducible from the git sha the release was cut from. |
| 275 |
strip = true |
| 276 |
|
| 277 |
[lints.rust] |
| 278 |
unused = "warn" |
| 279 |
unreachable_pub = "warn" |
| 280 |
|
| 281 |
[lints.clippy] |
| 282 |
pedantic = { level = "warn", priority = -1 } |
| 283 |
# Allow-list tuned from a measured breakdown across server/multithreaded/pter |
| 284 |
# (2026-07-22). These are the high-churn / low-signal pedantic lints; everything |
| 285 |
# else in `pedantic` stays a warning. Keep this block identical across repos. |
| 286 |
module_name_repetitions = "allow" |
| 287 |
# Doc lints. No docs-completeness push is underway. |
| 288 |
missing_errors_doc = "allow" |
| 289 |
missing_panics_doc = "allow" |
| 290 |
doc_markdown = "allow" |
| 291 |
# Numeric casts. Endemic and mostly intentional in size and byte math. |
| 292 |
cast_possible_truncation = "allow" |
| 293 |
cast_sign_loss = "allow" |
| 294 |
cast_precision_loss = "allow" |
| 295 |
cast_possible_wrap = "allow" |
| 296 |
cast_lossless = "allow" |
| 297 |
# Subjective structure and style nags. High churn, low signal. |
| 298 |
must_use_candidate = "allow" |
| 299 |
too_many_lines = "allow" |
| 300 |
struct_excessive_bools = "allow" |
| 301 |
similar_names = "allow" |
| 302 |
items_after_statements = "allow" |
| 303 |
single_match_else = "allow" |
| 304 |
# Frequent false-positives in TUI and router-heavy code. |
| 305 |
match_same_arms = "allow" |
| 306 |
unnecessary_wraps = "allow" |
| 307 |
type_complexity = "allow" |
| 308 |
|