Skip to content

Instantly share code, notes, and snippets.

@lpomfrey
Last active December 19, 2023 03:22
Show Gist options
  • Save lpomfrey/97381cf4316553b03622c665ae3a47da to your computer and use it in GitHub Desktop.
Save lpomfrey/97381cf4316553b03622c665ae3a47da to your computer and use it in GitHub Desktop.
AWS Lambda function for forwarding Alexa Intent requests to Home Assistant
# -*- coding: utf-8 -*-
import os
import json
import logging
import urllib3
_debug = os.environ.get('DEBUG', '').lower() in ('1', 'y', 'yes', 'true', 'on')
_logger = logging.getLogger('HomeAssistant-Intents')
_logger.setLevel(logging.DEBUG if _debug else logging.INFO)
def lambda_handler(event, context):
"""Handle incoming Alexa directive."""
_logger.debug('Event: %s', event)
base_url = os.environ.get('BASE_URL')
assert base_url is not None, 'Please set BASE_URL environment variable'
try:
token = event.get('session', {}).get('user', {}).get('accessToken')
except AttributeError:
token = None
if token is None and _debug:
token = os.environ.get('LONG_LIVED_ACCESS_TOKEN')
assert token, 'Could not get access token'
verify_ssl = os.environ.get('NOT_VERIFY_SSL', '').lower() not in ('1', 'y', 'yes', 'true', 'on')
http = urllib3.PoolManager(
cert_reqs='CERT_REQUIRED' if verify_ssl else 'CERT_NONE',
timeout=urllib3.Timeout(connect=2.0, read=10.0)
)
response = http.request(
'POST',
'{}/api/alexa'.format(base_url),
headers={
'Authorization': 'Bearer {}'.format(token),
'Content-Type': 'application/json',
},
body=json.dumps(event).encode('utf-8'),
)
if response.status >= 400:
return {
'event': {
'payload': {
'type': 'INVALID_AUTHORIZATION_CREDENTIAL'
if response.status in (401, 403) else 'INTERNAL_ERROR',
'message': response.data.decode("utf-8"),
}
}
}
return json.loads(response.data.decode('utf-8'))
@tiagofreire-pt
Copy link

Hello.

I followed this guide: https://github.com/home-assistant/home-assistant.io/blob/current/source/_integrations/alexa.intent.markdown

But I only could get success on linking the account if I remove the 8123 port on all home assistant links, in lambda function and alexa app, and port forward on my router from external 443 to 8123. Otherwise, the lambda function returns:

[ERROR] AssertionError: Could not get access token
Traceback (most recent call last):
  File "/var/task/lambda_function.py", line 29, in lambda_handler
    assert token, 'Could not get access token'

Could you check the code or the guide for this, please?

@lpomfrey
Copy link
Author

The guide and docs assume you've got an externally accessible Home Assistant instance, see the third requirement:

The Alexa Custom Skill API also needs your Home Assistant instance can be accessed from Internet. We strongly suggest you host HTTPS server and use validation certificate. Read more on our blog about how to set up encryption for Home Assistant. When running Hass.io using the Duck DNS add-on is the easiest method.

@tiagofreire-pt
Copy link

Yes, it's accessible from the internet but it only works if the external port is 443, instead of the 8123 proposed in the tutorial.

@lpomfrey
Copy link
Author

Yea, that's an Amazon requirement:

