GithubHelp home page GithubHelp logo

nabucasa / pycognito Goto Github PK

View Code? Open in Web Editor NEW

This project forked from capless/warrant

127.0 3.0 38.0 482 KB

Python library for using AWS Cognito. With support for SRP.

License: Apache License 2.0

Python 100.00%

pycognito's Introduction

pyCognito

Makes working with AWS Cognito easier for Python developers.

Getting Started

Python Versions Supported

  • 3.8
  • 3.9
  • 3.10
  • 3.11
  • 3.12

Install

pip install pycognito

Environment Variables

COGNITO_JWKS

Optional: This environment variable is a dictionary that represent the well known JWKs assigned to your user pool by AWS Cognito. You can find the keys for your user pool by substituting in your AWS region and pool id for the following example. https://cognito-idp.{aws-region}.amazonaws.com/{user-pool-id}/.well-known/jwks.json

Example Value (Not Real):

COGNITO_JWKS={"keys": [{"alg": "RS256","e": "AQAB","kid": "123456789ABCDEFGHIJKLMNOP","kty": "RSA","n": "123456789ABCDEFGHIJKLMNOP","use": "sig"},{"alg": "RS256","e": "AQAB","kid": "123456789ABCDEFGHIJKLMNOP","kty": "RSA","n": "123456789ABCDEFGHIJKLMNOP","use": "sig"}]}

Cognito Utility Class

Example with All Arguments

from pycognito import Cognito

u = Cognito('your-user-pool-id','your-client-id',
    client_secret='optional-client-secret',
    username='optional-username',
    id_token='optional-id-token',
    refresh_token='optional-refresh-token',
    access_token='optional-access-token',
    access_key='optional-access-key',
    secret_key='optional-secret-key')

Arguments

  • user_pool_id: Cognito User Pool ID
  • client_id: Cognito User Pool Application client ID
  • client_secret: App client secret (if app client is configured with client secret)
  • username: User Pool username
  • id_token: ID Token returned by authentication
  • refresh_token: Refresh Token returned by authentication
  • access_token: Access Token returned by authentication
  • access_key: AWS IAM access key
  • secret_key: AWS IAM secret key

Examples with Realistic Arguments

User Pool Id and Client ID Only

Used when you only need information about the user pool (ex. list users in the user pool)

from pycognito import Cognito

u = Cognito('your-user-pool-id','your-client-id')

Username

Used when the user has not logged in yet. Start with these arguments when you plan to authenticate with either SRP (authenticate) or admin_authenticate (admin_initiate_auth).

from pycognito import Cognito

u = Cognito('your-user-pool-id','your-client-id',
    username='bob')

Tokens

Used after the user has already authenticated and you need to build a new Cognito instance (ex. for use in a view).

from pycognito import Cognito

u = Cognito('your-user-pool-id','your-client-id',
    id_token='your-id-token',
    refresh_token='your-refresh-token',
    access_token='your-access-token')

u.verify_tokens() # See method doc below; may throw an exception

Cognito Attributes

After any authentication or other explicit verification of tokens, the following additional attributes will be available:

  • id_claims โ€” A dict of verified claims from the id token
  • access_claims โ€” A dict of verified claims from the access token

Cognito Methods

Register

Register a user to the user pool

Important: The arguments for set_base_attributes and add_custom_attributes methods depend on your user pool's configuration, and make sure the client id (app id) used has write permissions for the attributes you are trying to create. Example, if you want to create a user with a given_name equal to Johnson make sure the client_id you're using has permissions to edit or create given_name for a user in the pool.

from pycognito import Cognito

u = Cognito('your-user-pool-id', 'your-client-id')

u.set_base_attributes(email='[email protected]', some_random_attr='random value')

u.register('username', 'password')

Register with custom attributes.

Firstly, add custom attributes on 'General settings -> Attributes' page. Secondly, set permissions on 'Generals settings-> App clients-> Show details-> Set attribute read and write permissions' page.

from pycognito import Cognito

u = Cognito('your-user-pool-id', 'your-client-id')

u.set_base_attributes(email='[email protected]', some_random_attr='random value')

u.add_custom_attributes(state='virginia', city='Centreville')

u.register('username', 'password')
Arguments
  • username: User Pool username
  • password: User Pool password
  • attr_map: Attribute map to Cognito's attributes

Authenticate

Authenticates a user

If this method call succeeds the instance will have the following attributes id_token, refresh_token, access_token, expires_in, expires_datetime, and token_type.

from pycognito import Cognito

u = Cognito('your-user-pool-id','your-client-id',
    username='bob')

u.authenticate(password='bobs-password')
Arguments
  • password: - User's password

Admin Authenticate

Authenticate the user using admin super privileges

from pycognito import Cognito

u = Cognito('your-user-pool-id','your-client-id',
    username='bob')

u.admin_authenticate(password='bobs-password')
  • password: User's password

Initiate Forgot Password

Sends a verification code to the user to use to change their password.

u = Cognito('your-user-pool-id','your-client-id',
    username='bob')

u.initiate_forgot_password()
Arguments

No arguments

Confirm Forgot Password

Allows a user to enter a code provided when they reset their password to update their password.

u = Cognito('your-user-pool-id','your-client-id',
    username='bob')

u.confirm_forgot_password('your-confirmation-code','your-new-password')
Arguments
  • confirmation_code: The confirmation code sent by a user's request to retrieve a forgotten password
  • password: New password

Change Password

Changes the user's password

from pycognito import Cognito

#If you don't use your tokens then you will need to
#use your username and password and call the authenticate method
u = Cognito('your-user-pool-id','your-client-id',
    id_token='id-token',refresh_token='refresh-token',
    access_token='access-token')

