I'm a little unsure how I should be implementing an async await setup in Flask, specifically an extension called flask-mailing (not flask-mail)
I have a file called jobs.py which has this block at the bottom:
async def send_registration_email(uid, token):
message = Message(
subject="Flask-Mailing module",
recipients=["test#yahoo.com"],
body="This is the basic email body",
)
await mail.send_message(message)
return jsonify(status_code=200, content={"message": "email has been sent"})
In my views.py I have this:
#auth.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterUserForm()
if form.validate_on_submit():
user = User.create(
username=form.data['username'],
email=form.data['email'],
password=form.data['password'],
remote_addr=request.remote_addr,
)
s = URLSafeSerializer(current_app.secret_key)
token = s.dumps(user.id)
send_registration_email(user.id, token)
flash(
(
'Sent verification email to {email}'.format(
email=user.email
)
),
'success'
)
return redirect(url_for('home.index'))
return render_template('register.html', form=form)
The error I get is that no email is sent, and when I shut down the server I get this error:
RuntimeWarning: coroutine 'send_registration_email' was never awaited
Is it right that both the await and return should also be within the jobs.py or is it only the function send_registration_email that should be there?
Related
I am using some basic oauth2 authentication in Python, fast api. I want to hide all of the swagger docs behind a login, and ideally I would want all the login logic to be handled by the oauth2scheme, such that when a user logs in to see the docs, they do not need to reauthenticate in order to test any of the endpoints.
The closest I have gotten to a solution is logging in the user by saving a cookie to the request, then overwriting the swagger ui to check the cookie and logout the user. The only functionality I am missing is having the user be automatically authenticated once entering the docs based on this cookie. Relevant code below:
#dbparse.get("/", include_in_schema=False, response_class=HTMLResponse)
async def login_page(request: Request):
return templates.TemplateResponse("login.html", {"request": request})
#dbparse.post("/login", include_in_schema=False)
async def perform_login(
request: Request,
response: Response,
username: Optional[str] = Form(None),
password: Optional[str] = Form(None),
):
try:
if not (username and password):
return RedirectResponse(
"/?error=true", status_code=status.HTTP_303_SEE_OTHER
)
# Fake an oauth login form to route login attempts to previously written code
form = OAuth2PasswordRequestForm(username=username, password=password, scope="")
token = await generate_token(form)
response = RedirectResponse("/docs", status_code=status.HTTP_302_FOUND)
# Set the user's login cookie so that we can have them auth'd later
response.set_cookie(
key="Authorization",
value=f"{token['token_type'].capitalize()} {token['access_token']}",
)
return response
except:
return RedirectResponse("/?error=true", status_code=status.HTTP_303_SEE_OTHER)
#dbparse.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html(request: Request):
if request.cookies:
try:
token = await generate_token(None, request.cookies)
except:
response = RedirectResponse("/?expired=true", status_code=status.HTTP_303_SEE_OTHER)
response.delete_cookie(key="Authorization")
return response
return get_swagger_ui_html(...)
#dbparse.post("/token", include_in_schema=False)
async def generate_token(form_data: OAuth2PasswordRequestForm = Depends(), cached_token = None):
print("==========================\nGENERATE_TOKEN()\n==========================")
if cached_token:
try:
_, access_token = cached_token["Authorization"].split(" ")
_ = decode_token(access_token)
# Ensure that the token in cookies is working. (Confirmed to work as intended)
return {"access_token": access_token, "token_type": "bearer"}
except:
raise
user = authenticate(form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid username/password",
)
token = create_token(user)
return {"access_token": token, "token_type": "bearer"}
If there is an easier solution, I was unable to find it. The key functionality I am looking for is to use a cookie to authenticate the swagger docs for endpoint use. The endpoints need to require authentication, but ideally when you log in before seeing the docs, this authentication should carry over.
I am looking to get a simple login sequence on fastapi:
following This tutorial
from fastapi import FastAPI, Depends, HTTPException
from starlette.config import Config
from starlette.requests import Request
from starlette.middleware.sessions import SessionMiddleware
from starlette.responses import HTMLResponse, RedirectResponse
from authlib.integrations.starlette_client import OAuth, OAuthError
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key="!secret")
config = Config(".env")
oauth = OAuth(config)
CONF_URL = "https://accounts.google.com/.well-known/openid-configuration"
oauth.register(
name="google",
server_metadata_url=CONF_URL,
client_kwargs={"scope": "openid email profile"},
)
#app.get("/")
async def home(request: Request):
user = request.session.get("user")
if user is not None:
email = user["email"]
html = (
f"<pre>Email: {email}</pre><br>"
'documentation<br>'
'logout'
)
return HTMLResponse(html)
return HTMLResponse('login')
#app.get("/login", tags=["authentication"])
async def login(request: Request):
redirect_uri = request.url_for("auth")
return await oauth.google.authorize_redirect(request, redirect_uri)
#app.get("/auth")
async def auth(request: Request):
try:
token = await oauth.google.authorize_access_token(request)
except OAuthError as error:
return HTMLResponse(f"<h1>{error.error}</h1>")
user = token.get("userinfo")
if user:
request.session["user"] = dict(user)
return RedirectResponse(url="/")
#app.get("/logout", tags=["authentication"])
async def logout(request: Request):
request.session.pop("user", None)
return RedirectResponse(url="/")
# Try to get the logged in user
async def get_user(request: Request) -> Optional[dict]:
user = request.session.get("user")
if user is not None:
return user
else:
raise HTTPException(status_code=403, detail="Could not validate credentials.")
return None
# Endpoint to protect
#app.get("/other_endpoint/")
async def other_endpoint_function(
user: Optional[dict] = Depends(get_user), id: str
):
# Chek if other is authenticated
if user.is_authenticated:
function()
else:
# First redirect to the login page
RedirectResponse("login")
# Once logged in re-run the initial request
RedirectResponse(f"/other_endpoint/{id}")
I am looking to protect only the other_endpoint the homepage has a link to the login page.
check that the user is authenticated before running the function
If the user is authenticated, run function
If the user is not authenticated
redirect to the login page
re-run the function after authentication
So far, I have tried multiple implementations with RedirectResponse, but i ends up bypassing the authentication
Sorry for asking a basic thing. new to Python and Django ,
I want to resend email if the OTP from PUT request is incorrect.
I have a function which send email with otp automatically on Register.
But if user PUT incorrect OTP I want to resend that email with new otp, So I want to merge sent_email_otp into verifyEmail function.
So how could I achieve that?
#receiver(post_save, sender=CustomUser)
def send_email_otp(sender, instance, created, **kwargs):
if created:
try:
subject = "Your email needs to be verified to use site"
message = f'Hi, Dear {instance.name} use this following OTP to Get verified your email : OTP({instance.otpForEmail})'
email_from = settings.EMAIL_HOST_USER
recipient_list = [instance.email]
send_mail(subject, message, email_from, recipient_list)
print(f"Email Sent to {instance.email}")
except Exception as e:
print(e)
print("Something Wrong at send_email_otp")
#api_view(['PUT'])
#permission_classes([IsAuthenticated])
def verifyEmail(request, pk):
user = CustomUser.objects.get(id=pk)
data = request.data
otp_to_verify = data['otpForEmail']
if otp_to_verify == user.otpForEmail:
user.isEmailVerified = True
user.save()
message = {'detail': 'Your email is now verified'}
return Response(message, status=status.HTTP_400_BAD_REQUEST)
else:
message = {
'detail': 'OTP is not valid and expired, Use New OTP which we have sent you on the email'}
return Response(message, status=status.HTTP_400_BAD_REQUEST)
Edit:
If I simply call the send_email_otp() inside else statement of verifyEmail then this error comes :
TypeError: send_email_otp() missing 3 required positional arguments: 'sender', 'instance', and 'created'
You cant call the signal directly unless you provide it the expected input.
You will need to create another function for sending the otp. and call it in the post_save signal and in the view
def send_otp(name, email, otpForEmail):
subject = "Your email needs to be verified to use site"
message = f'Hi, Dear {name} use this following OTP to Get verified your email : OTP({otpForEmail})'
email_from = settings.EMAIL_HOST_USER
recipient_list = [email]
send_mail(subject, message, email_from, recipient_list)
print(f"Email Sent to {email}")
#receiver(post_save, sender=CustomUser)
def send_email_otp_on_post_save(sender, instance, created, **kwargs):
if created:
try:
send_otp(instance.name, instance.email, instance.otpForMail)
except Exception as e:
print(e)
print("Something Wrong at send_email_otp")
#api_view(['PUT'])
#permission_classes([IsAuthenticated])
def verifyEmail(request, pk):
user = CustomUser.objects.get(id=pk)
data = request.data
otp_to_verify = data['otpForEmail']
if otp_to_verify == user.otpForEmail:
user.isEmailVerified = True
user.save()
message = {'detail': 'Your email is now verified'}
return Response(message, status=status.HTTP_400_BAD_REQUEST)
else:
message = {
'detail': 'OTP is not valid and expired, Use New OTP which we have sent you on the email'}
send_otp(user.name, user.email, user.otpForEmail)
return Response(message, status=status.HTTP_400_BAD_REQUEST)
It would be hard for you to configure the same function to send_mail again to send OTP, as you said this sends the email when the user gets registered. So why not modify the verifyEmail itself.
First, you don't need a user instance here as the user is already authenticated and you already have the User Id.
So in the else statement of verifyEmail, you can send_email without calling send_email_otp() function.
Update the verifyEmail to.
#api_view(['PUT'])
#permission_classes([IsAuthenticated])
def verifyEmail(request, pk):
user = CustomUser.objects.get(id=pk)
data = request.data
otp_to_verify = data['otpForEmail']
if otp_to_verify == user.otpForEmail:
user.isEmailVerified = True
user.save()
message = {'detail': 'Your email is now verified'}
return Response(message, status=status.HTTP_400_BAD_REQUEST)
else:
subject = "Your email needs to be verified to use site "
message = f'Hi, Dear {user.name} use this following OTP to Get verified your email : OTP({user.otpForEmail})'
email_from = settings.EMAIL_HOST_USER
recipient_list = [user.email]
send_mail(subject, message, email_from, recipient_list)
print(f"Email Sent to {user.email}")
message = {
'detail': 'OTP is not valid and expired, Use New OTP which we have sent you on the email'}
return Response(message, status=status.HTTP_400_BAD_REQUEST)
I am working on this code. I am getting validation error 422 on this code and am not able to figure it out the issue
main.py
#app.post("/loginsuccess/", response_class=HTMLResponse)
async def login_success(request: Request, username: str = Form(...), password: str = Form(...)):
p = await User_Pydantic.from_tortoise_orm(await User.get(username=username, password=password))
json_compatible_item_data = jsonable_encoder(p)
if json_compatible_item_data is not None:
logger.info("Logged in Successfully")
return templates.TemplateResponse("homepage.html", {"request": request, "username":username})
else:
status_code:int
status_code = 500
logger.error("Invalid Credentials")
return templates.TemplateResponse("index.html", {"request":request, "status_code":status_code})
The error i have given the screenshot below
enter image description here
You should be able to get the form values directly from the request
async def login_success(request: Request):
form = await self.request.form()
username = form.get('username', '').lower().strip()
password = form.get('password', '').strip()
This should help you find where your error is. If username and password are empty, then you'll want to double check your HTML form
I have been churning through the software development recently and have had some success bending celery to my will.
I have used it successfully to send emails, and have just tried to use almost exactly the same code (after restarting all processes etc) to send sms through Twilio.
However I keep getting the following issue:
File "/Users/Rob/Dropbox/Python/secTrial/views.py", line 115, in send_sms
send_sms.delay(recipients, form.text.data)
AttributeError: 'function' object has no attribute 'delay'
My code is as follows:
#celery.task
def send_email(subject, sender, recipients, text_body):
msg = Message(subject, sender=sender)
for email in recipients:
msg.add_recipient(email)
msg.body = text_body
mail.send(msg)
#celery.task
def send_sms(recipients, text_body):
for number in recipients:
print number
num = '+61' + str(number)
print num
msg = text_body + 'this message to' + num
client.messages.create(to=num, from_="+14804054823", body=msg)
send_email.delay when called from my views.py works perfectly, however send_sms.delay fails every time with the above error.
Any help on troubleshooting this is appreciated.
-- As requested:
#app.route('/send_mail', methods=['GET', 'POST'])
#roles_accepted('Admin')
def send_mail():
form = SendMailForm(request.form)
if request.method == 'POST':
if form.validate_on_submit():
emails = db.session.query(User.email).all()
list_emails = list(zip(*emails)[0])
send_email.delay('Subject', 'sender#example.com', list_emails, form.text.data)
return render_template('send_generic.html', form=form)
#app.route('/send_sms', methods=['GET', 'POST'])
#roles_accepted('Admin')
def send_sms():
form = SendMailForm(request.form)
if request.method == 'POST':
if form.validate_on_submit():
recipients = db.session.query(User.mobile).all()
list_recipients = filter(None, list(zip(*recipients)[0]))
send_sms.delay(list_recipients, form.text.data)
return render_template('send_generic.html', form=form, send_sms=send_sms)
My send_sms celery decorated function is showing up as a registered task:
(env)RP:secTrial Rob$ celery inspect registered
-> celery#RP.local: OK
* app.send_email
* app.send_security_email
* app.send_sms
and for config I am simply using the guest:rabbitmq
CELERY_BROKER_URL = 'amqp://guest#localhost//'
CELERY_RESULT_BACKEND = 'amqp://guest#localhost//'
The view name send_sms conflicts with the celery task name. The name send_sms references the view, not the task, when used in the module that contains the view.
Use different name to avoid the overwriting.