Skip to main content

max / makenotwork

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