Note that the account linking URL must be a HTTPS URL on port 443, with a certificate from an Amazon approved CA authority (https://ccadb-public.secure.force.com/mozilla/IncludedCACertificateReport).
https://forums.developer.amazon.com/articles/38610/alexa-debugging-account-linking.html

It's actually noted on the Smart Home docs but not on the Custom Intent ones. I've submitted a pull request to clarify that on both.

@scyto
Copy link

scyto commented Feb 21, 2022

The NOT_VERIFY_SSL env var doesn't seem to work at all (and I expect the same is true for debug)
It doesn't matter how I set the environment variable it doesn't change the SSL behavior.

My belief based on some googling is that environment variables are NOT booleans as such all this code tests for is the presence of envvar.
But i am not sure and too code illiterate to know how to fix (i tried several of the suggestions in the link)

    verify_ssl = not bool(os.environ.get('NOT_VERIFY_SSL'))

    http = urllib3.PoolManager(
        cert_reqs='CERT_REQUIRED' if verify_ssl else 'CERT_NONE',
        timeout=urllib3.Timeout(connect=2.0, read=10.0)

i worked around this by hard coding it to certs required

#    verify_ssl = not bool(os.environ.get('NOT_VERIFY_SSL'))

    http = urllib3.PoolManager(
        cert_reqs='CERT_REQUIRED',  #if verify_ssl else 'CERT_NONE',
        timeout=urllib3.Timeout(connect=2.0, read=10.0)

---edit-- oh i see
all the code does is detect if the var is present, it doesn't care what it is set to to.
This means the docs at here are wrong.

NOT_VERIFY_SSL (optional): set to True to ignore the SSL issue, if you don’t have a valid SSL certificate or you are using self-signed certificate.
DEBUG (optional): set to True to log debugging messages.

one needs to REMOVE NOT_VERIFY_SSL to turn on SSL checking and add it to turn it off, what ever it is set to is irrelevant (i couldn't for the life of me figure out why when i set to false it did nothing, lol)

@BUGBEAR7
Copy link

Hello

I have been using “haaska” to achieve this and I recently switched my ISP and they use CG-NAT so port forwarding is not an option. I am force to use IPv6 . I got the IPv6 working with duckdns and letsencrypt.
So my question is will this work on IPv6 as well because “haaska” does not work .

@lpomfrey
Copy link
Author

lpomfrey commented Oct 3, 2022

Hello

I have been using “haaska” to achieve this and I recently switched my ISP and they use CG-NAT so port forwarding is not an option. I am force to use IPv6 . I got the IPv6 working with duckdns and letsencrypt. So my question is will this work on IPv6 as well because “haaska” does not work .

It's just using urllib3 so assuming you've got IPv6 set up properly in your OS it should work fine.

@Writtscher
Copy link

@lpomfrey Hi, I'm using your script and it is working fine in debug with a long live access token, tanks. But as soon as I remove the debug flag I get an ssl error

SSLError(CertificateError("hostname 'www.XXX.duckdns.org' doesn't match 'XXX.duckdns.org'"))': /api/alexa/smart_home

I have no idea how to fix this. I am using the generated duckdns addon certificate of my home assistant.

Could you please help?

@lpomfrey
Copy link
Author

@lpomfrey Hi, I'm using your script and it is working fine in debug with a long live access token, tanks. But as soon as I remove the debug flag I get an ssl error

SSLError(CertificateError("hostname 'www.XXX.duckdns.org' doesn't match 'XXX.duckdns.org'"))': /api/alexa/smart_home

I have no idea how to fix this. I am using the generated duckdns addon certificate of my home assistant.

Could you please help?

It looks like you either need to set the NOT_VERIFY_SSL environment variable to 1 or use a domain that you actually have a certificate for (e.g. XXX.duckdns.org instead of www.XXX.duckdns.org)

@Writtscher
Copy link

Writtscher commented Mar 22, 2023

@lpomfrey Thank you for your quick response. Now I am getting

{
  "errorMessage": "Could not get access token",
  "errorType": "AssertionError",
  "requestId": "63bf8698-793d-4cda-b812-8ca42095cdc1",
  "stackTrace": [
    "  File \"/var/task/lambda_function.py\", line 29, in lambda_handler\n    assert token, 'Could not get access token'\n"
  ]
}

My home assistant is accessible via https and port 443.


I figured it out. There is no user / token in the event. I am not sure why. I will see what is wrong because I am sure I've added account linking in my alexa skil.


@lpomfrey

I have kinda figured it out. The script is different for me. My token is not in token = event.get('session', {}).get('user', {}).get('accessToken'). For me it is in token = event.get('directive', {}).get('payload', {}).get('scope', {}).get('token').

But I could not find a solution for the verify ssl error. Even if I disable verify ssl...

Unverified HTTPS request is being made to host 'www.XXX.duckdns.org'. Adding certificate verification is strongly advised.

@lpomfrey
Copy link
Author

@lpomfrey Thank you for your quick response. Now I am getting

{
  "errorMessage": "Could not get access token",
  "errorType": "AssertionError",
  "requestId": "63bf8698-793d-4cda-b812-8ca42095cdc1",
  "stackTrace": [
    "  File \"/var/task/lambda_function.py\", line 29, in lambda_handler\n    assert token, 'Could not get access token'\n"
  ]
}

My home assistant is accessible via https and port 443.

I figured it out. There is no user / token in the event. I am not sure why. I will see what is wrong because I am sure I've added account linking in my alexa skil.

@lpomfrey

I have kinda figured it out. The script is different for me. My token is not in token = event.get('session', {}).get('user', {}).get('accessToken'). For me it is in token = event.get('directive', {}).get('payload', {}).get('scope', {}).get('token').

But I could not find a solution for the verify ssl error. Even if I disable verify ssl...

Unverified HTTPS request is being made to host 'www.XXX.duckdns.org'. Adding certificate verification is strongly advised.

It sounds like you might want to modify this script instead from the Home Assistant docs here

The SSL issue is just a warning that you're insecure because you turned off certificate verification.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment