Unable to verify Discord signature for bot on AWS Lambda Python 3 (Interactions_Endpoint_URL) - python-3.x

I am attempting to validate the signature for my bot application using discord's "INTERACTIONS ENDPOINT URL" in a lambda function running python 3.7. Using the documentation here under the "Security and Authorization" section, I still seem to be unable to get a valid return on the signature, with the exception being triggered each time. I'm unsure which aspect of the validation is incorrect. I am using AWS API Gateway to forward the headers to the lambda function in order to access them. Any help with pointing me in the right direction would be appreciated.
Edit:
Here is the output of the event in lambda for reference. I removed some of the values for security marked by <>.
{'body': {'application_id': '<AppID>', 'id': '<ID>', 'token': '<Token>', 'type': 1, 'user': {'avatar': '4cbeed4cdd11cac74eec2abf31086e59', 'discriminator': '9405', 'id': '340202973932027906', 'public_flags': 0, 'username': '<username>'}, 'version': 1}, 'headers': {'accept': '*/*', 'content-type': 'application/json', 'Host': '<AWS Lambda address>', 'User-Agent': 'Discord-Interactions/1.0 (+https://discord.com)', 'X-Amzn-Trace-Id': 'Root=1-60a570b8-00381f6e26f023df5f9396b1', 'X-Forwarded-For': '<IP>', 'X-Forwarded-Port': '443', 'X-Forwarded-Proto': 'https', 'x-signature-ed25519': 'de8c8e64be2058f40421e9ff8c7941bdabbf501a697ebcf42aa0419858c978e19c5fb745811659b41909c0117fd89430c720cbf1da33c9dcfb217f669c496c00', 'x-signature-timestamp': '1621455032'}}
import json
import os
from nacl.signing import VerifyKey
from nacl.exceptions import BadSignatureError
def lambda_handler(event, context):
# Your public key can be found on your application in the Developer Portal
PUBLIC_KEY = os.environ['DISCORD_PUBLIC_KEY']
verify_key = VerifyKey(bytes.fromhex(PUBLIC_KEY))
signature = event['headers']["x-signature-ed25519"]
timestamp = event['headers']["x-signature-timestamp"]
body = event['body']
try:
verify_key.verify(f'{timestamp}{body}'.encode(), bytes.fromhex(signature))
except BadSignatureError:
return (401, 'invalid request signature')

I was able to diagnose the issue. I was unable to verify the signature because AWS API Gateway was altering the body into JSON before it got to my lambda function. This made the signature verification come up as invalid each time. I solved this by checking Lambda Proxy Integration in the Integration Request section in API Gateway. Lambda Proxy Check Box. This allowed an unaltered body being sent to Lambda, which I could then verify my discord outgoing webhook. Below is my final code.
import json
import os
from nacl.signing import VerifyKey
from nacl.exceptions import BadSignatureError
def lambda_handler(event, context):
PUBLIC_KEY = os.environ['DISCORD_PUBLIC_KEY']
verify_key = VerifyKey(bytes.fromhex(PUBLIC_KEY))
signature = event['headers']["x-signature-ed25519"]
timestamp = event['headers']["x-signature-timestamp"]
body = event['body']
try:
verify_key.verify(f'{timestamp}{body}'.encode(), bytes.fromhex(signature))
body = json.loads(event['body'])
if body["type"] == 1:
return {
'statusCode': 200,
'body': json.dumps({'type': 1})
}
except (BadSignatureError) as e:
return {
'statusCode': 401,
'body': json.dumps("Bad Signature")
}

Related

AWS API Gateway 'Access-Control-Allow-Origin' header is not present

So, like many before me, I'm also facing the CORS error with AWS API gateway+Lambda(python) for a POST request.
Let me explain the Homeworks I did.
Followed the links and got a basic idea of how CORS works.
Tried enabling lambda proxy integration and tried without it as well.
During the manual configuration attempt I added the "Access-Control-Allow-Origin":'*' manually in API gateway method configurations.
At all times my lambda function is set to return the headers like below:
'headers': {
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
}
Postman is working fine as it worked for most of the people who had issues.
When I check the network traffic in chrome, I get the 'Access-Control-Allow-Origin': '*' as part of the header for OPTIONS. But when POST request has none of these headers I have added in the lambda.
The destination page is hosted in my local and AWS Amplify and both has the same issue.
Few images for reference.
Looking forward to all of your inputs.
Edit:
Adding my lambda code as requested:
import json
import urllib.parse
import boto3
import configparser
import os
import datetime
import json
print('Loading function')
# some more code here...
def lambda_handler(event, context):
logfilename = log(json.dumps(event, indent=2), "Debug")
response = {
'headers': {
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
},
"statusCode": 200,
"body": "{\"result\": \"Success.\"}"
}
return response
You need to add "Secure; SameSite=None" to the cookie you send with the lambda response and add withCredentials: true to your axios request.
Important! The cookie will not be accessible with JS inside you app (as it is httpOnly one). But it will be added to external HTTP requests (execute with axios withCredentials: true)

