How to generate JWT exp claim with java-time? - clojure

Most examples for JWT token use clj-time which is now deprecated in favor of native java.time. I'm trying to use java-time along with buddy to sign/verify tokens but I'm stuck trying to pass the exp claim to my token. Here's an example of what I have:
(ns test-app.test-ns
(:require
[buddy.sign.jwt :as jwt]
[buddy.auth.backends.token :as bat]
[java-time :as t]))
(def secret "myfishysecret")
(def auth-backend (bat/jws-backend {:secret secret
:options {:alg :hs512}}))
(def token (jwt/sign {:user "slacker"
:exp (t/plus (t/local-date-time) (t/seconds 60))
} secret {:alg :hs512}))
When tyring to test if I can unsign the token
(jwt/unsign token secret {:alg :hs512})
I get the following error:
Execution error (JsonGenerationException) at
cheshire.generate/generate (generate.clj:152). Cannot JSON encode
object of class: class java.time.LocalDateTime:
2021-01-22T12:37:52.206456
So, I tried to pass the same by encapsulating the call to (t/plus ...) inside a (str) but then I get this error:
class java.lang.String cannot be cast to class java.lang.Number
(java.lang.String and java.lang.Number are in module java.base of
loader 'bootstrap')
So, I'm stuck since I don't really know how to generate a valid exp number value using java-time (according to this question, format should be in seconds since the epoch). Older examples using clj-time just passed the exp claim value as
(clj-time.core/plus (clj-time.core/now) (clj-time.core/seconds 3600))
Any help is highly appreciated.
EDIT: Alan Thompson's answer works perfectly, for what's worth this would be the equivalent using the java-time wrapper:
(t/plus (t/instant) (t/seconds 60))

Here are 2 ways to do it:
(let [now+60 (-> (Instant/now)
(.plusSeconds 60))
now+60-unixsecs (.getEpochSecond now+60)]
(jwt/sign {:user "slacker" :exp now+60 } secret {:alg :hs512})
(jwt/sign {:user "slacker" :exp now+60-unixsecs} secret {:alg :hs512}))
and we have the now results:
now+60 => <#java.time.Instant #object[java.time.Instant 0x7ce0054c "2021-01-22T19:04:51.905586442Z"]>
now+60-unixsecs => <#java.lang.Long 1611342291>
So you have your choice of methods. It appears that buddy knows how to convert from a java.time.Instant, so going all the way to unix-seconds is unnecessary.
You may also be interested in this library of helper and convenience functions for working with java.time.

Related

Clojure, re-graph fetched data from graphql successfully, but callback didn't activate

So I use re-graph version 0.1.11 and I try fetching data from the endpoint. After fetching data, I checked network tab in the browser and I found the expected data after that it should activate my callback function, but it doesn't seem to work (but sometimes it works and after refreshing the page a few times it doesn't work again). Here is the code.
;; how I init re-graph
(rf/dispatch [::re-graph/init
::conf/graphql-client-name
{:ws-url url
:http-url url
:ws-reconnect-timeout 500
:resume-subscriptions? true}])
(re-frame.core/reg-event-fx
::fetch-expected-data
(fn [cofx event]
(let [app-db (:db cofx)
some-params (-> event second (cljs.core/js->clj :keywordize-keys true))
token (-> app-db (lens/get-in (auth-db/lens-token :group-level-x)))]
(re-frame.core/dispatch
[:re-graph.core/query
::conf/graphql-client-name
"query findExpectedData($query: FetchExpectedDataInput!, $token: String!) {
findExpectedData(query: $query, token: $token){
value1
value2
...
}
}"
{:query some-params
:token token}
;; this is where the problem occurs
;; even though i found the data in the network tab, but
;; this callback doesn't seem to work (sometimes it works sometimes it doens't)
[::fetched-data-completed]]))))
(re-frame.core/reg-event-fx
::fetched-data-completed
(fn [cofx [_ {:keys [data errors] :as payload}]]
(let [app-db (:db cofx)
error-message (-> errors :errors first :message)]
(if (or (nil? errors) (empty? errors))
(do (bla bla when success))
(pr error-message)))))
I'm stuck with this problem for a few months. maybe because I fetch a lot of data at the same time? or could be something else anyone knows?. By the way the actual code I use defmacro, but it works the same way as the above code.
So I managed to find the answer to my own question. It seems like app-db has not been initialized properly so I fixed that problem and everything works fine. hope it helps someone who struggle with this problem.

Clojure , how to pass correct param into function

I'm newbee in Clojure world. My pet project is writing of kafka consumer / producer. There a lot of topic in www ,but I've faced with misunderstanding.
There is a code
(ns producer
(:require [clojure.tools.logging :as log])
(:import (java.util Properties)
(org.apache.kafka.common.serialization StringSerializer)
(org.apache.kafka.clients.producer KafkaProducer ProducerRecord))
(:gen-class))
(defn create-kafka-producer [server]
(let [producer-props {
"value.serializer" StringSerializer
"key.serializer" StringSerializer
"bootstrap.servers" server }]
(KafkaProducer. producer-props)))
(defn send-single-message [producer topic-name record]
(.send producer (ProducerRecord. topic-name (str "Value: " (.value record)))))
(defn -main []
(def svr "localhost:8084")
(def producer (create-kafka-producer svr))
(send-single-message producer "Test msg"))
I want just pass some msg into kafka through send-single-message function. But as you see in code example is using (.value record) and when I'm trying to pass the "Test msg" string it crashed and show the followed error
Exception in thread "main" java.lang.IllegalArgumentException: No matching field found: value for class java.lang.String
I understand that is because I've pass string object, that has no .value
So, how is it possible to solve that issue? Thanks in advance
p.s. I've tried to pass other structure, but no results or different errors
As you noted, the error is because you're attempting to get the value field of the passed object, but you're passing in a String which doesn't have a value field.
Checking that ProducerRecord constructor signature, it takes a generic argument of type V (which is just an Object as far as Clojure is concerned). I'd just pass the String directly and omit the value field access.

