# Upstream parity — rustauth-fred
Better Auth **1.6.9** behavioral reference for contributors and parity audits.
RustAuth is inspired by Better Auth; it is not a line-by-line port.
| **Parity pin** | [`reference/upstream-better-auth/VERSION.md`](../../reference/upstream-better-auth/VERSION.md) (`1.6.9`) |
| **Upstream package** | `@better-auth/redis-storage` (ioredis) |
| **Upstream path** | `reference/upstream-src/1.6.9/repository/packages/redis-storage/` |
| **Rust crate** | `crates/rustauth-fred/` |
| **Parity level** | **High** vs RustAuth secondary-storage contract; **partial** vs literal upstream adapter |
| **Scope** | Server-side Redis/Valkey: `SecondaryStorage`, `RateLimitStore`, connection helpers. Sibling: [`rustauth-redis`](../rustauth-redis/UPSTREAM.md). Session key names and HTTP rate-limit middleware live in [`rustauth-core`](../rustauth-core/UPSTREAM.md). |
## Summary
`rustauth-fred` is the `fred` backend for RustAuth secondary KV and distributed
rate limiting. Adapter CRUD, TTL handling, `list_keys`/`clear`, and physical key
layout match `rustauth-redis` on a shared instance. Literal parity with
`@better-auth/redis-storage` is partial: RustAuth namespaces keys under
`secondary:`, adds `set_if_not_exists`/`take`, and uses different `ttl=0`
semantics. Rate limiting is a dedicated Lua store (`rate-limit:`) instead of
upstream's JSON blobs in secondary KV when `rateLimit.storage` defaults to
`secondary-storage` (`create-context.ts`).
Status symbols are defined in the [parity index](../../docs/parity/README.md#status-symbols).
## Feature parity
| Secondary storage (`get`/`set`/`delete`) | ✅ High | `{prefix}secondary:` namespace; `ttl=0` deletes key per `rustauth-core` contract |
| `set_if_not_exists` / `take` | 🎯 Extension | Required by `rustauth-core`; absent from upstream redis adapter |
| `list_keys` / `clear` | ✅ High | `SCAN` on `{prefix}secondary:*`; upstream `KEYS` on `{prefix}*` |
| Rate limit Redis store | 🎯 Extension | `FredRateLimitStore` + Lua; upstream reuses secondary KV as JSON |
| Shared connection bundle | ✅ High | `FredRustAuthStores` — one shared `fred` connection (`src/bundle.rs`) |
| Cross-adapter wire format | ✅ High | `{prefix}secondary:{key}`; byte-compatible with `rustauth-redis` |
| Better Auth Redis data import | ➖ Out of scope | Upstream flat `{prefix}{key}` vs RustAuth `secondary:` namespace; requires an explicit migration/rewrite tool, not adapter fallback reads |
| Auto RL when secondary configured | ✅ High | `FredRustAuthStores::apply_to_options` wires secondary storage and distributed RL together; core default policy remains explicit |
| Valkey URL aliases | 🎯 Extension | `valkey://` → `redis://` (`src/url.rs`) |
| TLS (`rediss://` / `valkeys://`) | ✅ High | Opt-in `native-tls` or `rustls` features on `fred` |
## Test coverage
| Adapter unit + validation | 27 | 0 | `src/storage.rs`, `src/script.rs`, `tests/config.rs` — no live Redis |
| Live Redis/Valkey integration | 18 | 0 | `tests/fred_rate_limit.rs` |
| Secondary-storage server flows | 3 | 4 | Sign-up, DB+secondary, password-reset vs `secondary-storage.test.ts` |
| Rate-limit store atomicity | 5 | ~4 relevant | Lua boundary, concurrency, handler `429`; upstream RL+secondary-KV in `rate-limiter.test.ts` (~20 total, mostly middleware rules) |
| Cross-adapter (`rustauth-redis`) | 2 | — | Shared physical keys and `take` |
| `set_if_not_exists` | 1 | — | Live Redis/Valkey create-once, non-overwrite, concurrency, and TTL coverage |
| Context defaults (RL storage mode) | — | 2 | `create-context.test.ts` (`secondary-storage` when `secondaryStorage` set) |
| Example app Fred bundle | 1 | — | `examples/full-app` profiles use `FredRustAuthStores` |
| **Total (this crate)** | **46** | **0 adapter + 4 secondary + ~6 RL/context** | `cargo nextest list -p rustauth-fred` plus focused example test |
Verify:
```bash
cargo nextest run -p rustauth-fred
```
Integration tests expect Redis on `127.0.0.1:6379` and/or Valkey on `127.0.0.1:6380`.
Override with `RUSTAUTH_FRED_REDIS_URL` / `RUSTAUTH_FRED_VALKEY_URL` (explicit URLs
fail closed when unreachable).
## Intentional differences
| Key layout | `{prefix}{logical_key}` | `{prefix}secondary:{logical_key}` | Isolate secondary KV from `rate-limit:` keys; match `rustauth-redis` |
| `ttl = 0` on `set` | Store without expiry | Delete key | `rustauth-core` expired-value contract |
| `list_keys` / `clear` | `KEYS` on full prefix | `SCAN` on `secondary:` only | Production-safe scans; `clear()` preserves rate-limit state (OPE-37) |
| Rate-limit backing | JSON in secondary KV | Dedicated Lua hash (`rate-limit:`) | Atomic multi-instance increments |
| Window reset | `timeSinceLastRequest > window` | Same (`>` in Lua) | Matches Better Auth server middleware |
| Default prefix | `better-auth:` | `rustauth:` | RustAuth namespace |
| Redis connection | Caller-owned ioredis instance | `FredRustAuthStores` shares one `fred` connection | Fewer connections when both stores are used |
## Open gaps and risks
| G1 | Better Auth Redis import | High | Intentional out of scope for this adapter: flat upstream keys need an explicit migration/rewrite tool instead of fallback reads that broaden the key namespace |
| G2 | Legacy Fred key layout | Med | Intentional breaking beta migration: pre-`secondary:` physical keys are not read after namespace change ([CHANGELOG](./CHANGELOG.md)) |
| G3 | Live Redis/Valkey required | Med | Integration tests skip unavailable default endpoints |
| G4 | Reconnect / cluster | Low | Delegated to `fred`; no RustAuth retry wrapper |
## Hardening notes
- Empty key prefix and zero scan count/window/max rejected before Redis I/O (fail-closed config).
- Rate limiting uses atomic Lua (`evalsha_with_reload`) for multi-instance safety.
- `clear()` scoped to `secondary:` so co-located `rate-limit:` keys survive (OPE-37).
- `SCAN` patterns escape Redis glob metacharacters in prefixes.
- `take()` uses `GETDEL`; concurrent take verified under live Redis.
- `set_if_not_exists()` uses Redis `SET NX` and is covered under live Redis/Valkey
for overwrite protection, concurrent create-once behavior, and TTL expiry.
- `examples/full-app` Fred profiles use `FredRustAuthStores`, demonstrating shared
Fred secondary-storage + distributed rate-limit wiring.
## Upstream lookup
1. Read the pin in [`reference/upstream-better-auth/VERSION.md`](../../reference/upstream-better-auth/VERSION.md).
2. Run `./scripts/fetch-upstream-better-auth.sh` if `reference/upstream-src/1.6.9/repository/` is missing.
3. Open the upstream server paths below (server-side only).
4. Map upstream → Rust:
| `packages/redis-storage/src/redis-storage.ts` | `src/storage.rs` (`FredSecondaryStorage`) |
| `packages/core/src/db/type.ts` (`SecondaryStorage`) | `rustauth-core` `SecondaryStorage` trait → `src/storage.rs` |
| `packages/better-auth/src/context/create-context.ts` (`rateLimit.storage` default) | `rustauth-core` `RateLimitOptions` + `src/bundle.rs` |
| `packages/better-auth/src/api/rate-limiter/index.ts` | `src/store.rs`, `src/script.rs` (`FredRateLimitStore`) |
| `packages/better-auth/src/db/secondary-storage.test.ts` | `tests/fred_rate_limit.rs` (sign-up / revoke flows) |
| `packages/better-auth/src/db/internal-adapter.ts` | Session logical keys (`active-sessions-*`, token keys) — [`rustauth-core`](../rustauth-core/UPSTREAM.md), not this crate |
| `packages/better-auth/src/api/rate-limiter/rate-limiter.test.ts` | `tests/fred_rate_limit.rs` (atomicity, `429`, window `>`) |
| — | `src/bundle.rs`, `src/url.rs`, `src/config.rs`, `src/error.rs` |
5. Add a failing Rust integration test before behavior changes; match key layout, TTL side effects, and rate-limit decisions.
### Crate files audited (14/14)
| `src/lib.rs`, `src/storage.rs`, `src/store.rs`, `src/script.rs` | Public surface + implementations |
| `src/bundle.rs`, `src/config.rs`, `src/url.rs`, `src/error.rs` | Wiring, options, URL normalize, errors |
| `tests/fred_rate_limit.rs`, `tests/config.rs` | Integration + config/Lua parsing |
| `README.md`, `Cargo.toml`, `CHANGELOG.md` | User docs, deps, migration notes |
| `examples/full-app` (fred usage) | `FredRateLimitStore` only — see G7 |
### Upstream server files audited
| `packages/redis-storage/src/redis-storage.ts` | Direct adapter contract (`get`/`set`/`delete`/`listKeys`/`clear`) |
| `packages/redis-storage/src/index.ts`, `README.md`, `CHANGELOG.md` | Exports; no behavior changes in 1.6.9 |
| `packages/core/src/db/type.ts` | Upstream `SecondaryStorage` interface (3 ops) |
| `packages/better-auth/src/context/create-context.ts` | `rateLimit.storage` default when secondary configured |
| `packages/better-auth/src/context/create-context.test.ts` | RL storage default + secondary wiring tests |
| `packages/better-auth/src/api/rate-limiter/index.ts` | Secondary-KV JSON rate-limit mode |
| `packages/better-auth/src/api/rate-limiter/rate-limiter.test.ts` | Server RL middleware (20 `it()`) |
| `packages/better-auth/src/db/secondary-storage.test.ts` | Server session + secondary flows (4 `it()`) |
| `packages/better-auth/src/db/internal-adapter.ts` | Logical key patterns (`active-sessions-*`, tokens) |
### Audit status (server-only)
**Complete** for the `rustauth-fred` crate boundary: Redis/Valkey adapter
(`SecondaryStorage`, `RateLimitStore`, connection helpers). All crate source,
tests, and upstream adapter contract files above were reviewed.
**Out of crate scope** (not required to finish this audit; tracked elsewhere):
| [`rustauth-core`](../rustauth-core/UPSTREAM.md) | Session/verification logical keys, RL middleware rules, auto-wiring |
| Upstream plugin consumers (`api-key`, `oauth-provider`, `sso`, `device-authorization`) | Call `secondaryStorage`; do not define Redis adapter behavior |
| `internal-adapter.test.ts` (~33 server tests) | Session persistence layer, not `redis-storage.ts` |
| `get-tables.ts` / schema tests | DB schema when secondary storage is enabled |
| `test/unit/magic-link-secondary-storage.test.ts` | Plugin E2E — not redis adapter scope |
**Open implementation gaps**: none inside the `rustauth-fred` crate boundary.
Remaining risks above are intentional migration/deployment constraints or delegated
to `fred`/sibling crates.
## Related docs
- [Crate README](./README.md) — usage and quick start
- [Parity index](../../docs/parity/README.md)