saslprep
RFC 4013 SASLprep string normalization for usernames and passwords used in SASL/SCRAM authentication.
Repository Health
Technical Analysis
saslprep is a focused Node.js implementation of RFC 4013 (SASLprep), the stringprep profile used to normalize usernames and passwords before they’re compared or hashed in SASL and SCRAM authentication handshakes. It maps non-ASCII space characters to a plain space, strips characters that are “commonly mapped to nothing” (like soft hyphens), applies Unicode NFKC normalization, and then rejects input that contains prohibited or unassigned code points or invalid bidirectional character sequences.
Because many database and messaging protocols (MongoDB, PostgreSQL SCRAM auth, XMPP) require credentials to be SASLprep-normalized before use, this package exists as the reusable, spec-conformant building block other authentication libraries depend on rather than re-implementing the RFC themselves.
What You Get
- A single
saslprep(input, opts)function with no configuration objects or classes to set up - Full RFC 3454/4013 character-class tables (unassigned, prohibited, bidirectional, mapped-to-nothing, non-ASCII space) precompiled into compact sparse bitfields
- Descriptive errors that cite the exact RFC section violated (prohibited character, unassigned code point, bidirectional rule)
- An
allowUnassignedoption for the RFC’s documented exception path around unassigned code points - Correct handling of astral (surrogate-pair) Unicode code points above U+FFFF
Common Use Cases
- Normalizing usernames/passwords inside a SASL or SCRAM authentication handshake implementation
- Pre-processing credentials before comparing or hashing them in a database driver (e.g. MongoDB, PostgreSQL SCRAM)
- Validating that user-supplied identity strings don’t contain prohibited or bidirectionally-invalid Unicode
- Building a custom XMPP or other SASL-based authentication client
Under The Hood
Architecture
The entire package is a single entry point (index.js) exporting one saslprep(input, opts) function; there are no classes or DI containers. It pulls precomputed Unicode character-class tables (unassigned code points, commonly-mapped-to-nothing, non-ASCII space, prohibited characters, and bidirectional R/AL and L classes) out of lib/memory-code-points.js — a gzip-compressed, base64-embedded buffer decompressed once at require time via zlib.gunzipSync — into sparse-bitfield instances for constant-time code-point membership tests. The exported function runs a strictly linear pipeline: map (space substitution, mapping-to-nothing removal) → NFKC normalize → prohibited-character check → unassigned-code-point check → bidirectional-rule check, throwing a plain Error the moment any rule is violated. Every check keys off the same code-point array produced by toCodePoints(), so the surrogate-pair-aware code-point conversion is the one abstraction the rest of the module depends on.
Tech Stack
Plain CommonJS Node.js (engines >=6), with a single runtime dependency, sparse-bitfield, for compact bit-array storage of large sparse Unicode code-point sets, plus Node’s built-in zlib for decompressing the embedded character tables. There’s no bundler or build step for the published package — it ships hand-written JS directly. generate-code-points.js is a separate, one-time codegen script (not part of the runtime require path) that regenerates code-points.js/lib/memory-code-points.js from the underlying stringprep tables. Dev tooling is ESLint (@nodertc/eslint-config) plus Prettier for style and Jest for tests, wired into Travis CI.
Code Quality
Two Jest test files cover a genuinely thorough spec-conformance surface for such a small package: ASCII/case preservation, astral code points via surrogate pairs, mapping-to-nothing removal, non-ASCII space substitution, NFKC normalization edge cases, every RFC 3454 Appendix C prohibited-character category (C.2.1 through C.9), the RandALCat/LCat bidirectional conflict rule, and unassigned-code-point handling both with and without allowUnassigned. Errors are explicit TypeError/Error throws with messages that name the violated RFC section rather than failing silently. There’s no TypeScript, but JSDoc annotates the main function; ESLint and Prettier are enforced, and Travis CI runs lint plus the unit-test suite on every push.
API Design
The public surface is deliberately minimal: one function, one options object with a single allowUnassigned boolean, no classes to instantiate — const saslprep = require('saslprep'); saslprep(str) is the entire integration. Error messages link back to the specific RFC subsection they violate, which helps a consumer diagnose a failed SCRAM handshake instead of getting an opaque failure. The tradeoffs are that no TypeScript type declarations ship with the package and documentation is limited to a short README usage snippet, so consumers who need the precise semantics of a thrown error have to consult the RFC text directly rather than expanded docs.