summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Andreas Benkard <code@mail.matthias.benkard.de>2012-03-27 20:14:38 +0200
committerMatthias Andreas Benkard <code@mail.matthias.benkard.de>2012-03-27 20:15:34 +0200
commit285529a532743d52af94d3d571178a413dd944c8 (patch)
tree1d25919aba233f3cde3c98dfab0f987dafb878b0
parent89e5ee140a4becefa4d4023102107727c5921efb (diff)
Implement BrowserID-based login.
-rw-r--r--project.clj1
-rw-r--r--schema.sql7
-rw-r--r--src/mulk/benki/auth.clj57
-rw-r--r--src/mulk/benki/util.clj6
-rw-r--r--static/js/browserid.js51
5 files changed, 109 insertions, 13 deletions
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});
+ }
+});