UNPKG

nomatic-jwt

Version:

JSON Web Token (JWT) utilities for Node.js

239 lines 8.02 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const crypto = require("crypto"); const CryptoJS = require("crypto-js"); const base64 = require("./base64"); const errors_1 = require("./errors"); class JWT { constructor(options = {}) { this.algorithm = options.algorithm || 'HS256'; this.expiresIn = options.expiresIn || (60 * 60); this.autoValidate = options.autoValidate || true; if (this.algorithm.startsWith('HS')) { this.key = options.key || crypto.randomBytes(128).toString('hex'); } else if (options.privateKey && options.publicKey) { this.privateKey = options.privateKey; this.publicKey = options.publicKey; } else { throw new errors_1.JWTError(`'privateKey' and 'publicKey' must be specified with ${this.algorithm} algorithm`); } this.timeOffset = options.timeOffset || 60; } static parsePayload(payload) { try { return JSON.parse(payload); } catch (error) { if (error.name !== 'SyntaxError') { throw error; } else { return payload; } } } get algorithm() { return this._algorithm; } set algorithm(algorithm) { if (algorithm.startsWith('HS') || algorithm.startsWith('RS')) { if (algorithm.endsWith('256') || algorithm.endsWith('384') || algorithm.endsWith('512')) { this._algorithm = algorithm; return; } } throw new errors_1.JWTError(`Invalid algorithm: ${algorithm}`); } get autoValidate() { return this._autoValidate; } set autoValidate(autoValidate) { this._autoValidate = autoValidate; } get expiresIn() { return this._expiresIn; } set expiresIn(expiresIn) { if (typeof expiresIn === 'number') { this._expiresIn = expiresIn; return; } throw new errors_1.JWTError('\'expiresIn\' must be a number'); } get key() { return this._key; } set key(key) { if (typeof key === 'string') { this._key = key; return; } throw new errors_1.JWTError('\'key\' must be a string'); } get privateKey() { return this._privateKey; } set privateKey(privateKey) { if (typeof privateKey === 'string') { this._privateKey = privateKey; return; } throw new errors_1.JWTError('\'privateKey\' must be a string'); } get publicKey() { return this._publicKey; } set publicKey(publicKey) { if (typeof publicKey === 'string') { this._publicKey = publicKey; return; } throw new errors_1.JWTError('\'publicKey\' must be a string'); } get timeOffset() { return this._timeOffset; } set timeOffset(timeOffset) { if (typeof timeOffset === 'number') { this._timeOffset = timeOffset; return; } throw new errors_1.JWTError('\'timeOffset\' must be a number'); } signRaw(data, key = null, algorithm = this.algorithm) { if (!key) { if (algorithm.startsWith('RS')) { key = this.privateKey; } else if (algorithm.startsWith('HS')) { key = this.key; } } let signature; if (algorithm.startsWith('RS')) { signature = crypto.createSign('RSA-SHA' + algorithm.substr(2)) .update(data) .sign(key, 'base64'); } else if (algorithm.startsWith('HS')) { signature = CryptoJS .enc .Base64 .stringify(CryptoJS['HmacSHA' + algorithm.substr(2)](data, key)); } else { throw new Error('Unknown or unsupported algorithm: ' + algorithm); } return base64.escape(signature); } verifyRaw(data, signature, key = null, algorithm = this.algorithm) { if (algorithm.startsWith('HS')) { if (!key) { key = this.key; } return signature === this.signRaw(data, key, algorithm); } else if (algorithm.startsWith('RS')) { if (!key) { key = this.publicKey; } return crypto.createVerify('RSA-SHA' + algorithm.substr(2)) .update(data) .verify(key, base64.unescape(signature), 'base64'); } else { throw new Error('Unknown or unsupported algorithm: ' + algorithm); } } decode(encoded, key = null, algorithm = this.algorithm) { if (!key) { if (algorithm.startsWith('RS')) { key = this.publicKey; } else if (algorithm.startsWith('HS')) { key = this.key; } } const encodedParts = encoded.split('.'); if (encodedParts.length !== 3) { throw new Error('Invalid number of encoded parts: ' + encoded.length); } const token = { header: JSON.parse(base64.decodeSafe(encodedParts[0])), payload: JWT.parsePayload(base64.decodeSafe(encodedParts[1])), signature: encodedParts[2] }; if (this.autoValidate) { return this.validate(token, key, algorithm); } else { return token; } } encode(payload, key = null, algorithm = this.algorithm) { if (!key) { if (algorithm.startsWith('RS')) { key = this.privateKey; } else if (algorithm.startsWith('HS')) { key = this.key; } } const header = { typ: 'JWT', alg: algorithm }; if (this.expiresIn && !(payload instanceof String) && !payload.exp) { const current = Math.floor((new Date().getTime() / 1000)); payload.exp = current + this.expiresIn; if (!payload.nbf) { payload.nbf = current; } if (!payload.iat) { payload.iat = current; } } const encoded = []; encoded.push(base64.encodeSafe(JSON.stringify(header))); encoded.push(base64.encodeSafe(JSON.stringify(payload))); encoded.push(this.signRaw(encoded.join('.'), key, algorithm)); return encoded.join('.'); } validate(token, key = null, algorithm = this.algorithm) { if (!key) { if (algorithm.startsWith('RS')) { key = this.publicKey; } else if (algorithm.startsWith('HS')) { key = this.key; } } const encoded = []; encoded.push(base64.encodeSafe(JSON.stringify(token.header))); encoded.push(base64.encodeSafe(JSON.stringify(token.payload))); const data = encoded.join('.'); if (!(this.verifyRaw(data, token.signature, key, algorithm))) { throw new errors_1.JWTSignatureError(); } if (!(token.payload instanceof String)) { if (token.payload['nbf']) { const current = Math.floor((Date.now() / 1000)); if (token.payload['nbf'] > (current + this.timeOffset)) { throw new errors_1.JWTError('JWT is not active yet'); } } if (token.payload['exp']) { const current = Math.floor((Date.now() / 1000)); if (current + this.timeOffset > token.payload['exp']) { throw new errors_1.JWTExpiredError(token.payload['exp']); } } } return token; } } exports.JWT = JWT; exports.default = new JWT(); //# sourceMappingURL=index.js.map