u.change_password('previous-password','proposed-password')
Arguments
  • previous_password: - User's previous password
  • proposed_password: - The password that the user wants to change to.

Confirm Sign Up

Use the confirmation code that is sent via email or text to confirm the user's account

from pycognito import Cognito

u = Cognito('your-user-pool-id','your-client-id')

u.confirm_sign_up('users-conf-code',username='bob')
Arguments
  • confirmation_code: Confirmation code sent via text or email
  • username: User's username

Update Profile

Update the user's profile

from pycognito import Cognito

u = Cognito('your-user-pool-id','your-client-id',
    id_token='id-token',refresh_token='refresh-token',
    access_token='access-token')

u.update_profile({'given_name':'Edward','family_name':'Smith',},attr_map=dict())
Arguments
  • attrs: Dictionary of attribute name, values
  • attr_map: Dictionary map from Cognito attributes to attribute names we would like to show to our users

Send Verification

Send verification email or text for either the email or phone attributes.

from pycognito import Cognito

u = Cognito('your-user-pool-id','your-client-id',
    id_token='id-token',refresh_token='refresh-token',
    access_token='access-token')

u.send_verification(attribute='email')
Arguments
  • attribute: - The attribute (email or phone) that needs to be verified

Get User Object

Returns an instance of the specified user_class.

u = Cognito('your-user-pool-id','your-client-id',
    id_token='id-token',refresh_token='refresh-token',
    access_token='access-token')

u.get_user_obj(username='bjones',
    attribute_list=[{'Name': 'string','Value': 'string'},],
    metadata={},
    attr_map={"given_name":"first_name","family_name":"last_name"}
    )
Arguments
  • username: Username of the user
  • attribute_list: List of tuples that represent the user's attributes as returned by the admin_get_user or get_user boto3 methods
  • metadata: (optional) Metadata about the user
  • attr_map: (optional) Dictionary that maps the Cognito attribute names to what we'd like to display to the users

Get User

Get all of the user's attributes. Gets the user's attributes using Boto3 and uses that info to create an instance of the user_class

from pycognito import Cognito

u = Cognito('your-user-pool-id','your-client-id',
    username='bob')

user = u.get_user(attr_map={"given_name":"first_name","family_name":"last_name"})
Arguments
  • attr_map: Dictionary map from Cognito attributes to attribute names we would like to show to our users

Get Users

Get a list of the user in the user pool.

from pycognito import Cognito

u = Cognito('your-user-pool-id','your-client-id')

user = u.get_users(attr_map={"given_name":"first_name","family_name":"last_name"})
Arguments
  • attr_map: Dictionary map from Cognito attributes to attribute names we would like to show to our users

Get Group object

Returns an instance of the specified group_class.

u = Cognito('your-user-pool-id', 'your-client-id')

group_data = {'GroupName': 'user_group', 'Description': 'description',
            'Precedence': 1}

group_obj = u.get_group_obj(group_data)
Arguments
  • group_data: Dictionary with group's attributes.

Get Group

Get all of the group's attributes. Returns an instance of the group_class. Requires developer credentials.

from pycognito import Cognito

u = Cognito('your-user-pool-id','your-client-id')

group = u.get_group(group_name='some_group_name')
Arguments
  • group_name: Name of a group

Get Groups

Get a list of groups in the user pool. Requires developer credentials.

from pycognito import Cognito

u = Cognito('your-user-pool-id','your-client-id')

groups = u.get_groups()

Check Token

Checks the exp attribute of the access_token and either refreshes the tokens by calling the renew_access_tokens method or does nothing. IMPORTANT: Access token is required

u = Cognito('your-user-pool-id','your-client-id',
    id_token='id-token',refresh_token='refresh-token',
    access_token='access-token')

u.check_token()
Arguments

No arguments for check_token

Verify Tokens

Verifies the current id_token and access_token. An exception will be thrown if they do not pass verification. It can be useful to call this method immediately after instantiation when you're providing externally-remembered tokens to the Cognito() constructor. Note that if you're calling check_tokens() after instantitation, you'll still want to call verify_tokens() afterwards it in case it did nothing. This method also ensures that the id_claims and access_claims attributes are set with the verified claims from each token.

u = Cognito('your-user-pool-id','your-client-id',
    id_token='id-token',refresh_token='refresh-token',
    access_token='access-token')

u.check_tokens()  # Optional, if you want to maybe renew the tokens
u.verify_tokens()
Arguments

No arguments for verify_tokens

Logout

Logs the user out of all clients and removes the expires_in, expires_datetime, id_token, refresh_token, access_token, and token_type attributes.

from pycognito import Cognito

#If you don't use your tokens then you will need to
#use your username and password and call the authenticate method
u = Cognito('your-user-pool-id','your-client-id',
    id_token='id-token',refresh_token='refresh-token',
    access_token='access-token')

u.logout()
Arguments

No arguments for logout

Associate Software Token

Get the secret code to issue the software token MFA code. Begins setup of time-based one-time password (TOTP) multi-factor authentication (MFA) for a user.

from pycognito import Cognito

#If you don't use your tokens then you will need to
#use your username and password and call the authenticate method
u = Cognito('your-user-pool-id','your-client-id',
    id_token='id-token',refresh_token='refresh-token',
    access_token='access-token')

secret_code = u.associate_software_token()
# Display the secret_code to the user and enter it into a TOTP generator (such as Google Authenticator) to have them generate a 6-digit code.
Arguments

No arguments for associate_software_token

Verify Software Token

Verify the 6-digit code issued based on the secret code issued by associate_software_token. If this validation is successful, Cognito will enable Software token MFA.

from pycognito import Cognito

