I'm trying to make twitch notifications for a discord server, but i get a bad request response of the twitch api. Here is my code:
from schildi import utils
from static import config
import requests
import json
client = utils.client
class stream_notifications(utils.cog):
def __init__(self, client):
self.client = client
r = requests.post(
f"https://id.twitch.tv/oauth2/token?client_id={config.CLIENT_ID}&client_secret={config.CLIENT_SECRET}&grant_type=client_credentials")
r_json = r.json()
print(r_json["access_token"])
headers = {
"Authorization": f"Bearer {r_json['access_token']}",
"Client ID": config.CLIENT_ID,
}
twitch_info = requests.get(f"https://api.twitch.tv/helix/search/channels?query=sirellasama",
headers=headers)
print(twitch_info.text)
#utils.cog.listener()
async def on_ready(self):
print(1)
def setup(client)
client.add_cog(stream_notifications())
As response i get 400 Bad Request, but the channel i am searcing for exists and i gave the required "query" parameter.
Can anyone help me with this error? I don't know what i am doing wrong
I've fixed the problem by myself, just by making a new application.
Related
I have a Spotify project needing authorization codes through their API. I built an API to redirect the user to Spotify's login and then turn back to my API along with the user's code.
The API:
import boto3
import requests
import base64
from fastapi import APIRouter
from fastapi.responses import RedirectResponse
from mangum import Mangum
from client import client_id, client_secret
app = APIRouter()
lambda_handler = Mangum(app,lifespan='off')
url = 'https://XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.lambda-url.me-south-1.on.aws'
#app.get("/")
async def root():
return RedirectResponse("/login/")
#app.get("/home/")
async def main(code: str):
encoded = base64.b64encode(
(client_id + ":" + client_secret).encode("ascii")
).decode("ascii")
base = "https://accounts.spotify.com/api/token"
payload = {
"grant_type": "authorization_code",
"code": code,
"redirect_uri": f"{url}/home/",
}
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": "Basic " + encoded,
}
tokens = requests.post(base, data=payload, headers=headers).json()
refresh_token = tokens["refresh_token"]
access_token = tokens["access_token"]
email_base_url = "https://api.spotify.com/v1/me"
email_headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": f"Bearer {access_token}",
}
email = requests.get(email_base_url, headers=email_headers).json()["email"]
dynamo = boto3.resource("dynamodb")
tokens = dynamo.Table("tokens")
item = {"email": email, "token": access_token, "refresh_token": refresh_token}
tokens.put_item(Item=item)
return {"message": "success"}
#app.get("/login/")
async def login():
base = "https://accounts.spotify.com/authorize?"
base += "response_type=code"
base += f"&client_id={client_id}"
base += "&scope=user-read-recently-played user-read-email"
base += f"&redirect_uri={url}/home/"
return RedirectResponse(base)
The API works as intended when I run it on localhost or use an ngrok tunnel. However, when I upload it to AWS Lambda and generate a function URL, the browser returns the error ERR_TOO_MANY_REDIRECTS for any of the three endpoints.
I've seen questions about this topic, but they all included CloudFront. I would be happy to provide information about the Lambda function as necessary.
Does it makes a different if you call baseurl/ vs baseurl/login ?
If someone is already logged in and hits the baseurl you redirecting him.
/ -> /login -> spotify -> /home
You can try sort / -> /login into one as its still just a redirection.
Update 1:
Do not use the same variable base in login and main.
Log the urls you are redirecting. This will help you understand what is going on.
Urlencode the url before passing it as url param. base += f"&redirect_uri= {urlencode(url)}/home/"
i am new in this part of programming and i have few questions. First of all my project. At one side i have a Flutter App and at the other side a MS SQL Server with data. This data i need on my device logically. I read the best way is to use FastAPI, its easy and has a good performance but i am not sure about security. I read something about OAuth2 but it looks to much because just one user will have permission to use the data (the server owner). Is it possible just to use a simple api key as a parameter? Something like this...
from fastapi import FastAPI
from SqlServerRequest import SqlServerRequest
app = FastAPI()
#app.get("/openOrders/{key}")
async def openOrders(key):
if key == "myverysecurekey":
return "SQLDATA"
else
return "Wrong key"
That way works but i am not sure about the security
What would you say?
I have been dealing with the same issue for a while. Instead of using a oauth I needed a simple X-API-Key in the header.
You can do that with the following code
from fastapi import FastAPI, Depends
from fastapi.security import APIKeyHeader
import os
os.environ['API-KEY'] = '1234'.
# You would use as an environment var in real life
X_API_KEY = APIKeyHeader(name='X-API-Key')
def api_key_auth(x_api_key: str = Depends(X_API_KEY)):
""" takes the X-API-Key header and validate it with the X-API-Key in the database/environment"""
if x_api_key != os.environ['API-KEY']:
raise HTTPException(
status_code=401,
detail="Invalid API Key. Check that you are passing a 'X-API-Key' on your header."
)
app = FastAPI()
#app.get("/do_something", dependencies=[Depends(api_key_auth)])
async def do_something():
return "API is working OK."
If your use case is just to serve a single user, and is not mission-critical, this might be a good way to start.
main.py
import os
import uvicorn
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from starlette import status
# Use token based authentication
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# Ensure the request is authenticated
def auth_request(token: str = Depends(oauth2_scheme)) -> bool:
authenticated = token == os.getenv("API_KEY", "DUMMY-API-KEY")
return authenticated
app = FastAPI()
#app.get("/openOrders")
async def open_orders(authenticated: bool = Depends(auth_request)):
# Check for authentication like so
if not authenticated:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Not authenticated")
# Business logic here
return {"message": "Authentication Successful"}
if __name__ == '__main__':
uvicorn.run("main:app", host="127.0.0.1", port=8080)
You can run this using python main.py
The client can then make requests like so:
import requests
url = "http://127.0.0.1:8080/openOrders"
payload={}
# The client would pass the API-KEY in the headers
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer DUMMY-API-KEY'
}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)
Client code in Dart
final response = await http.get(
Uri.parse('http://127.0.0.1:8080/openOrders'),
// Send authorization headers to the backend.
headers: {
HttpHeaders.authorizationHeader: 'Bearer DUMMY-API-KEY',
},
);
After reading the documentation I am struggling to understand how to use Authlib to implement Authorize Code Flow for an OpenID Connect provider. After reading the documentation I have had a go at implementing the following code listed below.
The /login endpoint uses authlib to redirect to authorization of Identity Provider, in this case Cognito. This redirects to /aws_cognito_redirect which I have currently implemented myself to resolve the code challenge to retrieve tokens.
My questions are:
How to use authlib to also resolve the code challenge instead of implementing this part myself?
Does Authlib provide functionality to return token(s) in HTTP Only cookie and verify tokens in subsequent requests containing the cookie? For example, does Authlib allow an endpoint to be decorated/marked to as protected, in which case it will verify the tokens in HTTP Only cookie?
Update
After inspecting the source code I eventually figured out how to resolve the code challenge using Authlib with FastAPI. The source code is included at the end of this question.
I am leaving the question open since the second part remains unanswered.
Currently, this question suggests that it is possible to use ResourceProtector class that would do what I need. However, that has a parse_request_authorization method that inspects the Authorisation header of a request for a bearer token. So...I am assuming the approach is to subclass ResourceProtector class and override this method to inspect request for HTTP only cookie and extract the JWT contained within for verification?? Is this feature implemented and provided by Authlib?
Alternatively, also investigating to see if I can integrate fastapi-login to achieve this functionality.
Appendix: Source Code
Initial Source Code With Custom Implementation For Resolving Code Challenge
import base64
from functools import lru_cache
import httpx
from authlib.integrations.starlette_client import OAuth
from fastapi import Depends, FastAPI, Request, Response
from fastapi.responses import RedirectResponse
from starlette.middleware.sessions import SessionMiddleware
from . import config
#lru_cache()
def get_settings() -> config.Settings:
"""Create config settings instance encapsulating app config."""
return config.Settings()
def get_auth_base_url(region: str, userpool_id: str) -> str:
# base_url = "https://cognito-idp.us-east-2.amazonaws.com/us-east-2_QqNgzdtT5"
base_url = "https://cognito-idp." + region + ".amazonaws.com/" + userpool_id
return base_url
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="secretly")
oauth = OAuth()
oauth.register(
"cognito",
client_id=get_settings().client_id,
client_secret=get_settings().client_secret,
server_metadata_url=get_auth_base_url(
get_settings().region, get_settings().userpool_id
)
+ "/.well-known/openid-configuration",
client_kwargs={"scope": "openid email"},
)
def encode_auth_header(client_id: str, client_secret: str) -> str:
"""Encode client id and secret as base64 client_id:client_secret."""
secret = base64.b64encode(
bytes(client_id, "utf-8") + b":" + bytes(client_secret, "utf-8")
)
return "Basic " + secret.decode()
#app.get("/login")
async def login(request: Request):
"""Redirect to /aws_cognito_redirect endpoint."""
cognito = oauth.create_client("cognito")
redirect_uri = request.url_for("read_code_challenge")
return await cognito.authorize_redirect(request, redirect_uri)
#app.get("/aws_cognito_redirect")
async def read_code_challenge(
request: Request,
response: Response,
settings: config.Settings = Depends(get_settings),
):
"""Retrieve tokens from oauth2/token endpoint and return session cookie."""
code = request.query_params["code"]
print("/aws_cognito_redirect received code := ", code)
auth_secret = encode_auth_header(settings.client_id, settings.client_secret)
headers = {"Authorization": auth_secret}
print("Authorization:" + str(headers["Authorization"]))
payload = {
"client_id": settings.client_id,
"code": code,
"grant_type": "authorization_code",
"redirect_uri": settings.redirect_uri,
}
token_url = (
"https://"
+ settings.domain
+ ".auth."
+ settings.region
+ ".amazoncognito.com/oauth2/token"
)
async with httpx.AsyncClient() as client:
tokens = await client.post(
token_url,
data=payload,
headers=headers,
)
tokens.raise_for_status()
print("Tokens\n" + str(tokens.json()))
response.set_cookie(key="jwt", value=tokens.content, httponly=True)
Updated Source Code To Demonstrate How To Resolve Code Challenge Using Authlib
import base64
from functools import lru_cache
from authlib.integrations.starlette_client import OAuth
from fastapi import FastAPI, Request
from starlette.middleware.sessions import SessionMiddleware
from . import config
#lru_cache()
def get_settings() -> config.Settings:
"""Create config settings instance encapsulating app config."""
return config.Settings()
#lru_cache
def get_auth_base_url(region: str, userpool_id: str) -> str:
"""Return cognito discover points base url from region and userpool ID."""
return ("https://cognito-idp." + region + ".amazonaws.com/" + userpool_id)
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="some-random-string")
oauth = OAuth()
cognito = oauth.register(
"cognito",
client_id=get_settings().client_id,
client_secret=get_settings().client_secret,
server_metadata_url=get_auth_base_url(
get_settings().region, get_settings().userpool_id
)
+ "/.well-known/openid-configuration",
client_kwargs={"scope": "openid email"},
)
def encode_auth_header(client_id: str, client_secret: str) -> str:
"""Encode client id and secret as base64 client_id:client_secret."""
secret = base64.b64encode(
bytes(client_id, "utf-8") + b":" + bytes(client_secret, "utf-8")
)
return "Basic " + secret.decode()
#app.get("/")
async def login(request: Request):
"""Redirect to /aws_cognito_redirect endpoint after sign-in."""
redirect_uri = request.url_for("read_code_challenge")
return await cognito.authorize_redirect(request, redirect_uri)
#app.get("/aws_cognito_redirect")
async def read_code_challenge(request: Request):
"""Request a token from cognito using code challenge response."""
return await cognito.authorize_access_token(request)
I have the below requests for GET & POST and the GET's work fine and the post shows a 200 response but when i check the external program it has not received any data from the post.
import requests
import json
class BearerAuth(requests.auth.AuthBase):
def __init__(self, token):
self.token = token
def __call__(self, r):
r.headers["authorization"] = "Bearer " + self.token
return r
class BearerAuth2(requests.auth.AuthBase):
def __init__(self, token):
self.token = token
def __call__(self, s):
s.headers["authorization"] = "api " + self.token
return s
headers={'content-type': 'application/json'}
response = requests.get('https://api', auth=BearerAuth('123'))
response2 = requests.get('https://api2', auth=BearerAuth('123'))
splunktok = requests.post('https://http-inputs', data={response, respons2}, headers=headers, auth=BearerAuth2('456'))
print(response.json(), response2.json())
What i want to do is get all the response body data from response & response 2 and use it in the POST to my external program. im not sure if i need to store the response as raw in variables first?
You are sending the requests.Response objects as a set:
data={response, respons2}
Assuming you intend to send the JSON responses, you can do something like:
data={**response.json(), **respons2.json()}
Ive worked this out. I added the below
after the first request i add
payload=response.json()
and then on the post i added
data=json.dumps(payload)
I've set up a webhook in a chat room in my Google Hangouts Chat.
I can successfully run their example code, which generates a message from the bot associated with the webhook in the chat:
from httplib2 import Http
from json import dumps
#
# Hangouts Chat incoming webhook quickstart
#
def main():
url = '<INCOMING-WEBHOOK-URL>'
bot_message = {
'text' : 'Hello from Python script!'}
message_headers = { 'Content-Type': 'application/json; charset=UTF-8'}
http_obj = Http()
response = http_obj.request(
uri=url,
method='POST',
headers=message_headers,
body=dumps(bot_message),
)
print(response)
if __name__ == '__main__':
main()
However, I wish to send this message using standard library packages, such as urllib.
But when I use urllib and run the below code, I get an urllib.error.HTTPError: HTTP Error 400: Bad Request. Why am I getting this error?
import json
import urllib.parse
import urllib.request
def main():
# python 3.6
url = '<INCOMING-WEBHOOK-URL>'
bot_message = {'text': 'Hello from Python script!'}
message_headers = {'Content-Type': 'application/json; charset=UTF-8'}
url_encoded = urllib.parse.urlencode(bot_message)
byte_encoded = url_encoded.encode('utf-8')
req = urllib.request.Request(url=url, data=byte_encoded, headers=message_headers)
response = urllib.request.urlopen(req)
print(response.read())
if __name__ == '__main__':
main()
The difference is in the body format. In the first version, you dump into json, while in the second you urlencode it.
replace
url_encoded = urllib.parse.urlencode(bot_message)
byte_encoded = url_encoded.encode('utf-8')
with
byte_encoded = json.dumps(bot_message).encode('utf-8')