How to add Friend auth to Chestnut template - authentication

I've struggled for a few days trying to get a simple use of the security library, Friend, to work with the Chestnut clj/cljs template.
A POST request to the the /login uri is supposed log me in and allow access the protected routes like /role-user. But for some reason I am not able to login, the POST returns a 303 and routes me back to the root page.
I added the Friend middleware inside the http-handler function. Is this the correct place to apply this sort of middleware? I thought maybe the reload or api-defaults middleware could be messing up friend middleware? However, removing them does not fix things.
(def http-handler
(if is-dev?
(-> #'routes
(reload/wrap-reload)
(friend/authenticate
{:allow-anon? true
:login-uri "/login"
:default-landing-uri "/"
:unauthorized-handler #(-> (h/html5 [:h2 "You do not have sufficient privileges to access " (:uri %)])
resp/response
(resp/status 401))
:credential-fn (fn [x]
(let [res (creds/bcrypt-credential-fn #users x)]
(log/info x)
(log/info res)
res))
:workflows [(workflows/interactive-form)]})
(wrap-defaults api-defaults))
(wrap-defaults routes api-defaults)))
Based on the print statements I was able to figure out that the credential-fn function does get called on the POST request, with the correct params, and the function returns the correct (authenticated) result.
This http-handler is used as such
(defn run-web-server [& [port]]
(let [port (Integer. (or port (env :port) 10555))]
(print "Starting web server on port" port ".\n")
(run-jetty http-handler {:port port :join? false})))
(defn run-auto-reload [& [port]]
(auto-reload *ns*)
(start-figwheel))
(defn run [& [port]]
(when is-dev?
(run-auto-reload))
(run-web-server port))
For what it's worth, here are my routes.
(defroutes routes
(GET "/" req
(h/html5
misc/pretty-head
(misc/pretty-body
(misc/github-link req)
[:h3 "Current Status " [:small "(this will change when you log in/out)"]]
[:p (if-let [identity (friend/identity req)]
(apply str "Logged in, with these roles: "
(-> identity friend/current-authentication :roles))
"anonymous user")]
login-form
[:h3 "Authorization demos"]
[:ul
[:li (e/link-to (misc/context-uri req "role-user") "Requires the `user` role")]]
[:p (e/link-to (misc/context-uri req "logout") "Click here to log out") "."])))
(GET "/login" req
(h/html5 misc/pretty-head (misc/pretty-body login-form)))
(GET "/logout" req
(friend/logout* (resp/redirect (str (:context req) "/"))))
(GET "/role-user" req
(friend/authorize #{::users/user} "You're a user!")))

I figured it out. (wrap api-defaults) does not allow sessions and Friend is trying to use them. I should be using site-defaults instead. See ring middleware for more info.

Related

How can I add custom header parameters to a API GET call in clojurescript

I'm building a demo application in clojurescript with KeeFrame and to retrieve a part of the information for this website I need to call an external API which requires a custom HTTP header parameter in the GET requests
I'm using re-frame.core for the API calls, which uses ajax.core.
I also tried to replace this with cljs-http.client. However the result is the same.
I already managed to add custom header parameters to the request header by using clj-http at server site. But this is not a solution I want to implement for this website because that means that I first have to rebuild the API I'm calling. So I can use it from my clojurescript without the parameter.
This code works. A correct GET request is generated
{:http-xhrio {
:method :get
:uri (str transuri "/acquirer/" 673072009 "/acquirerref/" acquirerRefNo)
:headers {"Accept" "application/json"}
:response-format (http/json-response-format {:keywords? true})
:on-failure [:common/set-error]}}
With "Accept: application/json" as a request header
This code does not work. Instead of a GET request an OPTIONS request is generated
{:http-xhrio {
:method :get
:uri (str transuri "/acquirer/" 673072009 "/acquirerref/" acquirerRefNo)
:headers {"Accept" "application/json" "Custom" "Value"}
:response-format (http/json-response-format {:keywords? true})
:on-failure [:common/set-error]}}
And in the request header "Accept: application/json" is not visible but "Access-Control-Request-Headers: custom" is
I expected a GET request with "Accept: application/json" and "Custom: Value" in the request header.
Can someone tell me what I'm doing wrong or provide me with a link with information about this?
Thanks in advance
The Browser will send a "preflight" OPTIONS request to verify that it is allowed to send the "Custom" request header. The server is supposed to approve by replying with "Access-Control-Allow-Headers".
See https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
I have not used KeeFrame, but I have a working example using a new re-frame lib I'm working on. It invokes an ajax request using this interceptor:
(def ajax-intc
"Interceptor for performing AJAX requests"
(interceptor
{:id :ajax-intc
:enter identity
:leave (fn [ctx] ; #todo (with-result ctx ...)
(let [ajax (:ajax ctx)]
;(t/spyx :ajax-intc-start ctx)
;(t/spyx :ajax-intc-start ajax)
(when-not (nil? ajax)
(t/spy :awt-ajax-intc--ajax ajax)
(let [method (t/grab :method ajax)
uri (t/grab :uri ajax)
ajax-opts-present (set/intersection (set (keys ajax)) ajax-options-keys)
opts-map (t/submap-by-keys ajax ajax-opts-present)]
;(t/spy :ajax-intc-ready (t/vals->map method uri opts-map))
(condp = method
:get (do
(t/spy :awt-ajax-intc--opts-map opts-map)
(ajax/GET uri opts-map))
:put (ajax/PUT uri opts-map)
:post (ajax/POST uri opts-map)
(throw (ex-info "ajax-intc: unrecognized :method" ajax))))))
ctx)}))
When invoked with this event:
(flame/dispatch-event [:ajax-demo :get "/fox.txt"
{:handler ajax-handler
:error-handler ajax-error-handler
:headers {"custom" "something"}
}])
one can see in the Chrome dev console that the headers come through:
:awt-localstore-load-intc--loaded-value-1 {}
core.cljs:192 :awt-ajax-intc--ajax => {:method :get, :uri "/fox.txt", :handler #object[flintstones$core$ajax_handler], :error-handler #object[flintstones$core$ajax_error_handler], :headers {"custom" "something"}}
core.cljs:192 :awt-ajax-intc--opts-map => {:handler #object[flintstones$core$ajax_handler], :error-handler #object[flintstones$core$ajax_error_handler], :headers {"custom" "something"}}
If you want to try it out, you can clone this repo: git#github.com:cloojure/cljs-enflame.git
and then run:
lein clean
lein figwheel
and see it run in the browser.

How to implement a JSONP call

Let me preface this saying I'm still very new to ClojureScript.
I'm trying to parse an API that has CORS turned off. My only choice is to use JSONP requests. But I have no idea how this is done. As far as I understand JSONP, it's the server that returns a callback script.
How do I implement this in ClojureScript? I have this snippet that I try to use:
(defn get-json [url callback]
(xhr/send url callback "GET"))
(defn logcallback [event]
(let [response (.-target event)]
(.log js/console (response))))
(get-json ("http://www.apiurl.com/method")
logcallback)
But I still receive a Origin "localhost:3449" is not allowed (CORS) error. I guess this is not really a JSONP request. How would I actually do this?
You can use goog.net.Jsonp. Here's a simple example:
(ns jsonp.core
(:import [goog.net Jsonp]
[goog Uri]))
(defn success-handler [res]
(.log js/console res))
(defn error-handler [res]
(js/alert (str "Found an error: " res)))
(let [url "http://en.wikipedia.org/w/api.php?action=opensearch&format=json&search=clojure"
jsonp (goog.net.Jsonp. (Uri. url))]
(.send jsonp nil success-handler error-handler))
If your endpoint returns a "wrapped payload" and the param for setting the callback name is not "callback", you can pass the callback param name as the second parameter to the Jsonp constructor. The following example gets a flickr feed which expects the "jsoncallback" param:
(let [url "http://api.flickr.com/services/feeds/photos_public.gne?format=json"
jsonp (goog.net.Jsonp. (Uri. url) "jsoncallback")]
(.send jsonp nil success-handler error-handler))
goog.net.Jsonp will take care of passing the extra param (jsoncallback in the example) and unwrap the payload by calling the function. In either case it will end calling the success or error handler.
You can take a look into clojurescript 101 by David Nolen. The post is from 2 years ago, but it should still work. There's also another clojurescript tutorial which has a similar example.

Ember-auth signin test fails with json

I am having some issues with testing my signin/signout and related features of my app. The app works, but the test fail.
For testing, I use a QUnit with testem (I also tried teaspoon)
test "after signin, should redirect user back to previous page", ->
visit '/library'
fillIn '.signin-email', 'example#example.com'
fillIn '.signin-password', 'examplepass'
click '.signin-btn'
andThen ->
equal(testing().path(), 'library', "Should redirect back to library (was #{testing().path()})")
After running the test, I get a failure:
(screenshot here )
Authentication: visiting restricted page as non authenticated user: after signin, should redirect user back to previous page (2, 0, 2)Rerun544 ms
{user_id: 2, auth_token: wVveiyDLuXBXu69pQ2XQwg}
Source:
at Test.QUnitAdapter.Test.Adapter.extend.exception (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:50149:5)
at superWrapper [as exception] (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:13374:16)
at Object.onerror (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:50009:22)
at onerror (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20453:16)
at EventTarget.trigger (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20286:22)
at null.<anonymous> (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20439:14)
at EventTarget.trigger (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20286:22)
at http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20588:17
Should redirect back to library (was signin)
Expected:
"library"
Result:
"signin"
Diff:
"library" "signin"
Source:
at http://localhost:7357/public/assets/spec/javascripts/integration/authentication_pages_spec.js.js:22:14
at andThen (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:50258:20)
at http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:49817:21
at isolate (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:49989:14)
at http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:49972:12
at invokeCallback (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20463:19)
at null.<anonymous> (http://localhost:7357/public/assets/application-aad0a1b2c887cc25124c361787446e83.js:20513:11)
Also, auth.coffee:
App.Auth = Em.Auth.extend
request: 'jquery'
response: 'json'
strategy: 'token'
session: 'cookie'
modules: [
'emberData',
'authRedirectable',
'actionRedirectable'
]
signInEndPoint: '/signin'
signOutEndPoint: '/signout'
tokenKey: 'auth_token'
tokenIdKey: 'user_id'
tokenLocation: 'param'
emberData:
userModel: 'user' # create user model on login
authRedirectable:
route: 'signin'
actionRedirectable:
signInRoute: 'library'
# signInSmart: true
# signInBlacklist: ['signin']
signOutRoute: 'index'
I am unable to find the source of the error, so maybe it is something to do with ember-auth. Any ideas would be very appreciated.
Update 1 [Jan 4th]:
I've written an additional test, which passes only halfway. The test is simpler than the previous in that it does not check a redirect, but only checks that the user name appears in the UI after signin.
test "after signin, TEST", ->
visit '/library'
fillIn '.signin-email', 'user#example.com'
fillIn '.signin-password', 'foobargaz'
click '.signin-btn'
andThen ->
ok exists('.menu-signout'), "signout button exists"
The assertions passes, but I get an additional error reporting the returned JSON as seen in this screenshot. The screenshot basically shows:
[Fail] {user_id: 2, auth_token: wVveiyDLuXBXu69pQ2XQwg}
[Pass] signout button exists
Additionally, I've also run the tests by mocking the ajax requests with mockjax, but with the same failure.
Third, I should note that I had to patch "ember-auth-request-jquery.js" to make it work with ember testing as suggested here
I'm pretty sure you're failing to wait on the first visit to happen, so here's how I read it (I'm no CS person)
You're telling Ember to go to library
Before being sure it's finished navigating you're trying to fill in 2 fields and click a button (all of which probably doesn't exist)
then you check to see if it's library, but while waiting after you thought you clicked, really the page finishes rendering the login page from the visit
Here's what js2coffe says it'd kind of look like (my main point is the then after the visit).
test "after signin, should redirect user back to previous page", ->
visit("/library").then ->
fillIn ".signin-email", "example#example.com"
fillIn ".signin-password", "examplepass"
click(".signin-btn").then ->
equal testing().path(), "library", "Should redirect back to library (was " + (testing().path()) + ")"
Update 1/4: Documentation changed on me
Now we move to educated guess time. Looking through the Ember-auth code it might not be creating any timers/promises that Ember is aware of, in affect Ember thinks it's finished the signin process immediately. So the click promise is resolved immediately and you run your test immediately (andThen waits on the global testing promise to resolve). To test the theory you can do some terrible timeout and see if it does indeed redirect after some time
test "after signin, should redirect user back to previous page", ->
visit "/library"
fillIn ".signin-email", "example#example.com"
fillIn ".signin-password", "examplepass"
click ".signin-btn"
stop()
Ember.run.later this, (->
start()
equal testing().path(), "library", "Should redirect back to library (was " + (testing().path()) + ")"
), 5000
It turns out my coffeescript was not the best in the world.
The module function in QUnit should NOT compile to:
module('Authentication: visiting restricted page as non authenticated user', function() {
return setup(function() {
return Em.run(App, App.advanceReadiness);
});
});
but to:
module('Authentication: visiting restricted page as non authenticated user', {
setup: function() {
Ember.run(App, App.advanceReadiness);
},
// This is also new
teardown: function() {
App.reset();
}
});
Additionally, in my spec_helper.coffee file I had something like this:
QUnit.testStart(function() {
// FIXME: this below made it fail every time
// Ember.run(function() {
// return App.reset();
// });
Ember.testing = true;
});
QUnit.testDone(function() {
Ember.testing = false;
});
QUnit.done(function() {
return Ember.run(function() {
return App.reset();
});
});
which seems to have caused some issues, so I just deleted it and the tests now pass.

Testing static routes in compojure

I am trying to write a test for the compojure static content route.
I am testing the routes by examining the ring response directly.
A minimal working example is as follows:
;; src/testing-webapps.core.clj
(ns testing-webapps.core
(:use [compojure.core]
[compojure.route :as route]))
(defroutes web-app
(route/resources "/")
(route/not-found "404"))
;; test/testing-webapps.core_test.clj
(ns testing-webapps.core-test
(:require [clojure.test :refer :all]
[testing-webapps.core :refer :all]))
(defn request [resource web-app & params]
(web-app {:request-method :get :uri resource :params (first params)}))
(deftest test-routes
(is (= 404 (:status (request "/flubber" web-app))))
(is (= "404" (:body (request "/flubber" web-app))))
(is (= 200 (:status (request "/test.txt" web-app)))))
Testing the 404 route works fine but calling (request "/test.txt" web-app) leads to an unexpected NullPointerException in ring.middleware.file-info/not-modified-since?.
Here is the top part of the stacktrace:
ERROR in (test-routes) (file_info.clj:27)
Uncaught exception, not in assertion.
expected: nil
actual: java.lang.NullPointerException: null
at ring.middleware.file_info$not_modified_since_QMARK_.invoke (file_info.clj:27)
ring.middleware.file_info$file_info_response.doInvoke (file_info.clj:44)
clojure.lang.RestFn.invoke (RestFn.java:442)
ring.middleware.file_info$wrap_file_info$fn__917.invoke (file_info.clj:64)
[...]
The static route works fine in the browser but not when called through my request function.
Is there an easier way to test a static route in compojure and why do I get a NullPointerException when calling the static route with my own request map?
Looking at the source for not-modified-since?, I think the issue is you have no headers in your request map, so it throws a NPE on this expr: (headers "if-modified-since"). Try changing your request method like so:
(defn request [resource web-app & params]
(web-app {:request-method :get
:headers {"content-type" "text/plain"} ; added a header
:uri resource
:params (first params)}))
You might also consider using ring-mock to create requests for testing. It insulates you a little bit from stuff like this.

Use JSON request body rather than request parameters for Friend authentication in a Clojure Web Application

I am using Friend to build authentication into a Compojure web application.
I have defined a bespoke authentication workflow for Friend:
(defn authentication-workflow []
(routes
(GET "/logout" req
(friend/logout* {:status 200}))
(POST "/login" {{:keys [username password]} :params}
(if-let [user-record (authenticate-user username password)]
(workflows/make-auth user-record {:cemerick.friend/workflow :authorisation-workflow})
{:status 401}))))
The authentication part is factored out:
(defn authenticate-user [username password]
(if-let [user-record (get-user-for-username username)]
(if (creds/bcrypt-verify password (:password user-record))
(dissoc user-record :password))))
This works, but...
I am using AngularJS and having to post request parameters leads to some ugly Angular code (cribbed elsewhere from a StackOverflow answer):
$http({
method: 'POST',
url: '/login',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
transformRequest: function(obj) {
var str = [];
for (var p in obj)
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
return str.join("&");
},
data: {
username: username,
password: password
}
});
I would much rather do this much simpler call instead and just post a JSON object via the request body:
$http.post('/login', {username: username, password: password})
I tried to use ":body" in the authentication handler instead of ":params" but the value of :body seemed neither JSON nor Clojure to me so I don't know how I can use it:
{username me#myapp.com, password password}
I already have JSON request/response mapping workflows working correctly for my REST API handlers, and I checked already that the request headers (e.g. ContentType) were correct for JSON.
So can this be done with Compojure/Friend, and if so how?
Here is some working code and an explanation...
First the Friend workflow, using the request body:
(defn authentication-workflow []
(routes
(GET "/logout" req
(friend/logout* {:status 200}))
(POST "/login" {body :body}
(if-let [user-record (authenticate-user body)]
(workflows/make-auth user-record {:cemerick.friend/workflow :authorisation-workflow})
{:status 401}))))
Second, the authentication function:
(defn authenticate-user [{username "username" password "password"}]
(if-let [user-record (get-user-for-username username)]
(if (creds/bcrypt-verify password (:password user-record))
(dissoc user-record :password))))
Third, the Compojure application with middlewares declared:
(def app
(-> (handler/site
(friend/authenticate app-routes
{:workflows [(authentication-workflow)]}))
(params/wrap-keyword-params)
(json/wrap-json-body)
(json/wrap-json-response {:pretty true})))
Finally a fragment of AngularJS code to post the credentials (username and password come from an AngularJS model):
$http.post('/login', {username: username, password: password});
So what happens is this...
The Angular javascript code posts JSON to the web application login URL. The "Content-Type" header is automatically set to "application/json" and the request body is automatically encoded as JSON, for example:
{"username":"batman#batcave.org","password":"tumblerrocks"}
On the server, the middleware parses the JSON to a Clojure map and presents it to the handler via the ":body" keyword:
{username batman#batcave.org, password tumblerrocks}
The request is then routed to the custom Friend authentication workflow.
Finally the submitted values are extracted from the Clojure map and used to authenticate the user.
I suspect that your wrappers are applied in the wrong order. Check that ring.middleware.json/wrap-json-body is applied before (outside of) the friend wrapper.
e.g.
(def my-handler (wrap-json-body (cemerick.friend/authenticate ...)))
Otherwise, a quick fix might be to just wrap your whole app in ring.middleware.json/wrap-json-params