I'm trying to access basic session data in a ring middleware to, in my case, copy user data into the request for easier access.
(def app
(->
(handler/site app-routes)
(wrap-user-auth) ; <--- my middleware
(session/wrap-session {:store (cookie-store {:key "something super secret"})})))
Middleware:
(defn wrap-user-auth [handler]
(fn [request]
(println request )
(let [user (get (request :session) :user nil)]
(let [response
(handler
(if-not (nil? user)
(assoc request :user user :is_authenticated? true)
(assoc request :user nil :is_authenticated? false)))]
response
))))
Session data is always empty when printing request, even though it has been set and is accessible through the views. I tried messing around with middleware order but can't figure out why the atom is empty.
Some random route where session data is available
(ANY "/foo" request (println (request :session)))
Found a solution.
Turns out this problem is 'somewhat' related to Compojure/Ring: Why doesn't a session with cookie-store survive a server restart?, which explains that 2 session middleware are being used: One by compojure, one by wrap-session. After changing both middleware to the same storage engine, the atom is filled with my session data - although I don't quite understand why.
I am putting this as an answer rather than a comment since it solves the actual problem. Happy to hear why this is happening though!
Related
I have a simple route and middleware setup with compojure/swagger that is utilizing a ring middleware.
(POST "/worlds" [request]
:body-params [name :- String]
:header-params [token :- String]
:middleware [wrap-api-auth]
:summary "Creates a new world with 'name'"
(ok (do-something-with-user)
(defn wrap-api-auth [handler]
(fn [request]
(let
[token (get (:headers request) "token")
user (db/get-user-by-token token)]
(if user
(handler request) ; pass to wrapped handler
(unauthorized {:error "unauthorized"})))))
This defines a simple route with some basic auth. The token passed in the header param is used to make a database query, which either returns a user and continues, or returns false and fails.
What I'm trying to accomplish is to pass the returned user back out so that I can use it later. I haven't had any luck, as I don't really know where I would try to add it to that I could access it later. I've tried to assoc it with the request but it doesn't appear that I can access it later. The ideal situation is I'm able to pass it to the do-something-with-user function.
Using assoc to add some data to the request should totally work.
You can find an example with some code that is very close to what I have in production at https://gist.github.com/ska2342/4567b02531ff611db6a1208ebd4316e6#file-gh-validation-clj-L124
In essence, that middleware calls
(handler (assoc request
:validation {:valid true
:validation valid?}))
So for your case, the following should just work:
(handler (assoc request
:user user))
If I understood correctly, the destructuring syntax you use is from compojure-api. According to the example at https://github.com/metosin/compojure-api/wiki/Middleware I'd say that the middleware set via the :middleware key behaves just as expected and you should be able to extract the :user from the request that ultimately ends up in your route.
So, just pass the request on to the do-something-with-user function:
(POST "/worlds" request
:body-params [name :- String]
:header-params [token :- String]
:middleware [wrap-api-auth]
:summary "Creates a new world with 'name'"
(ok (do-something-with-user request))
It should contain the :user you assoced into it in your middleware function. Note the missing brackets around request like mentioned in the comments to this answer.
I have POSTed data to a Pedestal endpoint "/my-post. I have routed that end point as such:
[[["/" {:get landing} ^:interceptors [(body-params/body-params) ...]
["/my-post {:post mypost-handler}
....
So to my mind this means that the body-params interceptor will fire for /my-post too.
In mypost-handler I have:
(defn mypost-handler
[request]
****HOW TO ACCESS THEN FORM DATA HERE ****
)
How do I now access the form data here? I can see from printing the request that I have a #object[org.eclipse.jetty.sever.HttpInputOverHTTP..] which will clearly need further processing before it is useful to me.
(I must say, the documentation for Pedestal is pretty sketchy at best...)
Something like this should work. Note the body-params interceptor on the mypost-handler route
(defn mypost-handler
[{:keys [headers params json-params path-params] :as request}]
;; json-params is the posted json, so
;; (:name json-params) will be the value (i.e. John) of name property of the posted json {"name": "John"}
;; handle request
{:status 200
:body "ok"})
(defroutes routes
[[["/mypost-handler" {:post mypost-handler}
^:interceptors [(body-params/body-params)]
]
]])
The mypost-handler is acting as a Ring handler, i. e. it should accept a Ring request map and return a Ring response map. Thus, you can expect a typical Ring request structure:
(defn mypost-handler
[{:keys [headers params json-params path-params] :as request}]
;; handle request
{:status 200
:body "ok"})
Here's more relevant info on defining such handlers in your route tables.
I'm struggling with understanding how to properly use sessions in Compojure/Ring.
Some of the examples I have come across:
https://github.com/brentonashworth/sandbar-examples/blob/master/sessions/src/sandbar/examples/session_demo.clj
http://rjevans.net/post/2628238502/session-support-in-compojure-ring
https://github.com/ring-clojure/ring/wiki/Sessions
These examples do not help me understand how to integrate sessions into something like a login mechanism.
(defroutes main-routes
(POST "/login" request (views/login request)))
;; views.clj
(defn login
[request]
(let [{params :params} request
{username :username} params
{password :password} params
{session :session} request]
(if (db/valid-user? username password)
(-> (logged-in request)
(assoc-in [:session :username] username))
(not-logged-in))))
I realize that this isn't correct as logged-in returns hiccup/html and I believe that the ring response map isn't added on until after the route is fully evaluated. This seems to be why all of the above examples show sessions being added to a complete response map. But, one of the features of Compojure to begin with was abstracting away the requirement of the development having to work with the response map. Therefore I feel like I must me missing something.
What would the correct way be to do the above?
If (logged-in request) returns the contents that should be rendered, then instead of associating :session :username onto the results of logged-in, you can return a proper response map:
{:body (logged-in request)
:session (assoc session :username username)}
:status, :headers, etc. have decent defaults if you do not provide them.
I am writing a web API using Compojure with basic-authentication middleware. The basic authentication part looks something like:
(defn authenticated? [id pass]
(and (= id "blah")
(= pass "blah"))
id and pass are passed in using the id:pass#website technique. My problem is, I would like to access this id and pass further on down where the routes are processed with the various GET, PUT, POST, etc. headings. But I can't figure out how to do this, and haven't had any luck googling around.
; i'd like to access id and pass here
(defroutes routes
(GET "/" [] {:status 200 :headers {} :body "hi!"})
...)
I presume the solution is for the above to somehow "add" id and pass to some set of variables that can be accessed where the routes are processed, but I have no idea how to add nor to access them.
Hopefully someone can point me in the right direction - thanks.
Assuming you are talking about https://github.com/remvee/ring-basic-authentication, the authenticated? fn can return a truthly value that will be added to the request in the :basic-authentication. Something like (untested):
(defn authenticated? [id pass]
(and (= id "gal")
(= pass "foo")
{:user id :passwd pass}))
(defroutes routes
(GET "/" {the-user :basic-authentication} {:status 200 :headers {} :body (str "hi! Mr. " (:user the-user) " your password is " (:passwd the-user))})
...)
The return of the authenticated? method is associated in request map referenced by the key :basic-authentication. Here goes an example with a route that returns the user. You could, however, return a map or any other object and access it through :basic-authentication key.
(defn authenticated? [user password] user)
(defroutes require-auth-routes
(GET "/return-user" request (.toString (:basic-authentication request)))
(def my-app
(routes
(-> require-auth-routes
(wrap-basic-authentication authenticated?)))
I have a clojure / compojure webapp with the following routes
(defroutes my-routes
(GET "/app/preview" request (my-preview-function request))
(ANY "*" request (str "ANY page <br>" (request :params))))
The preview GET request is made with a couple of parameters. I find this works most of the time but sometimes the /ebook/preview is not found and processing drops to the ANY route, in which case the output is similar to this,
ANY page
{:* "/app/preview", :section "50", :id "48"}
Can anyone suggest what might cause the /ebook/preview request to be skipped? It is definitely a GET request being made; the HTML does not have a POST for the /app/preview URL and to be doubly sure I added a POST route for /app/preview and that was not being hit.
JAR versions:
Clojure 1.2
compojure-0.6.2
ring-core-0.3.7
jetty-6.1.14
ring-jetty-adapter-0.3.1
ring-servlet-0.3.1jar
servlet-api-2.5-6.1.14
Routes are wrapped as follows
(require '[compojure.handler :as handler])
(defn wrap-charset [handler charset]
(fn [request]
(if-let [response (handler request)]
(if-let [content-type (get-in response [:headers "Content-Type"])]
(if (.contains content-type "charset")
response
(assoc-in response
[:headers "Content-Type"]
(str content-type "; charset=" charset)))
response))))
(def app (-> my-routes
handler/site
wrap-stateful-session
(wrap-charset "utf-8")
(wrap-file "public")))
(defn run []
(run-jetty (var app) {:join? false :port 8080}))
If you're trying to figure out what request is causing the problems, stop throwing away the request map with (request :params) and just have a look at request. That will give you a map with all the information Compojure has; you can inspect it, and pass it back into your routes later to observe what happens (after you make some changes, say).
If
(my-preview-function request)
returns nil, then the routing will try the next route. Take a look at (source GET) and see how it matches (or doesn't) your route.