@dr.pogodin/csurf
Version:
CSRF token middleware for ExpressJS
102 lines (96 loc) • 2.96 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
exports.verify = verify;
var _nodeCrypto = _interopRequireDefault(require("node:crypto"));
var _rndm = _interopRequireDefault(require("rndm"));
var _tsscmp = _interopRequireDefault(require("tsscmp"));
var _uidSafe = _interopRequireDefault(require("uid-safe"));
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 _nodeCrypto.default.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.
*/
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 (0, _tsscmp.default)(token, expected);
}
/**
* Token generation/verification class.
*/
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, (0, _rndm.default)(this.saltLength));
}
/**
* Create a new secret key.
*/
secret(callback) {
if (callback) {
(0, _uidSafe.default)(this.secretLength, callback);
return undefined;
}
return (0, _uidSafe.default)(this.secretLength);
}
/**
* Create a new secret key synchronously.
*/
secretSync() {
return _uidSafe.default.sync(this.secretLength);
}
}
exports.default = Tokens;
//# sourceMappingURL=tokens.js.map