Node.js backend - user login using AWS SDK and Amazon Cognito - node.js

Premise: I've only just started learning how to interact with AWS services and my knowledge of these is limited. I try my best to search the official documentation but, at times, it's either difficult to understand (for a novice) or some bits seem to be missing. Apologies in advance if I've missed something obvious!
I'm building a project where user will input data using a React frontend, this will be sent to a Node.js backend, and the backend will then communicate with the Cognito user pool using the SDK.
I have managed to set up routes for registration, confirmation and resend code using these API actions:
signUp
confirmSignUp
resendConfirmationCode
I don't seem to be able to find login-related actions (from basic login to mfa authentication etc.). The documentation mentions a hosted UI, but I want everything to be sent to my backend and use that to handle the flow.
What am I missing?
Thanks.

Here is this use case written using the AWS SDK for Python (Boto3) - not Node JS. (This example will be ported to other AWS SDKs soon).
It will point you in the right direction in terms of what methods to call and what request objects are needed.
There is an AWS CDK script that will setup the user pool for use with this code example here:
https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/resources/cdk/cognito_scenario_user_pool_with_mfa
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
"""
Purpose
Shows you how to use the AWS SDK for Python (Boto3) with Amazon Cognito to
do the following:
1. Sign up a user with a user name, password, and email address.
2. Confirm the user from a code sent in email.
3. Set up multi-factor authentication by associating an MFA application with the user.
4. Sign in by using a password and an MFA code.
5. Register an MFA device to be tracked by Amazon Cognito.
6. Sign in by using a password and information from the tracked device. This avoids the
need to enter a new MFA code.
This scenario requires the following resources:
* An existing Amazon Cognito user pool that is configured to allow self sign-up.
* A client ID to use for authenticating with Amazon Cognito.
"""
import argparse
import base64
import logging
import os
from pprint import pp
import sys
import webbrowser
import boto3
import qrcode
from warrant import aws_srp
from cognito_idp_actions import CognitoIdentityProviderWrapper
# Add relative path to include demo_tools in this code example without needing to set up.
sys.path.append('../..')
import demo_tools.question as q
logger = logging.getLogger(__name__)
# snippet-start:[python.example_code.cognito-idp.Scenario_SignUpUserWithMfa]
def run_scenario(cognito_idp_client, user_pool_id, client_id):
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
print('-'*88)
print("Welcome to the Amazon Cognito user signup with MFA demo.")
print('-'*88)
cog_wrapper = CognitoIdentityProviderWrapper(cognito_idp_client, user_pool_id, client_id)
user_name = q.ask("Let's sign up a new user. Enter a user name: ", q.non_empty)
password = q.ask("Enter a password for the user: ", q.non_empty)
email = q.ask("Enter a valid email address that you own: ", q.non_empty)
confirmed = cog_wrapper.sign_up_user(user_name, password, email)
while not confirmed:
print(f"User {user_name} requires confirmation. Check {email} for "
f"a verification code.")
confirmation_code = q.ask("Enter the confirmation code from the email: ")
if not confirmation_code:
if q.ask("Do you need another confirmation code (y/n)? ", q.is_yesno):
delivery = cog_wrapper.resend_confirmation(user_name)
print(f"Confirmation code sent by {delivery['DeliveryMedium']} "
f"to {delivery['Destination']}.")
else:
confirmed = cog_wrapper.confirm_user_sign_up(user_name, confirmation_code)
print(f"User {user_name} is confirmed and ready to use.")
print('-'*88)
print("Let's get a list of users in the user pool.")
q.ask("Press Enter when you're ready.")
users = cog_wrapper.list_users()
if users:
print(f"Found {len(users)} users:")
pp(users)
else:
print("No users found.")
print('-'*88)
print("Let's sign in and get an access token.")
auth_tokens = None
challenge = 'ADMIN_USER_PASSWORD_AUTH'
response = {}
while challenge is not None:
if challenge == 'ADMIN_USER_PASSWORD_AUTH':
response = cog_wrapper.start_sign_in(user_name, password)
challenge = response['ChallengeName']
elif response['ChallengeName'] == 'MFA_SETUP':
print("First, we need to set up an MFA application.")
qr_img = qrcode.make(
f"otpauth://totp/{user_name}?secret={response['SecretCode']}")
qr_img.save("qr.png")
q.ask("Press Enter to see a QR code on your screen. Scan it into an MFA "
"application, such as Google Authenticator.")
webbrowser.open("qr.png")
mfa_code = q.ask(
"Enter the verification code from your MFA application: ", q.non_empty)
response = cog_wrapper.verify_mfa(response['Session'], mfa_code)
print(f"MFA device setup {response['Status']}")
print("Now that an MFA application is set up, let's sign in again.")
print("You might have to wait a few seconds for a new MFA code to appear in "
"your MFA application.")
challenge = 'ADMIN_USER_PASSWORD_AUTH'
elif response['ChallengeName'] == 'SOFTWARE_TOKEN_MFA':
auth_tokens = None
while auth_tokens is None:
mfa_code = q.ask(
"Enter a verification code from your MFA application: ", q.non_empty)
auth_tokens = cog_wrapper.respond_to_mfa_challenge(
user_name, response['Session'], mfa_code)
print(f"You're signed in as {user_name}.")
print("Here's your access token:")
pp(auth_tokens['AccessToken'])
print("And your device information:")
pp(auth_tokens['NewDeviceMetadata'])
challenge = None
else:
raise Exception(f"Got unexpected challenge {response['ChallengeName']}")
print('-'*88)
device_group_key = auth_tokens['NewDeviceMetadata']['DeviceGroupKey']
device_key = auth_tokens['NewDeviceMetadata']['DeviceKey']
device_password = base64.standard_b64encode(os.urandom(40)).decode('utf-8')
print("Let's confirm your MFA device so you don't have re-enter MFA tokens for it.")
q.ask("Press Enter when you're ready.")
cog_wrapper.confirm_mfa_device(
user_name, device_key, device_group_key, device_password,
auth_tokens['AccessToken'], aws_srp)
print(f"Your device {device_key} is confirmed.")
print('-'*88)
print(f"Now let's sign in as {user_name} from your confirmed device {device_key}.\n"
f"Because this device is tracked by Amazon Cognito, you won't have to re-enter an MFA code.")
q.ask("Press Enter when ready.")
auth_tokens = cog_wrapper.sign_in_with_tracked_device(
user_name, password, device_key, device_group_key, device_password, aws_srp)
print("You're signed in. Your access token is:")
pp(auth_tokens['AccessToken'])
print('-'*88)
print("Don't forget to delete your user pool when you're done with this example.")
print("\nThanks for watching!")
print('-'*88)
def main():
parser = argparse.ArgumentParser(
description="Shows how to sign up a new user with Amazon Cognito and associate "
"the user with an MFA application for multi-factor authentication.")
parser.add_argument('user_pool_id', help="The ID of the user pool to use for the example.")
parser.add_argument('client_id', help="The ID of the client application to use for the example.")
args = parser.parse_args()
try:
run_scenario(boto3.client('cognito-idp'), args.user_pool_id, args.client_id)
except Exception:
logging.exception("Something went wrong with the demo.")
if __name__ == '__main__':
main()
# snippet-end:[python.example_code.cognito-idp.Scenario_SignUpUserWithMfa]

