# Upstream parity β rustauth-redis
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) |
| **Upstream package** | `@better-auth/redis-storage` (ioredis) |
| **Upstream path** | `reference/upstream-src/1.6.9/repository/packages/redis-storage/` |
| **Rust crate** | `crates/rustauth-redis/` |
| **Parity level** | **High** vs RustAuth secondary-storage contract; **partial** vs literal upstream adapter |
| **Scope** | Server-side Redis/Valkey: `SecondaryStorage`, `RateLimitStore`, connection helpers. Sibling: [`rustauth-fred`](../rustauth-fred/UPSTREAM.md). Session logical keys and HTTP rate-limit middleware live in [`rustauth-core`](../rustauth-core/UPSTREAM.md). |
## Summary
`rustauth-redis` is the `redis-rs` backend for RustAuth secondary KV and distributed
rate limiting. Adapter CRUD, TTL handling, `list_keys`/`clear`, and physical key
layout match [`rustauth-fred`](../rustauth-fred/UPSTREAM.md) 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 JSON blobs in secondary KV when `rateLimit.storage` defaults to
`secondary-storage`.
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 uses `KEYS` on `{prefix}*` |
| Rate limit Redis store | π― Extension | `RedisRateLimitStore` + Lua; upstream reuses secondary KV as JSON |
| Shared connection bundle | β
High | `RedisRustAuthStores` β one `ConnectionManager` for both stores |
| Cross-adapter wire format | β
High | Byte-compatible with `rustauth-fred` on same Redis instance |
| 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 | `RedisRustAuthStores::apply_to_options` wires secondary storage and distributed RL together; core default policy remains explicit |
| Session payload interchange | β Out of scope | Logical keys and JSON live in `rustauth-core`; this crate stores opaque strings only |
| Valkey URL aliases | π― Extension | `valkey://` / `valkeys://` normalized to `redis://` / `rediss://` |
| TLS (`rediss://` / `valkeys://`) | β
High | Opt-in `rustls` or `native-tls` crate features |
## Test coverage
| Adapter unit + validation | 10 | 0 | `src/lib.rs`, `src/secondary.rs`, `src/rate_limit.rs`, `tests/config.rs` |
| Live Redis/Valkey integration | 11 | 0 | `tests/redis_rate_limit.rs` β secondary CRUD, `set_if_not_exists`, rate-limit atomicity, shared bundle |
| Secondary-storage server flows | β | 4 `it()` | `packages/better-auth/src/db/secondary-storage.test.ts` (covered in `rustauth-fred` E2E) |
| Rate-limit middleware + storage mode | β | ~6 relevant | `rate-limiter.test.ts` + `create-context.test.ts` (middleware in `rustauth-core`) |
| **Total (this crate)** | **21** | **0 adapter + 4 secondary + ~6 RL/context** | `cargo nextest list -p rustauth-redis` |
Verify:
```bash
cargo nextest run -p rustauth-redis
```
Integration tests expect Redis on `127.0.0.1:6379` and/or Valkey on `127.0.0.1:6380`.
Override with `RUSTAUTH_REDIS_URL` / `RUSTAUTH_VALKEY_URL`.
## Intentional differences
| Key layout | `{prefix}{logical_key}` | `{prefix}secondary:{logical_key}` | Isolate secondary KV from `rate-limit:` keys; match `rustauth-fred` |
| `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 |
| Rate-limit backing | JSON in secondary KV | Dedicated Lua hash (`rate-limit:`) | Atomic multi-instance increments |
| Default prefix | `better-auth:` | `rustauth:` | RustAuth namespace |
| TLS URLs | Caller configures ioredis TLS | `rediss://` / `valkeys://` require `rustls` or `native-tls` feature | Explicit compile-time TLS backend |
| Redis client | Caller-owned ioredis | `redis-rs` `ConnectionManager` | Idiomatic Rust async stack |
## 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 | Session payloads not portable | Med | Out of scope: logical keys and JSON live in `rustauth-core`; this crate treats values as opaque strings |
| G3 | Live Redis/Valkey required | Med | Integration tests skip when default endpoints are unreachable |
## 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.
- `SCAN` patterns escape Redis glob metacharacters in prefixes.
- `take()` uses `GETDEL` for one-shot reads.
- `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.
## 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/` is missing.
3. Open `reference/upstream-src/1.6.9/repository/packages/redis-storage/`.
4. Map upstream β Rust:
| `packages/redis-storage/src/redis-storage.ts` | `src/secondary.rs` (`RedisSecondaryStorage`) |
| `packages/core/src/db/type.ts` (`SecondaryStorage`) | `rustauth-core` `SecondaryStorage` trait β `src/secondary.rs` |
| `packages/better-auth/src/context/create-context.ts` | `rustauth-core` `RateLimitOptions` + `src/bundle.rs` |
| `packages/better-auth/src/api/rate-limiter/index.ts` | `src/rate_limit.rs` (`RedisRateLimitStore`) |
| `packages/better-auth/src/db/secondary-storage.test.ts` | `tests/redis_rate_limit.rs` (adapter flows); sign-up E2E in `rustauth-fred` |
| β | `src/bundle.rs`, `src/url.rs` |
5. Add a failing Rust integration test before behavior changes; match key layout, TTL side effects, and rate-limit decisionsβnot TypeScript types.
## Related docs
- [Crate README](./README.md) β usage and quick start
- [Sibling `rustauth-fred`](../rustauth-fred/UPSTREAM.md) β same contract, `fred` client
- [Parity index](../../docs/parity/README.md)