How to connect a Python Flask web app with Hive Keychain by way of Javascript for Hive authentication

in #developers2 months ago

If you want to see where this is going: login to this site with your Hive Keychain

image.png

I'm learning to code. Well I'm re-learning to code at any rate. The first time I learned to code it was by reading, re-reading and reading again the ZX81 BASIC manual. I kept on coding from then until around 1997 when I completed my PhD in computational physics... and then I pretty much stopped coding.

But I'm back and I after a few small projects like recording the level of the Kinneret, also known as the Sea of Galilee in Israel, I decided to start a bit of a monster project to completely revolutionising the way Podcasts are funded.

That project is ongoing but along the way I wanted to get Hive Keychain working as a method to authenticate a user with my Python Flask based server application.

You can try this out on a demo here - there's not much working, I kept it to the bare minimum, but you can log in and log out using Hive KeyChain.

This is based on a terrific set of videos explaining the Python Flask micro web server created by an absolutely brilliant teacher, Corey Schafer.

Full playlist for Corey's Flask Blog

The base code for this comes from Corey's 6th video in the series:

Python Flask Tutorial: Full-Featured Web App Part 6 - User Authentication

And his code is on GitHub

There's one extra step about creating virtual environments which this video is good for

image.png

The Hive Keychain Parts

The bits I added can all be found in my Github repository: How to use Hive Keychain and Python Flask

The bulk of the changes are in the HTML Jinja2 template called login.html and in the routes.py file.

There was a need to combine Python with Javascript and I suck at Javascript. Thankfully I got some great help and that is what I want to share here. My Javascript is horribly wordy, but I hope it's easy to follow what is going on.

Huge thanks to @rishi556, @stoodkev and @ausbitbank all of whom helped me out.

I really hope putting this up here helps others!

<button class="btn btn-primary" id="Check Keychain" name="check-keychain" onClick="hiveKeychainSign()">Hive KeyChain Login</button>


(html comment removed:  Hive Keychain javascript part )
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
function hiveKeychainSign(){
    let name = document.querySelector("#acc_name").value;
    console.log(name);
    const keychain = window.hive_keychain;
    if (!name) {
        // need a name
        console.log('need a name');
        return
    }
    const signedMessageObj = { type: 'login', address: name, page: window.location.href };
    const messageObj = { signed_message: signedMessageObj, timestamp: parseInt(new Date().getTime() / 1000, 10) };
    keychain.requestSignBuffer(name, JSON.stringify(messageObj), 'Posting', response => {
        if (!response.success) { return; }
            //Successfully logged in
            console.log(response);
            //We added stuff here
            axios.post("/hive/login", response).then((res) => {
                console.log(res)
                let data = res.data;
                //You'd probably want to give the url back in as a json.
                //Whatever you send back will be save in data. Here' i'm assuming the format
                //data = {podcaster : "https://google.com"}
                window.location.href = `${data.loadPage}`;
            }).catch((err) => {
                console.log(err);
                //Deal with any error here
            })
        });
    };
</script>
(html comment removed:  Hive Keychain javascript part )
@app.route("/hive/login", methods=['GET','POST'])
def hive_login():
    """ Handle the answer from the Hive Keychain browser extension """
    if current_user.is_authenticated:
        return redirect(url_for('home'))
    if request.method == 'POST' and request.data:
        ans = json.loads(request.data.decode('utf-8'))
        if ans['success'] and validate_hivekeychain_ans(ans):
            acc_name = ans['data']['username']
            user = User.query.filter_by(username = acc_name).first()
            if user:
                login_user(user, remember=True)
                flash(f'Welcome back - @{user.username}', 'info')
                app.logger.info(f'{acc_name} logged in successfully')
                return make_response({'loadPage':url_for('home') }, 200)
                # return redirect(url_for('podcaster.dashboard'))
            else:
                user = User(username=acc_name)
                db.session.add(user)
                db.session.commit()
                result = login_user(user, remember=True)
                flash(f'Welcome - @{user.username}', 'info')
                app.logger.info(f'{acc_name} logged in for the first time')
                return make_response({'loadPage':url_for('home') }, 200)
                # return redirect(url_for('podcaster.dashboard'))
        else:
            flash('Not Authorised','danger')
            return make_response({'loadPage':url_for('login') }, 401)





def validate_hivekeychain_ans(ans):
    """ takes in the answer from hivekeychain and checks everything """
    """ https://bit.ly/keychainpython """

    acc_name = ans['data']['username']
    pubkey = PublicKey(ans['publicKey'])
    enc_msg = ans['data']['message']
    signature = ans['result']

    msgkey = verify_message(enc_msg, unhexlify(signature))
    pk = PublicKey(hexlify(msgkey).decode("ascii"))
    if str(pk) == str(pubkey):
        app.logger.info(f'{acc_name} SUCCESS: signature matches given pubkey')
        acc = Account(acc_name, lazy=True)
        match = False, 0
        for key in acc['posting']['key_auths']:
            match = match or ans['publicKey'] in key
        if match:
            app.logger.info(f'{acc_name} Matches public key from Hive')
            mtime = json.loads(enc_msg)['timestamp']
            time_since = time.time() - mtime
            if time_since < 30:
                app.logger.info(f'{acc_name} SUCCESS: in {time_since} seconds')
                return True , time_since
            else:
                app.logger.warning(f'{acc_name} ERROR: answer took too long.')
    else:
        app.logger.warning(f'{acc_name} ERROR: message was signed with a different key')
        return False, 0




brianoflondon hive footer.png

Sort:  

Believe it or not I was searching for this last week .

I basically use only python and I am not a web developer but I know some basic JS but no matter what I tried I couldn't get it to work . I will go through whatever you have put up :)

Thanks a lot.

This was insanely difficult for me too but I'm so glad I got it working and I'm sure it will help others.

The javascript stuff completely confuses me, there are so many competing ways to do everything including declaring functions that I'm always left confused by anyone else's code.

Congratulations @brianoflondon! You received a personal badge!

You powered-up at least 100 HP on Hive Power Up Day! This entitles you to a level 3 badge
Participate in the next Power Up Day and try to power-up more HIVE to get a bigger Power-Bee.
May the Hive Power be with you!

You can view your badges on your board and compare yourself to others in the Ranking

Check out the last post from @hivebuzz:

Feedback from the March 1st Hive Power Up Day
Hive Tour Update - Financial stages

Dear @brianoflondon
Programming language is something that is worldwide, whoever masters it, has the possibility to automate many processes, and for sure this will simplify your life.
Yours, Piotr