HyperDex with Flask-OpenID and Login

March 26, 2015

I’ve started work on building a cryptocurrency exchange with my friend Akhil, we’re using HyperDex and Python-Flask to power the project but more on that in a later post. This post is about using Flask-Login, Flask-OpenID and HyperDex. We couldn’t find any guides to use OpenID (almost all examples used SQLite) the way we wanted it and I figured a little example could help someone else using this combination of technologies. Akhil was the one that implemented this, but I’m going to steal it all. >:)

This is the HyperDex space that you’ll need to create. It stores the user’s email address, nickname, and phone number. Of course you’ll need the relevant WTForms and HTML pages to serve this.

a.add_space('''
    space users
        key string email
        attributes
            string       name,
            int          phone
        subspace name
        subspace phone
''')


If your website is going to have users, it needs some way for them to sign up! I’ve removed the code I used to fetch requested data from the WTForm that is displayed on the HTML Page.

oid = OpenID(app, os.path.join(basedir, 'tmp'))
@app.route('/signup', methods=['GET', 'POST'])
@oid.loginhandler
def signup():
    # Check if a user is already logged in.
    if g.user_email is not None:
        return redirect(url_for('index'))
    signup_form = SignupForm()
    # Validate user's signup form
    if signup_form.validate_on_submit():
        name   = request.form['name']
        openid = request.form['openid']
        # 3.
        session['login_type'] = "SIGNUP"
        session['name_var'] = name
        # 4.
        return oid.try_login(signup_form.openid.data, 
                            ask_for=['nickname', 'email'],
                            ask_for_optional=['fullname'])
    # 5.
    return render_template('signup.html', next=oid.get_next_url(), error=oid.fetch_error(),
                           title='f13x : Sign Up',
                           form=signup_form,
                           providers=app.config['OPENID_PROVIDERS'])


This is the login function, again, I’ve removed the code I used to fetch requested data from the WTForm.

@app.route('/login', methods=['POST', 'GET'])
@oid.loginhandler
def login():
    # Check if a user is already logged in
    if g.user_email is not None:
        return redirect(oid.get_next_url())

    # Make a cookie called SIGNUP to pass to handler to let it know that
    # the validate was for SIGNUP and not a regular LOGIN
    session['remember_me'] = login_form.remember_me.data
    session['login_type'] = "LOGIN"
    # Validate the users email
    openid = request.form['openid']
    return oid.try_login(openid, ask_for=['email', 'nickname'],
                                     ask_for_optional=['fullname'])
    # Go back to the login form
    return render_template('login.html', 
                            next=oid.get_next_url(), 
                            error=oid.fetch_error(), 
                            title='f13x : Log In', 
                            providers=app.config['OPENID_PROVIDERS'])


You’ve probably noticed that we’re storing the user’s email address in the session variable. Flask stores encrypted session data in a cookie. Here’s how to fetch that piece of information before each request.

@app.before_request
def lookup_current_user():
    g.user_email = None
    if 'openid' in session:
        openid = str(session['openid'])
        if c.count('users', { 'email' : openid}) == 1:
            g.user_email = openid


The all important logout function

@app.route('/logout')
@login_required
def logout():
    # logout a signed in user. Delete his cookie
    session.pop('openid', None)
    #flash(u'You were signed out')
    return redirect(oid.get_next_url())


You’re definitely wondering by now where HyperDex fits in, well you need to store the user’s emailId somewhere!

@oid.after_login
def create_or_login(resp):
    # Check if the login was successful & emailId was provided by the OpenID provider
    if str(resp.email) == None or str(resp.email) == "":
        return redirect(url_for('login'))
    # Check if the user already exists.
    user_exists = False
    if c.count('users', { 'email' : str(resp.email) }) == 1L:
        user_exists = True
    # The user is trying to login
    if session['login_type'] == "LOGIN":
        # If this is an existing user
        if not user_exists:
            return redirect(url_for('signup'))
    # The user is trying to signup
    if session['login_type'] == "SIGNUP":
        # The user already exists, redirect him to the login page, we don't want him signing up again
        if user_exists:
            return redirect(url_for('login'))
        # Sign up was successful, initiate his data!
        c.put('users', str(resp.email), {'name' : str(resp.nickname), 'phone' : 1234567890})
    # Create a cookie with the user's data
    session['openid'] = str(resp.email)
    # Redirect the user to the page he was trying to fetch
    return redirect(request.args.get('next') or url_for('index'))


Let’s say a user wants to update his phone number.

@app.route('/update_phone', methods=['GET', 'POST'])
@login_required
def add_funds():
    if request.method == 'POST':
        new_phone_number = float(request.form['new_phone_number'])
        
        c.atomic_add('users', g.user_email, {'phone' : new_phone_number})
        
        # Redirect the user to the main page
        return redirect(url_for('index'))


The code might feel a bit out of place, I’ll replace it with simpler code snippets if a lot of people don’t understand it.


Contact me

I'm a CS Master's student at Cornell University. Follow me on Twitter -- you'll enjoy my tweet(s), on the few ocassions that I post one; Shoot me an email - av445 [at] cornell [dot] edu. I'm quite the conversationalist. xD