Skip to content
← Public packages

@kody/alexa

Alexa custom skill webhook into Kody. Checks the skill id, maps common requests, and leaves utterance forwarding unwired.

src/verify.ts

70 lines · 2.2 KB · TypeScript
Raw
import { boolean, object, optional, parse, string } from 'remix/data-schema'

const verifyInput = object(
	{
		signatureCertChainUrl: optional(string()),
		signature: optional(string()),
		body: optional(string()),
		dryRun: optional(boolean()),
		allowUnsigned: optional(boolean()),
	},
	{ unknownKeys: 'error' },
)

const ALEXA_SIGNATURE_DOCS = [
	'https://developer.amazon.com/en-US/docs/alexa/custom-skills/host-a-custom-skill-as-a-web-service.html#verifying-that-the-request-was-sent-by-alexa',
	'https://developer.amazon.com/en-US/docs/alexa/custom-skills/request-and-response-json-reference.html',
] as const

export type VerifyAlexaRequestResult = {
	verified: false
	implemented: false
	skipped: true
	unsafeForProduction: true
	devBypass: boolean
	reason: string
	docs: string[]
	checked: {
		hasSignatureCertChainUrl: boolean
		hasSignature: boolean
		hasBody: boolean
	}
}

/**
 * Report Alexa request-signature status without performing crypto.
 * Use before treating a skill request as authentic. This head-start stub
 * always skips SignatureCertChainUrl verification.
 *
 * @param input - Optional SignatureCertChainUrl header, Signature header, raw body, and a loud dev flag
 * @returns A skipped, unsafe-for-production result. `verified` is always false.
 *
 * @example
 * import verifyAlexaRequest from 'kody:@kody/alexa/verify'
 *
 * const status = await verifyAlexaRequest({ dryRun: true })
 */
export default async function verifyAlexaRequest(
	raw: unknown = {},
): Promise<VerifyAlexaRequestResult> {
	const input = parse(verifyInput, raw)
	const devBypass = input.dryRun === true || input.allowUnsigned === true
	const reason = devBypass
		? 'Signature verification is not implemented. dryRun or allowUnsigned is set. That flag is unsafe for production.'
		: 'Signature verification is not implemented. This stub does not validate SignatureCertChainUrl or Signature. Unsafe for production.'

	return {
		verified: false,
		implemented: false,
		skipped: true,
		unsafeForProduction: true,
		devBypass,
		reason,
		docs: [...ALEXA_SIGNATURE_DOCS],
		checked: {
			hasSignatureCertChainUrl: Boolean(input.signatureCertChainUrl?.trim()),
			hasSignature: Boolean(input.signature?.trim()),
			hasBody: Boolean(input.body),
		},
	}
}