Related

send mail using service account and google api python

Edit:
after accepting the answer:
my question was about a free Gmail account (I didn't know there was a difference) and the answer is about a paid account (and it is a currect one),
the answer showed me that there was a difference and it led me the correct answer to my situation - use a passcode
im trying to send mail using google API and service account, but I'm getting the following erre:
An error occurred: <HttpError 400 when requesting https://gmail.googleapis.com/gmail/v1/users/me/drafts?alt=json returned "Precondition check failed.". Details: "[{'message': 'Precondition check failed.', 'domain': 'global', 'reason': 'failedPrecondition'}]">
this is my code:
from __future__ import print_function
import os.path
from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from oauth2client.service_account import ServiceAccountCredentials
import base64
from email.message import EmailMessage
# If modifying these scopes, delete the file token.json.
SCOPES = ['https://mail.google.com/']
def main():
"""Shows basic usage of the Gmail API.
Lists the user's Gmail labels.
"""
creds = None
creds = ServiceAccountCredentials.from_json_keyfile_name(
"""path_to_cred_file.json""", SCOPES)
try:
# Call the Gmail API
service = build('gmail', 'v1', credentials=creds)
message = EmailMessage()
message.set_content('This is automated draft mail')
message['To'] = 'somemail#gmail.com'
message['From'] = 'somemail#gmail.com'
message['Subject'] = 'Automated draft'
# encoded message
encoded_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
create_message = {
'message': {
'raw': encoded_message
}
}
# pylint: disable=E1101
draft = service.users().drafts().create(userId="me",
body=create_message).execute()
except HttpError as error:
# TODO(developer) - Handle errors from gmail API.
print(f'An error occurred: {error}')
if __name__ == '__main__':
main()
"Precondition check failed" usually means that you're trying to do something that cannot be done. In this case you're trying to send an email from a service account, which is not possible. This answer has a link to a relevant thread from the Google documentation. They say the following:
Service accounts dont work with gmail unless you set up domain wide delegation to a Gsuite account. The reason being is that a service account is its own user you need to delegate its permission to access your gmail account. This will only work with a gsuite domain email address.
This means that the service account by itself cannot send messages, but instead needs to be delegated access to a regular user account in order to send emails. To do this you can add the following line after your creds:
delegated_creds=credentials.with_subject("someuser#yourdomain.com")
#where someuser# is the email of the user that you're sending email as
After that you can use delegated_creds instead of creds to call the service.
Also, you seem to have gotten your sample from Google's guide, but note that your sample creates a draft instead of sending an email. The API call to send emails is a little different. With that in mind here's a complete example based on your code which worked for me:
#all the imports
SCOPES = ['https://mail.google.com/']
def main():
creds = None
creds = ServiceAccountCredentials.from_json_keyfile_name(
"""path_to_cred_file.json""", SCOPES)
delegated_creds=credentials.with_subject("someuser#yourdomain.com")
try:
# Call the Gmail API
service = build('gmail', 'v1', credentials=delegated_creds)
message = EmailMessage()
message.set_content('This is automated draft mail')
message['To'] = 'somemail#gmail.com'
message['From'] = 'somemail#gmail.com'
message['Subject'] = 'Automated draft'
encoded_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
create_message = {
'raw': encoded_message
}
email = service.users().messages().send(userId="me",
body=create_message).execute()
except HttpError as error:
# TODO(developer) - Handle errors from gmail API.
print(f'An error occurred: {error}')
if __name__ == '__main__':
main()
Finally, as explained in the thread I linked, this only works for Google Workspace accounts and you cannot delegate access to free Gmail accounts, so keep that in mind.

What are the firewall requirements (Port and Sites) for the python Gmail API and OAuth2.0?

I am planning to use the Gmail API for sending emails.
I will also be using OAuth 2.0 authentication.
The mechanism for authenticating is indicated in the quickstart guide:
from __future__ import print_function
import os.path
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
def main():
"""Shows basic usage of the Gmail API.
Lists the user's Gmail labels.
"""
creds = None
# The file token.json stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.json', 'w') as token:
token.write(creds.to_json())
try:
# Call the Gmail API
service = build('gmail', 'v1', credentials=creds)
results = service.users().labels().list(userId='me').execute()
labels = results.get('labels', [])
if not labels:
print('No labels found.')
return
print('Labels:')
for label in labels:
print(label['name'])
except HttpError as error:
# TODO(developer) - Handle errors from gmail API.
print(f'An error occurred: {error}')
if __name__ == '__main__':
main()
One example of sending an email:
def gmailAPISendEmail(self, message, userID="me"):
try:
service = self.gmailAPIService
self.GLogger.info("Attempting to send email message")
try:
response = service.users().messages().send(userId=userID, body=message).execute()
except socket.timeout:
pass
except:
self.GLogger.error("Failed to send email message")
tb = traceback.format_exc()
self.GLogger.exception(tb)
try:
responseID = str(response['id'])
except:
responseID = "CouldNotParseID"
self.GLogger.info("Successfully sent email message with ID (" + responseID +")")
return responseID
except:
self.GLogger.error("Failed to send email message")
tb = traceback.format_exc()
self.GLogger.exception(tb)
return False
An example of creating an email:
def createEmaiLWithAttachments(self,sendTo, sendFrom, subject,emailMsg , attachments, html=False):
try:
mimeMessage = MIMEMultipart()
mimeMessage['to'] = sendTo
mimeMessage['from'] = sendFrom
mimeMessage['subject'] = subject
if html:
msg= MIMEText(emailMsg, 'html')
else:
msg= MIMEText(emailMsg)
mimeMessage.attach(msg)
for attachment in attachments:
attachment_name = attachment[0]
attachment_instance = attachment[1]
content_type, encoding = mimetypes.guess_type(attachment_name)
if content_type is None or encoding is not None:
content_type = 'application/octet-stream'
main_type, sub_type = content_type.split('/', 1)
if main_type == 'text':
msg = MIMEText(attachment_instance, _subtype=sub_type)
elif main_type == 'image':
msg = MIMEImage(attachment_instance, _subtype=sub_type)
elif main_type == 'audio':
msg = MIMEAudio(attachment_instance, _subtype=sub_type)
else:
msg = MIMEBase(main_type, sub_type)
msg.set_payload(attachment_instance)
msg.add_header('Content-Disposition', 'attachment', filename=attachment_name)
mimeMessage.attach(msg)
raw_string = base64.urlsafe_b64encode(mimeMessage.as_string().encode()).decode()
theMessage = {'raw': raw_string}
return theMessage
except:
self.GLogger.error("An error occurred in createEmaiLWithAttachments")
tb = traceback.format_exc()
self.GLogger.exception(tb)
return False
I would like to know what the firewall requirements are?
Site/domain requirements
Port requirements
Protocol type (UDP or TCP)
I tried searching for the port requirements and the site/domain requirements, but I didn't find any information for the Gmail API.
I would need to know the firewall requirements for things such as:
Authentication
Refresh credentials
Send email
Fetch emails (messages.list, messages.get, threads.list etc.)
I have opened a Developer Support ticket with Google Workspace Support. An employee from Google who works with APIs has stated the following.
For general API request/response traffic for Google APIs:
Host : *.googleapis.com
Port : 443 & 80
Protocol : TCP
For general authentication traffic for Google APIs:
Host : *.google.com
Port : 443 & 80
Protocol : TCP
The host for the Gmail API request/response traffic specifically, according to here, would be:
Host : gmail.googleapis.com
Port : 443 & 80
Protocol : TCP
The following is information provided by a contributor to the google-api-python-client Github repository.
The python Gmail API uses the endpoints defined in the discovery document for the Gmail V1 API here.
The official support page captures the broader Google Workspace firewall settings.
The support page is where further questions or clarifications can be obtained.
List of IP Addresses needed for Google APIs can be found here. The IP Addresses would need to be allowed for inbound and outbound traffic.
This is another resource for API URL requirements.
Specifically for the Gmail service, there's a similar question here.
Where it makes a reference to Google documentation here and provides the following:
**The asterisk (*) is a wild card, and represents any value except a period**.
*.client-channel.google.com
accounts.google.com
apis.google.com
clients*.google.com
contacts.google.com
hangouts.google.com
*.googleusercontent.com
mail.google.com
ssl.gstatic.com
www.google.com
www.gstatic.com
ogs.google.com
play.google.com
As an alternative, if you do have a Google Workspace subscription and this is implemented through it you can open a Developer Support ticket to check if any additional information can be shared specific to your case.

I want to login with both and phone number in this DRF

I'm currently using a custom user model and can log in with my phone number. But now I want to log in with both phone and email. Please check the given code and let me know in the comments if any more details are needed. Thanks in advance.
class LoginSerializer(serializers.Serializer):
phone = serializers.CharField()
password = serializers .CharField()
def validate(self, data):
email = data. get ('email')
phone = data.get('phone')
password = data.get('password')
reg = False
print(phone, ",", password)
obj = CustomUser.objects.get(phone=phone)
if phone and password:
user = authenticate(username=obj.username, password=password)
if user:
data['user'] = user
print(user.id)
else:
msg = 'login failed'
raise exceptions.ValidationError(msg)
else:
msg = 'provide credientials'
raise exceptions.ValidationError(msg)
return data
I'm assuming you mean User could use email OR phone to login. Here are some feedback for your code:
password should be required=True
You should add the email field in the serializer. Like the phone field, it must be optional (required=False)
In validate, throw an error if neither email nor phone are provided
Also throw an error if BOTH are provided. It's email OR phone, not neither, not both.
Make sure to have a unique constraint in your User model for the email and phone field. If they are used for auth, no user should have the same email or phone
Then, fetch your user using either phone or email
If you use get, you can do a try/except block on the Model.DoesNotExists error. Otherwise, you can use filter and throw an error if you did not find exactly 1 user
If 1 user, keep going
If no user, invalid credentials
If several users, there's a huge problem in your DB
Assuming you found 1 user, try authenticate him
If success, return valid response
Else, return invalid creds
Do note that there are other ways of implementing this, like adding custom authentication backend as mentioned in the comments. But for now, those are some improvements you can make

how can repeat signup aws cognito [solve]

solve is here gist
when sign up in our app, sign up with aws cognito and send verify email with code.
if user close app not input verify, user have to re sign up. (saved in cognito userpool state UNCONFIRM)
and it occur two problem.
password may be changed (when re sign up)
renew verify code
my code is here python3 and warrant
#app.route('/signup/', methods=['POST'])
def signup():
u = Cognito(os.getenv('COGNITO_USER_POOL_ID'), os.getenv('COGNITO_CLIENT_ID'),
user_pool_region=os.getenv('COGNITO_REGION'))
u.add_base_attributes(name=user_name, email=user_email)
u.register(user_email, user_password)
return redirect(url_for('lobby'))
err code
botocore.errorfactory.UsernameExistsException: An error occurred (UsernameExistsException) when calling the SignUp operation: An account with the given email already exists.
how to re signup with renew password, and send new verify email
Thanks
oh, i solve
#app.route('/signup/', methods=['POST'])
def signup():
idp_client = boto3.client('cognito-idp')
'''
resp = idp_client.sign_up(ClientId=app_client_id,
Username=user_email,
Password=user_password,
UserAttributes=[{'Name': 'email', 'Value': user_email}])
'''
resp = idp_client.resend_confirmation_code(ClientId=os.getenv('COGNITO_CLIENT_ID'),
Username=user_email)
print(resp) #check result
return redirect(url_for('lobby'))
use boto3 i want (resend not acquire password)

How to get saved tracks for a specific user, or change the current user?

The Spotify API has an endpoint "Get a Users's Saved Tracks" GET https://api.spotify.com/v1/me/tracks but as you can see from me in the url, and in the documentation, this is only for the current user. How can I access information about a non current user, or change the current user?
For example, userA logs in, I get an access and refresh token for userA. userB logs in, replacing userA as the current user, I get userB's tokens. How can I now make make requests for information about userA?
You need to store the tokens you get from authenticating users.
Say you're using user sessions:
User A logs in.
You get the access and refresh tokens for user A.
You save these tokens to User A's session.
User B logs in.
You get the access and refresh tokens for user B.
You save these tokens to User B's session.
You'd do this the same way that you have already implemented user sessions.
And so when a user lands on your redirect URI, you save the tokens you received to their session.
And then when you need to use the Spotify API you use the tokens saved in the users session.
If you however want to do this for one end-user, then with a web server things get a little harder.
But with a CLI app things can be a little easier.
What you want to do is log user A and B into your application, manually saving both tokens independently.
This is as easy as making an authentication function that you call twice and save the results to two variables.
After this you can then call the API with the saved tokens.
And use user A's token when you want to get user A's saved tracks.
Here's a low-level example implementation in Python 3 using Requests, of getting the users tracks and user information, using different scopes. Where the comments are the part the code's at in the authorization code flow:
import time
import urllib.parse as parse
import webbrowser
import requests
from requests.auth import HTTPBasicAuth
OAUTH_AUTHORIZE_URL = 'https://accounts.spotify.com/authorize'
OAUTH_TOKEN_URL = 'https://accounts.spotify.com/api/token'
# Change to your application settings
class Settings:
client_id = ''
client_secret = ''
redirect_uri = ''
def authenticate(scope=None):
'''Implement OAuth 2 Spotify authentication'''
# Application: Request authorization to access data
payload = {'client_id': Settings.client_id,
'response_type': 'code',
'redirect_uri': Settings.redirect_uri,
'show_dialog': 'true'} # allow second account to login
if scope:
payload['scope'] = scope
auth_url = '{}?{}'.format(OAUTH_AUTHORIZE_URL, parse.urlencode(payload))
# Spotify: Displays scopes & prompts user to login (if required)
# User: Logs in, authorizes access
webbrowser.open(auth_url)
response = input('Enter the URL you were redirected to: ')
code = parse.parse_qs(parse.urlparse(response).query)['code'][0]
payload = {'redirect_uri': Settings.redirect_uri,
'code': code,
'grant_type': 'authorization_code'}
if scope:
payload['scope'] = scope
# Application: Request access and refresh tokens
# Spotify: Returns access and refresh tokens
auth = HTTPBasicAuth(Settings.client_id, Settings.client_secret)
response = requests.post(OAUTH_TOKEN_URL, data=payload, auth=auth)
if response.status_code != 200:
response.raise_for_status()
token_info = response.json()
token_info['expires_at'] = int(time.time()) + token_info['expires_in']
token_info['scope'] = scope
return token_info
if __name__ == '__main__':
user_a = authenticate(scope='user-library-read')
user_b = authenticate(scope='user-read-email user-read-private user-read-birthdate')
print('user_a', user_a)
print('user_b', user_b)
for url in ['https://api.spotify.com/v1/me/tracks',
'https://api.spotify.com/v1/me']:
for user in [user_a, user_b]:
token = 'Bearer ' + user['access_token']
# Application: Uses access token in requests to Web API
# Spotify: Returns request data
r = requests.get(url, headers={'authorization': token})
if r.status_code != 200:
print(r.text)
else:
print([
'{}: {}'.format(key, str(value)[:20])
for key, value in r.json().items()
])

Resources