#If you don't use your tokens then you will need to
#use your username and password and call the authenticate method
u = Cognito('your-user-pool-id','your-client-id',
    id_token='id-token',refresh_token='refresh-token',
    access_token='access-token')

secret_code = u.associate_software_token()
# Display the secret_code to the user and enter it into a TOTP generator (such as Google Authenticator) to have them generate a 6-digit code.
code = input('Enter the 6-digit code.')
device_name = input('Enter the device name')
u.verify_software_token(code, device_name)
Arguments
  • code: 6-digit code generated by the TOTP generator app
  • device_name: Name of a device

Set User MFA Preference

Enable and prioritize Software Token MFA and SMS MFA. If both Software Token MFA and SMS MFA are invalid, the preference value will be ignored.

from pycognito import Cognito

#If you don't use your tokens then you will need to
#use your username and password and call the authenticate method
u = Cognito('your-user-pool-id','your-client-id',
    id_token='id-token',refresh_token='refresh-token',
    access_token='access-token')

# SMS MFA are valid. SMS preference.
u.set_user_mfa_preference(True, False, "SMS")
# Software Token MFA are valid. Software token preference.
u.set_user_mfa_preference(False, True, "SOFTWARE_TOKEN")
# Both Software Token MFA and SMS MFA are valid. Software token preference
u.set_user_mfa_preference(True, True, "SOFTWARE_TOKEN")
# Both Software Token MFA and SMS MFA are disabled.
u.set_user_mfa_preference(False, False)
Arguments
  • sms_mfa: SMS MFA enabled / disabled (bool)
  • software_token_mfa: Software Token MFA enabled / disabled (bool)
  • preferred: Which is the priority, SMS or Software Token? The expected value is "SMS" or "SOFTWARE_TOKEN". However, it is not needed only if both of the previous arguments are False.

Respond to Software Token MFA challenge

Responds when a Software Token MFA challenge is requested at login.

from pycognito import Cognito
from pycognito.exceptions import SoftwareTokenMFAChallengeException

#If you don't use your tokens then you will need to
#use your username and password and call the authenticate method
u = Cognito('your-user-pool-id','your-client-id',
    username='bob')

try:
    u.authenticate(password='bobs-password')
except SoftwareTokenMFAChallengeException as error:
    code = input('Enter the 6-digit code generated by the TOTP generator (such as Google Authenticator).')
    u.respond_to_software_token_mfa_challenge(code)

When recreating a Cognito instance

from pycognito import Cognito
from pycognito.exceptions import SoftwareTokenMFAChallengeException

#If you don't use your tokens then you will need to
#use your username and password and call the authenticate method
u = Cognito('your-user-pool-id','your-client-id',
    username='bob')

try:
    u.authenticate(password='bobs-password')
except SoftwareTokenMFAChallengeException as error:
    mfa_tokens = error.get_tokens()

u = Cognito('your-user-pool-id','your-client-id',
    username='bob')
code = input('Enter the 6-digit code generated by the TOTP generator (such as Google Authenticator).')
u.respond_to_software_token_mfa_challenge(code, mfa_tokens)
Arguments
  • code: 6-digit code generated by the TOTP generator app
  • mfa_tokens: mfa_token stored in MFAChallengeException. Not required if you have not regenerated the Cognito instance.

Respond to SMS MFA challenge

Responds when a SMS MFA challenge is requested at login.

from pycognito import Cognito
from pycognito.exceptions import SMSMFAChallengeException

#If you don't use your tokens then you will need to
#use your username and password and call the authenticate method
u = Cognito('your-user-pool-id','your-client-id',
    username='bob')

try:
    u.authenticate(password='bobs-password')
except SMSMFAChallengeException as error:
    code = input('Enter the 6-digit code you received by SMS.')
    u.respond_to_sms_mfa_challenge(code)

When recreating a Cognito instance

from pycognito import Cognito
from pycognito.exceptions import SMSMFAChallengeException

#If you don't use your tokens then you will need to
#use your username and password and call the authenticate method
u = Cognito('your-user-pool-id','your-client-id',
    username='bob')

try:
    u.authenticate(password='bobs-password')
except SMSMFAChallengeException as error:
    mfa_tokens = error.get_tokens()

u = Cognito('your-user-pool-id','your-client-id',
    username='bob')
code = input('Enter the 6-digit code generated by the TOTP generator (such as Google Authenticator).')
u.respond_to_sms_mfa_challenge(code, mfa_tokens)
Arguments
  • code: 6-digit code you received by SMS
  • mfa_tokens: mfa_token stored in MFAChallengeException. Not required if you have not regenerated the Cognito instance.

Cognito SRP Utility

The AWSSRP class is used to perform SRP(Secure Remote Password protocol) authentication. This is the preferred method of user authentication with AWS Cognito. The process involves a series of authentication challenges and responses, which if successful, results in a final response that contains ID, access and refresh tokens.

Using AWSSRP

The AWSSRP class takes a username, password, cognito user pool id, cognito app id, an optional client secret (if app client is configured with client secret), an optional pool_region or boto3 client. Afterwards, the authenticate_user class method is used for SRP authentication.

import boto3
from pycognito.aws_srp import AWSSRP

client = boto3.client('cognito-idp')
aws = AWSSRP(username='username', password='password', pool_id='user_pool_id',
             client_id='client_id', client=client)
tokens = aws.authenticate_user()

Device Authentication Support

You must use the USER_SRP_AUTH authentication flow to use the device tracking feature. Read more about Remembered Devices

Receiving DeviceKey and DeviceGroupKey

Once the authenticate_user class method is used for SRP authentication, the response also returns DeviceKey and DeviceGrouKey. These Keys will later be used to confirm the device.

import boto3
from pycognito.aws_srp import AWSSRP

