Skip to content

allohouston/ddp-rate-limiter-mixin

 
 

Repository files navigation

@allohouston/ddp-rate-limiter-mixin

CI npm version GitHub Packages version coverage 100% Meteor 3.4+ Node >= 20 license

TypeScript Biome Vitest zero dependencies


English

Why?

Meteor's DDPRateLimiter.addRule() works, but it creates side effects scattered across your codebase. You never know where a limit is defined or what the threshold is.

This mixin lets you declare rate limits right where you define the method — explicit, colocated, easy to audit.

Install

# From npmjs.com
npm install @allohouston/ddp-rate-limiter-mixin

# Or from GitHub Packages (add to .npmrc: @allohouston:registry=https://npm.pkg.github.com)
npm install @allohouston/ddp-rate-limiter-mixin --registry=https://npm.pkg.github.com

Quick Start

import { ValidatedMethod } from "meteor/mdg:validated-method";
import { RateLimiterMixin } from "@allohouston/ddp-rate-limiter-mixin";

const sendMessage = new ValidatedMethod({
    name: "chat.sendMessage",
    mixins: [RateLimiterMixin],
    rateLimit: {
        numRequests: 5,
        timeInterval: 5000, // 5 requests per 5 seconds
    },
    validate: null,
    async run({ text, channelId }) {
        // your method logic
    },
});

That's it. 5 requests per 5 seconds, for all clients, enforced server-side.

Examples

Limit a specific user

const updateProfile = new ValidatedMethod({
    name: "users.updateProfile",
    mixins: [RateLimiterMixin],
    rateLimit: {
        matcher: { userId: "specificUserId" },
        numRequests: 3,
        timeInterval: 10000,
    },
    // ...
});

Custom matcher function

const deletePost = new ValidatedMethod({
    name: "posts.delete",
    mixins: [RateLimiterMixin],
    rateLimit: {
        matcher: {
            userId(userId) {
                // Only rate-limit non-admin users
                return userId !== "adminId";
            },
        },
        numRequests: 2,
        timeInterval: 60000, // 2 deletions per minute
    },
    // ...
});

Custom error message

const submitForm = new ValidatedMethod({
    name: "forms.submit",
    mixins: [RateLimiterMixin],
    rateLimit: {
        numRequests: 3,
        timeInterval: 60000,
        errorMessage: (data) =>
            `Too many submissions. Try again in ${Math.ceil(data.timeToReset / 1000)}s.`,
    },
    // ...
});

API Reference

RateLimiterMixin(methodOptions) → methodOptions

A mixin function for ValidatedMethod. Registers a DDPRateLimiter.addRule() on the server and returns the options with an added rateLimitRuleId.

On the client, returns methodOptions unchanged.

rateLimit options

Property Type Required Description
numRequests number yes Max requests per interval (must be >= 1)
timeInterval number yes Interval in ms (must be > 0)
matcher object no Filter which requests count towards the limit
callback function no Called after rule evaluation
errorMessage string | function no Custom error when limit is exceeded

matcher properties

All optional. Unspecified fields match all requests.

Property Type Description
userId string | (id: string) => boolean Match by user ID
connectionId string | (id: string) => boolean Match by DDP connection
clientAddress string | (addr: string) => boolean Match by IP address

name is always the method name, type is always "method".

callback(reply, ruleInput)

// reply
{
    allowed: boolean;           // was the call allowed?
    timeToReset: number;        // ms until rate limit resets
    numInvocationsLeft: number; // remaining calls in this interval
}

// ruleInput
{
    type: string;           // "method" or "subscription"
    name: string;           // method name
    userId: string;         // user ID
    connectionId: string;   // DDP connection ID
    clientAddress: string;  // client IP
}

errorMessage

Custom error message when the rate limit is exceeded. Can be a static string or a function receiving { timeToReset } that returns a string.

Uses DDPRateLimiter.setErrorMessageOnRule() (Meteor 3+). Silently ignored on older Meteor versions.

rateLimitRuleId

After the mixin runs, methodOptions.rateLimitRuleId contains the rule ID returned by DDPRateLimiter.addRule(). Use it with DDPRateLimiter.removeRule() or DDPRateLimiter.setErrorMessageOnRule() if needed.

TypeScript

Full type definitions included.

import { RateLimiterMixin } from "@allohouston/ddp-rate-limiter-mixin";
import type {
    MethodOptions,
    RateLimitConfig,
    RateLimitMatcher,
    RateLimitReply,
    RateLimitInput,
} from "@allohouston/ddp-rate-limiter-mixin";

Migration from v1

v2 is a breaking change:

Change v1 v2
Language JavaScript (Babel 6) TypeScript (strict)
Module format CJS only ESM + CJS (dual)
Runtime deps babel-runtime 0
Mutability Mutates methodOptions Returns new object
rateLimitRuleId Not exposed Exposed in returned options
errorMessage Not supported Supported (string or function)
Input validation Basic type checks Strict (NaN, Infinity, null, arrays)
Node.js Any >= 20
Meteor 1.x - 2.x 3.4+

Links


Français

Pourquoi ?

Le DDPRateLimiter.addRule() de Meteor fonctionne, mais il crée des effets de bord dispersés dans le code. On ne sait jamais où une limite est définie ni quel est le seuil.

Ce mixin permet de déclarer les limites directement dans la définition de la méthode — explicite, colocalisé, facile à auditer.

