@deeblr/auth
v0.4.0
Published
Deeblr Auth meta-package: a complete, batteries-included authentication facade (registration, login, password management, email verification, account deletion, framework-agnostic middleware) built on @deeblr/auth-core.
Readme
@deeblr/auth
The complete, batteries-included Deeblr Auth facade: registration, login,
password management (change/forgot/reset), email verification, account
deletion, and framework-agnostic middleware — all built on top of
@deeblr/auth-core.
Install
npm install @deeblr/authQuick start
import { DeeblrAuth } from "@deeblr/auth";
import { myAdapter } from "./my-adapter";
const auth = new DeeblrAuth({
adapter: myAdapter,
secret: process.env.AUTH_SECRET!, // >= 16 chars, e.g. `openssl rand -base64 32`
});
const user = await auth.register({
email: "[email protected]",
password: "correct-horse-battery-staple",
name: "Ada Lovelace", // custom fields are supported
});
const { user: loggedIn } = await auth.login({
email: "[email protected]",
password: "correct-horse-battery-staple",
});Configuration
new DeeblrAuth({
adapter, // required — a DatabaseAdapter (see @deeblr/auth-core)
secret: "...", // required — signs password-reset / email-verification tokens
password: { // optional password policy (all default to permissive)
minLength: 10,
requireUppercase: true,
requireLowercase: true,
requireNumbers: true,
requireSymbols: true,
},
tokens: {
passwordResetMinutes: 30, // default 30
emailVerificationMinutes: 1440, // default 24h
},
requireEmailVerification: false, // if true, login() throws EmailNotVerifiedError until verified
plugins: [], // any @deeblr/auth-core plugin
logger, emailAdapter, security, // pass-through to @deeblr/auth-core
});Public API
| Method | Description |
|---|---|
| register(input) | Creates a user. Supports custom fields beyond email/password. Returns the user directly. |
| login(input) | Authenticates by email/password. Checks account state (disabled/lockedUntil) and, if configured, email verification, before delegating to core. Returns { user, session? }. |
| logout(input) | Ends a session. Strategy-agnostic — works the same with no session plugin, a future @deeblr/auth-session, or @deeblr/auth-jwt. |
| user(id) | Retrieves a user by id. Throws UserNotFoundError. |
| changePassword(input) | Verifies the current password, validates the new one against policy, updates it. |
| forgotPassword({ email }) | Issues a signed reset token and emails it. Never reveals whether the email exists. |
| resetPassword({ token, newPassword }) | Verifies the token and updates the password. |
| requestEmailVerification({ userId }) | Issues a signed verification token and emails it. |
| verifyEmail({ token }) | Verifies the token and marks the account verified. |
| deleteAccount({ userId }) | Deletes the account. Emits auth:beforeDeleteAccount / auth:afterDeleteAccount. |
| isAuthenticated(context) | Pure check on an already-resolved AuthContext. |
| forRequest(context) | Returns a request-scoped { user(), isAuthenticated(), logout() } — genuinely zero-argument and safe under concurrency (see below). |
| use(plugin), hooks, services, state, initialize(), destroy(), core | Pass through to the underlying DeeblrAuthCore. |
Why no zero-argument auth.user() / auth.isAuthenticated()?
A single DeeblrAuth instance is shared across every concurrent request in
a real server. A "current user" stored directly on that instance would leak
between requests — that's a correctness bug, not an ergonomics trade-off
worth taking. Instead:
// A session/JWT strategy plugin (or, for now, your own resolver) produces
// an AuthContext per request:
const context = await resolveAuthContext(request); // { user, session? }
// Then you get genuinely zero-argument, request-scoped calls:
const requestAuth = auth.forRequest(context);
await requestAuth.user();
requestAuth.isAuthenticated();
await requestAuth.logout();This is also the seam @deeblr/auth-express / @deeblr/auth-nextjs will
build on: they'll construct the AuthContext per request and attach
auth.forRequest(context) to req.auth (or equivalent) automatically.
Middleware
import { createAuthMiddleware } from "@deeblr/auth";
// `resolver` is supplied by a session/JWT strategy plugin once one exists.
// This package ships the factory, not a resolver — there's nothing to
// resolve a session/token FROM without one of those installed yet.
const middleware = createAuthMiddleware(resolver, { required: true });
const context = await middleware({ headers: request.headers });Hooks and events
Every flow emits on auth.hooks, which is the exact same HookBus as
@deeblr/auth-core. Two naming styles are both live:
- Colon-namespaced (from core, extended here):
auth:beforeRegister,auth:afterRegister,auth:beforeLogin,auth:afterLogin,auth:beforeLogout,auth:afterLogout,auth:beforeDeleteAccount,auth:afterDeleteAccount,auth:passwordChanged,auth:passwordResetRequested,auth:passwordReset,auth:emailVerificationRequested,auth:emailVerified. - Dot-notation domain events (this package):
user.registered,user.login,user.logout,user.deleted,password.changed,password.reset,email.verified— re-emitted on the same bus whenever the correspondingauth:*hook fires, so either naming style works.
auth.hooks.on("user.registered", async ({ user }) => {
await sendWelcomeEmail(user);
});Errors
All errors extend AuthError and carry a stable code. New in this
package: EMAIL_NOT_VERIFIED, PASSWORD_TOO_WEAK, ACCOUNT_DISABLED,
ACCOUNT_LOCKED, INVALID_RESET_TOKEN (plus everything already in
@deeblr/auth-core: INVALID_CREDENTIALS, USER_ALREADY_EXISTS,
USER_NOT_FOUND, etc).
Password reset / email verification tokens
Reset and verification links use signed, stateless HMAC-SHA256 tokens —
nothing is persisted, so no database schema changes are required. The
trade-off: a token can't be individually revoked before its TTL expires
(default 30 minutes for reset, 24 hours for verification). A future
@deeblr/auth-security denylist is the natural place to add single-use
revocation later without changing this format.
License
MIT
