1 | import { createHmac } from 'crypto';
|
2 | export class SignaturError extends Error {
|
3 | constructor(type, message) {
|
4 | super();
|
5 | this.name = 'SignaturError';
|
6 | this.message = message;
|
7 | this.type = type;
|
8 | }
|
9 | toJSON() {
|
10 | return {
|
11 | error: {
|
12 | type: this.type,
|
13 | message: this.message,
|
14 | },
|
15 | };
|
16 | }
|
17 | }
|
18 | function urlSafeBase64(s) {
|
19 | return s.replace(/\+/gi, '-').replace(/\//gi, '_').replace(/=/gi, '');
|
20 | }
|
21 | export function signSync(data, secret, options) {
|
22 | const { separator = '.' } = options || {};
|
23 | if (null == data) {
|
24 | throw new TypeError(`Expected 'data' to be defined, but received '${JSON.stringify(data)}'`);
|
25 | }
|
26 | if ('string' !== typeof (secret) || !secret.length) {
|
27 | throw new TypeError(`Expected 'secret' to be defined, but received '${secret}'`);
|
28 | }
|
29 | const stringData = JSON.stringify({ data });
|
30 | const encoded = Buffer.from(stringData, 'utf8').toString('base64');
|
31 | const signature = createHmac('sha256', secret).update(stringData).digest('base64');
|
32 | return urlSafeBase64(`${encoded}${separator}${signature}`);
|
33 | }
|
34 | export function unsignSync(signature, secret, options) {
|
35 | const { separator = '.' } = options || {};
|
36 | if ('string' !== typeof (signature) || !signature.length) {
|
37 | throw new TypeError(`Expected 'signature' to be defined, but received '${signature}'`);
|
38 | }
|
39 | if ('string' !== typeof (secret) || !secret.length) {
|
40 | throw new TypeError(`Expected 'secret' to be defined, but received '${secret}'`);
|
41 | }
|
42 | const [hash, enc] = signature.split(separator, 2);
|
43 | const decoded = Buffer.from((hash + '==='.slice((hash.length + 3) % 4))
|
44 | .replace(/\-/gi, '+')
|
45 | .replace(/_/gi, '/'), 'base64')
|
46 | .toString('utf8');
|
47 | const signedDecoded = urlSafeBase64(createHmac('sha256', secret).update(decoded).digest('base64'));
|
48 | if (enc !== signedDecoded) {
|
49 | throw new SignaturError('invalid_signature', 'Signature not match');
|
50 | }
|
51 | return JSON.parse(decoded).data;
|
52 | }
|
53 | export async function sign(data, secret, options) {
|
54 | return signSync(data, secret, options);
|
55 | }
|
56 | export async function unsign(signature, secret, options) {
|
57 | return unsignSync(signature, secret, options);
|
58 | }
|
59 |
|
\ | No newline at end of file |