FastAPI as backend for Telethon - fastapi

I'm trying to make api auth with telethon work. I'm sending request to endpoint where telegram client is initialized and trying to send code request to telegram. But there is input() and I didn't find any solution to pass code as variable
#router.get('/code')
async def send_code_request(phone: str):
client = get_telegram_client(phone)
await client.start(phone)
return {'msg': 'code sent'}

I found easier solution, but there is one con - when authorizing via session sign_in() method is requiring to execute send_code_request() method first so there is will be 2 same code messages
async def get_telegram_client(session: str = None) -> TelegramClient:
return TelegramClient(
StringSession(session),
api_id=settings.TELEGRAM_API_ID,
api_hash=settings.TELEGRAM_API_HASH
)
#router.post('/code')
async def send_authorizarion_code(payload: TelegramSendCode):
client = await get_telegram_client()
await client.connect()
try:
await client.send_code_request(payload.phone)
except FloodWaitError as e:
return {
'FloodWaitError': {
'phone_number': e.request.phone_number,
'seconds': e.seconds
}}
else:
return {
'msg': 'code sent',
'session': client.session.save()
}
#router.post('/auth')
async def authorize(payload: TelegramAuth):
client = await get_telegram_client(payload.session)
await client.connect()
await client.send_code_request(payload.phone)
await client.sign_in(code=payload.code, phone=payload.phone)
return {'msg': 'signed in'}

I'm assuming you're using .start() for that.
.start() accepts a callback that is by default input() you can pass your own input like so.
client.start(code_callback=your_callback) and your callback should should return the code.
This can all be found here in the start docs

Related

How to make a node.js Asynchronous API call and use a returned value?

It seems like has been asked a 1000 times but in reading all of the responses I still cannot seem to figure out how to make this work.
Here is my use case.
Make a call to an Auth Endpoint and return an access token
do some updates to the options of a 2nd API call
make the next api call with the updated options
I know how to make this work with traditional promises or callbacks but in my case step 1 & 2 could be optional so what I want to do is if step 1 & 2 are required call an async function to get the token and update the options. If 1 & 2 are not required then just make the 2nd API call.
I am trying to use axios but no matter what I do the response is either undefined or
Here is my code, can someone please explain the best way to do this, or is it just easier to use traditional promise\callbacks?
var options = {
'method': httpVerb ,
'url': url,
'headers': {
'Content-Type': 'application/json'
},
body: JSON.stringify(bodyArgs)
};
if(authBody){
auth = getAuth(authBody)
options.headers["Authorization"] = "Bearer " + auth.access_token;
console.log(auth)
}
const getAuth = async (options) => {
try{
const resp = await axios.post(options.authURL, options )
if(resp.status === 200){
return resp.data;
}else{
return ""
}
}catch(err){
console.error(err);
}
}
auth = getAuth(authBody) // wrong
getAuth is an async function, and async functions always return Promises even if the return statements within them return simple values.
If you want to wait for the return value of getAuth, you're going to have to do so in a then function on the return value, or await the result from within a different async function. Consequently, if you have an optional asynchronous step, it is safer to make the whole process appear asynchronous to abstract away the cases where you need to wait to refresh the token.

get_current_user doesn't work (OAuth2PasswordBearer problems)

