diff --git a/README.md b/README.md index 93967e4..316c955 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ if (!isValid) { | Intercom | `X-Hub-Signature` | HMAC-SHA1 | | Mailchimp | `X-Mailchimp-Signature` | HMAC-SHA256 (base64) | | GitLab | `X-Gitlab-Token` | Token comparison | +| Home Assistant | `X-HA-Secret` | Token comparison | | Typeform | `Typeform-Signature` | HMAC-SHA256 (base64) | | Crystallize | `X-Crystallize-Signature` | JWT + HMAC-SHA256 | | Zendesk | `X-Zendesk-Webhook-Signature` | HMAC-SHA256 + timestamp | @@ -389,6 +390,42 @@ app.post('/webhook/gitlab', express.json(), (req, res) => { }); ``` +### Home Assistant + +```typescript +import { verify } from 'webhook-verify'; + +app.post('/webhook/homeassistant', express.json(), (req, res) => { + // Home Assistant uses token comparison via X-HA-Secret header + const isValid = verify( + 'homeassistant', + '', + req.headers, + process.env.HA_SHARED_SECRET + ); + + if (!isValid) { + return res.status(401).send('Invalid token'); + } + + const { entity_id, state } = req.body; + // Process Home Assistant event... +}); +``` + +Home Assistant configuration example: + +```yaml +rest_command: + send_event: + url: "https://your-webhook-url.com/ha/event" + method: POST + headers: + X-HA-Secret: !secret webhook_secret + content_type: "application/json" + payload: '{"entity_id": "{{ entity_id }}", "state": "{{ state }}"}' +``` + ### Crystallize ```typescript diff --git a/package.json b/package.json index 2059335..e5f329c 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,8 @@ "square", "hubspot", "segment", + "homeassistant", + "home-assistant", "ed25519", "rsa", "security", diff --git a/src/headers.ts b/src/headers.ts index 6cd2d81..2fba7db 100644 --- a/src/headers.ts +++ b/src/headers.ts @@ -164,6 +164,12 @@ const providerHeaders: Record SignatureData | nu return { signature: token, rawSignature: token, eventType: event }; }, + homeassistant: (headers) => { + const token = getHeader(headers, 'x-ha-secret'); + if (!token) return null; + return { signature: token, rawSignature: token }; + }, + typeform: (headers) => { const signature = getHeader(headers, 'typeform-signature'); if (!signature) return null; @@ -286,6 +292,7 @@ export function getHeaderNames(provider: Provider): Record { intercom: { signature: 'x-hub-signature' }, mailchimp: { signature: 'x-mailchimp-signature' }, gitlab: { token: 'x-gitlab-token', event: 'x-gitlab-event' }, + homeassistant: { token: 'x-ha-secret' }, typeform: { signature: 'typeform-signature' }, crystallize: { signature: 'x-crystallize-signature' }, zendesk: { signature: 'x-zendesk-webhook-signature', timestamp: 'x-zendesk-webhook-signature-timestamp' }, diff --git a/src/providers/homeassistant.ts b/src/providers/homeassistant.ts new file mode 100644 index 0000000..093ab84 --- /dev/null +++ b/src/providers/homeassistant.ts @@ -0,0 +1,33 @@ +import { secureCompare } from '../utils/crypto.js'; +import type { ProviderVerifier } from '../types.js'; + +/** + * Home Assistant webhook verification + * + * Home Assistant uses a simple secret token comparison via X-HA-Secret header. + * The signature parameter should contain the token from the header. + * + * Home Assistant Configuration Example: + * ```yaml + * rest_command: + * send_event: + * url: "https://your-webhook-url.com/ha/event" + * method: POST + * headers: + * X-HA-Secret: "your-shared-secret" + * content_type: "application/json" + * payload: '{"entity_id": "{{ entity_id }}", "state": "{{ state }}"}' + * ``` + * + * @see https://www.home-assistant.io/integrations/rest_command/ + */ +export const homeassistant: ProviderVerifier = { + verify(_payload, signature, secret) { + if (!signature || !secret) { + return false; + } + + // Home Assistant simply compares the token from the header with the secret + return secureCompare(signature, secret); + }, +}; diff --git a/src/providers/index.ts b/src/providers/index.ts index c823536..5e02d3c 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -3,6 +3,7 @@ import { crystallize } from './crystallize.js'; import { discord } from './discord.js'; import { github } from './github.js'; import { gitlab } from './gitlab.js'; +import { homeassistant } from './homeassistant.js'; import { hubspot } from './hubspot.js'; import { intercom } from './intercom.js'; import { linear } from './linear.js'; @@ -31,6 +32,7 @@ export const providers: Record = { discord, github, gitlab, + homeassistant, hubspot, intercom, linear, @@ -55,6 +57,7 @@ export { discord, github, gitlab, + homeassistant, hubspot, intercom, linear, diff --git a/src/types.ts b/src/types.ts index a033755..6b632bd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -22,7 +22,8 @@ export type Provider = | 'zendesk' | 'square' | 'hubspot' - | 'segment'; + | 'segment' + | 'homeassistant'; /** * Base options available to all providers