• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Adyen / adyen-node-api-library / 13605899990

01 Mar 2025 02:48PM UTC coverage: 59.563%. First build
13605899990

Pull #1476

github

web-flow
Merge 8b1da676b into 61beb9569
Pull Request #1476: Fix HMAC validation for Banking webhooks

265 of 811 branches covered (32.68%)

Branch coverage included in aggregate %.

3 of 4 new or added lines in 1 file covered. (75.0%)

2027 of 3037 relevant lines covered (66.74%)

24.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.14
/src/utils/hmacValidator.ts
1
/*
2
 *                       ######
3
 *                       ######
4
 * ############    ####( ######  #####. ######  ############   ############
5
 * #############  #####( ######  #####. ######  #############  #############
6
 *        ######  #####( ######  #####. ######  #####  ######  #####  ######
7
 * ###### ######  #####( ######  #####. ######  #####  #####   #####  ######
8
 * ###### ######  #####( ######  #####. ######  #####          #####  ######
9
 * #############  #############  #############  #############  #####  ######
10
 *  ############   ############  #############   ############  #####  ######
11
 *                                      ######
12
 *                               #############
13
 *                               ############
14
 * Adyen NodeJS API Library
15
 * Copyright (c) 2020 Adyen B.V.
16
 * This file is open source and available under the MIT license.
17
 * See the LICENSE file for more info.
18
 */
19

20
import { createHmac, timingSafeEqual } from "crypto";
6✔
21
import { NotificationRequestItem } from "../typings/notification/models";
22
import { ApiConstants } from "../constants/apiConstants";
6✔
23

24
type DataToSign = NotificationRequestItem | { [key: string]: string };
25

26
class HmacValidator {
27
    public static HMAC_SHA256_ALGORITHM = "sha256";
6✔
28
    public static DATA_SEPARATOR = ":";
6✔
29

30
    /**
31
     * Calculate HMAC signature of the payload data
32
     * @param data payload as String or as NotificationRequestItem
33
     * @param key HMAC key
34
     * @returns HMAC signature
35
     */
36
    public calculateHmac(data: string | NotificationRequestItem, key: string): string {
37
        const dataString = typeof data !== "string" ? this.getDataToSign(data) : data;
20✔
38
        const rawKey = Buffer.from(key, "hex");
20✔
39
        return createHmac(HmacValidator.HMAC_SHA256_ALGORITHM, rawKey).update(dataString, "utf8").digest("base64");
20✔
40
    }
41

42
    /**
43
     * @deprecated use Use validateHMACSignature with correct parameter order instead
44
     * Validate HMAC signature for Banking webhooks
45
     * @param hmacKey 
46
     * @param hmacSign 
47
     * @param notification 
48
     * @returns 
49
     */
50
    public validateBankingHMAC(hmacKey: string, hmacSign: string, notification: string): boolean {
51
        const expectedSign = createHmac(HmacValidator.HMAC_SHA256_ALGORITHM, Buffer.from(hmacSign, "hex")).update(notification, "utf8").digest("base64");
2✔
52
        if(hmacKey?.length === expectedSign.length) {
2!
53
            return timingSafeEqual(
2✔
54
                Buffer.from(expectedSign, "base64"),
55
                Buffer.from(hmacKey, "base64")
56
            );
57
        }
58
        return false;
×
59
    }
60

61
    /**
62
     * Validate HMAC signature for Banking/Management webhooks
63
     * @param hmacKey HMAC key
64
     * @param hmacSignature HMAC signature to validate 
65
     * @param data webhook payload (as string)
66
     * @returns true when HMAC signature is valid
67
     */
68
    public validateHMACSignature(hmacKey: string, hmacSignature: string, data: string): boolean {
69
        const expectedSign = createHmac(HmacValidator.HMAC_SHA256_ALGORITHM, Buffer.from(hmacKey, "hex")).update(data, "utf8").digest("base64");
2✔
70
        if(hmacSignature?.length === expectedSign.length) {
2!
71
            return timingSafeEqual(
2✔
72
                Buffer.from(expectedSign, "base64"),
73
                Buffer.from(hmacSignature, "base64")
74
            );
75
        }
NEW
76
        return false;
×
77
    }
78

79
    /**
80
     * Validate HMAC signature for Payment webhooks
81
     * @param notificationRequestItem webhook payload (as NotificationRequestItem object)
82
     * @param key HMAC key
83
     * @returns true when HMAC signature is valid
84
     */
85
    public validateHMAC(notificationRequestItem: NotificationRequestItem, key: string): boolean {
86
        if (notificationRequestItem.additionalData?.[ApiConstants.HMAC_SIGNATURE]) {
12!
87
            const expectedSign = this.calculateHmac(notificationRequestItem, key);
10✔
88
            const merchantSign = notificationRequestItem.additionalData?.[ApiConstants.HMAC_SIGNATURE];
10!
89
            if(merchantSign?.length === expectedSign.length) {
10!
90
                return timingSafeEqual(
6✔
91
                    Buffer.from(expectedSign, "base64"),
92
                    Buffer.from(merchantSign, "base64")
93
                );
94
            }
95
            return false;
4✔
96
        }
97
        throw Error(`Missing ${ApiConstants.HMAC_SIGNATURE}`);
2✔
98
    }
99

100
    private isNotificationRequestItem(item: DataToSign): item is NotificationRequestItem {
101
        return !Object.values(item).every((value): boolean => typeof value === "string");
28✔
102
    }
103

104
    /**
105
     * extract fields to be used to calculate the HMAC signature
106
     * @param notificationRequestItem webhook payload
107
     * @returns data to sign (as string)
108
     */
109
    public getDataToSign(notificationRequestItem: DataToSign): string {
110
        if (this.isNotificationRequestItem(notificationRequestItem)) {
22✔
111
            const signedDataList = [];
18✔
112
            signedDataList.push(notificationRequestItem.pspReference);
18✔
113
            signedDataList.push(notificationRequestItem.originalReference);
18✔
114
            signedDataList.push(notificationRequestItem.merchantAccountCode);
18✔
115
            signedDataList.push(notificationRequestItem.merchantReference);
18✔
116
            signedDataList.push(notificationRequestItem.amount.value);
18✔
117
            signedDataList.push(notificationRequestItem.amount.currency);
18✔
118
            signedDataList.push(notificationRequestItem.eventCode);
18✔
119
            signedDataList.push(notificationRequestItem.success);
18✔
120
            return signedDataList.join(HmacValidator.DATA_SEPARATOR);
18✔
121
        } else {
122
            const keys: string[] = [];
4✔
123
            const values: string[] = [];
4✔
124
            const replacer = (str: string): string =>
4✔
125
                str.replace(/\\/g, "\\\\").replace(/:/g, "\\:");
20✔
126
            Object.entries(notificationRequestItem).sort().forEach(([key, value]): void => {
4✔
127
                keys.push(replacer(key));
10✔
128
                values.push(replacer(value));
10✔
129
            });
130

131
            return [...keys, ...values].join(HmacValidator.DATA_SEPARATOR);
4✔
132
        }
133
    }
134
}
135

136
export default HmacValidator;
6✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc