[workspace] resolver = "2" members = [ "crates/core", "crates/render", "crates/discord", "crates/webui", "crates/http-api", "crates/discord-authenticator", ] [workspace.package] version = "0.1.4" edition = "2021" rust-version = "1.90" license = "MIT OR Apache-2.0" repository = "https://gitea.milesward.dev/mward4/Discord-Authenticator" authors = ["Discord Authenticator contributors"] [workspace.dependencies] tokio = { version = "1", features = ["full"] } axum = { version = "0.8", features = ["macros", "multipart"] } tower-http = { version = "0.6", features = ["trace", "limit"] } # rustls, never native-tls: an OpenSSL dependency would break the static # musl build. `multipart` carries the rendered tile up to Discord. reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json", "multipart"] } serde = { version = "1", features = ["derive"] } serde_json = "1" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } anyhow = "1" thiserror = "2" clap = { version = "4", features = ["derive", "env"] } uuid = { version = "1", features = ["v4", "serde"] } time = { version = "0.3", features = ["serde", "serde-human-readable", "formatting"] } parking_lot = "0.12" url = "2" percent-encoding = "2" base64 = "0.22" # ── TOTP primitives ── # The RFC 6238 core is ~30 lines over HMAC, and we implement it directly # rather than pulling `totp-rs`. The deciding factor was otpauth:// parsing: # real-world URIs routinely carry a path issuer that disagrees with the # `?issuer=` query param (Microsoft does this), and secrets that are # lowercase or `=`-padded (Google, Dropbox). totp-rs rejects all of those, # so we'd be writing the normalisation and reconciliation layer anyway. # Pinned to the mature 0.10/0.12 RustCrypto generation. hmac = "0.12" sha1 = "0.10" sha2 = "0.10" base32 = "0.5" # ── Secrets at rest ── # XChaCha20-Poly1305 over AES-GCM for the 192-bit nonce: large enough to # generate randomly per record with no birthday-collision worry, and # constant-time in software on every target without AES-NI. chacha20poly1305 = "0.10" argon2 = "0.5" rand = "0.8" zeroize = { version = "1", features = ["derive"] } # ── Imaging ── # default-features = false is REQUIRED: `image`'s default feature set # enables `avif`, dragging in the whole rav1e encoder for a format we never # touch. (Never enable `avif-native` — that one is C bindings to libdav1d # and would break the static musl build outright.) image = { version = "0.25", default-features = false, features = ["png", "jpeg", "gif", "bmp", "webp"] } rqrr = "0.10" # Tile rendering. The tile is drawn as SVG and rasterised, rather than # blitting glyphs by hand: text centring, rounded corners and gradients are # all one attribute each in SVG and a pile of arithmetic otherwise. # # `default-features = false` matters here too — resvg defaults to # `system-fonts` + `memmap-fonts`, and a static binary in a slim container has # no system fonts to find. Text would silently render blank. We disable both # and register the embedded faces explicitly. resvg = { version = "0.48", default-features = false, features = ["text"] } usvg = { version = "0.48", default-features = false, features = ["text"] } tiny-skia = "0.12" da-core = { path = "crates/core" } da-render = { path = "crates/render" } da-discord = { path = "crates/discord" } da-webui = { path = "crates/webui" } da-http-api = { path = "crates/http-api" } [workspace.lints.rust] unsafe_code = "deny" rust_2018_idioms = { level = "warn", priority = -1 } [workspace.lints.clippy] pedantic = { level = "warn", priority = -1 } module_name_repetitions = "allow" missing_errors_doc = "allow" missing_panics_doc = "allow" must_use_candidate = "allow" doc_markdown = "allow" items_after_statements = "allow" cast_possible_truncation = "allow" cast_lossless = "allow" cast_sign_loss = "allow" cast_precision_loss = "allow" similar_names = "allow" too_many_lines = "allow" [profile.release] lto = "thin" codegen-units = 1 strip = "symbols" opt-level = 3