Search code examples
account-kit

Account Kit returning previous number and account kit ID when a new number is verified


I am testing Account Kit (Basic Web version - phone number verification) on a Django (Python) based web app. One thing I try is logging with multiple accounts on localhost, and trying to link a different number to each one successively. If a number has already successfully attached to a previous account, I show an "already taken" error prompt. Standard stuff.

I've been noticing that I sporadically get the "already taken" error prompt on unused numbers as well. Investigating deeper, I found that although I had input and verified (via SMS) a new number, the account kit ID and mobile number returned to me was the previous pair.

I can't tell why this is happening. Can someone help me in debugging this? In case it matters, my authorization flow uses the app secret.


Following are some relevant snippets. First, the Account Kit Manager class I've written:

from myproj.account_kit_settings import FAID, AKAS

class AccountKitManager(object):
    obj = None

    def __init__(self, app_id, app_secret):
        self.app_secret = app_secret
        self.app_access_token = 'AA|{0}|{1}'.format(app_id, app_secret)

    def get_user_cred(self, auth_code):
        if not self.obj:
            self.set_user_cred(auth_code)
        return self.obj

    def set_user_cred(self, auth_code, url=None):
        if not url:
            url = 'https://graph.accountkit.com/v1.2/access_token?grant_type=authorization_code&code={0}&access_token={1}&appsecret_proof={2}'.\
            format(auth_code,self.app_access_token,self.get_appsecret_proof(self.app_access_token))
        data = self.retrieve_data(url)
        data = self.evaluate_data(data)
        string_obj = self.retrieve_user_cred(data["access_token"])
        self.obj = self.evaluate_data(string_obj)

    def retrieve_user_cred(self, user_access_token, url=None):
        if not url:
            url = 'https://graph.accountkit.com/v1.2/me/?access_token={0}&appsecret_proof={1}'.\
            format(user_access_token,self.get_appsecret_proof(user_access_token))
        return self.retrieve_data(url)

    def retrieve_data(self, url):
        return requests.get(url).text

    def evaluate_data(self, data):
        return ast.literal_eval(data)

    def get_appsecret_proof(self, access_token):
        h = hmac.new(self.app_secret.encode('utf-8'),msg=access_token.encode('utf-8'),digestmod=hashlib.sha256)
        return h.hexdigest()

Next, here's how I use it:

mobile_data = AccountKitManager(FAID, AKAS)

def account_kit_handshake(csrf, state, status, auth_code):
    if csrf == state and status=='PARTIALLY_AUTHENTICATED':
        user_data = mobile_data.get_user_cred(auth_code)
        if FAID == user_data["application"]["id"]:
            return user_data["id"], user_data["phone"]
        else:
            # app id mismatch
            return None, None
    else:
        # csrf mismatch, or could not authenticate
        return None, None

def get_requirements(request):
    status = request.GET.get('status', None)
    auth_code = request.GET.get('code', None)
    state = request.GET.get('state', None)
    return account_kit_handshake(request.session["csrf"], state, status, auth_code)


def verify_consumer_number(request,*args,**kwargs):
    AK_ID, MN_data = get_requirements(request)
    request.session.pop("csrf",None)
    if AK_ID and MN_data:
        if someone_elses_number(MN_data['national_number'], request.user.id):
            return render(request,"used_number.html",{})
        else:
            save_consumer_credentials.delay(AK_ID, MN_data, request.user.id)
            return redirect("classified_listing")
    else:
        return render(request,"unverified_number.html",{})

UPDATE: Seems the user access token isn't always being returned. This could be a problem with variable scope.


Solution

  • The problem emanated from the scope of the AccountKitManager class instance. It was being set globally (i.e. see mobile_data variable in my code). Making this variable local solved the problem.