Getting "Internal Server Error" 502:Bad Gateway Error

I have just started working on AWS.
I am building a system connection between lambda, RDS MYSQL Database and API gateway.
Created a lambda function in python which inserts the data into the MYSQL database and configured API gateway with the lambda function. and when I am testing the lambda function within lambda console, everything is working fine. but when I am trying to call API from postman, it results in "message": "Internal server error" and 502 bad gateway.
import pymysql
import sys
import logging
import json
logger = logging.getLogger()
logger.setLevel(logging.INFO)
try:
conn = pymysql.connect(
host='',
port=int(3306),
user="",
passwd="",
db="")
except:
logger.error("ERROR: Unexpected error: Could not connect to MySql instance.")
sys.exit()
logger.info("SUCCESS: Connection to RDS mysql instance succeeded")
cursor=conn.cursor()
def lambda_handler(event, context):
print(event)
http_method = event['httpMethod']
if http_method == "GET":
Serial_Number = int(event['queryStringParameters']['Serial_Number'])
platform = int(event['queryStringParameters']['platform'])
architecture = int(event['queryStringParameters']['architecture'])
elif http_method == "POST":
body = json.loads(event['body'])
Serial_Number = body['Serial_Number']
platform = body['platform']
architecture = body['architecture']
return{
'statusCode' : 200,
'headers': {'Content-Type': 'application/json'},
'body' : json.dumps(Insertion(Serial_Number, platform, architecture)),
'messageReason' : "Successfully updated Details"
}
def Insertion(Serial_Number, platform, architecture):
item_count = 0
with conn.cursor() as cur:
cur.execute("insert into system_data (Serial_Number, platform, architecture) values(%s, %s, %s)", (Serial_Number, platform, architecture))
conn.commit()
cur.execute("select * from system_data")
for row in cur:
item_count += 1
logger.info(row)
return "Added %d items to RDS MySQL table" %(item_count)
But when I am trying to call API with postman, I am getting "internal server error" in postman.
Http status 502 in Api Gateway with Lambda Integration is related to a bad-formatted response from lambda. The valid structure of response is described in this guide
https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format
This is why in lambda test console you get a 200 - OK response as it is a valid general json but testing from Api Gateway is not valid as is not the expected structure.
From your code, the problem is originated due to a non-valid field "messageReason" in response. Try removing this field and include it in headers or body
502 Bad Gateway Exception, usually for an incompatible output returned from a Lambda proxy integration backend and occasionally for out-of-order invocations due to heavy loads.
I had the same problem because I sent response_body=None. Then, I created a Dictionary {}, and it worked.
return {
'statusCode': 200,
'headers':
{
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'OPTIONS,POST'
},
'body': json.dumps(response_body)
}

Why am I getting a 429 error with the Reddit API?

