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 🙏

© 2024 – Pkg Stats / Ryan Hefner

sanitize-against

v1.7.0

Published

Highly customizable, but simple to use, sanitization of objects and primitives.

Downloads

292

Readme

Sanitize Against

Sanitize Against is a library for sanitizing objects, intended to filter the options of incoming requests on the server. It is highly customizable, but simple to use and tries to be decently fast while being safe.

Why?

  • If you want to filter out unwanted properties from objects. Thus preventing __proto__ poisoning.

  • If you want a simple way to implement default properties or set the others as required to enforce a certain structure.

  • If you want to sanitize your object's properties, to be of a certain type (e.g. number) and/or have a certain value range (e.g. 0-100).

Installation

 $ npm i sanitize-against

ESM

import sanitize, { OR, AND, NOT } from "sanitize-against"

CJS

const { sanitize, OR, AND, NOT } = require("sanitize-against")

Usage

Simple example of how to integrate it into a web server. We will look at the specific patterns and outputs later.

const userPattern = {
  name: String,
  "age?": Number,
  pin: OR(Number, String),
  settings: {
    darkMode: false,
    fontSize: 16
  }
}

const against = sanitize(userPattern)

// example route
app.post("/user", (req, res) => {
  try {
    const user = against(JSON.parse(req.body))
    // Do something with user
  }
  catche(e) {
    // The error.msg is not terribly useful.
    res.status(400).send("error")
  }
})

Patterns

The main constructor function sanitize takes a pattern as input. A pattern is not necessarily an object. The simplest pattern conceptually, and at the same time the most powerful one, would be a function.

Function

This function will be called (with the input as first argument) when a new input is supposed to be sanitized. The function should throw when the input is not valid and otherwise return the okay (sanitized) value.

const pattern = (input: any) => {
  if (input === 2) throw new Error("Input is 2")
  else return input
}

const against = sanitize(pattern)

against(1) // 1
against(2) // throws
against("litterally anything else") // "litterally anything else"

While this is hardly an abstraction at all, it is the basis for all other patterns. The function can be used to implement any kind of validation. For example, you could use it to check if the input is a number and if it is in a certain range. To simplify the process of creating such constraints, there are predefined patterns, or rather values which are mapped to predefined functions. We will describe them in the next couple sections. But it is important to keep in mind the option to fall back to a function when the predefined patterns are not specific enough. In a later chapter we will introduce boolesch logic, which can be used to combine (predefined) patterns with custom ones.

Note: I use pattern, constraint and custom functions interchangeably.

Primitives

You may use the values Number, Boolean and String (the primitive value constructors in the global scope) to specify that the input should be of that type. If the input is not of that type, or not set at all, it will throw. There is intentionally no attempt made at converting e.g. a string to a number. This is to prevent unexpected behavior and tbh I don't see a use case for it, as the frontend should be capable of converting it, and correctly sending it as a number. If you want to allow strings and numbers, you can use the OR combinator see below. If you disagree, please open an issue and explain your use case, and in the meantime simply fall back to a custom function.

const pattern = Number
const against = sanitize(pattern)

against(1) // 1
against(123) // 123
against("1") // throws
against(undefined) // throws
against(false) // throws

Primitives as part of objects

We can also use the primitive values as part of an object structure pattern. This will enforce the type of the property and that there is a property. If you want a property to be optional, please see optional properties or make it implicitly optional by defining specific values (of type boolean, number or string).

const pattern = {
  name: String,
  age: Number,
  isCool: Boolean
}

const against = sanitize(pattern)

// gotten from a client
const request = {
  name: "John",
  age: 20,
  isCool: true
}

let user = against(request)

user = {
  name: "John",
  age: 20,
  isCool: true
}

against({
  name: "John",
  age: "20",
  isCool: "true"
}) // throws

against({
  name: "John",
  age: 20
}) // throws

Primitive defaults

You can specify a default value for a primitive by using specific values as the pattern. This works for numbers, strings and booleans. The default value will be used if the input is undefined or null. If the input is not undefined or null, it will be checked against the pattern. If the input is not of the correct type, it will throw.

const against = sanitize("default value")

against("anything") // "anything"
against(undefined) // "default value"

