Skip to main content

max / makenotwork

14.7 KB · 332 lines History Blame Raw
1 [package]
2 name = "makenotwork"
3 version = "0.16.0"
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 # Shared theme palette + the bundled theme set (Tier 0 creator theming).
154 makeover = "3.0"
155
156 # The description layer, for the tier G1 spike only: one read-only screen served
157 # through quasi beside its Askama equivalent, so the cost of describing a screen
158 # here is measured rather than argued. Not load-bearing for any shipped route.
159 # See wiki look-wave-2, tier G.
160 quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.56" }
161 # The description vocabulary quasi's screen types are built from. Pinned here
162 # rather than reached through quasi-router's re-export because a described
163 # screen names FieldKind and Tone directly; it has to track what quasi-router
164 # resolves or the two `layout::` paths are different crates.
165 makeover-layout = "0.35.0"
166 # The webview renderer's field emitter, called directly by
167 # `quasi::rich_field` so one markdown editor's markup comes from the same
168 # place a whole described screen's would. Pinned here rather than reached
169 # through quasi-webview for makeover-layout's reason: two `form::` paths from
170 # two resolutions are two crates, and the `data-format="markdown"` rules in
171 # `static/layout.css` are generated from this one.
172 makeover-webview = "0.59.1"
173 # For the request head the per-viewer state factory reads. axum re-exports it,
174 # but the factory's signature is quasi-axum's and names `http::request::Parts`.
175 http = "1.3.1"
176 quasi-axum = { git = "https://makenot.work/git/max/quasi.git", version = "0.56" }
177 quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.56" }
178 # The first-party widget set. The carousel was proved here against three pages
179 # and then moved there; what is left in `quasi/widgets/carousel.rs` is the
180 # Askama glue. Not `makeover-basics`: a widget is an assembly of `Node`s, so it
181 # sits above quasi-router rather than inside the description suite.
182 quasi-basics = { git = "https://makenot.work/git/max/quasi.git", version = "0.56" }
183
184 # Git source browser
185 gix = { version = "0.86", default-features = false, features = ["blame", "mailmap", "revision", "sha1", "max-performance-safe"] }
186 syntect = { version = "5", default-features = false, features = ["default-syntaxes", "default-themes", "html", "regex-fancy"] }
187 regex = "1"
188 semver = "1"
189
190 # S3 Storage
191 s3-storage = { path = "../shared/s3-storage" }
192
193 # Stripe Payments: async-stripe 1.0.0-rc.6 (split into sub-crates).
194 # The umbrella `async-stripe` provides the HTTP client; resource types live in
195 # per-domain sub-crates. The `deserialize` feature on each resource crate is
196 # required to derive `serde::Deserialize` on Subscription, Invoice, etc: we
197 # parse them from webhook payloads ourselves (no built-in webhook helper in the
198 # rc line; see `payments::webhooks::verify_signature` for our HMAC check).
199 # `default-tls` is native-tls, which drags openssl-sys in through hyper-tls;
200 # cargo-deny bans it. rustls-tls-native keeps the system trust store (matching
201 # reqwest's platform verifier elsewhere in the tree) and carries the `__hyper`
202 # feature the client needs; the provider must be named separately or
203 # async-stripe's connector.rs refuses to compile.
204 async-stripe = { version = "1.0.0-rc.6", default-features = false, features = ["rustls-tls-native", "rustls-ring"] }
205 async-stripe-shared = { version = "1.0.0-rc.6", features = ["deserialize"] }
206 async-stripe-billing = { version = "1.0.0-rc.6", features = ["deserialize", "subscription", "billing_portal_session"] }
207 async-stripe-checkout = { version = "1.0.0-rc.6", features = ["deserialize", "checkout_session"] }
208 async-stripe-connect = { version = "1.0.0-rc.6", features = ["deserialize", "account", "account_link", "transfer", "transfer_reversal"] }
209 async-stripe-core = { version = "1.0.0-rc.6", features = ["deserialize", "balance", "refund", "customer"] }
210 async-stripe-product = { version = "1.0.0-rc.6", features = ["deserialize", "product", "price"] }
211 async-stripe-types = { version = "1.0.0-rc.6", features = ["deserialize"] }
212 # reqwest 0.13's `rustls` feature is an alias for `__rustls-aws-lc-rs` and there
213 # is no ring counterpart, so the default feature set is what would drag the C
214 # backend back in. `rustls-no-provider` keeps the platform verifier and lets
215 # `crypto::install_default_crypto_provider` supply ring; the rest of this list is
216 # reqwest's own default set (`charset`, `http2`, `system-proxy`) restated, since
217 # naming a TLS feature means turning defaults off.
218 reqwest = { version = "0.13", default-features = false, features = [
219 "json", "cookies", "form", "query",
220 "charset", "http2", "system-proxy", "rustls-no-provider",
221 ] }
222 urlencoding = "2.1.3"
223
224 # URL parsing
225 url = "2.5.8"
226 apple-codesign = { version = "0.29.0", default-features = false }
227 authenticode = { version = "0.6.0", features = ["std", "object"] }
228 x509-cert = "0.2.5"
229 const-oid = { version = "0.9", features = ["db"] }
230 object = { version = "0.39", features = ["pe"] }
231 ammonia = "4"
232 lightningcss = { version = "1.0.0-alpha.71", default-features = false, features = ["visitor"] }
233
234 [[bin]]
235 name = "mnw-admin"
236 path = "src/bin/mnw-admin.rs"
237
238 # Writes the OpenAPI spec to openapi.json. The spec is the SyncKit wire
239 # contract and the client is in another repo, so it ships as a committed
240 # artifact rather than only as a served endpoint.
241 [[bin]]
242 name = "export-openapi"
243 path = "src/bin/export-openapi.rs"
244
245 [[bin]]
246 name = "export-operational-mail-doc"
247 path = "src/bin/export-operational-mail-doc.rs"
248
249 [build-dependencies]
250 # Emits static/geometry.css (makeover-geometry) and static/layout.css
251 # (makeover-webview). The same generator GO and BB run; only the output paths
252 # differ, since the server serves its stylesheets rather than bundling them.
253 makeover-build = "0.51.0"
254 # Read directly for the embeds' own copy of the spacing layer, which they need
255 # because an iframe cannot link a stylesheet. makeover-build does not re-export
256 # it, so the pin lives here and has to track the one makeover-build resolves.
257 makeover-geometry = "0.7"
258 # Cuts the two house faces into `static/fonts/` at build time. It is a git
259 # dependency because quasi-type is `publish = false`, which is also why
260 # makeover-build cannot do this and only emits the CSS that fetches the result.
261 #
262 # The rev is pinned, and it is the same rev shop's `shop-font` and Alloy's
263 # `QUASI_TYPE_REV` name. That is the point of pinning it here: the house face is
264 # one face, so the one a browser downloads has to be the one the image installs
265 # and the terminal bundles. The three move together, in one pass, or a user sees
266 # two Quasi Monos. Under the tree's `[patch]` block the rev is ignored and the
267 # working copy is what builds.
268 quasi-type = { git = "https://makenot.work/git/max/quasi-type.git", version = "0.1", rev = "e40330602e68fbdd2a9d2917bdec33a7ce573a1f" }
269 # Writes the two scripts the renderer ships into `static/`, from the constants
270 # the crate carries. They are the renderer's behaviour rather than the app's --
271 # a bulk bar's count, a readout that keeps ticking -- and the shell has always
272 # named their addresses, so without this the head links two files that 404.
273 # Written from the crate rather than copied into the repo for the reason the
274 # constants exist: a stale copy in a static directory is a silently dead bulk
275 # bar, and the version has to be the one the emitter agrees with.
276 quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.56" }
277
278 [dev-dependencies]
279 tower = { version = "0.5.3", features = ["util"] }
280 http-body-util = "0.1"
281 webauthn-authenticator-rs = { version = "=0.6.1-dev", features = ["softpasskey"] }
282 tempfile = "3"
283 proptest = "1"
284 wiremock = "0.6"
285 pom-contract = { path = "../shared/pom-contract" }
286 # The parity harness's normalizer. Already in the graph as ammonia's parser, so
287 # this pins the same build rather than adding one; a dev-dependency because
288 # nothing in the served binary parses HTML.
289 html5ever = "0.39"
290
291 [profile.release]
292 # Drop the symbol table from the shipped binary. Release builds already carry no
293 # debuginfo, so this is purely the symbol table: measured 161.3 MiB -> 120.7 MiB
294 # on 0.10.19 (25.1%) for no runtime cost. Every tier rsyncs that difference on
295 # every promote. Nothing here reads a symbolized backtrace (no `backtrace` crate,
296 # no RUST_BACKTRACE in the unit file). If a panic ever does need symbolizing,
297 # rebuild the sha with `--config profile.release.strip=false`: the build is
298 # reproducible from the git sha the release was cut from.
299 strip = true
300
301 [lints.rust]
302 unused = "warn"
303 unreachable_pub = "warn"
304
305 [lints.clippy]
306 pedantic = { level = "warn", priority = -1 }
307 # Allow-list tuned from a measured breakdown across server/multithreaded/pter
308 # (2026-07-22). These are the high-churn / low-signal pedantic lints; everything
309 # else in `pedantic` stays a warning. Keep this block identical across repos.
310 module_name_repetitions = "allow"
311 # Doc lints. No docs-completeness push is underway.
312 missing_errors_doc = "allow"
313 missing_panics_doc = "allow"
314 doc_markdown = "allow"
315 # Numeric casts. Endemic and mostly intentional in size and byte math.
316 cast_possible_truncation = "allow"
317 cast_sign_loss = "allow"
318 cast_precision_loss = "allow"
319 cast_possible_wrap = "allow"
320 cast_lossless = "allow"
321 # Subjective structure and style nags. High churn, low signal.
322 must_use_candidate = "allow"
323 too_many_lines = "allow"
324 struct_excessive_bools = "allow"
325 similar_names = "allow"
326 items_after_statements = "allow"
327 single_match_else = "allow"
328 # Frequent false-positives in TUI and router-heavy code.
329 match_same_arms = "allow"
330 unnecessary_wraps = "allow"
331 type_complexity = "allow"
332