I have been experimenting with flask and the Reddit api, but no matter what I try, I seem to be hitting the 429 'too many requests' error every time I try to receive the access token.
The initial user auth works without any issue, and I receive the code back and can validate that I've auth'd the app in my Reddit settings. I've also made sure to use a unique user-agent, as this seemed to resolve the issue for most people, and am not using any words like 'bot', 'curl', 'web' or anything else which would be a likely blocker.
As far as I'm aware, I am also well under the tolerance of getting rate limited with too many requests.
Given that this is the first time I'm using both flask, and the Reddit API, I'm sure I'm missing something obvious,but after 4 hours, copious Googling and reading all the docs, I don't understand what I'm getting wrong here.
import requests
import requests.auth
from flask import Flask, redirect, request, url_for
import string
import random
app = Flask(__name__)
client_id = "client id of my web app"
client_secret = "client secret of my web app"
base_auth_url = 'https://www.reddit.com/api/v1'
authorization_endpoint = '/authorize'
access_token_endpoint = '/access_token'
#app.route("/login")
def get_the_auth_code():
state = state_generator()
params = {
'client_id': client_id,
'response_type': 'code',
'state': state,
'redirect_uri': 'http://localhost:5000/redditor',
'scope': 'identity',
'user-agent': 'myapp v0.1 by /u/myredditusername'
}
return redirect(url_builder(authorization_endpoint, params))
#app.route("/redditor")
def get_the_access_token():
code = request.args.get('code')
client_auth = requests.auth.HTTPBasicAuth(client_id, client_secret)
post_data = {
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': 'http://localhost:5000/redditor',
'user-agent': 'myapp v0.1 by /u/myredditusername'
}
response = requests.post(base_auth_url + access_token_endpoint, auth=client_auth, data=post_data)
token_json = response.json()
return token_json
def state_generator(size=25, chars=string.ascii_uppercase + string.digits):
return ''.join(random.choice(chars) for _ in range(size))
def url_builder(endpoint, parameters):
params = '&'.join(['%s=%s' % (k, v) for k, v in parameters.items()])
url = '%s%s?%s' % (base_auth_url, endpoint, params)
return url
if __name__ == "__main__":
app.run(debug=True)
With help from the Reddit community (specifically /u/nmtake), I found out where I was going wrong.
In my get_the_access_token() function, I was adding the user-agent field to my data parameters, instead of declaring it as part of the header.
Instead of:
post_data = {
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': 'http://localhost:5000/redditor',
'user-agent': 'myapp v0.1 by /u/myredditusername'
}
response = requests.post(base_auth_url + access_token_endpoint, auth=client_auth, data=post_data)
I am now using the following, which works perfectly:
post_data = {
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': 'http://localhost:5000/redditor',
}
post_headers = {
'user-agent': 'myapp v0.1 by /u/myredditusername'
}
response = requests.post(base_auth_url + access_token_endpoint, auth=client_auth, data=post_data, headers=post_headers)

Google Actions Push Notification with Python?