This works as part of objects analog to the primitives section.

Objects

You can use objects as patterns to specify the structure of the input. The input must be an object and must have the same properties as the pattern. If the input is not an object, or if it is missing properties, or if some of the values of the object do not match the nested pattern, it will throw. If the input has more properties than the pattern, they will be ignored.

const pattern = {
  name: String,
  age: Number,
  isCool: true
}

const against = sanitize(pattern)

let user = against({
  name: "John",
  age: 20,
  anotherProp: "which will be ignored"
})

user = {
  name: "John",
  age: 20,
  isCool: true
}

Of course, you can nest objects.

const against = sanitize({
  name: String,
  age: Number,
  isCool: true,
  settings: {
    darkMode: false,
    fontSize: Number
  }
})

Optional properties

You can add a ? at the end of a property name to make it explicitly optional. If the property is not set, it will be ignored. If the property is set, it will be checked against the nested pattern (so you can easily make e.g.: an optional Number property without a default, if it is not set). This ? at the end of a pattern property name does not need to be present in the input object to be sanitized (imagine it like the typescript optional flag, just at runtime).

const against = sanitize({
  "name?": String,
  "age?": (input) => {
    if (typeof input !== "number") throw new Error("Not a number")
    if (input < 0 || input > 150) throw new Error("Not in range")
    return Math.round(input)
  },
  isCool: true
})

against({
  name: "John"
}) // { name: "John", isCool: true }

against({
  name: "John",
  age: 20
}) // { name: "John", age: 20, isCool: true }

against({
  name: "John",
  age: -1
}) // throws

against({
  name: "John",
  age: 20.1
}) // { name: "John", age: 20, isCool: true }

against({}) // { isCool: true }

Just as patterns with default are optional by default, object patterns solely with static properties are optional by default. This means that you could just pass nothing into the against function above and get an empty object back (filled with default values ofc if available).

against() // { isCool: true }

Combinators

Combinators are class instances which take patterns as arguments and act as a new, combined, pattern.

OR

A simple combinator is the OR combinator. It takes n patterns and passes if any of them passes. It tries to sanitize the input with each pattern in order. If one of them succeeds, it returns the sanitized value of that pattern. If none of them succeed, it throws.

const pattern = OR(
  Number,
  String
)

const against = sanitize(pattern)

against(1) // 1
against("1") // "1"
against(true) // throws
against({}) // throws
against() // throws

AND

The AND combinator takes n patterns and passes if all of them pass. It tries to sanitize the input with each pattern in order. If one of them fails, it throws. If all of them succeed, it returns the sanitized value of the last pattern.

const pattern = AND(
  Number,
  (input) => {
    if (input < 0) throw new Error("Not positive")
    return input
  }
)

const against = sanitize(pattern)

against(1) // 1
against(0) // 0
against(-1) // throws
against("1") // throws

NOT

The NOT combinator takes one pattern and passes if the input does not match the pattern. It tries to sanitize the input with the pattern. If it succeeds, it throws. If it fails, it returns the input.

const pattern = NOT(
  Number
)

const against = sanitize(pattern)

against(1) // throws
against("1") // "1"
against(true) // true

Combinations

Of course, you can combine the combinators, with each other, and with objects.

const isPositive = (input) => {
  if (input < 0) throw new Error("Not positive")
  return input
}

const pattern = {
  name: String,
  dob: OR(
    String,
    AND(
      Number,
      (input) => {
        if (input < 0) throw new Error("Not positive")
        return input
      }
    )
  )
}

const against = sanitize(pattern)

against({
  name: "John",
  dob: "1990-01-01"
}) // { name: "John", dob: "1990-01-01" }

against({
  name: "John",
  dob: 123
}) // { name: "John", dob: 123 }

against({
  name: "John",
  dob: -1
}) // throws

against({
  name: "John"
}) // throws

Arrays

Not supported yet.

Polyfills

This module requires Object.hasOwn (>= Node 16.9.0, >= Chrome 93). If you need to support older environments, you can lazy load all polyfills with the following code:

import { polyfill } from "sanitize-against"

await polyfill()

Contribute

All feedback is appreciated. Create a pull request or write an issue.