This is actually the first time it doesn't work, I mean I've practiced this before, but now I have no idea what's wrong.
So I am trying to implement basic function get_current_user for FastAPI , but somehow it doesn't work.
When I try in swagger Authorization works fine, but endpoint with current user simply doesn't work.
So this is part that belongs to endpoint file:
router = APIRouter(prefix='/api/v1/users')
router1 = APIRouter()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='/api-token-auth/')
#router1.post('/api-token-auth/')
async def auth(form: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
user = await utils.get_user_by_username(form.username, db) # type: User
if not user:
raise HTTPException(status_code=400, detail="Incorrect username or password")
if not utils.validate_password(form.password, user.hashed_password):
raise HTTPException(status_code=400, detail="Incorrect username or password")
return await utils.create_token(user.id, db)
async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
print(token)
user = await utils.get_user_by_token(token, db)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
return user
#router.get("/me", response_model=DisplayUser)
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_user
and this is function that creates token (I have checked and it is 1000% works and returns string):
async def create_token(user_id: int, db: Session):
"""Token generation"""
letters = string.ascii_lowercase
token = ''.join(random.choice(letters) for _ in range(25))
created_token = Token(
expires=datetime.now() + timedelta(weeks=2),
user_id=user_id,
token=token
)
db.add(created_token)
db.commit()
db.refresh(created_token)
token = AuthUser.from_orm(created_token)
return token.token
But when I print(token) in get_current_user function it prints undefined . And I dunno why. Am I using dependency wrong or something?
Thanks in advance!
Since it prints undefined it seems like the frontend is expecting the response in a different format (since undefined is what using an undefined object key in Javascript as a key will result in).
The OAuth2 response should have the token under access_token by default:
access_token (required) The access token string as issued by the authorization server.
token_type (required) The type of token this is, typically just the string “bearer”.
Example response from the above link:
{
"access_token":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3",
"token_type":"bearer",
"expires_in":3600,
"refresh_token":"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk",
"scope":"create"
}
In your "create_token(user.id, db)" ensure the returned token contains these two values.
{
"access_token":"",
"token_type":"bearer"
}

Best way to use httpx async client and tenacity?

I'm getting fairly different results with two different implementations.
Here is implementation 1
request_semaphore = asyncio.Semaphore(5)
async def _send_async_request(client: AsyncClient, method, auth, url, body):
async with request_semaphore:
try:
async for attempt in AsyncRetrying(stop=stop_after_attempt(3), wait=wait_fixed(1)):
with attempt:
response = await client.request(method=method, url=url, auth=auth, json=body)
response.raise_for_status()
return response
except RetryError as e:
pass
And here is implementation 2:
request_semaphore = asyncio.Semaphore(5)
#retry(stop=stop_after_attempt(3), wait=wait_fixed(1))
async def _send_single_async_request(self, client: AsyncClient, method, auth, url, body):
async with request_semaphore:
response = await client.request(method=method, url=url, auth=auth, json=body)
response.raise_for_status()
return response
async def _send_async_request(self, client: AsyncClient, method, auth, url, body):
try:
await self._send_single_async_request(client, method, auth, request)
except RetryError as e:
pass
I'm testing it against a stable REST API. Here are the bench marks:
100 successful POST requests:
Implementation 1: 0:59 mins
Implementation 2: 0:57 mins
100 failed POST requests:
Implementation 1: 3:26 mins
Implementation 2: 2:09 mins
These results are consistent. Can anyone help me understand why my first implementation is slower than my second?
edit: FYI, here's how i'm calling the above functions (the above funcs actually receive a request tuple with url and body, edited it for clarity)
async def _prepare_async_requests(method, auth, requests):
async with AsyncClient() as client:
task_list = [self._send_async_request(client, method, auth, request) for request in requests]
return [await task for task in asyncio.as_completed(task_list)]
def send_async_requests(auth, method, requests):
loop = asyncio.get_event_loop()
responses = loop.run_until_complete(self._prepare_async_requests(method, auth, requests))
return responses

Github OAuth using Firebase - how to get user's username

I followed the Firebase's guide on how to authenticate with Github. https://firebase.google.com/docs/auth/web/github-auth
The return result from Firebase's signInWithRedirect method contains the user's displayName and email, etc. However, it doesn't seem to contain user's 'login' username which is the key for invoking most of Github's API calls.
I am sure there is a way to get it, but I just can't seem to find any documentation. Does anyone happen to know how to solve it?
I ended up using Github's API to get user's username with accessToken.
You should be able to get the user's GitHub username through a parameter called "username" (see more here: https://github.com/firebase/firebase-simple-login/blob/master/docs/v1/providers/github.md)
Note: firebase-simple-login was deprecated on October 3th, 2014
You can use get the authenticated user from this GitHub's api
Or if you use octokit javascript rest api client, you can do something like this
octokit = new Octokit({auth: userAccessToken })
octokit.users.getAuthenticated()
.then(result => {
console.log(result.data.login) // this is the username
})
Note: you'll get accessToken after GitHub <-> firebase login
Hope this is helpful!
You can get the username in additionalUserInfo:
const githubProvider = new firebaseClient.auth.GithubAuthProvider();
githubProvider.addScope('read:user');
githubProvider.setCustomParameters({
allow_signup: false,
});
firebaseClient.initializeApp(clientConfig);
async function submit() {
try {
const response = await firebaseClient
.auth()
.signInWithPopup(githubProvider);
console.log(response.additionalUserInfo);
} catch (error) {
alert(error);
}
}
You Can use email to do authorized requests insted username:
Username: mayGitHubEmail#mail.com
Password: accessToken
like this with Postman
body sent
Here is a sample using class func in Swift using Alamofire and SwiftyJSON pods:
import Alamofire
import SwiftyJSON
enum NetworkError: Error {
case url
case server
case auth
}
class GistServices {
class func makePostApiCall(toUrl path: String, withBody parameters: JSON, usingCredentials: Bool = false) -> Result<Data?, NetworkError> {
guard let url = URL(string: path) else {
return .failure(.url)
}
if let email = UserAuthSingleton.shared.get(), let password = UserAuthSingleton.shared.getUserToken() {
var result: Result<Data?, NetworkError>!
var request = AF.request(url, method: .post, parameters: parameters)
if(usingCredentials){
let credentialData = "\(email):\(password)".data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))!
let base64Credentials = credentialData.base64EncodedString()
let headers = [HTTPHeader(name: "Authorization", value: "Basic \(base64Credentials)"),
HTTPHeader(name: "Accept", value: "application/json"),
HTTPHeader(name: "Content-Type", value: "application/json")]
request = AF.request(url, method: .post, parameters: parameters.dictionaryValue, encoder: JSONParameterEncoder.default, headers: HTTPHeaders(headers))
}
request
.validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.response { (response) in
switch response.result {
case .failure(_):
result = .failure(.server)
case .success(let value):
result = .success(value)
}
}
return result
}
return .failure(.auth)
}
}

