In order to validate a captcha in Clojurescript I'm using cljs-http like this:
(def verify-url "https://www.google.com/recaptcha/api/siteverify")
;...
(http/post verify-url {:json-params {:secret key :response captcha-data}})
;...
...and the result is is:
{:status 0,
:success false,
:body "",
:headers {},
:trace-redirects ["https://www.google.com/recaptcha/api/siteverify"
"https://www.google.com/recaptcha/api/siteverify"],
:error-code :http-error,
:error-text " [0]"}
What does this mean?
Thanks!
Most probably your preflight (OPTIONS) requiest gets failed. Check your Developer Console for something like:
XMLHttpRequest cannot load
https://www.google.com/recaptcha/api/siteverify. Response to preflight
request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:3449' is therefore not allowed
access. The response had HTTP status code 405.
Related
I'm sending the following response in clojure ring:
(res/set-cookie
(res/redirect (env :some-url))
"some-id"
(->
req
foo-ns/bar
:id
)
{:max-age (* 30 24 60 60 1000) :path "/"})
And on printing this response I get:
{:status 302, :headers {"Location" "http://localhost:5000"}, :body "", :cookies {"some-id" {:value "1341313515135490454", :max-age 2592000000, :path "/"}}}
But on the client side, the cookie isn't set, which I can see in the console. What am I doing wrong?
It looks like you're using ring.response/set-cookie to set the cookie. That will set the cookie attributes under :cookies in your response map. Before returning the response to the browser, you need to encode those cookies into a Set-Cookie header that the browser can understand. To do this, add the ring.middleware.cookies/wrap-cookies middleware to your middleware stack.
You should expect your response to look something like:
{:status 302
:body ""
:headers {"Location" "http://localhost:5000"
"Set-Cookie" "some-id=1341313515135490454; max-age=2592000000; path=/"}}
I am making a get and post requests like so:
(http-cljs.client/get "someurl.com/my")
and
(http-cljs.client/post "someurl.com/my")
On the server, I have the route:
{"/my" do-something}
While do-something runs with the get request, it doesn't with the post and in the client I get 403 forbidden. In the response I get "Invalid Anti-forgery token".
These are the middleware I'm using:
(defn config []
{:http-port (Integer. (or (env :port) 5000))
:middleware [[wrap-defaults site-defaults]
wrap-with-logger
wrap-gzip
[wrap-reload {:dir "../../src"}]
wrap-params
wrap-keyword-params
wrap-json-body
wrap-edn-params
]})
When I use api-defaults however, there's no 403 forbidden, and it only happens with the site-defaults. Why is this the case?
The configuration for wrap-defaults which is site-defaults will turn on the anti-forgery-middleware
If you look at the doc string of wrap-anti-forgery you will find:
"Middleware that prevents CSRF attacks. Any POST request to the handler
returned by this function must contain a valid anti-forgery token, or else an
access-denied response is returned.
The anti-forgery token can be placed into a HTML page via the
*anti-forgery-token* var, which is bound to a (possibly deferred) token.
The token is also available in the request under
`:anti-forgery-token`.
By default, the token is expected to be POSTed in a form field named
'__anti-forgery-token', or in the 'X-CSRF-Token' or 'X-XSRF-Token'
headers.
Accepts the following options:
:read-token - a function that takes a request and returns an anti-forgery
token, or nil if the token does not exist
:error-response - the response to return if the anti-forgery token is
incorrect or missing
:error-handler - a handler function to call if the anti-forgery token is
incorrect or missing
:strategy - a strategy for creating and validating anti-forgety tokens,
which must satisfy the
ring.middleware.anti-forgery.strategy/Strategy protocol
(defaults to the session strategy:
ring.middleware.anti-forgery.session/session-strategy)
Only one of :error-response, :error-handler may be specified.
Anti-forgery for forms is used to prevent replay attacks.
More info on CSRF attacks here: https://en.wikipedia.org/wiki/Cross-site_request_forgery
wrap-cors does not return access control headers when there is a bad request against my api endpoint. I believe this is because I am using a exception handler which might be blocking the middleware from running. I want to know how I can still execute the middleware for this route and append the cors headers to the response of bad requests.
exception handler
(defn- bad-request-handler
"Handles bad requests."
[f]
(f
(ring/response {:status "bad request"})))
app
(def app
(api
{:exceptions {:handlers
{::ex/request-validation (bad-request-handler response/bad-request)}}}
(POST "/" [] :body [item {(schema/required-key :item) schema/Bool}]
:middleware [#(wrap-cors % :access-control-allow-origin [#".*"]
:access-control-allow-methods [:post])]
:responses {200 {:description "ok"}
400 {:description "bad request"}} item)))
I've decided to append my own headers rather than using r0man/ring-cors.
I can determine the contents of the Access-Control-Allow-Methods by retrieving the :request-method value from the request.
However, this makes the assumption that the bad request handler will only ever be called by valid routes.
(defn- append-cors
"Allow requests from all origins"
[methods]
{"Access-Control-Allow-Origin" "*"
"Access-Control-Allow-Methods" methods})
(defn- bad-request-handler
"Handles bad requests."
[f]
(fn [^Exception e data request]
(->
(f request)
(update-in [:headers] merge (->
(request :request-method)
(name)
(clojure.string/upper-case)
(append-cors)))
(assoc :body {:status "bad request"}))))
I'm still not really sure why the cors headers are only added when the request is allowed.
I'd like to get OpenID connect working in my little luminus project. I'm a little new to the workflow in luminus/ring/compojure (coming from django, flask, and servlets mostly). I've successfully redirected to Google so I get the "code" back from Google, but then I need to make one more request to Google before logging in the user and this call requires another callback the user is not involved in, so I need to put the user's request on hold like a promise, but I'm not sure how that part works in compojure.
; this is my code that redirects them to Google, where they accept
(defn login [params]
(let [google-oauth2-client-id (System/getenv "GOOGLE_OAUTH2_CLIENT_ID")
base-url "https://accounts.google.com/o/oauth2/auth"
args {"client_id" google-oauth2-client-id
"response_type" "code"
"scope" "openid email"
"redirect_uri" "http://localhost:3000/oauth2Callback"
"state" "anti-forgery here"}]
(assert google-oauth2-client-id "can't find GOOGLE_OAUTH2_CLIENT_ID in environment")
(redirect (str base-url "?" (make-query-string args)))
)
)
; this is my code handling Google's first response
(defn oauth2-callback [params]
; params has the code to send to Google
; here I should send another request to google that comes back to another callback like oauth2-token-callback that processes the request to the user in the current context
(redirect "/youreloggedin")
)
By the end of this method I should be sending the user a message saying they're logged in, but I need to wait until the request comes back. How is this workflow handled in luminus?
Solved. I didn't realize I could just ignore the callback parameter.
(client/post "https://www.googleapis.com/oauth2/v3/token"
{:headers {"X-Api-Version" "2"}
:content-type :application/x-www-form-urlencoded
:form-params {:code (params :code)
:client_id (System/getenv "GOOGLE_OAUTH2_CLIENT_ID")
:client_secret (System/getenv "GOOGLE_OAUTH2_CLIENT_SECRET")
:redirect_uri "http://localhost:3000/oauth2Callback" ; ignored
:grant_type "authorization_code"
}
:as :auto ; decode the body straight to hash (if possible)
})
Based on the documentation for Google's OAuth2 for web servers here, the flow consists of the following steps:
Your application redirects a browser to a Google URL; the URL includes query parameters that indicate the type of access being requested.
The result is an authorization code, which Google returns to your application in a query string.
After receiving the authorization code, your application can exchange the code (along with a client ID and client secret) for an access token and, in some cases, a refresh token.
If I understood your question correctly, step 3 does not necessarily involve a callback to your server, you can just perform the request to Google with an HTTP client. I recently implemented OAuth2 for GitHub in this project, step 3 is implemented in this function:
(defn callback
"Handles the callback from GitHub OAuth flow."
[code]
(let [params {:form-params {:client_id client-id
:client_secret client-secret
:code code}}
{:keys [body]} (client/post access-token-url params) ;; This is doing the POST
;; request to GitHub.
token ((qs-map body) "access_token")] ;; Getting the token from
;; the response here.
{:status 302
:headers {"location" "/repos"
"set-cookie" (str "token=" token ";Path=/")}}))
I used clj-http as the HTTP client but any other will do.
I'm trying to implement a collection resource with Liberator where a POST request to the collection URL (e.g. /posts) would create a new blog post item. That's working fine. What is not working is responding to the POST request with a 201 Created response including a Location header pointing to the new URL (e.g. /posts/1).
I can either respond with a 201 Created, but then I'm not able to include the Location header response, and hence the client won't know what the new URL is, or alternatively I can set :post-redirect? true, and return a 303 See Other response with a Location header.
Is there any way to return a 201 Created and the Location header from a Liberator POST handler?
Every handler can return a full ring response including headers by using ring-response:
(defresource baz
:method-allowed? true
:new? true
:exists? true
:post! (fn [ctx] {::location "http://example.com"})
:post-redirect? false
:handle-created (fn [{l ::location }]
(ring-response {:headers {"Location" l}}))