@dr.pogodin/csurf
Version:
CSRF token middleware for ExpressJS
93 lines (88 loc) • 2.52 kB
JavaScript
import crypto from 'crypto';
import rndm from 'rndm';
import uid from 'uid-safe';
import compare from 'tsscmp';
const EQUAL_GLOBAL_REGEXP = /=/g;
const PLUS_GLOBAL_REGEXP = /\+/g;
const SLASH_GLOBAL_REGEXP = /\//g;
/**
* Hash a string with SHA256, returning url-safe base64.
*/
function hash(str) {
return crypto.createHash('sha256').update(str, 'ascii').digest('base64').replace(PLUS_GLOBAL_REGEXP, '-').replace(SLASH_GLOBAL_REGEXP, '_').replace(EQUAL_GLOBAL_REGEXP, '');
}
/**
* Tokenize a secret and salt.
*/
function privateTokenize(secret, salt) {
return `${salt}-${hash(`${salt}-${secret}`)}`;
}
/**
* Verify if a given token is valid for a given secret.
*/
export function verify(secret, token) {
if (!secret || typeof secret !== 'string') {
return false;
}
if (!token || typeof token !== 'string') {
return false;
}
const index = token.indexOf('-');
if (index === -1) {
return false;
}
const salt = token.slice(0, index);
const expected = privateTokenize(secret, salt);
return compare(token, expected);
}
/**
* Token generation/verification class.
*/
export default class Tokens {
/**
* @param [options]
* @param [options.saltLength=8] The string length of the salt
* @param [options.secretLength=18] The byte length of the secret key
*/
constructor(options) {
const opts = options ?? {};
const saltLength = opts.saltLength ?? 8;
if (typeof saltLength !== 'number' || !Number.isFinite(saltLength) || saltLength < 1) {
throw new TypeError('option saltLength must be finite number > 1');
}
const secretLength = opts.secretLength ?? 18;
if (typeof secretLength !== 'number' || !Number.isFinite(secretLength) || secretLength < 1) {
throw new TypeError('option secretLength must be finite number > 1');
}
this.saltLength = saltLength;
this.secretLength = secretLength;
}
/**
* Create a new CSRF token.
*
* @param secret The secret for the token.
*/
create(secret) {
if (!secret || typeof secret !== 'string') {
throw new TypeError('argument secret is required');
}
return privateTokenize(secret, rndm(this.saltLength));
}
/**
* Create a new secret key.
*/
secret(callback) {
if (callback) {
uid(this.secretLength, callback);
return undefined;
}
return uid(this.secretLength);
}
/**
* Create a new secret key synchronously.
*/
secretSync() {
return uid.sync(this.secretLength);
}
}
//# sourceMappingURL=tokens.mjs.map