I'm trying to figure out how to initiate a push notification using DialogFlow/Google Actions. I've cobbled together some test code, but when I test it, I get a '403 Forbidden' response. Can somebody provide me a good example of how to do this using Python 3.
`
import urllib.request
import json
notification = {
'userNotification': {
'title': 'Test Notification',
},
'target': {
'userId': 'user-id here',
'intent': 'Notification Intent',
'locale': 'en-US',
}
}
my_url = 'https://actions.googleapis.com/v2/conversations:send'
access_token = 'access token here'
request = urllib.request.Request(my_url)
request.add_header('Content-Type', 'application/json; charset=utf-8')
payload = { 'auth': { 'bearer': access_token },
'json': 'true',
'body': { 'customPushMessage': notification, 'isInSandbox':
'true' } };
jsondata = json.dumps(payload)
jsondataasbytes = jsondata.encode('utf-8')
response = urllib.request.urlopen(request, jsondataasbytes)
`
Can anybody provide any suggestions about how to get this to work?
=============================================================
Update: I revised the auth header as suggested and now I'm getting '401:Unauthorized'. I'm not sure if I'm creating the access token properly. Here's how I'm doing it:
I created an RSA256 private key on the Google Console website. I use that key to encode a JWT containing these fields:
{
"iss": [
"My Service Name",
"\"my_service-account#service_name.iam.gserviceaccount.com\""
],
"iat": 1544018697,
"exp": 1544019898,
"aud":
"https://www.googleapis.com/auth/actions.fulfillment.conversation\"",
"sub": [
"Push Notification",
"\"my-service-account#service_name.iam.gserviceaccount.com\""
]
}
I don't know if this is correct: the documentation for all of this is hard to pull together.
UPDATE 2:
I modified the code suggested by Prisoner, and I'm now able to get what appears to be a valid access_token. This is the modified code:
from oauth2client.client import GoogleCredentials
service_account_file = 'path-to-service_account_file'
credentials = GoogleCredentials.from_stream(SERVICE_ACCOUNT_FILE)
access_token = credentials.get_access_token().access_token
When I try to use the access token, I'm still getting a '401 Unauthorized' response. Has anybody actually done this? If so, can you give me some specifics on the correct URL to use and how to format the urllib.request message?
You've placed some things into the body of the request that belong in the header. In particular, the "403 forbidden" suggests that the Authorization header is either wrong or missing, and in your case, it looks like it is missing since you're trying to put it in the body.
The body of what you're sending should just contain the JSON with the customPushMessage attribute.
I'm not very familiar with python, but I think something like this is more what you want:
request = urllib.request.Request(my_url)
request.add_header('Authorization', 'bearer '+access_token)
request.add_header('Content-Type', 'application/json; charset=utf-8')
payload = { 'customPushMessage': notification }
jsondata = json.dumps(payload)
jsondataasbytes = jsondata.encode('utf-8')
response = urllib.request.urlopen(request, jsondataasbytes)
If you continue to get the "403 Forbidden" message - make sure your access_token is current and is actually an access token. Access tokens are created from the service account key, but are not the key itself. They have a limited lifetime (usually 1 hour), while the key is long-lasting.
Update about generating an access token from the keys.
You can't just create and sign a JWT to use as an access token.
The easiest way is to use the Google APIs Client Library for Python, which includes a library to handle OAuth with service accounts.
I haven't tested, but you should be able to do something like this, setting SERVICE_ACCOUNT_FILE to the location of where the keys are stored.
from google.oauth2 import service_account
SCOPES = ['https://www.googleapis.com/auth/actions.fulfillment.conversation']
SERVICE_ACCOUNT_FILE = '/path/to/service.json'
credentials = service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES)
access_token = credentials.get_access_token()
Ok, I've figured out how to get the the access_token, so that part of the problem is solved. I've run into another problem getting updates permission working for my test account, but I'm going to post a new question to cover that. I adapted code from another answer and this seems to work:
# Adapted from https://stackoverflow.com/questions/51821919/how-to-send-push-notification-from-google-assistant-through-dialogflow-fulfilmen/51961677#51961677
import io
import json
import requests
from google.oauth2 import service_account
import google.auth.transport.requests
def send_notification(path_to_service_account="default", intent="default"):
if path_to_service_account == "default":
PATH_TO_SERVICE_ACCOUNT = 'path to downloaded service account json file from Google Cloud Console'
else:
PATH_TO_SERVICE_ACCOUNT = path_to_service_account
if intent == "default":
INTENT = 'Notification Intent'
else:
INTENT = intent
REQUIRED_SCOPE = 'https://www.googleapis.com/auth/actions.fulfillment.conversation'
USER_ID = 'user id here'
INTENT = 'Your intent name'
# Get access token
with io.open(PATH_TO_SERVICE_ACCOUNT, 'r', encoding='utf-8') as json_fi:
credentials_info = json.load(json_fi)
credentials = service_account.Credentials.from_service_account_info(
credentials_info, scopes=[REQUIRED_SCOPE])
request = google.auth.transport.requests.Request()
credentials.refresh(request)
headers = {
'Authorization': 'Bearer ' + credentials.token
}
payload = {
'customPushMessage': {
'userNotification': {
'title': 'App Title',
'text': 'Simple Text'
},
'target': {
'userId': USER_ID,
'intent': INTENT,
# Expects a IETF BCP-47 language code (i.e. en-US)
'locale': 'en-US'
}
}
}
r = requests.request("POST", 'https://actions.googleapis.com/v2/conversations:send', data=json.dumps(payload), headers=headers)
print(str(r.status_code) + ': ' + r.text)

Microsoft Face API [find similar] api key error

So I'm trying to follow The microsoft face api documentation here for the "FindSimilar" feature. There is an example at the bottom of the page where I use this code:
########### Python 3.2 #############
import http.client, urllib.request, urllib.parse, urllib.error, base64
headers = {
# Request headers
'Content-Type': 'application/json',
'Ocp-Apim-Subscription-Key': '{api key}',
}
params = urllib.parse.urlencode({
})
try:
conn = http.client.HTTPSConnection('westus.api.cognitive.microsoft.com')
conn.request("POST", "/face/v1.0/findsimilars?%s" % params, "{body}",
headers)
response = conn.getresponse()
data = response.read()
print(data)
conn.close()
except Exception as e:
print("[Errno {0}] {1}".format(e.errno, e.strerror))
I'm getting an error where it tells me my subscription key is invalid, but I checked my azure account status and I see no issues:
b'\n\t\t\t\t\t{"error":{"code":"Unspecified","message":"Access denied due to invalid subscription key. Make sure you are subscribed to an API you are trying to call and provide the right key."}}\n \t\t'
Access denied due to invalid subscription key. Make sure you are subscribed to an API you are trying to call and provide the right key.
It indicates that Invalid subscription Key or user/plan is blocked. I recommand that you could check the APi Key.
headers = {
# Request headers
'Content-Type': 'application/json',
'Ocp-Apim-Subscription-Key': '3c658abc64b348c1a5...',
}

Resources