@aria-framework/kit
v0.9.0
Published
Aria App Framework — kit module. Small dependency-free server utilities: open-redirect guard (safeReturnTo), SQL LIKE escaping, magic-byte upload validation (fileSniff), Crockford base32 tracking IDs, and person-name compose/split helpers.
Readme
@aria-framework/kit
Aria App Framework — kit module. Five small, dependency-free server utilities (Node builtins only, plain CommonJS, no build step).
const { safeReturnTo, escapeLike, fullName, splitName, fileSniff, trackingId }
= require('@aria-framework/kit');safeReturnTo — open-redirect guard
Validates a post-login returnTo path. Only on-site paths survive (absolute,
single leading slash, no scheme/host, no // protocol-relative trick);
anything else collapses to the fallback.
safeReturnTo(req.query.returnTo, { fallback: '/dashboard' });
safeReturnTo(rt, { fallback: '/', denyPrefix: '/portal' }); // also keep users out of auth pagesescapeLike — SQL LIKE escaping
Escapes %, _ and \ so user input can sit inside a LIKE pattern. Pair
with ESCAPE '\' in the query:
db.prepare("... WHERE name LIKE ? ESCAPE '\\'").all(`%${escapeLike(q)}%`);fileSniff — magic-byte upload validation
The client-declared Content-Type is attacker-controlled; this checks the file's actual leading bytes against the declared MIME's container family (docx/xlsx are both ZIP, doc/xls both OLE — exact subtypes can't be told apart by magic, so families are the honest granularity).
if (!fileSniff.matches(file.path, file.mimetype)) reject(file);
fileSniff.sniff(path); // → 'image/png' | 'zip' | 'ole' | 'isobmff' | 'text' | ... | nullCovers png/jpeg/gif/webp, pdf, text/csv, office (old + OOXML), heic/heif, mp4/mov/webm/avi/ogg, mp3/m4a/wav, zip/7z/rar/gzip.
trackingId — customer-facing reference codes
AB12-CD34-EF56 — Crockford base32 (no I, L, O, U: these codes get read
aloud and retyped), crypto.randomInt for unbiased unguessable picks,
60 bits of space.
trackingId.generate(); // random, no collision check
trackingId.generateUnique((id) => // guaranteed unique per YOUR storage
db.prepare('SELECT 1 FROM tickets WHERE tracking_id = ?').get(id));generateUnique is storage-agnostic — pass any (id) => truthy-if-exists
check (SQL, ORM, HTTP, in-memory). Throws after maxAttempts (default 10).
fullName / splitName — person-name convention helpers
For the first_name + last_name model where display_name/name is a
maintained composite (never hand-written):
fullName('Ann', 'Bee'); // 'Ann Bee' (trims, drops blanks)
splitName('Johan van der Merwe') // { first_name: 'Johan van der', last_name: 'Merwe' }splitName splits on the last space (last word = surname). Compound
surnames split imperfectly by design. If an app backfills a split in SQL,
implement the same last-space rule and keep them in step.
isEmail — shared email-address validator (since 0.2.0)
One definition of "looks like an email" for every form: trim, non-empty,
≤254 chars (RFC 5321 cap), one @ with a dotted domain. Pragmatic by design —
mail servers are the real validators; this guards forms and lookups.
if (!isEmail(req.body.email)) errors.push('A valid email is required.');Changelog
- 0.2.0 — added
isEmail(v)(replaces per-route regex copies that had already diverged on length handling); removed the redundantpersonNamenamespace export — use the flatfullName/splitName. - 0.1.0 — first release. Extracted from Support101
lib/(safeReturnTo, likeEscape, fileSniff, trackingId, personName). One API change vs the app originals:trackingId.generateUniquetakes anisTaken(id)callback instead of a better-sqlite3 handle + hardcoded tickets table.
