Skip to content

feat(auth/rust): generate Rust/axum backend from auth.candy — eval green#47

Merged
koolamusic merged 2 commits into
mainfrom
feat/codegen-auth-rust
May 9, 2026
Merged

feat(auth/rust): generate Rust/axum backend from auth.candy — eval green#47
koolamusic merged 2 commits into
mainfrom
feat/codegen-auth-rust

Conversation

@koolamusic

Copy link
Copy Markdown
Contributor

Summary

Phase D of the candy alpha plan. Closes alpha criterion 4 (auth on
a second target language) and demonstrates the codegen prompts produce
idiomatic, eval-green code in Rust as well as Go.

A Rust/axum backend generated from `examples/auth/auth.candy` using
the codegen prompts merged in #41. Same JWT-self-contained design as
the Go target (#45) but using the Rust ecosystem libs from
`examples/auth/preferences.candy`. All 14 hurl scenarios pass against
the running server. `cargo fmt`, `cargo clippy -- -D warnings`,
`cargo build --release` all clean. 4 PasswordStrength unit tests
pass against the spec's policy examples.

What landed

Path LOC Purpose
`Cargo.toml` + lockfile crate manifest pinning preferences.candy libs
`src/main.rs` 40 tokio bind + DI + tracing setup
`src/lib.rs` 31 module re-exports + `Deps`
`src/auth/actors.rs` 245 UserRepo, JwtConfig, RevokedJtiRepo, SignupIdempotencyRepo
`src/auth/flows.rs` 141 Signup, Login, Logout
`src/auth/controllers.rs` 219 axum routes, BearerAuth, LogoutBearerAuth
`src/auth/policies.rs` 95 password_strength + 4 inline #[test] cases
`src/auth/events.rs` 45 typed event payloads
`src/runtime/db.rs` 48 rusqlite open + schema migration
`src/shared/types.rs` 76 branded types (Email, Password, Token, ...)
`src/shared/errors.rs` 39 declared error variants

Total: 1,038 Rust LOC (under the 3,000 budget).

Library pins (per `examples/auth/preferences.candy`)

Library Version Purpose
`axum` 0.7 HTTP framework
`tokio` 1.x async runtime
`rusqlite` 0.31 (bundled) DB
`jsonwebtoken` latest JWT (HS256)
`argon2` 0.5 password hashing
`uuid` 1.x v7 id generation

No KSUID-substituted-for-JWT shortcuts; preferences honored exactly.

Commits (atomic, in order)

SHA Subject
`83ef720` `fix: add trailing digit to fixtures.env passwords` (mirror of PR #45's fixture fix)
`2721f76` `feat(codegen): generate Rust/axum auth backend from auth.candy` (the meat)
`bcd1047` `chore: add .gitignore to exclude rust build artifacts`

Spec → realisation choices (mirror of Go target's HANDOFF)

Spec field Realisation
`Session.user` JWT `sub` claim
`Session.issued` JWT `iat` claim
`Session.expires` JWT `exp` claim
`Session.revoked` Membership in `revoked_jtis` table

`auth: bearer` splits into two middlewares for the same principled
reasons as the Go target (HANDOFF §3):

  • `BearerAuth` — parse + verify sig + check exp + check revocation.
  • `LogoutBearerAuth` — parse + verify sig + check exp; intentionally
    skips revocation so `logout-replay` returns 204 per the eval.

PasswordStrength order — blocklist before length

The spec policy example `"password123" → InBlocklist` (11 chars, in
blocklist) requires the implementation to check the blocklist BEFORE
length, otherwise `"password123"` would hit `TooShort` first. Same
ordering as the Go target's `policies.go`.

Verification

cd examples/auth/targets/rust
cargo fmt --all -- --check          # clean
cargo clippy --all -- -D warnings   # clean
cargo build --release                # clean
cargo test --tests                   # 4 PasswordStrength tests pass

cargo build --release && rm -f /tmp/auth-rust-dev.db
PORT=8086 DB_PATH=/tmp/auth-rust-dev.db JWT_SECRET=test-secret \\
  ./target/release/auth > /tmp/auth-rust.log 2>&1 &
sleep 2
PATH=\$HOME/bin:\$PATH hurl --variables-file ../../../../evals/auth/fixtures.env \\
  --variable BASE_URL=http://localhost:8086 \\
  --test ../../../../evals/auth/auth.hurl
# Success (14 request(s)) — 0 failures

Note on fixture-fix duplication

This PR ships the same auth/fixtures.env fix as PR #45. If #45 merges
first this PR will rebase cleanly; if this merges first #45 rebases.
Same one-line change either way.

Closes / Refs

koolamusic added 2 commits May 8, 2026 09:08
Implements Phase D of the candy alpha plan. All 14 hurl scenarios pass.

- actor User: rusqlite-backed UserRepo with create/findBy/email_exists
- actor Session: realized as self-contained HS256 JWT (jsonwebtoken);
  no sessions table — revocation via revoked_jtis(jti PK)
- flow Signup: idempotent on Key; argon2id password hashing; uuid-v7 ids
- flow Login: opaque InvalidCredentials; constant error shape
- flow Logout: idempotent via INSERT OR IGNORE on revoked_jtis
- policy PasswordStrength: blocklist > length > digit; all 4 spec
  examples covered as unit tests
- Two bearer middlewares: BearerAuth (sig+exp+revocation) and
  LogoutBearerAuth (sig+exp only) so logout-replay returns 204
- In-process event bus (tokio::sync::broadcast) for eager delivery
  of UserSignedUp, UserLoggedIn, SessionRevoked events
- cargo fmt, clippy -D warnings, build --release, test --all all green
@koolamusic koolamusic force-pushed the feat/codegen-auth-rust branch from bcd1047 to 7711fb3 Compare May 8, 2026 09:11
@koolamusic koolamusic merged commit 2345346 into main May 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant