Unlike go-security-redis (contracts only, no code in this repo), go-security-sql is a real, implemented separate module — github.com/thuongh2/go-security/go-security-sql — providing lockout.Store and session.Store adapters over stdlib database/sql. Same reasoning for the separate module: go-security’s own go.mod never gains a SQL driver, only applications that opt in pay that dependency cost.

Installation

Pluggable by design: any datasource, any config

The module ships Postgres 13+ and MySQL 8.0+ as built-in dialects, but it does not lock you into them. Each adapter package exports its own Dialect interface:
Postgres{} and MySQL{} are exported zero-value structs implementing these. Write your own type (SQLite, MariaDB, SQL Server, CockroachDB, any other flavor) and pass it to NewStore — no fork required:
The module never opens a connection itself — you supply an already-configured *sql.DB (any driver, any pool/TLS/DSN settings you want); NewStore only ever consumes it.

What a custom dialect owns vs. what it can’t touch

A Dialect implementation is trusted to get right:
  • Incr atomicity (no lost updates under concurrency)
  • Session expiry: Get must not resurrect an expired row
  • Save/Delete idempotency on an already-gone id
  • Its own schema/column types (nothing here forces a specific SQL dialect’s DDL)
It structurally cannot weaken two invariants enforced upstream in Store, before dispatch ever reaches the dialect:
  • The lockout key is SHA-256 hashed before a dialect ever sees it — a custom dialect only ever receives the fixed-width digest, never the raw username/IP composite.
  • Session attributes are encrypted (AES-256-GCM) before a dialect ever sees them, unless the caller explicitly opts out — a dialect only ever reads/writes opaque ciphertext bytes.

Proving a custom dialect: the conformance harness

Same harness pattern as go-security-redis — a dialect proves parity with the built-ins by passing the in-memory reference’s own test:
The module’s own test suite does exactly this for a SQLite-backed dialect (modernc.org/sqlite, pure Go) defined entirely outside package lockout — proof a third party can add a datasource without touching library internals.

Migrations

sql.RunMigrations(ctx, db, sql.DialectPostgres) (or DialectMySQL) applies the embedded, idempotent (CREATE ... IF NOT EXISTS) schema for the two built-in dialects — independent of Dialect/NewStore construction. A custom dialect brings its own schema; see the module’s README for the column-level contract (lockout_counters, sessions tables) it must satisfy.

Session encryption is mandatory by default

sqlsession.Options.EncryptionKey (AES-256-GCM, 32 bytes) is required unless you explicitly set AllowPlaintextAttributes: true — OAuth2 tokens can land in session attributes during login, so a plaintext-by-default SQL session store would contradict this codebase’s existing at-rest encryption requirement elsewhere. See the module README for the full rationale, the GC scheduling story (no internal background sweeper — a DB-backed store shouldn’t spawn one per replica), and known caller risks.

See also

  • go-security-sql/README.md — full reference: install, schema contract, “Implementing your own dialect,” MySQL deadlock/retry story, GC risk, encryption details, migrating from the pre-Dialect enum API.
  • docs/INTEGRATION.md — “A SQL-backed alternative” section.
  • lockoutlockout.Store, lockout.StoreConformance.
  • sessionsession.Store interface.