Installation

# Depuis npmjs.com
npm install @allohouston/ddp-rate-limiter-mixin

# Ou depuis GitHub Packages (ajouter dans .npmrc : @allohouston:registry=https://npm.pkg.github.com)
npm install @allohouston/ddp-rate-limiter-mixin --registry=https://npm.pkg.github.com

Démarrage rapide

import { ValidatedMethod } from "meteor/mdg:validated-method";
import { RateLimiterMixin } from "@allohouston/ddp-rate-limiter-mixin";

const sendMessage = new ValidatedMethod({
    name: "chat.sendMessage",
    mixins: [RateLimiterMixin],
    rateLimit: {
        numRequests: 5,
        timeInterval: 5000, // 5 requêtes par 5 secondes
    },
    validate: null,
    async run({ text, channelId }) {
        // votre logique
    },
});

C'est tout. 5 requêtes par 5 secondes, pour tous les clients, appliqué côté serveur.

Exemples

Limiter un utilisateur spécifique

const updateProfile = new ValidatedMethod({
    name: "users.updateProfile",
    mixins: [RateLimiterMixin],
    rateLimit: {
        matcher: { userId: "specificUserId" },
        numRequests: 3,
        timeInterval: 10000,
    },
    // ...
});

Matcher personnalisé

const deletePost = new ValidatedMethod({
    name: "posts.delete",
    mixins: [RateLimiterMixin],
    rateLimit: {
        matcher: {
            userId(userId) {
                // Limiter uniquement les utilisateurs non-admin
                return userId !== "adminId";
            },
        },
        numRequests: 2,
        timeInterval: 60000, // 2 suppressions par minute
    },
    // ...
});

Message d'erreur personnalisé

const submitForm = new ValidatedMethod({
    name: "forms.submit",
    mixins: [RateLimiterMixin],
    rateLimit: {
        numRequests: 3,
        timeInterval: 60000,
        errorMessage: (data) =>
            `Trop de soumissions. Réessayez dans ${Math.ceil(data.timeToReset / 1000)}s.`,
    },
    // ...
});

Référence API

RateLimiterMixin(methodOptions) → methodOptions

Fonction mixin pour ValidatedMethod. Enregistre une règle DDPRateLimiter.addRule() côté serveur et retourne les options avec un rateLimitRuleId ajouté.

Côté client, retourne methodOptions sans modification.

Options rateLimit

Propriété Type Requis Description
numRequests number oui Requêtes max par intervalle (doit être >= 1)
timeInterval number oui Intervalle en ms (doit être > 0)
matcher object non Filtre les requêtes à comptabiliser
callback function non Appelée après évaluation de la règle
errorMessage string | function non Message d'erreur quand la limite est atteinte

Propriétés du matcher

Toutes optionnelles. Les champs absents matchent toutes les requêtes.

Propriété Type Description
userId string | (id: string) => boolean Filtrer par ID utilisateur
connectionId string | (id: string) => boolean Filtrer par connexion DDP
clientAddress string | (addr: string) => boolean Filtrer par adresse IP

name est toujours le nom de la méthode, type est toujours "method".

callback(reply, ruleInput)

// reply
{
    allowed: boolean;           // l'appel est-il autorisé ?
    timeToReset: number;        // ms avant réinitialisation de la limite
    numInvocationsLeft: number; // appels restants dans l'intervalle
}

// ruleInput
{
    type: string;           // "method" ou "subscription"
    name: string;           // nom de la méthode
    userId: string;         // ID utilisateur
    connectionId: string;   // ID de connexion DDP
    clientAddress: string;  // IP du client
}

errorMessage

Message d'erreur personnalisé quand la limite est dépassée. Peut être une chaîne statique ou une fonction recevant { timeToReset } et retournant une chaîne.

Utilise DDPRateLimiter.setErrorMessageOnRule() (Meteor 3+). Silencieusement ignoré sur les anciennes versions de Meteor.

rateLimitRuleId

Après exécution du mixin, methodOptions.rateLimitRuleId contient l'ID de la règle retourné par DDPRateLimiter.addRule(). Utilisable avec DDPRateLimiter.removeRule() ou DDPRateLimiter.setErrorMessageOnRule().

TypeScript

Définitions de types complètes incluses.

import { RateLimiterMixin } from "@allohouston/ddp-rate-limiter-mixin";
import type {
    MethodOptions,
    RateLimitConfig,
    RateLimitMatcher,
    RateLimitReply,
    RateLimitInput,
} from "@allohouston/ddp-rate-limiter-mixin";

Migration depuis v1

v2 est un breaking change :

Changement v1 v2
Langage JavaScript (Babel 6) TypeScript (strict)
Format module CJS uniquement ESM + CJS (dual)
Deps runtime babel-runtime 0
Mutabilité Mute methodOptions Retourne un nouvel objet
rateLimitRuleId Non exposé Exposé dans les options retournées
errorMessage Non supporté Supporté (string ou function)
Validation Vérification de type basique Stricte (NaN, Infinity, null, arrays)
Node.js Tout >= 20
Meteor 1.x - 2.x 3.4+

Liens


License

MIT

About

A mixin for mdg:validated-method to add rate limitation support to Meteor's methods.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages

  • TypeScript 88.8%
  • JavaScript 7.4%
  • HTML 3.0%
  • Other 0.8%