nomatic-jwt
Version:
JSON Web Token (JWT) utilities for Node.js
239 lines • 8.02 kB
JavaScript
"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