client = boto3.client('cognito-idp')
aws = AWSSRP(username='username', password='password', pool_id='user_pool_id',
             client_id='client_id', client=client)
tokens = aws.authenticate_user()
device_key = tokens["AuthenticationResult"]["NewDeviceMetadata"]["DeviceKey"]
device_group_key = tokens["AuthenticationResult"]["NewDeviceMetadata"]["DeviceGroupKey"]

Confirming a Device

The confirm_device class method is used for confirming a device, it takes two inputs, tokens and DeviceName (DeviceName is optional). The method returns two values, response and device_password. device_password will later be used to authenticate your device with the Cognito user pool.

response, device_password = user.confirm_device(tokens=tokens)

Updating Device Status

The update_device_status class method is used to update whether or not your device should be remembered. This method takes three inputs, is_remembered, access_token and device_key. is_remembered is a boolean value, which sets the device status as "remembered" on True and "not_remembered" on False, access_token is the Access Token provided by Cognito and device_key is the key provided by the authenticate_user method.

response = user.update_device_status(False, tokens["AuthenticationResult"]["AccessToken"], device_key)

Authenticating your Device

To authenticate your Device, you can just add device_key, device_group_key and device_password to the AWSSRP class.

import boto3
from pycognito.aws_srp import AWSSRP

client = boto3.client('cognito-idp')
aws = AWSSRP(username='username', password='password', pool_id='user_pool_id',
             client_id='client_id', client=client, device_key="device_key", 
             device_group_key="device_group_key", device_password="device_password")
tokens = aws.authenticate_user()

Forget Device

To forget device, you can call the forget_device class method. It takes access_token and device_key as input.

resonse = aws.forget_device(access_token='access_token', device_key='device_key')

SRP Requests Authenticator

pycognito.utils.RequestsSrpAuth is a Requests authentication plugin to automatically populate an HTTP header with a Cognito token. By default, it'll populate the Authorization header using the Cognito Access Token as a bearer token.

RequestsSrpAuth handles fetching new tokens using the refresh tokens.

Usage

import requests
from pycognito.utils import RequestsSrpAuth

auth = RequestsSrpAuth(
  username='myusername',
  password='secret',
  user_pool_id='eu-west-1_1234567',
  client_id='4dn6jbcbhqcofxyczo3ms9z4cc',
  user_pool_region='eu-west-1',
)

response = requests.get('http://test.com', auth=auth)

pycognito's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

pycognito's Issues

`admin_reset_user_password` has incorrect default value for `client_metadata`

This module's admin_reset_user_password function has an optional client_metadata parameter. The default value of this parameter is None, but if you call the function without specifying any client metadata, you get this error:

Parameter validation failed:
Invalid type for parameter ClientMetadata, value: None, type: <class 'NoneType'>, valid types: <class 'dict'>

The solution is to pass an empty dictionary to client_metadata, which should be the default for that parameter.

admin_disable_user

admin_disable_user never uses the provided username parameter. It will disable the current user - if any:

    def admin_disable_user(self, username):
        """
        Disable a user
        :param username:
        :return:
        """
        self.client.admin_disable_user(
            UserPoolId=self.user_pool_id,
            Username=self.username,
        )

It should be:

    def admin_disable_user(self, username):
        """
        Disable a user
        :param username:
        :return:
        """
        self.client.admin_disable_user(
            UserPoolId=self.user_pool_id,
            Username=username,
        )

Exception: pycognito.exceptions.TokenVerificationException: Your 'access_token' token could not be verified (The token is not yet valid (iat)).

Today I kept receiveing the following exception:

