Skip to content

AUTH command for RESP protocol #7

Description

@Saxy

Area

RESP2 (Redis-compatible)

Problem or motivation

Tellstone's RESP listener accepts connections from any client with no authentication. Anyone who can reach port 6379 can read, write, and delete data. This makes it impossible to expose Tellstone beyond a trusted network boundary. Redis solves this with the AUTH command — users expect the same from a Redis-compatible system

Proposed solution

Implement the AUTH command following Redis AUTH semantics:

AUTH <password>             # single-password mode (Redis < 6.0 compatible)
AUTH <username> <password>  # ACL mode (Redis 6.0+, future-proofs for ACL work)

Wire protocol: RESP2 simple string reply +OK\r\n on success, or RESP2 error -ERR invalid password\r\n on failure. This matches the Redis AUTH return specification.

Configuration:

  • --require-pass / TSD_REQUIRE_PASS — server password (plaintext flag, hashed at startup)
  • When unset, AUTH is a no-op and all connections are immediately authenticated (zero overhead)

Implementation — touch points in existing code:

  1. internal/resp/server.goconnState struct (line 39): Add authenticated bool field. New connections start with authenticated: false when requirePass is set, or authenticated: true when it's empty.

  2. internal/resp/server.godispatch method (line 195): Add a new case branch before the existing command dispatch:

    case EqualFold(cmd, "AUTH"):
        // Validate against requirePass, update st.authenticated

    When requirePass is empty, respond +OK\r\n immediately (no-op, backward-compatible).

  3. internal/resp/server.godispatch guard (line 195): After the AUTH case, add an early return if !st.authenticated:

    if !st.authenticated {
        return AppendError(out, "NOAUTH Authentication required")
    }

    This matches Redis's error message for unauthenticated connections.

  4. internal/resp/server.goServer struct (line 46): Add requirePass string field, set from config in NewServer.

  5. Password hashing: Hash with bcrypt.GenerateFromPassword at startup (stored as []byte in Server). Per-connection comparison via bcrypt.CompareHashAndPassword. This is called only on AUTH command, never on the hot path — zero overhead for authenticated connections.

  6. Logging: Failed AUTH attempts logged at LevelWarn with remote IP (same pattern as the protocol error log at line 156).

  7. config/config.go: Add RequirePass string field with --require-pass / TSD_REQUIRE_PASS flag/env.

Commands allowed before AUTH: Per Redis behavior, only AUTH, PING, and QUIT should be accepted on unauthenticated connections. Existing dispatch cases for GET/SET/DEL return NOAUTH error.

Zero-overhead guarantee: When --require-pass is not set, requirePass is empty string, st.authenticated starts as true, and the AUTH case returns +OK in O(1). No bcrypt calls, no state checks beyond the existing switch.

Alternatives considered

No response

Additional context

References:

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions