Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | 1x 1x 1x 1x 3x 3x 3x 27x 1x 26x 26x 26x 29x 4x 25x 25x 3x 22x 22x 22x 22x 31x 31x 3x 28x 1x 27x 4x 23x 5x 18x 23x 13x 10x 10x 10x 1x | 'use strict';
const crypto = require('crypto');
const base64 = require('./Base64');
const helper = require('./Helper');
const Hmac = require('./Hmac');
/**
* Message verifier is similar to the encryption. However, the actual payload
* is not encrypted and just base64 encoded. This is helpful when you are
* not concerned about the confidentiality of the data, but just want to
* make sure that is not tampered after encoding.
*/
class MessageVerifier {
/**
* @param {string} secret
*/
constructor(secret) {
this.secret = secret;
/**
* The key for signing and encrypting values. It is derived
* from the user provided secret.
*/
this.cryptoKey = crypto.createHash('sha256').update(this.secret).digest();
this.separator = '.';
}
/**
* Signs a value with the secret key. The signed value is not encrypted, but just
* signed for avoiding tampering to the original message.
*
* @param {*} value Any `JSON.stringify` valid value
* @param {Date|null|undefined} expiresAt
* @param {string} purpose
* @returns
*/
sign(value, expiresAt, purpose) {
if (value === null || value === undefined) {
throw new Error('"MessageVerifier.sign" cannot sign null or undefined values');
}
const encoded = base64.urlEncode(helper.safeStringify({ message: value, expiresAt, purpose }));
const hash = new Hmac(this.cryptoKey).generate(encoded);
return `${encoded}${this.separator}${hash}`;
}
/**
* Unsign a previously signed value with an optional purpose
* @param {string} value
* @param {string} purpose
* @returns
*/
unsign(value, purpose) {
if (typeof value !== 'string') {
throw new Error('MessageVerifier.unsign expects a string value');
}
/**
* Ensure value is in correct format
*/
const [encoded, hash] = value.split(this.separator);
if (!encoded || !hash) {
return null;
}
/**
* Ensure value can be decoded
*/
const decoded = base64.urlDecode(encoded);
Iif (!decoded) {
return null;
}
const isValid = new Hmac(this.cryptoKey).compare(encoded, hash);
return isValid ? this.verify(decoded, purpose) : null;
}
/**
* Verifies the message for expiry and purpose
*/
verify(message, purpose) {
const parsed = helper.safeJsonParse(message);
/**
* Safe parse returns the value as it is when unable to JSON.parse it. However, in
* our case if value was correctly parsed, it should never match the input
*/
if (parsed === message) {
return null;
}
/**
* Missing ".message" property
*/
if (!parsed.message) {
return null;
}
/**
* Ensure purposes are same.
*/
if (parsed.purpose !== purpose) {
return null;
}
/**
* Ensure isn't expired
*/
if (this.isExpired(parsed)) {
return null;
}
return parsed.message;
}
isExpired(message) {
if (!message.expiresAt) {
return false;
}
try {
const expiresAt = new Date(message.expiresAt);
return isNaN(expiresAt.getTime()) || expiresAt < new Date();
} catch (error) {
return true;
}
}
}
module.exports = MessageVerifier;
|