Skip to main content

max / makenotwork

13.0 KB · 308 lines History Blame Raw
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