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

cors-whitelist-ip

v1.0.3

Published

Framework-agnostic CORS whitelist middleware with IP and domain validation, supporting pluggable storage and caching

Readme

cors-whitelist-ip

A framework-agnostic CORS whitelist middleware with IP and domain validation, supporting pluggable storage and caching.

Features

  • Framework-agnostic: Works with Express, Fastify, Koa, NestJS, or plain Node.js HTTP
  • Pluggable storage: Implement your own storage adapter for any database
  • Optional caching: Built-in in-memory cache or bring your own (Redis, etc.)
  • IP validation: Whitelist specific IPv4/IPv6 addresses
  • Domain patterns: Support for wildcard domains (e.g., *.example.com)
  • API key association: Link domains to organizations and API keys
  • Zero production dependencies: Only peer dependencies for framework integrations

Installation

npm install cors-whitelist-ip

Quick Start

Express

import express from 'express';
import { CorsWhitelist, InMemoryStorageAdapter } from 'cors-whitelist-ip';
import { createExpressMiddleware } from 'cors-whitelist-ip/express';

const app = express();

const storage = new InMemoryStorageAdapter({
  globalWhitelist: ['https://trusted-app.com', '*'], // '*' allows all origins
  whitelistEntries: [
    { id: 1, type: 'DOMAIN', value: '*.example.com', isActive: true },
    { id: 2, type: 'IP', value: '192.168.1.100', isActive: true },
  ],
});

const corsWhitelist = new CorsWhitelist({ storage, debug: true });

app.use(createExpressMiddleware(corsWhitelist));

app.get('/api/data', (req, res) => {
  res.json({ message: 'Hello!' });
});

app.listen(3000);

Fastify

import Fastify from 'fastify';
import { fastifyCorsWhitelist } from 'cors-whitelist-ip/fastify';
import { InMemoryStorageAdapter } from 'cors-whitelist-ip/storage';

const fastify = Fastify();

const storage = new InMemoryStorageAdapter({
  globalWhitelist: ['https://trusted-app.com'],
});

fastify.register(fastifyCorsWhitelist, {
  storage,
  hook: 'onRequest',
  debug: true,
});

fastify.get('/api/data', async () => {
  return { message: 'Hello!' };
});

fastify.listen({ port: 3000 });

Koa

import Koa from 'koa';
import { CorsWhitelist, InMemoryStorageAdapter } from 'cors-whitelist-ip';
import { createKoaMiddleware } from 'cors-whitelist-ip/koa';

const app = new Koa();

const storage = new InMemoryStorageAdapter({
  globalWhitelist: ['https://trusted-app.com'],
});

const corsWhitelist = new CorsWhitelist({ storage });

app.use(createKoaMiddleware(corsWhitelist));

app.use((ctx) => {
  ctx.body = { message: 'Hello!' };
});

app.listen(3000);

NestJS

// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { CorsWhitelistModule, getCorsWhitelistMiddleware } from 'cors-whitelist-ip/nestjs';
import { InMemoryStorageAdapter } from 'cors-whitelist-ip/storage';

@Module({
  imports: [
    CorsWhitelistModule.forRoot({
      storage: new InMemoryStorageAdapter({
        globalWhitelist: ['https://trusted-app.com'],
      }),
      debug: true,
    }),
  ],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(getCorsWhitelistMiddleware()).forRoutes('*');
  }
}

With async configuration (e.g., using ConfigService):

import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot(),
    CorsWhitelistModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (configService: ConfigService) => ({
        storage: new InMemoryStorageAdapter({
          globalWhitelist: configService.get<string[]>('CORS_WHITELIST') || ['*'],
        }),
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(getCorsWhitelistMiddleware()).forRoutes('*');
  }
}

Configuration

CorsWhitelistOptions

interface CorsWhitelistOptions {
  // Required: Storage adapter for fetching whitelist data
  storage: StorageAdapter;

  // Optional: Cache adapter for caching whitelist lookups
  cache?: CacheAdapter;

  // Cache TTL in seconds (default: 3600)
  cacheTtl?: number;

  // Custom logger (default: console)
  logger?: Logger;

  // CORS headers configuration
  corsHeaders?: {
    allowMethods?: string;    // default: 'GET, POST, PUT, DELETE, PATCH, OPTIONS'
    allowHeaders?: string;    // default: 'Content-Type, Authorization, X-Requested-With'
    allowCredentials?: boolean; // default: true
    maxAge?: number;          // default: 86400 (24 hours)
    exposeHeaders?: string;
  };

