I need a CSRF token in order for my websockets client to connect to my server. In order to get the token to my client, I embed the token from ring.middleware.anti-forgery/*anti-forgery-token* in a <meta> tag, like so:
(defn head []
[:head
...
(let [csrf-token (force ring.middleware.anti-forgery/*anti-forgery-token*)]
[:meta {:name "csrf-token"
:content csrf-token}])
...])
However, instead of getting an actual token back (i.e. a 60-character-long random base64 string) in the rendered tag, it renders as
<meta content="Unbound: #'ring.middleware.anti-forgery/*anti-forgery-token*" name="csrf-token">
I also have ring-default's site-defaults wrapped around the routes as middleware, which includes ring.middleware.anti-forgery.
Here is the relevant file, handler.clj:
(ns spotify-client.handler
(:require
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
[hiccup.page :refer [include-js html5]]
[ring.middleware.anti-forgery]
[compojure.core :refer :all]))
(defn head []
[:head
...
(let [csrf-token (force ring.middleware.anti-forgery/*anti-forgery-token*)]
[:meta {:name "csrf-token"
:content csrf-token}])
...])
(defn loading-page []
(html5
(head)
[:body {:class "body-container"}
[:div#app]
(include-js "/js/app.js")
[:script "spotify_client.core.init_BANG_()"]]))
(defn index-handler [_]
{:status 200
:headers {"Content-Type" "text/html"}
:body (loading-page)
(defroutes main-routes
(GET "/" [] index-handler)
...
)
(def app (wrap-defaults main-routes site-defaults))
I'm also running all of this using lein run.
Now, according to this link, the anti-forgery token var would become bound in the context of whatever the current request was, and the ring-anti-forgery middleware is what binds it. When my project was brand new and I had just added wrap-defaults (on the initial ten or so calls to start/reload the server), the token var was bound and rendered correctly. However, after no signficant changes, the token var will not bind and it renders as the "Unbound" string. Once the ring-anti-forgery middleware stopped being called(?) and stopped binding *anti-forgery-token* to an actual value, the var was never bound again. I've tried making a new project a couple of times already and the token var stops being bound after 5-10 server reloads. What could cause the ring-anti-forgery middleware within ring-defaults to stop working even though wrap-defaults is in my code?
According to the docs, the *anti-forgery-token* is a dynamic variable that is only defined within the context of a wrap-anti-forgery middleware call.
Add [ring.middleware.anti-forgery :refer [wrap-anti-forgery]] to your require form, and then add wrap-anti-forgery to your app declaration:
(ns spotify-client.handler
(:require
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
[hiccup.page :refer [include-js html5]]
[ring.middleware.anti-forgery :refer [wrap-anti-forgery]]
[compojure.core :refer :all]))
...
(def app
(-> main-routes
(wrap-anti-forgery)
(wrap-defaults site-defaults))
Related
We use the compojure-api to get us some nice swagger integration in our ring apps. The :swagger {:deprecated true} meta works like a champ to get the swagger page correct, but I have a requirement that I put a specific header on the response when the route is :swagger {:deprecated true}. I am struggling to figure out how to do this with the middleware pattern that I've been using to do similar response header manipulations.
(ns bob.routes
(:require [clojure.tools.logging :as log]
[compojure.api.sweet :refer :all]
[ring.util.http-response :as status]
[schema.core :as s]
[ring.swagger.schema :as rs]))
(s/defschema BobResponse {:message (rs/describe String "Message")})
(defn wrap-bob-response-header [handler]
(fn [request]
(let [response (handler request)]
;; can I reach into the request or the response to see what
;; route served this and if it has the :swagger {:deprecated true}
;; meta on it and NOT emit the x-bob header if it does?
(assoc-in response [:headers "x-bob"] "Robert"))))
(defroutes bob-routes
(context "" []
:middleware [wrap-bob-response-header]
:tags ["bob"]
:description ["Tease out how to do swagger driven response header"]
(GET "/notdeprectated" [:as request]
:swagger {:deprecated false}
:new-relic-name "GET_notdeprecated"
:return BobResponse
(status/ok {:message "All is well"}))
(GET "/isdeprecated" [:as request]
:swagger {:deprecated true}
:new-relic-name "GET_isdeprecated"
:return BobResponse
(status/ok {:message "You came to the wrong neighborhood."}))))
How do I modify wrap-bob-response-header to only emit x-bob on routes with :swagger {:deprecated true}?
With Compojure-API, the middleware are invoked in-place, at the path context they are defined at. In your example, the wrap-bob-response-header doesn't yet know where the request is going to go (or will it even match anything). If it knew, you could use the injected route information from the request (see https://github.com/metosin/compojure-api/blob/master/src/compojure/api/api.clj#L71-L73) to determine if the endpoints would have the swagger information set.
What you could do, is mount the header-setting middleware only to the routes that need it.
There is a library called reitit (also by Metosin) which solves this by applying a route-first architecture: the full path lookup is done first and the middleware chain is applied after that. Because of this, all the middleware know the endpoint they are mounted to. Middleware can just query the endpoint data (either at request-time or at compile-time) and act accordingly. They can even decide not to mount to that spesific route.
Reitit is feature-par with compojure-api, just with different syntax, e.g. fully data-driven.
Good examples in the blog: https://www.metosin.fi/blog/reitit-ring/
PS. I'm co-author of both of the libs.
EDIT.
Solution to inject data to the response after a match:
1) create middleware that adds data (or meta-data) to the response
2) add or modify a restructuring handler to mount the middleware from 1 into the endpoint, with the given data (available in the handler)
3) read the data in the response pipeline and act accordingly
(defn wrap-add-response-data [handler data]
(let [with-data #(assoc % ::data data)]
(fn
([request]
(with-data (handler request)))
([request respond raise]
(handler #(respond (with-data %)) raise)))))
(defmethod compojure.api.meta/restructure-param :swagger [_ swagger acc]
(-> acc
(assoc-in [:info :public :swagger] swagger)
(update-in [:middleware] into `[[wrap-add-response-data ~swagger]])))
(def app
(api
(context "/api" []
(GET "/:kikka" []
:swagger {:deprecated? true}
(ok "jeah")))))
(app {:request-method :get, :uri "/api/kukka"})
; {:status 200, :headers {}, :body "jeah", ::data {:deprecated? true}}
I am using ring with compojure and friend to realize basic password authentication in a toy app. Now I was trying to implement an example and my ring server causes a redirect loop in every browser.
This is my code:
(ns kassenclo.handlers
(:use [ring.util.response]
[ring.middleware.resource]
[ring.middleware.params])
(:require [compojure.core :refer :all]
[compojure.route :as route]
[ring.middleware.session :refer [wrap-session]]
[ring.middleware.keyword-params :refer [wrap-keyword-params]]
[kassenclo.views :as views]
[cemerick.friend :as friend]
[cemerick.friend.workflows :refer [make-auth]]))
(defroutes app
(GET "/" [] views/tally-view)
(GET "/inventory" [] (friend/authorize #{::user} views/inventory-view))
(route/not-found (html [:h1 "This page could not be found, please go back."])))
(defn auth-workflow [request]
(let [speak (get-in request [:params :speak])
credential-fn (get-in request [::friend/auth-config :credential-fn])]
(make-auth (credential-fn speak))))
(defn auth-credential [password]
(if (= password "pass")
{:identity password :roles #{::user}}))
(def handler
(-> app
(friend/authenticate {:workflows [auth-workflow]
:credential-fn auth-credential})
(wrap-keyword-params)
(wrap-params)
(wrap-session)
(wrap-resource "public")))
Simple debugging shows me that the server is alternating between auth-workflow and auth-credential a few times before it is stopped. Can anybody point out to me what I am missing?
// Edit:
The curious thing is that this redirect-loop happens on every route, even on / where friend is not used in the defroutes command.
I found out that the make-auth function, which wraps the authentication-map so it has the correct form has to be applied on the return value of the auth-credential before returning it. If it happens afterwards like in my original post friend rejects it and we get a authentication loop.
I wrote simple client-server app referring to "ClojureScript: Up and Running".
https://github.com/phaendal/clojure-simple-client-server
As shown in below server-code, /text prints request and body to console and returns body from (slurp (:body req)).
But if :auto-refresh? is set to true in project.clj, (slurp (:body req)) will returns empty string instead of sent value.
Why it returns empty? and How to get request-body with auto-refresh?
(ns client-server.server
(:gen-class)
(:require [compojure.route :as route]
[compojure.core :as compojure]
[ring.util.response :as response]))
(defn simple-print [req]
(let [body (slurp (:body req) :encoding "utf-8")]
(println req)
(println (str "slurped: " body))
body))
(compojure/defroutes app
(compojure/POST "/text" request (simple-print request))
(compojure/GET "/" request
(-> "public/index.html"
(response/resource-response)
(response/content-type "text/html")
(response/charset "utf-8")))
(route/resources "/"))
When you set auto-refresh: true, lein-ring also adds ring.middleware.params through wrap-params. see https://github.com/weavejester/ring-refresh/blob/master/src/ring/middleware/refresh.clj#L90-L102.
The ring.middleware.params do its job to parse form-parameter from request body by draining the request body through slurp just like you do in your handler. see https://github.com/mmcgrana/ring/blob/master/ring-core/src/ring/middleware/params.clj#L29
So, the request body already emptied by the time you try to slurp it in your handler.
Also, when you try to POST, please pay attention to the content-type sent. It's application/www-form-urlencoded by default and it needs parameter name and value. see http://www.w3.org/MarkUp/html-spec/html-spec_8.html#SEC8.2.1
Just sending the plain value just like you do in your clojurescript does not play nice with form-parameter parser. In your project example, ring param middleware just skip it because the value sent from your javascript does not conform to the spec. If you put name and value in your POST request, it shows up in :params key in your request.
I'm writing a Ring middleware, and also using Compojure. I want my middleware to look in the :params map to see if a specific key was provided by the user. In my middleware function, the request map does not contain a :params map, though. In the final request handler, there is a :params map. I'm thinking the :params map it not being set before my custom middleware, but I can't figure out how to get it to actually be set.
Any ideas?
(ns localshop.handler
(:use [ring.middleware.format-response :only [wrap-restful-response]]
[compojure.core])
(:require [localshop.routes.api.items :as routes-api-items]
[localshop.middleware.authorization :as authorization]
[compojure.handler :as handler]))
;; map the route handlers
(defroutes app-routes
(context "/api/item" [] routes-api-items/routes))
;; define the ring application
(def app
(-> (handler/api app-routes)
(authorization/require-access-token)
(wrap-restful-response)))
Above is my handler.clj file, and below is the middleware itself.
(ns localshop.middleware.authorization)
(defn require-access-token [handler]
(fn [request]
(if (get-in request [:params :token])
(handler request)
{:status 403 :body "No access token provided"})))
I actually figured this out. This works if you adjust the (def app ... ) portion of the code so that it matches the following:
(def app
(-> app-routes
(wrap-restful-response)
(authorization/require-access-token)
(handler/api)))
I'm trying to make any of the following mappings work to map http://mysite.org/add?http://sitetoadd.com or http://mysite.org/add?u=http://sitetoadd.com
(GET "/add?:url" [url] url)
(GET "/add?u=:url" [url] url)
(GET "/add" {params :params} (params :u))
(GET "/add" {params :params} (params "u"))
(GET "/add" [u] u)
But it just fails and I don't know why. On the other hand, this works:
(GET "/add/:url" [url] url)
but I can't use it because I have to pass a url and http://mysite.org/add/http://sitetoadd.com is invalid while http://mysite.org/add?http://sitetoadd.com is ok.
EDIT: dumping request i've seen that params is empty. I thought that it would contain POST and GET parameters, but the only place where I can find the params I pass is in :query-string ("u=asd"). It seems that a middleware is needed to parse query strings. My question stands still, by the way.
See https://github.com/weavejester/compojure under Breaking Changes. The params map is no longer bound by default. If you have your example routes inside a "(defroutes main-routes ... )", make sure you activate it through "(handler/site main-routes)" as explained on https://github.com/weavejester/compojure/wiki/Getting-Started as it is the site or api method that makes sure the params map gets bound by default.
Here's an example that works:
(ns hello-world
(:use compojure.core, ring.adapter.jetty)
(:require [compojure.route :as route]
[compojure.handler :as handler]))
(defroutes main-routes
(GET "/" {params :params} (str "<h1>Hello World</h1>" (pr-str params)))
(route/not-found "<h1>Page not found</h1>"))
(defn -main [& m]
(run-jetty (handler/site main-routes) {:port 8080}))