npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@erithered/eri-auth-system

v4.0.0

Published

Fastify auth plugin with JWT access/refresh/reset tokens, email-based password reset, and logout-all-devices support. Coded by a human, assisted by AI to set up a comprehensive code quality framework and extracted from its original project into a standalo

Readme

Eri's auth system

@erithered/eri-auth-system — Fastify auth plugin with JWT access/refresh/reset tokens, email-based password reset, and logout-all-devices support.

Coded by a human, assisted by AI to set up a comprehensive code quality framework (strict TypeScript, ESLint + Prettier, knip, husky + lint-staged, conventional commits, vitest with 80%+ coverage, semantic-release, publint, attw, cspell) and extracted from its original project into a standalone publishable package.


npm version build coverage node license downloads

InstallDocumentationResourcesCommunityContact


Table of Contents


Tech Stack

| Layer | Technology | Version | | ---------------- | ----------------- | ------- | | Runtime | Node.js | >=18 | | Framework | Fastify | >=4 | | JWT | @fastify/jwt | >=8 | | Cookies | @fastify/cookie | >=9 | | Validation | @sinclair/typebox | ^0.32 | | Password Hashing | argon2 | ^0.31 | | Language | TypeScript | ^5.9 | | Testing | Vitest | ^4.1 |

↑ Back to top


Architecture

graph TB
    subgraph Client["Client"]
        HTTP[HTTP Requests]
        Cookie["refreshToken Cookie"]
    end

    subgraph Plugin["@erithered/eri-auth-system"]
        Routes["/v1/auth/* Routes<br/>7 endpoints"]
        Secrets["Secrets<br/>access / refresh / reset"]
        JWT["@fastify/jwt<br/>3 namespaces"]
        Validation["Request Validation<br/>(TypeBox schemas)"]
    end

    subgraph You["Your Application"]
        Callbacks["PluginOptions Callbacks"]
        DB["Your Database / ORM"]
    end

    HTTP --> Routes
    Cookie --> Routes
    Routes --> JWT
    Routes --> Validation
    JWT --> Secrets
    Routes --> Callbacks
    Callbacks --> DB

The plugin registers 7 routes under a configurable prefix (default /auth). Each route uses one of three isolated @fastify/jwt namespaces (access / refresh / reset) for token handling. Business logic is delegated to consumer-provided callbacks, making the plugin agnostic to your database or ORM choice. Request validation uses TypeBox schemas. Error responses are customisable via the analyseError callback.

Routes

All routes are registered under the configured prefix (default /auth).

| Method | Path | Auth | Description | | ------ | -------------------- | ------ | ----------------------------------------------- | | POST | /auth/signup | — | Create a new user account | | POST | /auth/login | — | Authenticate with ID + password | | POST | /auth/refresh | Cookie | Rotate the access token using the refresh token | | PATCH | /auth/logout | Cookie | Revoke the current refresh token | | POST | /auth/askPwdReset | — | Request a password-reset email | | PATCH | /auth/pwdReset | Token | Complete a password reset | | GET | /auth/is-logged-in | Bearer | Validate the current access token |

  • Auth: Cookie — requires a signed refreshToken cookie
  • Auth: Bearer — requires an Authorization: Bearer <token> header
  • Auth: Token — token is sent in the request body

JWT Namespaces

Three isolated @fastify/jwt namespaces, each with its own secret and purpose:

| Namespace | Decorator | Expiry | Transport | | ----------- | --------------------- | ------ | ---------------------------------------- | | access | request.accessUser | 15 min | Authorization: Bearer header | | refresh | request.refreshUser | 7 days | Signed HTTP-only cookie (refreshToken) | | reset | — | 15 min | URL query parameter (sent via email) |

The refresh namespace includes a trusted function that checks revocation status via getTokenRevokedAt — even a valid, unexpired refresh token is rejected if it has been revoked.

Response format

Successful responses return JSON or plain text. All errors follow this shape (customisable via the analyseError callback):

{ "statusCode": 401, "error": "Invalid credentials", "message": "This password is invalid" }

↑ Back to top


Features

  • Three JWT namespaces — access (15 min), refresh (7 days, signed cookie), reset (15 min, email link), each with an isolated secret
  • Email-based password reset — signed reset token delivered via your sendResetEmail callback
  • Refresh token revocation — expired or revoked tokens are rejected; custom revocation checking via getTokenRevokedAt
  • Logout-all-devices — revoke every active refresh token on password reset
  • Custom error handling — map database or system errors to user-facing messages via analyseError
  • CSRF-protected refresh/refresh is POST-only with server-side Origin validation; refresh cookie uses SameSite=Lax
  • Full TypeScript types — Fastify decorator augmentations included, zero extra configuration
  • 80 %+ test coverage — integration tests covering all 7 routes and error branches

↑ Back to top


Getting Started

Installation

npm install @erithered/eri-auth-system

Peer dependencies — your application must provide these:

npm install fastify @fastify/jwt @fastify/cookie @sinclair/typebox

Quickstart

