diff --git a/examples/persona/persona.py b/examples/persona/persona.py new file mode 100644 index 00000000..ff785a4f --- /dev/null +++ b/examples/persona/persona.py @@ -0,0 +1,59 @@ +from flask import Flask, render_template, session, request, json, abort, g + +import requests + + +app = Flask(__name__) +app.config.update( + DEBUG=True, + SECRET_KEY='my development key', + PERSONA_JS='https://login.persona.org/include.js', + PERSONA_VERIFIER='https://verifier.login.persona.org/verify', +) +app.config.from_envvar('PERSONA_SETTINGS', silent=True) + + +@app.before_request +def get_current_user(): + g.user = None + email = session.get('email') + if email is not None: + g.user = email + + +@app.route('/') +def index(): + """Just a generic index page to show.""" + return render_template('index.html') + + +@app.route('/_auth/login', methods=['GET', 'POST']) +def login_handler(): + """This is used by the persona.js file to kick off the + verification securely from the server side. If all is okay + the email address is remembered on the server. + """ + resp = requests.post(app.config['PERSONA_VERIFIER'], data={ + 'assertion': request.form['assertion'], + 'audience': request.host_url, + }, verify=True) + if resp.ok: + verification_data = json.loads(resp.content) + if verification_data['status'] == 'okay': + session['email'] = verification_data['email'] + return 'OK' + + abort(400) + + +@app.route('/_auth/logout', methods=['POST']) +def logout_handler(): + """This is what persona.js will call to sign the user + out again. + """ + session.clear() + return 'OK' + + +if __name__ == '__main__': + app.run() diff --git a/examples/persona/static/persona.js b/examples/persona/static/persona.js new file mode 100644 index 00000000..c96561d6 --- /dev/null +++ b/examples/persona/static/persona.js @@ -0,0 +1,50 @@ +$(function() { + /* convert the links into clickable buttons that go to the + persona service */ + $('a.signin').on('click', function() { + navigator.id.request(); + return false; + }); + + $('a.signout').on('click', function() { + navigator.id.logout(); + return false; + }); + + /* watch persona state changes */ + navigator.id.watch({ + loggedInUser: $CURRENT_USER, + onlogin: function(assertion) { + /* because the login needs to verify the provided assertion + with the persona service which requires an HTTP request, + this could take a bit. To not confuse the user we show + a progress box */ + var box = $('
') + .hide() + .text('Please wait ...') + .appendTo('body') + .fadeIn('fast'); + $.ajax({ + type: 'POST', + url: $URL_ROOT + '_auth/login', + data: {assertion: assertion}, + success: function(res, status, xhr) { window.location.reload(); }, + error: function(xhr, status, err) { + box.remove(); + navigator.id.logout(); + alert('Login failure: ' + err); + } + }); + }, + onlogout: function() { + $.ajax({ + type: 'POST', + url: $URL_ROOT + '_auth/logout', + success: function(res, status, xhr) { window.location.reload(); }, + error: function(xhr, status, err) { + alert('Logout failure: ' + err); + } + }); + } + }); +}); diff --git a/examples/persona/static/spinner.png b/examples/persona/static/spinner.png new file mode 100644 index 00000000..c9cd8358 Binary files /dev/null and b/examples/persona/static/spinner.png differ diff --git a/examples/persona/static/style.css b/examples/persona/static/style.css new file mode 100644 index 00000000..6f0c5f15 --- /dev/null +++ b/examples/persona/static/style.css @@ -0,0 +1,39 @@ +html { + background: #eee; +} + +body { + font-family: 'Verdana', sans-serif; + font-size: 15px; + margin: 30px auto; + width: 720px; + background: white; + padding: 30px; +} + +h1 { + margin: 0; +} + +h1, h2, a { + color: #d00; +} + +div.authbar { + background: #eee; + padding: 0 15px; + margin: 10px -15px; + line-height: 25px; + height: 25px; + vertical-align: middle; +} + +div.signinprogress { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.8) url(spinner.png) center center no-repeat; + font-size: 0; +} diff --git a/examples/persona/templates/index.html b/examples/persona/templates/index.html new file mode 100644 index 00000000..bcccdfc1 --- /dev/null +++ b/examples/persona/templates/index.html @@ -0,0 +1,23 @@ +{% extends "layout.html" %} +{% block title %}Welcome{% endblock %} +{% block body %} +

Welcome

+

+ This is a small example application that shows how to integrate + Mozilla's persona signin service into a Flask application. +

+ The advantage of persona over your own login system is that the + password is managed outside of your application and you get + a verified mail address as primary identifier for your user. +

+ In this example nothing is actually stored on the server, it + just takes over the email address from the persona verifier + and stores it in a session. + {% if g.user %} +

+ You are now logged in as {{ g.user }} + {% else %} +

+ To sign in click the sign in button above. + {% endif %} +{% endblock %} diff --git a/examples/persona/templates/layout.html b/examples/persona/templates/layout.html new file mode 100644 index 00000000..a329b8a5 --- /dev/null +++ b/examples/persona/templates/layout.html @@ -0,0 +1,27 @@ + +{% block title %}{% endblock %} | My Blog + + + + + + +

+

Mozilla Persona Example

+
+ {% if g.user %} + Signed in as {{ g.user }} + (Sign out) + {% else %} + Not signed in. + {% endif %} +
+
+{% block body %}{% endblock %}