python3.5: with aiohttp is it possible to serve several responses concurently?

I'm using the latest version (1.0.2) of aiohttp with python3.5 I have the following server code
import asyncio
from aiohttp.web import Application, Response, StreamResponse, run_app
async def long(request):
resp = StreamResponse()
name = request.match_info.get('name', 'Anonymous')
resp.content_type = 'text/plain'
for _ in range(1000000):
answer = ('Hello world\n').encode('utf8')
await resp.prepare(request)
resp.write(answer)
await resp.write_eof()
return resp
async def init(loop):
app = Application(loop=loop)
app.router.add_get('/long', long)
return app
loop = asyncio.get_event_loop()
app = loop.run_until_complete(init(loop))
run_app(app)
If I then run two curl requests curl http://localhost:8080/long in different terminals, only the first one will receive data
My thought was that using asyncio you could, in a monothreaded code, start serving other response, while an other is waiting for I/O
Most of the code I found online about concurent+asyncio only talks about the client side, but not server side
Am I missing something or is my comprehension of how asyncio works is flawed ?
Just push await resp.drain() after resp.write() for giving aiohttp a chance to switch between tasks:
import asyncio
from aiohttp.web import Application, Response, StreamResponse, run_app
async def long(request):
resp = StreamResponse()
name = request.match_info.get('name', 'Anonymous')
resp.content_type = 'text/plain'
await resp.prepare(request) # prepare should be called once
for _ in range(1000000):
answer = ('Hello world\n').encode('utf8')
resp.write(answer)
await resp.drain() # switch point
await resp.write_eof()
return resp
async def init(loop):
app = Application(loop=loop)
app.router.add_get('/long', long)
return app
loop = asyncio.get_event_loop()
app = loop.run_until_complete(init(loop))
run_app(app)

Resources