File ".venv/lib/python3.10/site-packages/pycognito/__init__.py", line 496, in authenticate
    self._set_tokens(tokens)

  File ".venv/lib/python3.10/site-packages/pycognito/__init__.py", line 768, in _set_tokens
    self.verify_token(

  File ".venv/lib/python3.10/site-packages/pycognito/__init__.py", line 267, in verify_token
    raise TokenVerificationException(

pycognito.exceptions.TokenVerificationException: Your 'access_token' token could not be verified (The token is not yet valid (iat)).

After investigating, it appears to be some time synchronization issue.

The default time leeway for PyJWT is 0, which was too short for me.

My fix was to pass the parameter leeway to the jwt_api.jwt.decode_complete call (__init__.py line 255 as of today).

A better fix would be to pass that parameter somewhere in the chain. Either in the constructor or the authenticate method call.

Is it possible to update the version of importlib-metadata?

I'm using django-ses and djlint.
I got the following warning when I installed django-ses.

djlint 1.9.3 requires importlib-metadata<5.0.0,>=4.11.0, but you have importlib-metadata 1.7.0 which is incompatible.

django-ses 3.1.0 requires importlib-metadata<2.0,>=1.0; python_version < "3.8", but you have importlib-metadata 4.12.0 which is incompatible.

I found that the import-metadata version specification that django-ses depends on is out of date. Is it possible to update this version?

Erroneous 'UserNotFoundException' on respond_to_sms_mfa_challenge()

When setting up login for users with SMS security enabled, I'm getting a 'User not found' exception.

import boto3
from pycognito import Cognito, MFAChallengeException
from pycognito.aws_srp import AWSSRP

def login(self):
    client = boto3.client('cognito-idp', self.region_name)
    aws = Cognito(self.pool_id, self.app_client_id, username=self.email)
    
    try:
        response = aws.authenticate(password=password)
    except MFAChallengeException as mfa:
        # I print the exception body; it includes a session and correct challenge parameters, and I am receiving the text code
        sms_code = input('Please check your text messages and enter the security code:')  # verified to be the correct input string
        aws.respond_to_sms_mfa_challenge(sms_code)  # done this with and without the mfa.get_tokens() value; same error

My error:
botocore.errorfactory.UserNotFoundException: An error occurred (UserNotFoundException) when calling the RespondToAuthChallenge operation: User does not exist.

I've verified the SMS code / username combo elsewhere, so it's specific to this use case.

ValueError Sometimes Raised When Authenticating

A ValueError is sometimes being raised when attempting to authenticate credentials. The error occurs when a salt value contains a leading dash. For example: -16e462a84f2df159eee0c14642974ee7. This seems to only happen when a given username does not exist within the userpool? But not always.

The value error contains this message: non-hexadecimal number found in fromhex() arg at position 0

I have tracked it down to this snippet (line 174 in aws_srp.py):

x_value = hex_to_long(hex_hash(pad_hex(salt) + username_password_hash))

in the hex_hash function:

def hex_hash(hex_string):
    return hash_sha256(bytearray.fromhex(hex_string))

I would submit a fix but I am not sure how the application is expected to handle these kinds of scenarios.

2021.03.1: pycognito.exceptions.TokenVerificationException: Your 'id_token' token could not be verified

@pvizeli @tsibley

Since Pycognito is updated to 2021.03.1 , and used in HA 2021.5.x, since its part of the new NabuCasa 0.43.0 , i receive error below... in this release there was a new PR :

#43
seems related to a token change?

the below error, i only receive when i do a full HassOS reboot , so in first boot, the "Cloud" Integrations fails to start/load, so i can only acces HA on local IP... if i do a restart of HA afterwards, then it succeeds, and cloud loads succesfully
the error below, only comes when i do a full reboot of HassOs 5.13 (tested also 6.0 rc1)

Any idea what could be wrong?

2021-05-10 11:42:54 ERROR (MainThread) [hass_nabucasa.remote] Unexpected error in Remote UI loop
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/hass_nabucasa/remote.py", line 379, in _certificate_handler
    if not await self.load_backend():
  File "/usr/local/lib/python3.8/site-packages/hass_nabucasa/remote.py", line 137, in load_backend
    resp = await cloud_api.async_remote_register(self.cloud)
  File "/usr/local/lib/python3.8/site-packages/hass_nabucasa/cloud_api.py", line 16, in check_token
    await cloud.auth.async_check_token()
  File "/usr/local/lib/python3.8/site-packages/hass_nabucasa/auth.py", line 172, in async_check_token
    await self._async_renew_access_token()
  File "/usr/local/lib/python3.8/site-packages/hass_nabucasa/auth.py", line 199, in _async_renew_access_token
    await self.cloud.run_executor(cognito.renew_access_token)
  File "/usr/local/lib/python3.8/concurrent/futures/thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/local/lib/python3.8/site-packages/pycognito/__init__.py", line 636, in renew_access_token
    self._set_tokens(refresh_response)
  File "/usr/local/lib/python3.8/site-packages/pycognito/__init__.py", line 708, in _set_tokens
    self.verify_token(tokens["AuthenticationResult"]["IdToken"], "id_token", "id")
  File "/usr/local/lib/python3.8/site-packages/pycognito/__init__.py", line 254, in verify_token
    raise TokenVerificationException(
pycognito.exceptions.TokenVerificationException: Your 'id_token' token could not be verified.
2021-05-10 11:42:56 ERROR (MainThread) [hass_nabucasa.iot] Unexpected error
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/hass_nabucasa/iot_base.py", line 108, in connect
    await self._handle_connection()
  File "/usr/local/lib/python3.8/site-packages/hass_nabucasa/iot_base.py", line 147, in _handle_connection
    await self.cloud.auth.async_check_token()
  File "/usr/local/lib/python3.8/site-packages/hass_nabucasa/auth.py", line 172, in async_check_token
    await self._async_renew_access_token()
  File "/usr/local/lib/python3.8/site-packages/hass_nabucasa/auth.py", line 199, in _async_renew_access_token
    await self.cloud.run_executor(cognito.renew_access_token)
  File "/usr/local/lib/python3.8/concurrent/futures/thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/local/lib/python3.8/site-packages/pycognito/__init__.py", line 636, in renew_access_token
    self._set_tokens(refresh_response)
  File "/usr/local/lib/python3.8/site-packages/pycognito/__init__.py", line 708, in _set_tokens
    self.verify_token(tokens["AuthenticationResult"]["IdToken"], "id_token", "id")
  File "/usr/local/lib/python3.8/site-packages/pycognito/__init__.py", line 254, in verify_token
    raise TokenVerificationException(
pycognito.exceptions.TokenVerificationException: Your 'id_token' token could not be verified.

Issues renewing auth_token

I am trying to renew the auth token using renew_access_token

Traceback (most recent call last):
  File "testpycognito.py", line 80, in <module>
    u.renew_access_token( )
  File "/home/ubuntu/.local/lib/python3.8/site-packages/pycognito/__init__.py", line 617, in renew_access_token
    refresh_response = self.client.initiate_auth(
  File "/home/ubuntu/.local/lib/python3.8/site-packages/botocore/client.py", line 337, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/home/ubuntu/.local/lib/python3.8/site-packages/botocore/client.py", line 656, in _make_api_call
    raise error_class(parsed_response, operation_name)
botocore.errorfactory.NotAuthorizedException: An error occurred (NotAuthorizedException) when calling the InitiateAuth operation: Unable to verify secret hash for client 3ol********************bd3

This is not an access problem
I manually performed the operation outside of the package and it works

     username = jwt.decode(id_token, verify=False)
      response = cidpClient.initiate_auth(
                 ClientId=clientId,
                 AuthFlow='REFRESH_TOKEN_AUTH',
                 AuthParameters={
                     'REFRESH_TOKEN': refresh_token,
                     'SECRET_HASH': get_secret_hash(clientId, clientSecret, username["cognito:username"]),
                  })

Also the SECURE_HASH from both operations are the same

Any help will be appreciated

Allow passing through endpoint_url, use_ssl and verify to botoclient

In some implementations of logins are proxying calls through their own systems. Botoclient has a endpoint_url parameter, but pycognito doesn't allow to pass it. Also, when using this lib from an internal corporate network, the ssl fails as there is one self signed cert in the chain, I would like to be able to configure those.

RequestSrpAuth should have it and Cognito, which takes it and packs in kwargs for boto client.

  • use_ssl
  • verify
  • endpoint_url

register fails when using client_secret

It seems impossible to register a user when using a client secret.

Steps to reproduce: have a user pool with no users and with an app client with a secret.

u = Cognito(user_pool_id='eu-central-1_...',
    client_id='...',
    client_secret='...')
u.set_base_attributes(email='[email protected]')
u.register('person', 'password')

Crashes with:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-22-d291537f4dbd> in <module>
      1 u.set_base_attributes(email='[email protected]')
----> 2 u.register('person', '...')

~/.local/lib/python3.8/site-packages/pycognito/__init__.py in register(self, username, password, attr_map)
    346             "UserAttributes": cognito_attributes,
    347         }
--> 348         self._add_secret_hash(params, "SecretHash")
    349         response = self.client.sign_up(**params)
    350 

~/.local/lib/python3.8/site-packages/pycognito/__init__.py in _add_secret_hash(self, parameters, key)
    690         """
    691         if self.client_secret is not None:
--> 692             secret_hash = AWSSRP.get_secret_hash(
    693                 self.username, self.client_id, self.client_secret
    694             )

~/.local/lib/python3.8/site-packages/pycognito/aws_srp.py in get_secret_hash(username, client_id, client_secret)
    199     @staticmethod
    200     def get_secret_hash(username, client_id, client_secret):
--> 201         message = bytearray(username + client_id, "utf-8")
    202         hmac_obj = hmac.new(bytearray(client_secret, "utf-8"), message, hashlib.sha256)
    203         return base64.standard_b64encode(hmac_obj.digest()).decode("utf-8")

TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'

This is caused by https://github.com/pvizeli/pycognito/blob/6c173cf0bb26e40a941be188670c7c592b46eeb8/pycognito/__init__.py#L693 operating on self.username which is not initialized.
I think adding self.username = username around https://github.com/pvizeli/pycognito/blob/6c173cf0bb26e40a941be188670c7c592b46eeb8/pycognito/__init__.py#L334 would fix it without breaking any code using _add_secret_hash. Please let me know if this is OK for you and I will submit a PR.

Security Vulnerability in Upstream Dependency (ecdsa)

Hi there --

In a monthly automated scan, a dependency of this library showed a security vulnerability.

Tracing the dependency tree, it looks like pycognito -> python-jose[cryptography] -> ecdsa. Normally I would look to the source of the issue for a fix, but it seems that:

It's particularly unfortunate since python-jose claims that the library in question isn't even in use for python-jose[cryptography]. Alas, for reporting reasons, my team will need to address it regardless.

I was hoping you could provide me some clarity on whether or not you intend to address the vulnerability within the scope of this library.

Thank you for reading, and thank you for your contributions to OSS!

After authentication, I want to return the token to frontend people.

After authentication, I want to return the token to the front-end team, but as I can see you are not returning anything from the authentication, so whatever line of code I wrote after the authenticate line, never runs.
Example:

u = Cognito('your-user-pool-id','your-client-id', username='bob')
u.authenticate(password='bobs-password')
print("@"*50)

In this line of code, the print statement never executes, because we didn't get any response from the authenticate method.

NEW_PASSWORD_REQUIRED error

So I have a user set to NEW_PASSWORD_REQUIRED and the code failed on the admin_authenticate method....

this is the error:
self.verify_token(tokens["AuthenticationResult"]["IdToken"], "id_token", "id") KeyError: 'AuthenticationResult'

so my idea is to add a verification here to raise or redirect the user to set a new password.... any idea?
Here is the token result I got from the admin_initiated_auth

{'ChallengeName': 'NEW_PASSWORD_REQUIRED', 'Session': 'very_long_string', 'ChallengeParameters': {'USER_ID_FOR_SRP': 'cognito_test', 'requiredAttributes': '[]', 'userAttributes': '{"email_verified":"true","email":"[email protected]"}'}, 'ResponseMetadata': {'RequestId': '111111-d49a-40f5-bef6-111111', 'HTTPStatusCode': 200, 'HTTPHeaders': {'date': 'Wed, 25 Mar 2020 13:22:28 GMT', 'content-type': 'application/x-amz-json-1.1', 'content-length': '1190', 'connection': 'keep-alive', 'x-amzn-requestid': '1111-d49a-40f5-bef6-1111'}, 'RetryAttempts': 0}}

Support cognito id-tokens with at_hash claims

For some reason access tokens acquired via boto3 do not have at_hash claims, but when you use id tokens from the hosted ui, they have at_hash claims. To use jose.jwt.decode on these tokens you have to pass in the access_token at the same time as the ID token or it raises an error.

It may seem like a weird use case to use tokens from the hosted UI with the library but there is some stuff the hosted UI can't do (like update user attributes) that it's nice to use pycognito for. It would be nice if this could be resolved, it might be as simple as changing the decode call to always include the access token.

Return the boto3 responses

Objective

I want to verify that a method call did what was expected.

How to accomplish it

Returning the underlying boto3 response from a method call would achieve this.

For example the init.py/initiate_forgot_password method could be

def initiate_forgot_password(self):
        """
        Sends a verification code to the user to use to change their password.
        """
        params = {"ClientId": self.client_id, "Username": self.username}
        self._add_secret_hash(params, "SecretHash")
        return self.client.forgot_password(**params) # Added return

Justification

Calling initiate_forgot_password when a user hasn't verified their signup information doesn't seem to send a password request. The type definitions for mypy_boto3 indicate that it could be useful to see the response information.

From mypy_boto3_congito_idp

def forgot_password(
        self,
        *,
        ClientId: str,
        Username: str,
        SecretHash: str = ...,
        UserContextData: UserContextDataTypeTypeDef = ...,
        AnalyticsMetadata: AnalyticsMetadataTypeTypeDef = ...,
        ClientMetadata: Mapping[str, str] = ...
    ) -> ForgotPasswordResponseTypeDef:
        """
        Calling this API causes a message to be sent to the end user with a confirmation
        code that is required to change the user's password.

        [Show boto3 documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cognito-idp.html#CognitoIdentityProvider.Client.forgot_password)
        [Show boto3-stubs documentation](https://youtype.github.io/boto3_stubs_docs/mypy_boto3_cognito_idp/client/#forgot_password)
        """

So this:

resp = user.initiate_forgot_password()
print(resp) # empty

becomes:

resp = user.initiate_forgot_password()
print(resp)
{
    'CodeDeliveryDetails': {'Destination': 's***@y***', 'DeliveryMedium': 'EMAIL', 'AttributeName': 'email'},
    'ResponseMetadata': {
        ...,
        'HTTPStatusCode': 200,
        'HTTPHeaders': {
            ...
        },
        'RetryAttempts': 0
    }
}

which at least let's you see if the medium is near what was expected. In my case I noticed a user's email was wrong

Failing Registration prodecure

Hello,
I got the following error during the registration procedure.

u.register(username, password)
File "/site-packages/pycognito/init.py", line 350, in register
self._add_secret_hash(params, "SecretHash")
File "/site-packages/pycognito/init.py", line 695, in _add_secret_hash
secret_hash = AWSSRP.get_secret_hash(
File "site-packages/pycognito/aws_srp.py", line 201, in get_secret_hash
message = bytearray(username + client_id, "utf-8")
TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'

The username has None value in get_secret_hash because the attribute passed via registrer function is not updating the self.username .
To use the register procedure , i am passing the attribute username to the Cognito object, but this is not described in the documentation.

Timeout resolving pycognito dependency with poetry

Using poetry I'm seeing poetry fail to resolve (taking hours on Github Actions) when pycognito is in the dependency list.

It appears to be because the dependency specifier boto3>=1.10.49 includes many hundreds of released versions of boto3, poetry has to download the individual packages to inspect them to work out the transitive dependencies. This isn't an issue with your dependency specification as there shouldn't be any requirement to only support the most recent 10's of releases. But on the off chance this helps other users, I'm going to restrict my own project to require a more recent version of boto3 to work around poetry.

Mail verification help

Is it possible to create a user without the need to verify the email?

I know that the AWS interface, there is a flag for this ...

Flow using USER_SRP_AUTH fails

Hi, firstly thanks for maintaining this.

I'm not a developer, just a user with python knowledge struggling through old threads and forks to implement a complete workflow in python to login a user get a bearer token and start using an API.

Here is shortened code with key bits:-

from pycognito.aws_srp import AWSSRP

client = boto3.client('cognito-idp', region_name='eu-central-1', config=my_config)

try:
    aws = AWSSRP(username=user, password=passw, pool_id=pool_id, client_id=client_id, client=client)
except client.exceptions.NotAuthorizedException as e:
    print(f"error: {e}")

auth_init = client.initiate_auth(AuthFlow='USER_SRP_AUTH', AuthParameters=aws.get_auth_params(), ClientId=client_id)

c_resp = aws.process_challenge(auth_init['ChallengeParameters'])

response = client.respond_to_auth_challenge(ClientId=client_id, ChallengeName=auth_init['ChallengeName'], ChallengeResponses=c_resp)

It fails at last line with "botocore.errorfactory.NotAuthorizedException" wrong username or password.

I think I'm missing a step in my flow since the auth_init and c_resp seem to produce the correct parameters:-

auth_init:
{'ChallengeName': 'PASSWORD_VERIFIER',
'ChallengeParameters': {'SALT': 'a9a...', 'SECRET_BLOCK': '9p1/...', 'SRP_B': '979', 'USERNAME': 'a2c...', 'USER_ID_FOR_SRP': 'a2cc....'}, 'ResponseMetadata': {'RequestId': 'c7bea..', 'HTTPStatusCode': 200, 'HTTPHeaders': {'date': 'Wed, 29 Sep 2021 08:56:46 GMT', 'content-type': 'application/x-amz-json-1.1', 'content-length': '2730', 'connection': 'keep-alive', 'x-amzn-requestid': 'c7bea...'}, 'RetryAttempts': 0}}

c_resp:
{'TIMESTAMP': 'Wed Sep 29 08:56:46 UTC 2021',
'USERNAME': 'a2cc.....',
'PASSWORD_CLAIM_SECRET_BLOCK': '9p1/....',
'PASSWORD_CLAIM_SIGNATURE': 'g+CZ...'}

I realise this is probably not an issue with your code, rather my poor implementation, but any guidance would be appreciated.

error logout

I can't find a correct way to log in as an administrator.

Get_Users returning everything

Is there a way to do a case-insensitive search of a username?

I'm trying to do:

cog = Cognito(...)
users = cog.get_users(attr_map={"username":"someUserName"})

However, the list of users returned appears to be everything in my pool. Is this the expected behavior, or am I using this incorrectly?

How would I find a user called "someusername" vs "someUserName"? I've found that the search feature in the AWS Cognito user search page is case-sensitive, which makes searching very error prone when usernames may contain inconsistent casing.

Non-semantic version number?

It looks like the latest release is inconsistent with the semantic versioning that was happening before. Was this intentional?

image

Easy API for custom jwt claims

Currently we have an an easy access to attributes but not to custom JWT claims added by a pre-token generation lambda

You can can get the decoded ID Token with

Cognito.verify_token(id_token,"id_token","id")

but it's kind of hacky, otherwise you're on your own to decode the ID token and extract the claims.

It would be nice to have a proper route to access these custom claims!

Device confirmation?

Hi,
(great library by the way!)

Are there any plans to add device confirmation support into pycognito? specifically, so that the boto3 confirm_device and update_device_status are supported? This would then allow device-based authentication (i.e. with a token) once the initial user authentication has passed.

I'm working on adding this support to my own copy of this library. If I succeed, would a PR be of interest?

thanks
James

Unable to get user data

I'm trying to fetch data from a user using pycognito.

This code give all de users in the pool, so the connection to AWS is working fine

    c = Cognito(
        cognito_service.COGNITO_USER_POOL_ID, cognito_service.COGNITO_APP_CLIENT_ID
    )
    response = c.get_users(attr_map={})

But the same code using get_user_obj

    c = Cognito(
        cognito_service.COGNITO_USER_POOL_ID, cognito_service.COGNITO_APP_CLIENT_ID
    )
    response = c.get_user_obj(username=username, attribute_list=[{}])

Always return a dummy object with all values set to None except for the username that is populated allways, also with not existing users.

I have tested also get_user

    c = Cognito(
        cognito_service.COGNITO_USER_POOL_ID,
        cognito_service.COGNITO_APP_CLIENT_ID,
        username=username,
    )
    response = c.get_user(attr_map={})

That raise a boto3 exception

botocore.exceptions.ParamValidationError: Parameter validation failed:
Invalid type for parameter AccessToken, value: None, type: <class 'NoneType'>, valid types: <class 'str'>

My cognito pool use only "email", not "username" for login, I'm not sure if is de reason for the error.

Any hint to solve my issues fetching the user data?

Cognito to S3 authentication

Hi @pvizeli

I've managed to setup Cognito user pool and identity pools. I'm also able to authenticate and I get back id_token, access_token, refresh_token, Pool_JWK (This contains a couple of RSA keys with Key_id and Key_secret) but I am not able to use any of these to connect to S3 bucket.

Could you please help or share links on how I could use these tokens to access AWS services?

Thank you all for taking the time to help

admin_create_user actually requires temporary_password parameter

The comments of the admin_create_user method indicate that it is an optional parameter and if omitted, Cognito will generate a temporary password. When I try to use the method in this manner, I get errors that the temporary password provided is invalid.

Based on the boto3 documentation for this action at https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cognito-idp.html#CognitoIdentityProvider.Client.admin_create_user, it looks like the TemporaryPassword parameter needs to be omitted from the request to let Cognito generate the temporary password for you, but the code in this library is always passing the parameter in, even if it is an empty string or None.

check_token (refresh token) error if use secret

I have an error with regenerating the token after it expires, if you use an app with the secret.
I pass both the username and secret parameters, but it says that

"""

 u = Cognito(COGNITO_POOL_ID, APP_CLIENT_ID, client_secret=APP_SECRET_KEY, username=email1, id_token=id_token, refresh_token=refresh_token, access_token=access_token)

u.check_token()

An error occurred (NotAuthorizedException) when calling the InitiateAuth operation: SecretHash does not match for the client: XXXXXXXXXX
"""
the secret is correct because other functions such as registration or profile update work

without the secret instead it works correctly

Cache request to .../.well-known/jwks.json

Would it make sense to cache requests made to https://cognito-idp.{aws-region}.amazonaws.com/{user-pool-id}/.well-known/jwks.json since otherwise we would always make an request to get basically static data.

I know about the env variable COGNITO_JWKS but it does not feel right to hardcode this information inside an env variable. Ofcourse I could implement the caching outside of the Cognito class und set the env variable but why not have it directly inside instead?

Breaking Security Fix in Upstream Dependency (cryptography)

Hello,

A vulnerability scan flagged the upstream dependency cryptography (pycognito -> pyjwt[crypto] -> cryptography) that is fixed for versions > 42.0.4. See: GHSA-6vqw-3v5j-54x4

This update to cryptography causing breaking changes where:

from pycognito import Cognito

idToken = '<idToken>'
accessToken = '<accessToken>'

userPoolId='<userPoolId>'
clientId = '<clientId>'
region='<region>'

u = Cognito(
    user_pool_id=userPoolId,
    client_id=clientId,
    user_pool_region=region,
    id_token=idToken,
    access_token=accessToken
)
u.verify_tokens()

Results in:

>> TypeError: argument 'data': from_buffer() cannot return the address of a unicode object

with the error occurring upstream in jwt

If you attempt to pass the tokens in as bytes you get:

>> TypeError: a bytes-like object is required, not 'str'

Where the error occurs within pycognito.

I have created a PR to fix this breaking change by handling the cases at these two points of failure: #222

Signing in fails if locale.LC_TIME is set to a non-English language

In the process_challenge function, there is a call to strftime that depends on the current locale. If it is not English, AWS will respond with this error:

TIMESTAMP format should be EEE MMM d HH:mm:ss z yyyy in english

The problem can be reproduced like this:

locale.setlocale(locale.LC_TIME, "nb_NO.utf-8")
u = Cognito(...)
u.authenticate(...)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.