import Fastify from 'fastify';
import { authPlugin, type PluginOptions } from '@erithered/eri-auth-system';

const app = Fastify();

await app.register(authPlugin, {
  accessSecret: process.env.ACCESS_SECRET!,
  refreshSecret: process.env.REFRESH_SECRET!,
  resetSecret: process.env.RESET_SECRET!,
  siteUrl: 'https://myapp.com',
  findUser: async (id) => db.users.findById(id),
  createUser: async (id, email, birthday) => db.users.create(id, email, birthday),
  revokeToken: async (token) => db.tokens.revoke(token),
  sendResetEmail: async (to, link) => mailer.send(to, link),
  createRefreshToken: async (userId, token, expiresAt) =>
    db.refreshTokens.insert({ userId, token, expiresAt }),
  updateUserPassword: async (userId, hash) => db.users.updatePassword(userId, hash),
  logoutAllDevices: async (userId) => db.tokens.revokeAll(userId),
  allowedOrigins: ['https://myapp.com'],
  analyseError: async (err) => (err.code === 'P2002' ? 'This user ID is already taken' : null),
  getTokenRevokedAt: async (token) => db.tokens.revokedAt(token),
});

await app.listen({ port: 3000 });

⚠️ Security notice: /login and /askPwdReset are brute-force targets. It is strongly recommended to add @fastify/rate-limit to your app. The /refresh endpoint is CSRF-protected via POST-only + server-side Origin validation.

↑ Back to top


File Structure & Naming

src
├── auth-plugin.test.ts
├── index.ts
├── plugins
│   └── authenticate.ts
├── routes
│   └── v1
│       └── auth
│           ├── ask-pwd-reset.ts
│           ├── is-logged-in.ts
│           ├── login.ts
│           ├── logout.ts
│           ├── pwd-reset.ts
│           ├── refresh.ts
│           └── signup.ts
└── types
    ├── fastify.d.ts
    └── plugin-options.ts

Naming conventions:

  • Filenames — kebab-case throughout (e.g. ask-pwd-reset.ts, plugin-options.ts)
  • Routes — co-located by version under src/routes/v1/auth/
  • Plugins — Fastify plugin decorators in plugins/
  • Types — TypeScript type definitions in types/
  • Test file — single integration test at src/auth-plugin.test.ts

↑ Back to top


Documentation

Full API documentation generated by TypeDoc is available in the docs/ directory.

PluginOptions

All fields are required. The plugin validates their presence at registration time.

Secrets:

| Field | Type | Description | | --------------- | -------- | ----------------------------------------------------------------------------- | | accessSecret | string | Secret for signing/verifying short-lived access JWT (15 min) | | refreshSecret | string | Secret for signing/verifying long-lived refresh JWT (7 days) | | resetSecret | string | Secret for signing/verifying password-reset JWT (15 min) | | siteUrl | string | Base URL used to construct the password-reset link (e.g. https://myapp.com) |

Callbacks:

| Callback | Signature | Called when… | | -------------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | findUser | (userId: string) => Promise<{ id, hashedPassword, email } \| null> | Login, password reset — looks up a user by ID | | createUser | (userId: string, email: string, birthday: string) => Promise<void> | Signup — creates a new user record | | revokeToken | (token: string \| undefined) => Promise<void> | Logout — marks a refresh token as revoked | | sendResetEmail | (to: string, resetLink: string) => Promise<void> | Password reset request — delivers the reset link | | createRefreshToken | (userId: string, token: string, expiresAt: Date) => Promise<void> | Login — persists a refresh token for later revocation | | updateUserPassword | (userId: string, hashedPassword: string) => Promise<void> | Password reset — updates the stored password hash | | logoutAllDevices | (userId: string) => Promise<void> | Password reset — revokes all active refresh tokens | | analyseError | (error: unknown) => Promise<string \| null> | Error handling — maps a caught error to a user-facing message | | allowedOrigins | string[] (optional) | Trusted origins for cross-origin request validation on /refreshDefaults to the origin from siteUrl | | getTokenRevokedAt | (token: string) => Promise<Date \| null> | Every request with a refresh cookie — checks if the token was revoked |

TypeScript

This package is fully typed. Augmentations for FastifyInstance, FastifyRequest, and FastifyReply are provided in src/types/fastify.d.ts and are automatically available when you import the plugin.

import { authPlugin, type PluginOptions } from '@erithered/eri-auth-system';

If you need to reference the auth decorators on your Fastify instance, add a fastify.d.ts file in your project:

import '@erithered/eri-auth-system';

↑ Back to top


Resources

↑ Back to top


Community

↑ Back to top


Contact

↑ Back to top


Contributors

Contributors

↑ Back to top


Thanks & Acknowledgments

  • Fastify team for the plugin ecosystem
  • @fastify/jwt for JWT namespace support
  • @sinclair/typebox for runtime type validation
  • Vitest team for the testing framework
  • Everyone who contributed, opened an issue, PR, or star

↑ Back to top


License

Distributed under the ISC License.


This README was generated using a coding agent and the README-template.md.