-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added request validation (outcommented for the moment)
can't really test it until an actual proposal comes in, so we'll test this out in prod with the monitoring events from sorted oracles once that's ready
- Loading branch information
Showing
7 changed files
with
109 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { SecretManagerServiceClient } from "@google-cloud/secret-manager"; | ||
import config from "./config.js"; | ||
|
||
/** | ||
* Get the Quicknode Security Token from GCloud Secret Manager | ||
* | ||
* NOTE: This will fail locally because the local function will lack the necessary permissions to access the secret. | ||
* That's why read the webhook URL from our .env file when running locally. We could probably make it work by having | ||
* the local function impersonate the service account used by the function in GCP, but that was a rabbit hole I didn't | ||
* want to go down when a simple .env approach also works for local testing. | ||
*/ | ||
export default async function getQuicknodeSecurityToken(): Promise<string> { | ||
const secretManager = new SecretManagerServiceClient(); | ||
const secretFullResourceName = `projects/${config.GCP_PROJECT_ID}/secrets/${config.QUICKNODE_SECURITY_TOKEN_SECRET_ID}/versions/latest`; | ||
const [version] = await secretManager.accessSecretVersion({ | ||
name: secretFullResourceName, | ||
}); | ||
|
||
const securityToken = version.payload?.data?.toString(); | ||
|
||
if (!securityToken) { | ||
throw new Error( | ||
"Failed to retrieve Quicknode security token from secret manager", | ||
); | ||
} | ||
|
||
return securityToken; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import type { Request } from "@google-cloud/functions-framework"; | ||
import crypto from "crypto"; | ||
import getQuicknodeSecurityToken from "./get-quicknode-security-token"; | ||
|
||
export default async function validateRequestOrigin(req: Request) { | ||
const quicknodeSecurityToken = await getQuicknodeSecurityToken(); | ||
const givenSignature = req.headers["x-qn-signature"]; | ||
const nonce = req.headers["x-qn-nonce"]; | ||
const contentHash = req.headers["x-qn-content-hash"]; | ||
const timestamp = req.headers["x-qn-timestamp"]; | ||
|
||
if (!nonce || typeof nonce !== "string") { | ||
console.error("No valid quicknode nonce found in request headers:", nonce); | ||
return; | ||
} | ||
|
||
if (!contentHash || typeof contentHash !== "string") { | ||
console.error( | ||
"No valid quicknode content hash found in request headers:", | ||
contentHash, | ||
); | ||
return; | ||
} | ||
|
||
if (!timestamp || typeof timestamp !== "string") { | ||
console.error( | ||
"No valid quicknode timestamp found in request headers:", | ||
contentHash, | ||
); | ||
return; | ||
} | ||
|
||
const hmac = crypto.createHmac("sha256", quicknodeSecurityToken); | ||
hmac.update(`${nonce}${contentHash}${timestamp}`); | ||
|
||
const expectedSignature = hmac.digest("base64"); | ||
|
||
if (givenSignature === expectedSignature) { | ||
console.log( | ||
"The signature given matches the expected signature and is valid.", | ||
); | ||
return; | ||
} else { | ||
throw new Error( | ||
"Signature validation failed for the request's Quicknode signature header", | ||
); | ||
} | ||
} |