@roastery-capsules/auth
v0.1.0
Published
Authentication and session management capsule for the Roastery CMS ecosystem, featuring JWT tokens, rate-limited login, and route protection guards.
Maintainers
Readme
@roastery-capsules/auth
Authentication and session management capsule for the Roastery CMS ecosystem.
Overview
auth provides a single-credential login endpoint, revocable session access keys, and a route-protection guard, packaged as a Blend capsule for Barista (Elysia-based) apps:
- Login endpoint —
POST /auth/login, rate-limited, issues a secure HTTP-only session cookie. - Auth guard — protects routes by verifying the session cookie against a revocable, cached access key.
- Rate limiting — progressive login attempt tracking with lazy, automatic recovery.
- No bundled infrastructure — JWT signing/verification and cache persistence are entirely delegated to injected capsules; this package ships no implementation of either.
Technologies
| Tool | Purpose |
|------|---------|
| @roastery-capsules/jwt | Injected JWT signing and verification |
| @roastery-adapters/cache | Injected cache abstraction for access keys and attempt tracking |
| @roastery/barista | Web framework (Elysia-based) for controllers and guards |
| @roastery/blend | Capsule packaging and dependency manifest |
| @roastery/terroir | Exception hierarchy and runtime schema validation |
| @roastery/pantry | Shared constants and OpenAPI response helpers |
| tsup | Bundling to ESM + CJS with .d.ts generation |
| Bun | Runtime, test runner, and package manager |
| Knip | Unused exports and dependency detection |
| Husky + commitlint | Git hooks and conventional commit enforcement |
Installation
bun add @roastery-capsules/authConsumption modes
This package can be consumed in two ways:
As a Blend capsule — register the Auth manifest and let the platform validate its environment and dependencies before mounting it:
import { Auth } from '@roastery-capsules/auth';
const capsule = new Auth();
app.use(capsule.plugin);As granular subpaths — import only what you need:
import { authController } from '@roastery-capsules/auth/plugins/controllers';
import { auth } from '@roastery-capsules/auth/plugins/guards';
import { AccessKey, LoginAttempt, verifyCredentials } from '@roastery-capsules/auth/utils';
import { AuthEnvDependenciesDTO, VerifyCredentialsDTO } from '@roastery-capsules/auth/dtos';Dependency-injection contract
Neither authController nor the auth guard construct their own collaborators — they read them off the Barista instance's decorators:
| Decorator | Type | Source |
|---|---|---|
| env | AuthEnvDependenciesDTO | Validated environment (AUTH_EMAIL, AUTH_PASSWORD, optional IGNORE_AUTH) |
| cache | BaristaCacheInstance | @roastery-adapters/cache |
| jwt | JsonWebToken | @roastery-capsules/jwt |
The host application must decorate all three before mounting this capsule's plugin or guard:
app
.decorate('env', validatedEnv)
.decorate('cache', cacheInstance)
.decorate('jwt', jwtInstance)
.use(authController)
.use(auth);Missing pieces throw at registration time: MissingPluginDependencyException when cache/jwt are absent, InvalidEnvironmentException when AUTH_EMAIL/AUTH_PASSWORD are missing from env.
Login Controller
authController exposes a POST /auth/login endpoint that handles the full authentication flow.
import { authController } from '@roastery-capsules/auth/plugins/controllers';
app.use(authController);Flow:
- Checks the rate limit (max 5 attempts, lazy recovery of 1 attempt/hour)
- Validates the request body against the configured
AUTH_EMAIL/AUTH_PASSWORD - On failure, consumes one attempt and throws a
BadRequestException - On success, resets the rate limit, mints a UUID access key and stores it in cache
- Signs a JWT containing
{ ACCESS_KEY, EMAIL }and returns it in a secure, HTTP-onlyACCESS_TOKENcookie
Response codes:
| Status | Meaning | |--------|---------| | 200 | Login successful | | 400 | Invalid credentials | | 429 | Too many attempts | | 503 | Cache unavailable |
Auth Guard
auth protects routes by verifying the ACCESS_TOKEN cookie against the cached session access key.
import { auth } from '@roastery-capsules/auth/plugins/guards';
app.use(auth);On each request:
- Reads and
jwt.verifys theACCESS_TOKENcookie - Compares the token's
ACCESS_KEYagainst the cached value for thatEMAIL - Throws
UnauthorizedException(missing/invalid cookie or key mismatch) orResourceNotFoundException(missingEMAILin the token payload) on failure
Sessions are revocable: deleting the cache entry — or simply logging in again, which overwrites it — invalidates any previously issued token. Setting IGNORE_AUTH truthy in the environment turns the guard into a no-op, for local development only.
Utils
| Export | Purpose |
|---|---|
| AccessKey | Cache wrapper for the session access key of an e-mail (access-key:<email>, TTL CACHE_EXPIRATION_TIME.SAFE) |
| LoginAttempt | Rate limiter (login-attempts:<email>), max 5 attempts with lazy recovery of 1/hour |
| verifyCredentials | Validates a login payload's shape and compares it against the configured credential pair |
Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| AUTH_EMAIL | Yes | Configured authentication email |
| AUTH_PASSWORD | Yes | Configured authentication password |
| IGNORE_AUTH | No | When truthy, disables the auth guard entirely (development only). Default false |
CACHE_PROVIDER/REDIS_URL and any JWT signing secret are required by the injected @roastery-adapters/cache and @roastery-capsules/jwt dependencies, not by this capsule directly.
Exports Reference
import { Auth } from '@roastery-capsules/auth'; // Blend capsule manifest
import { authController } from '@roastery-capsules/auth/plugins/controllers'; // POST /auth/login
import { auth } from '@roastery-capsules/auth/plugins/guards'; // Route-protection guard
import AuthTags from '@roastery-capsules/auth/plugins/tags'; // OpenAPI tag metadata
import { AccessKey, LoginAttempt, verifyCredentials } from '@roastery-capsules/auth/utils';
import { AuthEnvDependenciesDTO, VerifyCredentialsDTO } from '@roastery-capsules/auth/dtos';Development
# Run tests
bun run test:unit
# Run tests with coverage
bun run test:coverage
# Build for distribution
bun run build
# Check for unused exports and dependencies
bun run knip
# Full setup (build + bun link)
bun run setupLicense
MIT
