Skip to content

Commit

Permalink
fix: Add Flagsmith signature header when testing webhook.
Browse files Browse the repository at this point in the history
Fixes #2786.
We are trying to create the same signature as the webhook
in the python code. This commit assumes that the python code
will use the same approach to create signature for long term.
  • Loading branch information
shubham-padia committed Mar 25, 2024
1 parent 21cc2d3 commit a52cb1e
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 2 deletions.
2 changes: 2 additions & 0 deletions api/core/signing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@


def sign_payload(payload: str, key: str):
# TestWebHook on the frontend replicates this exact function, change the
# function there if this changes.
return hmac.new(
key=key.encode(), msg=payload.encode(), digestmod=hashlib.sha256
).hexdigest()
3 changes: 3 additions & 0 deletions api/edge_api/identities/edge_request_forwarder.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ def _get_headers(request_method: str, headers: dict, payload: str = "") -> dict:
# ref: https://groups.google.com/g/django-developers/c/xjYVJN-RguA/m/G9krDqawchQJ
if request_method == "GET":
headers.pop("Content-Length", None)

# TestWebHook on the frontend replicates this exact function, change the
# function there if this changes.
signature = sign_payload(payload, settings.EDGE_REQUEST_SIGNING_KEY)
headers[FLAGSMITH_SIGNATURE_HEADER] = signature
return headers
4 changes: 4 additions & 0 deletions api/webhooks/webhooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ def _call_webhook(
headers = {"content-type": "application/json"}
json_data = json.dumps(data, sort_keys=True, cls=DjangoJSONEncoder)
if webhook.secret:
# TestWebHook on the frontend replicates this exact function, change the
# function there if this changes.
signature = sign_payload(json_data, key=webhook.secret)
headers.update({FLAGSMITH_SIGNATURE_HEADER: signature})

Expand Down Expand Up @@ -199,6 +201,8 @@ def call_webhook_with_failure_mail_after_retries(
headers = {"content-type": "application/json"}
json_data = json.dumps(data, sort_keys=True, cls=DjangoJSONEncoder)
if webhook.secret:
# TestWebHook on the frontend replicates this exact function, change the
# function there if this changes.
signature = sign_payload(json_data, key=webhook.secret)
headers.update({FLAGSMITH_SIGNATURE_HEADER: signature})

Expand Down
49 changes: 47 additions & 2 deletions frontend/web/components/TestWebhook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,63 @@ import Button from './base/forms/Button'
type TestWebhookType = {
webhook: string
json: string
secret: string
}

const TestWebhook: FC<TestWebhookType> = ({ json, webhook }) => {

// from https://stackoverflow.com/questions/24834812/space-in-between-json-stringify-output
const stringifyWithSpaces = (str: string) => {
const obj = JSON.parse(str)
let result = JSON.stringify(obj, null, 1) // stringify, with line-breaks and indents
result = result.replace(/^ +/gm, " ") // remove all but the first space for each line
result = result.replace(/\n/g, "") // remove line-breaks
result = result.replace(/{ /g, "{").replace(/ }/g, "}") // remove spaces between object-braces and first/last props
result = result.replace(/\[ /g, "[").replace(/ \]/g, "]") // remove spaces between array-brackets and first/last items
return result
}

const getSignature = (body: string, secret: string, setSign: React.Dispatch<React.SetStateAction<string>>) => {
var enc = new TextEncoder()
let str = ''

window.crypto.subtle.importKey(
"raw",
enc.encode(secret),
{
name: "HMAC",
hash: {name: "SHA-256"}
},
false,
["sign"]
).then( key => {
window.crypto.subtle.sign(
"HMAC",
key,
enc.encode(stringifyWithSpaces(body)) // We do this bc the python output is single line with one space before each value
).then(signature => {
var b = new Uint8Array(signature)
str = Array.prototype.map.call(b, x => x.toString(16).padStart(2, '0')).join("")
setSign(str)
})
})
}

const TestWebhook: FC<TestWebhookType> = ({ json, webhook, secret }) => {
const [error, setError] = useState<string | null>(null)
const [loading, setLoading] = useState(false)
const [success, setSuccess] = useState(false)
const [sign, setSign] = useState('')
getSignature(json, secret, setSign)
const headers = {
'X-Flagsmith-Signature': sign,
}

const submit = () => {
setError(null)
setLoading(true)
setSuccess(false)
data
.post(webhook, JSON.parse(json), null)
.post(webhook, JSON.parse(json), headers)
.then(() => {
setLoading(false)
setSuccess(true)
Expand Down
1 change: 1 addition & 0 deletions frontend/web/components/modals/CreateAuditWebhook.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ const CreateAuditWebhook = class extends Component {
<TestWebhook
json={Constants.exampleAuditWebhook}
webhook={this.state.url}
secret={this.state.secret}
/>
{isEdit ? (
<Button
Expand Down
1 change: 1 addition & 0 deletions frontend/web/components/modals/CreateWebhook.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ const CreateWebhook = class extends Component {
<TestWebhook
json={Constants.exampleWebhook}
webhook={this.state.url}
secret={this.state.secret}
/>
{isEdit ? (
<Button
Expand Down

0 comments on commit a52cb1e

Please sign in to comment.