  // Allow requests with no origin header (default: true)
  allowNoOrigin?: boolean;

  // Custom function to extract client IP
  getClientIp?: (req: GenericRequest) => string;

  // Enable debug logging (default: false)
  debug?: boolean;
}

Custom Storage Adapter

Implement the StorageAdapter interface to use your own database:

import { StorageAdapter, WhitelistEntry } from 'cors-whitelist-ip';

class PostgresStorageAdapter implements StorageAdapter {
  constructor(private db: Pool) {}

  async getGlobalWhitelist(): Promise<string[]> {
    const result = await this.db.query(
      'SELECT domains FROM global_cors_whitelist WHERE is_active = true'
    );
    return result.rows[0]?.domains || [];
  }

  async getWhitelistEntries(): Promise<WhitelistEntry[]> {
    const result = await this.db.query(
      'SELECT * FROM organisation_whitelist WHERE is_active = true'
    );
    return result.rows;
  }

  async getOrganisationWhitelist(orgId: string | number): Promise<WhitelistEntry[]> {
    const result = await this.db.query(
      'SELECT * FROM organisation_whitelist WHERE organisation_id = $1 AND is_active = true',
      [orgId]
    );
    return result.rows;
  }

  async findOrganisationByDomain(domain: string): Promise<string | number | null> {
    const result = await this.db.query(
      `SELECT organisation_id FROM organisation_whitelist
       WHERE type = 'DOMAIN' AND is_active = true
       AND (value = $1 OR ($1 LIKE '%' || SUBSTRING(value FROM 3)))`,
      [domain]
    );
    return result.rows[0]?.organisation_id || null;
  }

  async getApiKey(orgId: string | number): Promise<string | null> {
    const result = await this.db.query(
      'SELECT api_key FROM organisation_api_key WHERE organisation_id = $1',
      [orgId]
    );
    return result.rows[0]?.api_key || null;
  }

  async isIpWhitelisted(ip: string): Promise<boolean> {
    const result = await this.db.query(
      `SELECT 1 FROM organisation_whitelist
       WHERE type = 'IP' AND value = $1 AND is_active = true LIMIT 1`,
      [ip]
    );
    return result.rows.length > 0;
  }

  async isDomainWhitelisted(domain: string): Promise<boolean> {
    const result = await this.db.query(
      `SELECT 1 FROM organisation_whitelist
       WHERE type = 'DOMAIN' AND is_active = true
       AND (value = $1 OR $1 LIKE '%' || SUBSTRING(value FROM 3)) LIMIT 1`,
      [domain]
    );
    return result.rows.length > 0;
  }
}

Custom Cache Adapter

Implement the CacheAdapter interface for Redis or other caching solutions:

import { CacheAdapter } from 'cors-whitelist-ip';
import Redis from 'ioredis';

class RedisCacheAdapter implements CacheAdapter {
  constructor(private redis: Redis) {}

  async get<T>(key: string): Promise<T | null> {
    const value = await this.redis.get(key);
    return value ? JSON.parse(value) : null;
  }

  async set<T>(key: string, value: T, ttlSeconds: number): Promise<void> {
    await this.redis.set(key, JSON.stringify(value), 'EX', ttlSeconds);
  }

  async delete(key: string): Promise<void> {
    await this.redis.del(key);
  }

  async deletePattern(pattern: string): Promise<void> {
    const keys = await this.redis.keys(pattern);
    if (keys.length > 0) await this.redis.del(...keys);
  }

  async clear(): Promise<void> {
    await this.redis.flushdb();
  }

  async has(key: string): Promise<boolean> {
    return (await this.redis.exists(key)) === 1;
  }
}

API

CorsWhitelist

const cors = new CorsWhitelist(options);

// Initialize (optional, for adapters that need setup)
await cors.initialize();

// Handle a request (used internally by framework adapters)
const shouldContinue = await cors.handleRequest(req, res);

// Validate an origin directly
const result = await cors.validateOrigin('https://example.com');
// Returns: { allowed: boolean, reason: string, organisationId?, apiKey? }

// Invalidate cache
await cors.invalidateCache();           // Clear all CORS cache
await cors.invalidateCache('example');  // Clear cache matching pattern

// Clean up
await cors.dispose();

Validation Flow

  1. Global Whitelist: Check if origin is in the global whitelist (supports * wildcard)
  2. Domain Lookup: Find if domain is associated with an organization
  3. API Key Check: Verify organization has a valid API key
  4. IP Whitelist: Check if origin IP is whitelisted
  5. Domain Pattern: Match against wildcard patterns (e.g., *.example.com)

License

MIT