How to correctly sign GDAX request in Clojure

I have been struggling with signing requests for private GDAX endpoints for a while. Everything I have tried results in a 400 response with a message of "invalid signature." I have read their documentation on the matter several times, which can be found here. My current code is below. I'm using clj-http for making requests. I'm using their /time endpoint response for the timestamp, and I'm using pandect for the sha256 HMAC generation. I've tried converting the secret-decoded to a string using String. before passing it to sha256-hmac. I've also examined the request using clj-http's debug flag. It looks to me that I am following their directions precisely, but something must be wrong. I've done a lot of online searching before posting here. Any help would be greatly appreciated.
(defn get-time
[]
(-> (str (:api-base-url config) "/time")
(http/get {:as :json})
:body))
(defn- create-signature
([timestamp method path]
(create-signature timestamp method path ""))
([timestamp method path body]
(let [secret-decoded (b64/decode (.getBytes (:api-secret config)))
prehash-string (str timestamp (clojure.string/upper-case method) path body)
hmac (sha256-hmac prehash-string secret-decoded)]
(-> hmac
.getBytes
b64/encode
String.))))
(defn- send-signed-request
[method path & [opts]]
(let [url (str (:api-base-url config) path)
timestamp (long (:epoch (get-time)))
signature (create-signature timestamp method path (:body opts))]
(http/request
(merge {:method method
:url url
:as :json
:headers {"CB-ACCESS-KEY" (:api-key config)
"CB-ACCESS-SIGN" signature
"CB-ACCESS-TIMESTAMP" timestamp
"CB-ACCESS-PASSPHRASE" (:api-passphrase config)
"Content-Type" "application/json"}
:debug true}
opts))))
(defn get-accounts []
(send-signed-request "GET" "/accounts"))
(send-signed-request "GET" "/accounts")))
I've figured out the issue. Just in case anyone happens to have this very specific problem, I'm posting the solution. My error was that I was using the sha256-hmac function from pandect, which returns a string hmac, then I was converting that to a byte array, base64 encoding it, and converting it back to a string. Somewhere in those conversions, or perhaps in the pandect function's conversion, the value is altered in an erroneous way.
What works is using the sha256-hmac* function (note the asterisk) from pandect, which returns a raw byte array hmac, then base64 encoding that result directly, and converting it to a string. Below is the corrected, working code snippet, with which I was able to make a request to a private GDAX endpoint.
(defn create-signature
([timestamp method path]
(create-signature timestamp method path ""))
([timestamp method path body]
(let [secret-decoded (b64/decode (.getBytes (:api-secret config)))
prehash-string (str timestamp (clojure.string/upper-case method) path body)
hmac (sha256-hmac* prehash-string secret-decoded)]
(-> hmac
b64/encode
String.))))

How can I call a function in clojure and get a result?

I am trying to implement an amazon aws binding with Clojure, so that I can search for a book by sending an isbn and get an xml report of it. I am then trying to retrieve the salesrank and book title from the report. Here is what I am trying to use: https://github.com/FreeAgent/clj-amazon
Instead of using REPL, I made a core2.clj inside the src folder and added this code into it:
(ns clj_amazon.core2
(:use clj-amazon.core)
(:use clj-amazon.product-advertising)
(:gen-class))
(defn -main [& args]
(def ACCESS-KEY "my access code")
(def SECRET-KEY "my secret key" )
(def ASSOCIATE-ID "my id")
(def gibson-opus-search (with-signer (ACCESS-KEY, SECRET-KEY) (item-search :search-index "Books", :keywords "Neuromancer", :associate-tag ASSOCIATE-ID, :condition "New")))
(gibson-opus-search)
)
If I remove
(gibson-opus-search)
and run "lein run" in the command line, I get no errors. I have the correct access key/secret key, and it seems that the code is working fine. But I also get no report printed. I'm new to Clojure, so I'm not understanding what I should do next. I tried to call the function with
(gibson-opus-search)
, but then i get this error: wrong number of args(0) passed to persistentarraymap. How can I fix this?
You are defining gibson-opus-search with def which is for for assigning values, not defining functions. You need to define it with defn then call it with the right number of arguments.

How can I make JSON responses be pretty-printed when using Rook?

I am using Rook framework for web services. I want to make API responses be pretty-printed. It seems that the response encoding is all handled by the wrap-restful-format function from ring.middleware.format. So I tried to replace the rook/wrap-with-standard-middleware function with my own version that passes different options through to ring.middleware.format.
(defn make-encoders-seq []
[(ring.middleware.format-response/make-encoder
(fn [s]
(json/generate-string s {:pretty true}))
"application/json")])
(defn wrap-with-standard-middleware-modified
[handler]
(-> handler
(ring.middleware.format/wrap-restful-format :formats [:json-kw :edn]
:response-options
[:encoders (make-encoders-seq)])
ring.middleware.keyword-params/wrap-keyword-params
ring.middleware.params/wrap-params))
(def handler (-> (rook/namespace-handler
["resource" 'my-app.resource])
(rook/wrap-with-injection :data-store venues)
wrap-with-standard-middleware-modified))
This compiles fine but it doesn't work to pretty print the responses, it seems like the custom encoder is never called.
Rook 1.3.9
ring-middleware-format 0.6.0
cheshire 5.4.0 (for json/generate-string in above)
Try to change your format/wrap-restful-format to:
(ring.middleware.format/wrap-restful-format :formats (concat (make-encoders-seq) [:edn])