SmartThingsCommunity/smartapp-sdk-nodejs

Signature verification failing with query parameters

Closed this issue · 1 comments

AmineI commented

Describe the bug

When receiving a call from the SmartThings platform, the signature verification mechanism fails when the WebHook URL includes a query parameter. The actual url that is signed is the path without any query parameters.

This affects cases where WebHook URLs are like /api/smartapp?code=my-R4nd0mCode==, as implemented by hosts such as Azure Functions, which protects against unwanted calls via a "code" query parameter.

To Reproduce
Steps to reproduce the behavior:
Start a simple smartApp sample, on a server endpoint such as below :

server.post('/api/smartapp', async (req, res) => {
        //Do anything with a query parameter
	await smartApp.handleHttpCallback(req, res)
})

Add the[https://host]/api?code=my-R4nd0mCode== Webhook URL to SmartThings Developer Workspace.
Attempt to validate the domain.

Expected behavior
The requests from SmartThings to this endpoint should succeed.

Actual behavior
The requests from SmartThings fail. The url [https://host]/api/smartapp?code=my-R4nd0mCode==, is properly called, but the signature verification from the SDK fails with an error "Forbidden - failed verifySignature" in the logs. The signature verification fails as signature contains the path /api/smartapp as the target url.

Here are the test cases and behavior I confirmed :

  • URL does not contain any query parameters (ie no ? character in the request) : works
  • URL contains a query parameter. does not work
    Example : [https://host]/api/smartapp?code=my-R4nd0mCode== or [https://host]/api/smartapp?code=my.

Additional context

I did not send a Pull Request, as I am unsure if this should be fixed in the module or in the SmartThings platform signature mechanism.

A workaround is to insert an assignment of req.originalUrl = req.path before the smartApp.handleHttpCallback call.
A potential fix could be to include this statement here

if (request.originalUrl) {
request.url = request.originalUrl
}
const isAuthorized = await this._authorizer.isAuthorized(request)

Another workaround would be to use handleHttpCallbackUnverified in such cases, although this causes other issues (see #254).

AmineI commented

Merged in #259 🎉 .