I am trying to use the friend-json-auth library with my app in tandem with compojure instead immutant as they have listed in their example app.
https://github.com/marianoguerra/immutant-recipes/tree/master/friend-json-auth
I am using angular as my client and I make this post request:
this.login = function ( username, password ) { return $http({
method: 'POST',
url: 'http://localhost:3000/api/session',
headers: {
'Content-Type': 'application/json',
'Data-Type': 'json'
},
data: { username : "jane", password : "user_password" }
}) };
but the reponse I get back is
POST http://localhost:3000/api/session 401 (Unauthorized)
Here is my POST endpoint for /api/session:
controller.clj
(ns recursiftion.controller
(:use [ring.adapter.jetty :only [run-jetty]]
[recursiftion.websocket :only [wamp-handler]]
[recursiftion.config :only [conf]]
[ring.middleware params keyword-params nested-params]
[ring.util.response :only (redirect)]
[cemerick.friend.util :only (gets)]
[clojure.data.json :only (read-json json-str)]
)
(:require [compojure.core :refer :all]
[compojure.handler :as handler]
[compojure.route :as route]
[clojure.java.io :as io]
[ring.util.io :refer [string-input-stream]]
[ring.util.response :as resp]
[ring.util.response :refer [response]]
[ring.middleware.json :as middleware]
[ring.middleware.cors :refer [wrap-cors]]
[environ.core :refer [env]]
[cheshire.core :refer :all]
[recursiftion.model :as model]
[clojure.pprint :refer [pprint]]
[cemerick.friend :as friend]
(cemerick.friend [workflows :as workflows]
[credentials :as creds])
[ring.middleware.session :as ring-session]
[marianoguerra.friend-json-workflow :as json-auth]
[ring.middleware.file :as ring-file]
[ring.middleware.file-info :as ring-file-info]))
(defroutes app-routes
(POST "/api/session" request
(let [node_object (or (get-in request [:params :data])
(get-in request [:body :data])
"ROUTER_ERROR")]
(json-auth/handle-session request)))
)
(def app
(-> (handler/api app-routes)
(middleware/wrap-json-body {:keywords? true})
(middleware/wrap-json-response)
(wrap-cors routes #"^http://localhost:9000$")))
(def users {"root" {:username "root"
:password (creds/hash-bcrypt "admin_password")
:roles #{::admin}}
"jane" {:username "jane"
:password (creds/hash-bcrypt "user_password")
:roles #{::user}}})
(def secure-app
(-> app
(friend/authenticate
{:login-uri "/friend-json-auth/api/session"
:unauthorized-handler json-auth/unauthorized-handler
:workflows [(json-auth/json-login
:login-uri "/friend-json-auth/api/session"
:login-failure-handler json-auth/login-failed
:credential-fn (partial creds/bcrypt-credential-fn users))]})
(ring-session/wrap-session)))
(defn -main []
(run-jetty app {:port (if (nil? (System/getenv "PORT"))
8000 ; localhost or heroku?
(Integer/parseInt (System/getenv "PORT")))}) )
project.clj
(defproject helloworld "1.0.0-SNAPSHOT"
:description "csc358 final project"
:url "http://localhost:9000/"
:license {:name "FIXME: choose"
:url "http://example.com/FIXME"}
:dependencies [[org.clojure/clojure "1.7.0"]
[compojure "1.1.8"]
[ring/ring-json "0.3.1"]
[ring/ring-core "1.4.0"]
[ring-router "0.2-SNAPSHOT"]
[ring/ring-jetty-adapter "1.3.2"]
[jumblerg/ring.middleware.cors "1.0.1"]
[c3p0/c3p0 "0.9.1.2"]
[org.clojure/java.jdbc "0.2.3"]
[cheshire "5.4.0"]
[environ "0.2.1"]
[hiccup "1.0.0"]
[clojurewerkz/neocons "3.1.0-rc1"]
[org.clojure/data.json "0.2.5"]
[clj-wamp "1.0.2"]
[clj-http "2.0.0"]
[com.cemerick/friend "0.2.1"]
[org.marianoguerra/friend-json-workflow "0.2.1"]]
:min-lein-version "2.0.0"
:plugins [[environ/environ.lein "0.2.1"]
[lein-ring "0.7.3"]]
:hooks [environ.leiningen.hooks]
:profiles {:dev {:dependencies [[ring-mock "0.1.3"]]}}
:ring {:handler recursiftion.controller/app})
I am very grateful for help you might be able to offer.
On his example client he uses JSON.stringify to pass a JSON string as a map to the AJAX request. In your request you're passing a js object instead of a JSON string as the value of data:
https://github.com/marianoguerra/immutant-recipes/blob/master/friend-json-auth/resources/s/js/app.js
function login(username, password) {
var data = JSON.stringify({"username": username, "password": password});
return $.ajax({
type: "POST",
url: "api/session",
dataType: "json",
contentType: "application/json",
data: data
});
}
Related
Can't import ring.middleware.anti-forgery
(ns msa-debugger-clojure.core
(:require
[reagent.core :as r :refer [atom]]
[reagent.session :as session]
[reitit.frontend :as reitit]
[clerk.core :as clerk]
[accountant.core :as accountant]
[reagent-forms.core :refer [bind-fields]]
[reagent-forms.core :as reagent-forms]
[ring.middleware.anti-forgery :as anti-forgery]
))
As said in readme, add dependency to project.clj:
:dependencies [[org.clojure/clojure "1.10.0"]
[ring-server "0.5.0"]
[reagent "0.8.1"]
[reagent-utils "0.3.2"]
[ring "1.7.1"]
[ring/ring-defaults "0.3.2"]
[ring/ring-anti-forgery "1.3.0"]
[hiccup "1.0.5"]
[yogthos/config "1.1.1"]
[org.clojure/clojurescript "1.10.520"
:scope "provided"]
[metosin/reitit "0.2.13"]
[pez/clerk "1.0.0"]
[venantius/accountant "0.2.4"
:exclusions [org.clojure/tools.reader]]
[reagent-forms "0.5.43"]
]
What i am doing wrong? :c
ring is a Clojure library and does not work in ClojureScript.
I am trying to recreate the example in http://react-component.github.io/table/examples/animation.html to add animation to a table in a re-frame app. The table is rendered using antizer which is a ClojureScript library for Ant Design react components. For the animation I'm trying to use rc-animate (as in the example) which is a JavaScript library.
To integrate rc-animate, I followed the official Webpack guide and created a src/js/index.js file:
import Animate from 'rc-animate';
window.Animate = Animate;
My project.clj is:
(defproject ant-table-animation "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.10.238"]
[reagent "0.8.1"]
[re-frame "0.10.5" :exclusions [reagent]]
[antizer "0.3.1"]]
:plugins [[lein-cljsbuild "1.1.7"]]
:min-lein-version "2.5.3"
:source-paths ["src/clj" "src/cljs"]
:clean-targets ^{:protect false} ["resources/public/js/compiled" "target"]
:figwheel {:css-dirs ["resources/public/css"]}
:profiles
{:dev
{:dependencies [[binaryage/devtools "0.9.10"]
[cider/piggieback "0.3.9"]
[figwheel-sidecar "0.5.16"]
[day8.re-frame/re-frame-10x "0.3.3"]]
:plugins [[lein-figwheel "0.5.16"]]}
:prod { }
}
:cljsbuild
{:builds
[{:id "dev"
:source-paths ["src/cljs"]
:figwheel {:on-jsload "ant-table-animation.core/mount-root"}
:compiler {:closure-defines {re-frame.trace.trace_enabled_QMARK_ true}
:main ant-table-animation.core
:output-to "resources/public/js/compiled/app.js"
:output-dir "resources/public/js/compiled/out"
:asset-path "js/compiled/out"
:source-map-timestamp true
:preloads [devtools.preload, day8.re-frame-10x.preload]
:external-config {:devtools/config {:features-to-install :all}}
:infer-externs true
:npm-deps false
:foreign-libs [{:file "dist/index_bundle.js"
:provides ["rc-animate" "rc-animate-child"]
:global-exports {rc-animate Animate
rc-animate-child AnimateChild}}]
}}
{:id "min"
:source-paths ["src/cljs"]
:compiler {:main ant-table-animation.core
:output-to "resources/public/js/compiled/app.js"
:optimizations :advanced
:closure-defines {goog.DEBUG false}
:pretty-print false}}
]}
)
and in my views.cljs I try to render the table like this:
(ns ant-table-animation.views
(:require
[re-frame.core :as re-frame]
[ant-table-animation.subs :as subs]
[ant-table-animation.events :as events]
[antizer.reagent :as ant]
[reagent.core :as reagent]
[rc-animate]
))
(.log js/console rc-animate)
(defn AnimateBody
[props]
(.createElement
js/React
rc-animate
(.assign js/Object #js {:transitionName "move", :component "tbody"} props)))
(.log js/console AnimateBody)
(defn orders
[]
(let [orders #(re-frame/subscribe [::subs/orders])
width 80]
[ant/table
{:columns
[{:title "Product Name" :dataIndex :product :width width}
{:title "Quantity" :dataIndex :quantity :width width}
{:title "Unit Price" :dataIndex :price :width width}
{:title "Actions" :dataIndex "actions" :width width
:render
#(reagent/as-element
[ant/button
{:icon "delete" :type "danger"
:on-click
(fn []
(re-frame/dispatch [::events/order-deleted (.-product %2)]))}])}]
:dataSource orders
:size "small"
:components {:body {:wrapper AnimateBody}}
:pagination {:page-size 20}
:scroll {:y 300}}]))
(defn main-panel []
(let [name (re-frame/subscribe [::subs/name])]
[:div
[:h1 "Hello from " #name]
[orders]
]))
I am not sure at all about the
(defn AnimateBody
[props]
(.createElement
js/React
rc-animate
(.assign js/Object #js {:transitionName "move", :component "tbody"} props)))
being equivalent to the line from the example
const AnimateBody = props => <Animate transitionName="move" component="tbody" {...props} />;
The table renders correctly, but when I try to delete a row it fails with the following error trace:
react-dom.development.js:55 Uncaught Error: Unable to find node on an unmounted component.
at invariant (react-dom.development.js:55)
at findCurrentFiberUsingSlowPath (react-dom.development.js:4256)
at findCurrentHostFiber (react-dom.development.js:4266)
at findHostInstance (react-dom.development.js:17676)
at Object.findDOMNode (react-dom.development.js:18145)
at AnimateChild.transition (AnimateChild.js:83)
at AnimateChild.componentWillLeave (AnimateChild.js:70)
at performLeave (Animate.js:339)
at Array.forEach (<anonymous>)
at Animate.componentDidUpdate (Animate.js:188)
at commitLifeCycles (react-dom.inc.js:15386)
at commitAllLifeCycles (react-dom.inc.js:16646)
at HTMLUnknownElement.callCallback (react-dom.inc.js:143)
at Object.invokeGuardedCallbackDev (react-dom.inc.js:193)
at invokeGuardedCallback (react-dom.inc.js:250)
at commitRoot (react-dom.inc.js:16800)
at completeRoot (react-dom.inc.js:18192)
at performWorkOnRoot (react-dom.inc.js:18120)
at performWork (react-dom.inc.js:18024)
at performSyncWork (react-dom.inc.js:17996)
at requestWork (react-dom.inc.js:17884)
at scheduleWork (react-dom.inc.js:17689)
at Object.enqueueForceUpdate (react-dom.inc.js:11855)
at Object.Component.forceUpdate (react.inc.js:479)
at reagent$impl$batching$run_queue (batching.cljs?rel=1541330682770:39)
at Object.flush_queues (batching.cljs?rel=1541330682770:86)
at Object.run_queues (batching.cljs?rel=1541330682770:76)
at batching.cljs?rel=1541330682770:63
at re_frame_10x.cljs?rel=1541164419576:125
It is also indicated that:
The above error occurred in the <Animate> component:
in Animate (created by ant_table_animation.views.animateBody)
in ant_table_animation.views.animateBody (created by BaseTable)
in table (created by BaseTable)
in BaseTable (created by Connect(BaseTable))
in Connect(BaseTable) (created by BodyTable)
in div (created by BodyTable)
in BodyTable (created by ExpandableTable)
in div (created by ExpandableTable)
in div (created by ExpandableTable)
in div (created by ExpandableTable)
in ExpandableTable (created by Connect(ExpandableTable))
in Connect(ExpandableTable) (created by Table)
in Provider (created by Table)
in Table (created by LocaleReceiver)
in LocaleReceiver (created by Table)
in div (created by Spin)
in AnimateChild (created by Animate)
in div (created by Animate)
in Animate (created by Spin)
in Spin (created by Table)
in div (created by Table)
in Table (created by ant_table_animation.views.orders)
in ant_table_animation.views.orders (created by ant_table_animation.views.main_panel)
in div (created by ant_table_animation.views.main_panel)
in ant_table_animation.views.main_panel
I am a Clojure beginner, and I know even less for React; this is where I ended up after a week of trying but now I feel stuck. I have uploaded my project in github for anyone that would like to give it a try.
The Unable to find node on an unmounted component error was occurring because of version issues with React. I was able to handle it by explicitly using the version of React used by the rc-animate library - 16.5.2 in my project.clj:
...
[reagent "0.8.1" :exclusions [cljsjs/react cljsjs/react-dom [cljsjs/react-dom-server]]]
[cljsjs/react "16.5.2-0"]
[cljsjs/react-dom "16.5.2-0"]
[cljsjs/react-dom-server "16.5.2-0"]
...
[antizer "0.3.1" :exclusions [cljsjs/react cljsjs/react-dom [cljsjs/react-dom-server]]]]
...
For the correct definition of the AnimateBody component I had to use a combination of reagent/adapt-react-class, reagent/as-element and reagent/reactify-component.
Specifically, in my views.cljs I define the component as:
(def animate (reagent/adapt-react-class rc-animate))
(def animateBody
(fn [props]
(reagent/as-element [animate (assoc props :transition-name "move" :component "tbody")])))
and then pass it to the ant/table component with:
...
:components {:body {:wrapper (reagent/reactify-component animateBody)}}
...
I have two files, one html and one css. I have tried to turn them into a heroku app and even used the lein command to create a heroku friendly skeleton and plug these two files in, but cannot get it to work for the life of me. There is something very basic that I don't yet understand about how to coordinate a view with the back-end control. And the hello world tutorials aren't helping me because they do not show me how to do different things or explain what needs to change in my defroutes function, for example, for that to be accomplished. In short, my question is this: How can I coordinate these two files into a Clojure project to make the html render as the front page of a webapp and then deploy it on heroku?
html:
<html>
<head>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<img id="sun" src="http://goo.gl/dEEssP">
<div id='earth-orbit'>
<img id="earth" src="http://goo.gl/o3YWu9">
</div>
</body>
</html>
web.clj file in "lein new heroku ..." project:
(ns solar_system.web
(:require [compojure.core :refer [defroutes GET PUT POST DELETE ANY]]
[compojure.handler :refer [site]]
[compojure.route :as route]
[clojure.java.io :as io]
[ring.middleware.stacktrace :as trace]
[ring.middleware.session :as session]
[ring.middleware.session.cookie :as cookie]
[ring.adapter.jetty :as jetty]
[ring.middleware.basic-authentication :as basic]
[cemerick.drawbridge :as drawbridge]
[environ.core :refer [env]]))
(defn- authenticated? [user pass]
;; TODO: heroku config:add REPL_USER=[...] REPL_PASSWORD=[...]
(= [user pass] [(env :repl-user false) (env :repl-password false)]))
(def ^:private drawbridge
(-> (drawbridge/ring-handler)
(session/wrap-session)
(basic/wrap-basic-authentication authenticated?)))
(defroutes app
(ANY "/repl" {:as req}
(drawbridge req))
(GET "/" []
{:status 200
:headers {"Content-Type" "text/plain"}
:body (pr-str ["Hello" :from 'Heroku])}) ; <= Should I change this part here?
(ANY "*" []
(route/not-found (slurp (io/resource "404.html")))))
(defn wrap-error-page [handler]
(fn [req]
(try (handler req)
(catch Exception e
{:status 500
:headers {"Content-Type" "text/html"}
:body (slurp (io/resource "500.html"))}))))
(defn -main [& [port]]
(let [port (Integer. (or port (env :port) 5000))
;; TODO: heroku config:add SESSION_SECRET=$RANDOM_16_CHARS
store (cookie/cookie-store {:key (env :session-secret)})]
(jetty/run-jetty (-> #'app
((if (env :production)
wrap-error-page
trace/wrap-stacktrace))
(site {:session {:store store}}))
{:port port :join? false})))
;; For interactive development:
;; (.stop server)
;; (def server (-main))
project.clj file
(defproject solar_system "1.0.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://solar_system.herokuapp.com"
:license {:name "FIXME: choose"
:url "http://example.com/FIXME"}
:dependencies [[org.clojure/clojure "1.4.0"]
[compojure "1.1.1"]
[ring/ring-jetty-adapter "1.1.0"]
[ring/ring-devel "1.1.0"]
[ring-basic-authentication "1.0.1"]
[environ "0.2.1"]
[com.cemerick/drawbridge "0.0.6"]]
:min-lein-version "2.0.0"
:plugins [[environ/environ.lein "0.2.1"]]
:hooks [environ.leiningen.hooks]
:profiles {:production {:env {:production true}}})
example of typical handler code that renders text:
(ns hello-world.core
(:use ring.adapter.jetty))
(defn app [req]
{:status 200
:headers {"Content-Type" "text/plain"}
:body "Hello, world"}) ; <= Could I just change this part to slurp in
; the html file and stick it in a file in my
; root directory to get a successful 'git push heroku master'?
Modifying your code:
(defroutes app
(ANY "/repl" {:as req}
(drawbridge req))
(GET "/" []
{:status 200
:headers {"Content-Type" "text/html"} ; change content type
:body (slurp "resources/public/my-file.html")}) ; wherever your file is
(ANY "*" []
(route/not-found (slurp (io/resource "404.html")))))
How I'd write it:
(defroutes app
(ANY "/repl" {:as req} (drawbridge req))
(GET "/" [] (slurp "resources/public/my-file.html")) ; wherever your file is
(route/resources "/") ; special route for serving static files like css
; default root directory is resources/public/
(route/not-found (slurp (io/resource "404.html")))) ; IDK what io/resource does
; you might not need it
I'm sure I must be doing something wrong...here are the relevant lines of clojure:
(ns command.command-server
(:use [org.httpkit.server :only [run-server]])
(:use [storage.core-storage])
(:use compojure.core)
(:use [command.event-loop :only [enqueue]])
(:require [compojure.handler :as handler]
[compojure.route :as route]
[ring.middleware.json :as middleware]))
(def app
(-> (handler/api app-routes)
(middleware/wrap-json-body)
(middleware/wrap-json-response)
(middleware/wrap-json-params)))
;in app-routes, the rest left out for brevity
(POST "/request" {json :params}
(do
(queue-request json)
(response {:status 200})
))
(defn queue-request [evt]
(let [new-evt (assoc evt :type (keyword (:type evt)))]
(println (str (type (:val1 evt))))
(enqueue new-evt)))
The "println" near the end is showing the type of :val1 as java.lang.String when I send the following from jquery:
$.ajax({
type: "POST",
url: 'http://localhost:9090/request',
data: {type: 'add-request', val1: 12, val2: 50},
success: function(data){
console.log(data);
}
});
So what am I doing wrong?
This could be down to the jQuery request, rather than the ring middleware.
To be honest, I don't know much about jQuery, but I just came across this answer which looks like it could explain what's happening. In short, the data from your query as it stands will be encoded as strings in the URL. These will be parsed by ring into strings, not integers, as the URL encoding does not specify the type. If you want JSON encoding, you'll need to specify it explicitly. Like so:
$.ajax({
type: "POST",
url: 'http://localhost:9090/request',
data: JSON.stringify({type: 'add-request', val1: 12, val2: 50}),
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function(data){
console.log(data);
});
EDIT - The source code is on github if you're interested. Thanks
I am a bit confused as to how to access json data that has been posted to a url in clojure; I just can't seem to get it to work.
This is my route:
(cc/POST "/add"
request
(str ": " request))
I am not entirely sure what I have to put in place of request - I just saw some blog online and tried to follow it, but couldn't get it to work.
Here's how I'm trying to post: (from fiddler)
note: request header port is different in the image; it's a mistake, I was trying to mess with that data to see what it says so ignore that part in the above image please
In curl, I'm just doing this:
curl -X POST -H "Content-Type: application/json" -d '{"foo":"bar","baz":5}'
http://localhost:3005/add
It looks like clojure is not receiving the json data that I posted at all.
Here's what the request var contains:
: {:scheme :http, :query-params {}, :form-params {}, :request-method :post,
:query-string nil, :route-params {}, :content-type "\"application/json\"",
:uri "/event", :server-name "localhost", :params {},
:headers {"user-agent" "Fiddler", "content-type" "\"application/json\"",
"content-length" "23", "host" "localhost:3005"},
:content-length 23, :server-port 3005, :character-encoding nil, :body #}
As you can see, all params are empty...
I am using compojure and cheshire - and I can convert data into json and return them just fine for GET routes.. I need to figure out how to pass json and convert it into clojure data..
thanks
That's because :params is filled by a ring middleware which deals with "form-encoded" body.
You can use ring-json to wrap your application into this other middleware. It will parse the JSON body and fill :params accordingly. (https://github.com/ring-clojure/ring-json)
Here is an example that does what you want. The code was taken from this project. In the README you will see some of the access patterns that this supports. The code is a little messy, but it should illustrate how this can be done.
(ns poky.protocol.http.jdbc.text
(:require [poky.kv.core :as kv]
(compojure [core :refer :all]
[route :as route]
[handler :as handler])
[ring.util.response :refer [response not-found]]
(ring.middleware [format-response :as format-response ]
[format-params :as format-params])
[cheshire.core :as json]
[ring.middleware.stacktrace :as trace]))
;
; curl -d"some data" -H'Content-Type: application/text' -v -X PUT http://localhost:8080/xxx
; curl -d'{"bla":"bar"}' -H'Content-Type: application/json' -v -X PUT http://localhost:8080/bla
(def valid-key-regex #"[\d\w-_.,]+")
; FIXME: this should be split- one fn for get, one for mget
(defn- wrap-get
[kvstore ks params headers body]
(response
(let [eks (clojure.string/split ks #",")
nks (count eks)
multi (> nks 1)
ret (if multi (kv/mget* kvstore eks) (kv/get* kvstore ks))]
(condp = (get headers "accept")
"application/json" ret
"text/plain" (if multi (throw (Exception. "Multi get unsupported with Accept: text/plain")) (get ret ks))
ret))))
(defn- wrap-put
[kvstore ks params headers body]
(if (and
(= (get headers "content-type") "application/json")
(get params (keyword ks) nil))
(kv/set* kvstore ks (get params (keyword ks)))
(kv/set* kvstore ks body))
(response ""))
(defn api
[kvstore]
(let [api-routes
(routes
(GET ["/:ks" :ks valid-key-regex] {{:keys [ks] :as params} :params body :body headers :headers}
(wrap-get kvstore ks params headers body))
(PUT ["/:ks" :ks valid-key-regex] {{:keys [ks] :as params} :params
body :body body-params :body-params headers :headers}
(let [body (slurp body)
body (if (empty? body) body-params body)]
(wrap-put kvstore ks params headers body))))]
(-> (handler/api api-routes)
(format-params/wrap-format-params
:predicate format-params/json-request?
:decoder #(json/parse-string % true)
:charset format-params/get-or-guess-charset)
(format-response/wrap-format-response
:predicate format-response/serializable?
:encoders [(format-response/make-encoder json/encode "application/json")
(format-response/make-encoder identity "text/plain")]
:charset "utf-8")
trace/wrap-stacktrace)))
Hope this helps.
Just an update to a previous answers, now is enough to add formats key with list of formats which will be processed by Ring to your handler.
So something like
(def app (noir.util.middleware/app-handler
[your-routes]
:formats [:json-kw]))