From 285529a532743d52af94d3d571178a413dd944c8 Mon Sep 17 00:00:00 2001 From: Matthias Andreas Benkard Date: Tue, 27 Mar 2012 20:14:38 +0200 Subject: Implement BrowserID-based login. --- project.clj | 1 + schema.sql | 7 ++++++ src/mulk/benki/auth.clj | 57 ++++++++++++++++++++++++++++++++++++++----------- src/mulk/benki/util.clj | 6 +++++- static/js/browserid.js | 51 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 13 deletions(-) create mode 100644 static/js/browserid.js diff --git a/project.clj b/project.clj index 896a190..d29e1d7 100644 --- a/project.clj +++ b/project.clj @@ -36,6 +36,7 @@ [org.openid4java/openid4java-consumer "0.9.6" :type "pom"] [org.jsoup/jsoup "1.6.1"] [org.apache.abdera/abdera-parser "1.1.1"] + [clj-apache-http "2.3.2"] ] :dev-dependencies [[swank-clojure "1.4.0-SNAPSHOT"] [clj-stacktrace "0.2.3"]] diff --git a/schema.sql b/schema.sql index a12d549..155c7a2 100644 --- a/schema.sql +++ b/schema.sql @@ -21,6 +21,13 @@ CREATE TABLE openids( FOREIGN KEY("user") REFERENCES users ); +CREATE TABLE user_email_addresses( + "user" INTEGER NOT NULL, + email VARCHAR NOT NULL, + PRIMARY KEY(email), + FOREIGN KEY("user") REFERENCES users +); + CREATE TABLE page_keys( "user" INTEGER NOT NULL, page VARCHAR NOT NULL, diff --git a/src/mulk/benki/auth.clj b/src/mulk/benki/auth.clj index 93e106e..03d9746 100644 --- a/src/mulk/benki/auth.clj +++ b/src/mulk/benki/auth.clj @@ -2,7 +2,7 @@ (:refer-clojure) (:use [clojure core repl pprint] [hiccup core page-helpers] - [mulk.benki util db] + [mulk.benki config util db] [clojure.core.match :only [match]] [noir core] @@ -10,7 +10,8 @@ (:require [noir.session :as session] [noir.response :as response] [noir.request :as request] - [clojure.java.jdbc :as sql]) + [clojure.java.jdbc :as sql] + [com.twinql.clojure.http :as http]) (:import [org.openid4java.consumer ConsumerManager] [org.openid4java.message ParameterList])) @@ -50,6 +51,31 @@ (layout "Authentication Failed" [:p "OpenID authentication failed."])))) +(defpage [:post "/login/browserid/verify"] {assertion :assertion} + ;; NB. Can implement this ourselves if we want. + (let [reply (http/post "https://browserid.org/verify" + :query {:assertion assertion + :audience (:base-uri @benki-config)} + :as :json) + result (:content reply) + status (:status result) + email (:email result)] + (if (= (:status result) "okay") + (with-dbt + (let [record (first (query "SELECT * FROM user_email_addresses WHERE email = ?" email)) + user-id (and record (:user record))] + (if user-id + (let [return-uri (session/flash-get)] + (session/put! :user user-id) + (response/json {:email email, :returnURI return-uri})) + {:status 418, + :headers {"Content-Type" "text/plain"}, + :body "I couldn't find you in the database."}))) + {:status 418, + :headers {"Content-Type" "text/plain"}, + :body "Your BrowserID request was crooked."}))) + + (defpage [:post "/login/return"] [] (return-from-openid-provider)) @@ -76,14 +102,21 @@ (defpage "/login" [] (session/flash-put! (or (session/flash-get) - (get-in (request/ring-request) [:headers "Referer"]))) + (get-in (request/ring-request) [:headers "referer"]))) (layout login-page-layout "Benki Login" - [:form {:action (resolve-uri "/login/authenticate"), - :method "GET" - :id "openid_form"} - [:div {:id "openid_choice"} - [:p "Please select your OpenID provider:"] - [:div {:id "openid_btns"}]] - [:div {:id "openid_input_area"} - [:input {:type "text", :name "openid_identifier", :id "openid_identifier"}] - [:input {:type "submit"}]]])) + [:div#browserid-box + [:h2 "BrowserID login"] + [:a#browserid {:href "#"} + [:img {:src (resolve-uri "/3rdparty/browserid/sign_in_orange.png") + :alt "Sign in using BrowserID"}]]] + [:div#openid-login-panel + [:h2 "OpenID login"] + [:form {:action (resolve-uri "/login/authenticate"), + :method "GET" + :id "openid_form"} + [:div {:id "openid_choice"} + [:p "Please select your OpenID provider:"] + [:div {:id "openid_btns"}]] + [:div {:id "openid_input_area"} + [:input {:type "text", :name "openid_identifier", :id "openid_identifier"}] + [:input {:type "submit"}]]]])) diff --git a/src/mulk/benki/util.clj b/src/mulk/benki/util.clj index 99e739c..505c637 100644 --- a/src/mulk/benki/util.clj +++ b/src/mulk/benki/util.clj @@ -32,11 +32,15 @@ ;; defpartial is just defn + html. (defpartial layout [kind title & content] (html5 {:xml? true} - [:head + [:head {:data-logged-in (if *user* "true" "false")} [:title title] ;; jQuery [:script {:type "text/javascript" :src (resolve-uri "/3rdparty/jquery/jquery-1.7.min.js")}] + [:script {:type "text/javascript" + :src (resolve-uri "/3rdparty/browserid/include.js")}] + [:script {:type "text/javascript" + :src (resolve-uri "/js/browserid.js")}] [:link {:type "text/css" :rel "stylesheet" :href (resolve-uri "/style/benki.css")}] diff --git a/static/js/browserid.js b/static/js/browserid.js new file mode 100644 index 0000000..dce8d42 --- /dev/null +++ b/static/js/browserid.js @@ -0,0 +1,51 @@ +// -*- js-indent-level: 2 -*- + +jQuery(function($) { + var loggedIn = function(res) { + console.log(res); + if (res.returnURI) { + window.location.assign(res.returnURI); + } else { + window.location.reload(true); + } + }; + var loggedOut = function(res) { + }; + + var gotAssertion = function(assertion) { + // got an assertion, now send it up to the server for verification + if (assertion) { + $.ajax({ + type: 'POST', + url: '/login/browserid/verify', + data: { assertion: assertion }, + success: function(res, status, xhr) { + if (res === null) { + loggedOut(); + } + else { + loggedIn(res); + } + }, + error: function(res, status, xhr) { + //console.log(res); + //console.log(status); + alert("Whoops, I failed to authenticate you! " + res.responseText); + } + }); + } else { + loggedOut(); + } + } + + $('#browserid').click(function() { + navigator.id.get(gotAssertion, {allowPersistent: true}); + return false; + }); + + // Query persistent login. + var login = $('head').attr('data-logged-in'); + if (login === "false") { + navigator.id.get(gotAssertion, {silent: true}); + } +}); -- cgit v1.2.3