11import axios , { AxiosError } from 'axios'
2+ import crypto from 'crypto'
23import { Base } from './base'
34import { FileScanner } from './filesScanner'
4- import { NightfallResponse , NightfallError , ScanText , ScanFile } from './types'
5+ import {
6+ Config , NightfallResponse , NightfallError , ScanText , ScanFile
7+ } from './types'
58
69export class Nightfall extends Base {
710 /**
8- * Create an instance of the Nightfall client. Although you can supply
9- * your API key manually when you initiate the client, we recommend that
10- * you configure your API key as an environment variable named
11- * NIGHTFALL_API_KEY. The client automatically reads `process.env.NIGHTFALL_API_KEY`
12- * when you initiate the client like so: `const client = new Nightfall()`.
11+ * Create an instance of the Nightfall client. Although you can supply your API key and webhook signing secret
12+ * manually when you initiate the client, we recommend that you configure them as an environment variables named
13+ * `NIGHTFALL_API_KEY` and `NIGHTFALL_WEBHOOK_SIGNING_SECRET`. The client automatically reads
14+ * `process.env.NIGHTFALL_API_KEY` and `process.env.NIGHTFALL_WEBHOOK_SIGNING_SECRET` when you initiate the
15+ * client like so: `const client = new Nightfall()`.
1316 *
14- * @param apiKey Your Nightfall API key
17+ * @param {Object } config An optional object to initialise the client with your key manually
18+ * @param {string } config.apiKey Your Nightfall API Key
19+ * @param {string } config.webhookSigningSecret Your webhook signing secret
1520 */
16- constructor ( apiKey ?: string ) {
17- super ( apiKey )
21+ constructor ( config ?: Config ) {
22+ super ( config )
1823 }
1924
2025 /**
@@ -65,7 +70,13 @@ export class Nightfall extends Base {
6570 */
6671 async scanFile ( filePath : string , policy : ScanFile . ScanPolicy , requestMetadata ?: string ) : Promise < NightfallResponse < ScanFile . ScanResponse > > {
6772 try {
68- const fileScanner = new FileScanner ( this . API_KEY , filePath )
73+ const fileScanner = new FileScanner (
74+ {
75+ apiKey : this . API_KEY ,
76+ webhookSigningSecret : this . WEBHOOK_SIGNING_SECRET ,
77+ } ,
78+ filePath ,
79+ )
6980 await fileScanner . initialize ( )
7081 await fileScanner . uploadChunks ( )
7182 await fileScanner . finish ( )
@@ -83,4 +94,38 @@ export class Nightfall extends Base {
8394 return Promise . reject ( error )
8495 }
8596 }
97+
98+ /**
99+ * A helper method to validate incoming webhook requests from Nightfall. In order to use this method, you
100+ * must initialize the Nightfall client with your `webhookSigningSecret` or declare your signing secret as
101+ * an environment variable called `NIGHTFALL_WEBHOOK_SIGNING_SECRET`. For more information,
102+ * visit https://docs.nightfall.ai/docs/creating-a-webhook-server#webhook-signature-verification.
103+ *
104+ * @param requestBody The webhook request body
105+ * @param requestSignature The value of the X-Nightfall-Signature header
106+ * @param requestTimestamp The value of the X-Nightfall-Timestamp header
107+ * @param threshold Optional - The threshold in seconds. Defaults to 300 seconds (5 minutes).
108+ * @returns A boolean that indicates whether the webhook is valid
109+ */
110+ validateWebhook ( requestBody : ScanFile . WebhookBody , requestSignature : string , requestTimestamp : number , threshold ?: number ) : boolean {
111+ // Do not continue if the client isn't initialized with a webhook signing secret
112+ if ( ! this . WEBHOOK_SIGNING_SECRET && ! process . env . hasOwnProperty ( 'NIGHTFALL_WEBHOOK_SIGNING_SECRET' ) ) {
113+ throw new Error ( 'Please initialize the Nightfall client with a webhook signing secret or configure your signing secret as an environment variable.' )
114+ }
115+
116+ // First verify that the request occurred recently (before the configured threshold time)
117+ // to protect against replay attacks
118+ const defaultThreshold = threshold || 300
119+ const now = Math . round ( new Date ( ) . getTime ( ) / 1000 )
120+ const thresholdTimestamp = now - defaultThreshold
121+ if ( requestTimestamp < thresholdTimestamp || requestTimestamp > now ) {
122+ return false
123+ }
124+
125+ // Validate request signature using the signing secret
126+ const hashPayload = `${ requestTimestamp } :${ JSON . stringify ( requestBody ) } `
127+ const computedSignature = crypto . createHmac ( 'sha256' , this . WEBHOOK_SIGNING_SECRET ) . update ( hashPayload ) . digest ( 'hex' )
128+
129+ return computedSignature === requestSignature
130+ }
86131}
0 commit comments