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 ownDialect
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:
*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
ADialect implementation is trusted to get right:
Incratomicity (no lost updates under concurrency)- Session expiry:
Getmust not resurrect an expired row Save/Deleteidempotency on an already-gone id- Its own schema/column types (nothing here forces a specific SQL dialect’s DDL)
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: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-Dialectenum API.docs/INTEGRATION.md— “A SQL-backed alternative” section.- lockout —
lockout.Store,lockout.StoreConformance. - session —
session.Storeinterface.
