A django-axes-style login-lockout engine for TypeScript — persistent failed-attempt tracking, failure limits, tiered cooloff, and a pluggable store. A framework-agnostic core plus a thin NestJS adapter.
authlock — a login-lockout engine for Node: a framework-agnostic core
(@authlock/core) with a first-class NestJS integration
(@nest-native/lockout).
Note
Published and following semver — @authlock/core
and @nest-native/lockout
are on npm (OIDC trusted publishing with provenance). Version history is in
the changelog; full documentation lives at
nest-native.dev/lockout.
Every app with a login form needs brute-force protection: after N failed
attempts for a username or IP, stop accepting attempts for a while, then cool
off. Django solves this with django-axes.
@authlock/core brings the same model to TypeScript — and, crucially, keeps the
engine framework-agnostic so it is not tied to any one web framework.
- Configurable identity dimensions — lock on username, IP, a combination,
or
+user_agent. A lock trips if any configured parameter key exceeds the limit within the cooloff window. - Failure limit → lockout, with cooloff — including tiered cooloff
(each subsequent lockout waits longer), reset-on-success, a whitelist
predicate, and a
Retry-Aftervalue. - Pluggable store — an in-memory store ships in the box; a Drizzle-backed store (the batteries-included default, with an atomic cross-instance increment) is the durable option.
- fail-open by default — if the store errors, the engine allows and logs, so
a database blip never locks everyone out.
failMode: 'closed'is available for high-security deployments.
| Package | What it is |
|---|---|
@authlock/core |
the framework-agnostic, zero-runtime-dependency engine — usable from Express, inversify, tsyringe, or a bare script |
@nest-native/lockout |
a thin NestJS DI adapter — LockoutGuard + LockoutService + LockoutModule (NestJS 10 / 11 / 12) |
The neutral core is the cross-framework story — there are no bespoke inversify/tsyringe adapter packages.
django-axes hooks Django's ambient user_login_failed signal, so it is
install-and-forget. NestJS has no equivalent signal bus, so
@nest-native/lockout cannot be. It offers explicit wiring instead: a
LockoutGuard (reject-if-locked, pre-auth), a LockoutService
(reportFailure / reportSuccess you call from your login handler), and a
documented Passport-strategy recipe. The docs lead with this honestly.
Every change runs the full gate — build, typecheck (both packages), coverage
with c8 enforced at 100% on the core (statements, branches, functions,
lines), cognitive-complexity enforcement (SonarJS threshold 15) on the core,
tarball validation, sample version sync, a supply-chain audit of the published
surface, the docs build, and the samples:
npm run ciThe core (@authlock/core) is held to the strict non-negotiables (100%
coverage, complexity ≤ 15, zero published runtime deps). The adapter
(@nest-native/lockout) is a thin DI shell tested pragmatically (its own
test:nestjs lane), not to 100%.
Two optional, local-only layers sit on top (forks work without Docker):
- Full mode —
npm run infra:up && npm run test:fullruns the gated Drizzle-store round-trips against disposable Postgres + MySQL containers (compose.yaml);npm run infra:downcleans up. - Mutation testing —
npm run test:mutation(incremental Stryker run). Scope withSTRYKER_MUTATE; never runs in CI.
Details, including the pre-PR ritual and agent instructions, are in GUIDELINES_NEST_LOCKOUT.md.
- Admin UI / dashboard, template/URL lockout rendering.
- Deep
X-Forwarded-Forproxy matrix — the library exposes an identity extractor hook and does not trust proxy headers by default. - Bespoke inversify/tsyringe adapter packages — the neutral core is the cross-framework story.
- Rate limiting — a distinct concern; use
@nestjs/throttler. This library is about failed-authentication lockout.
See the 00-showcase sample and the documentation.
Part of the nest-native family. Not affiliated with the NestJS core team or the django-axes project. MIT licensed.