I'm working on a clojure web application project for my college,and i am trying to connect datomic database with friend authentication but is kinda buggy...i will explain further...
First i am doing user registration(user insertion in datomic database) like this and it is working.
(defn insert-user [firstname lastname email password sex date] (.get (.transact conn
[{
:db/id #db/id[:db.part/user -1000001]
:user/name firstname
:user/lastName lastname
:user/username email
:user/password (creds/hash-bcrypt password)
:user/gender sex
:user/birthDate date}
]))
(resp/redirect "/")
)
The routes handler and friend authenticator looks like this...function main is to start the app.
(def page (handler/site
(friend/authenticate
routes
{:allow-anon? true
:login-uri "/login"
:default-landing-uri "/login"
:unauthorized-handler #(-> (html5 [:h2 "You do not have sufficient privileges to access " (:uri %)])
resp/response
(resp/status 401))
:credential-fn (partial creds/bcrypt-credential-fn users)
:workflows [(workflows/interactive-form)]})
(wrap-keyword-params routes)
(wrap-nested-params routes)
(wrap-params routes)
))
(defn -main []
(run-jetty page {:port 8080 :join? false}))
And for the end the datomic query for users to match with creds/bcrypt-credential-fn function of friend.
(defn upit-korisnici []
(def temp (d/q '[:find ?u ?p
:where [?user :user/username ?u]
[?user :user/password ?p]
]
(d/db conn)))
(def users (into {} (map (fn [[k v]] [k {:username k :password v}]) temp)))
users
)
The thing that is bugging me and leaving me helpless is that when i register(insert user),the user is inserted in datomic database but when i try to log in i can't.It says wrong email and password but the new user is there.When i restart the whole app and try to login with the new users credentials it goes through and logs on.Does anyone know how to solve this problem?
Edit:
I solved this problem by changing :credential-fn (partial creds/bcrypt-credential-fn users) to :credential-fn #(creds/bcrypt-credential-fn users %).
You seem to think it will automatically update your user data, but it isn't going to because user is not a function it is plain data. What happens is (def user... is run then the results are bound to the name user, you are not binding the computation so the data is never updated. You make a similar mistake for temp. The query is run once then the results are bound to temp, and never reevaluated. You should bind them to a function so that it revaluates.
I started working on an UI for the friend lib with datomic persistence: https://github.com/sveri/friend-ui/ You can have a look at it, maybe it solves your problem already, of course you can take code from it, add pull requests / whatever. When I have time I will implement whatever is needed.
Currently it supports:
Sign up
Login
Logout
Twitter Bootstrap support for the templates
It would be nice if we could bundle the work done, as this will be something that many people will need in future.
Related
I'm using buddy-auth and following the tutorial, when I run the example application I get a HTTP auth username/password dialog as expected, but in my own app I just get the "Unauthorized" exception, no dialog appears.
;; fn needs to return a non-falsey value to indicate a positive authentication, the returned value is stored under the `:identity` key in the request.
(defn auth-user [request authdata]
(let [username (:username authdata)
password (:password authdata)]
username)) ;; FIXME: lookup username/password
(def auth-backend (http-basic-backend {:realm "MyApp" :authfn auth-user}))
;; my endpoint handler
(defn test-handler [r]
(if (authenticated? r)
(render (str "LOGGED IN" (:identity r)))
(throw-unauthorized)))
;; ROUTES (compojure)
(defroutes app
(GET "/test" [] test-handler))
;; ring handler
(def site
(-> (routes app)
(wrap-authentication auth-backend) ;; <---
(wrap-defaults)
(wrap-with-exception-handling)))
I don't think auth-user is ever called.
It seems I need (wrap-authorization auth-backend) too.
(def site
(-> (routes app)
(wrap-authentication auth-backend)
(wrap-authorization auth-backend)
(wrap-defaults)
(wrap-with-exception-handling)))
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!
I finally got OAuth workflows working for Twitter and Facebook logins on my Clojure Compojure project, but I need to handle new users. Where in the Friend workflow should I be creating non-existent users in my data store? Inside credential-fn perhaps?
That is correct. The credential-fn from one of my project looks right now something like:
(defn linkedin-auth [{access-token :access-token :as m}]
(let [options {:query-params {:oauth2_access_token access-token
:format "json"}}
account-info (http-json "https://api.linkedin.com/v1/people/~" options)
email (http-json "https://api.linkedin.com/v1/people/~/email-address" options)]
(register-or-load-from-db
{:roles #{::user}
:identity email
:name (str (:firstName account-info) " " (:lastName account-info))}))))
And the workflow config:
(oauth2/workflow {:client-config linkedin-client-config
:uri-config linkedin-uri-config
:credential-fn #'linkedin-auth
:login-uri "/linkedinlogin"})
I'm using the ring basic-authentication library available for compojure. The authenticated? function takes a username and a password in order to authenticate, but in my particular case I need to access other parameters passed in by the user request besides a username and a password.
For instance, consider a case where there are multiple servers, and a user has an account on a particular server. That user would therefore need to authenticate with (log-on to) a particular server. Therefore I need his username, password, AND server to do the authentication.
Handling a "normal" case which just calls for a username and password might look something like this (using example sql to hit a database):
; curl my_id:my_pass#localhost/my_request
(defn authenticated? [id password]
(first (select user (where {:id id :password password}) (limit 1))))
I'd like to do something like this:
; curl my_id:my_pass#localhost/my_server/my_request
(defn authenticated? [id password ??server??]
(first (select user (where {:server server :id id :password password}) (limit 1))))
I guess my question is, how do I access all request params from inside authenticated? Or, alternatively, how do I pass the server id into the authenticated? function?
Thanks.
In accordance with the comments to the question above, the approach would look like this (just a sketch, I haven't tested whether this works due to lack of a running setup with ring):
(defn wrap-basic-auth-server [handler authfn]
(fn [request]
(-> request
(wrap-basic-authentication (partial authfn (get-in request [:params :server])))
handler)))
What's happening here is that the code is assuming that your server url param will have been added by wrap-params (from ring.middleware.params) to the request map. wrap-basic-authentication is called then with the handler (typical ring middleware, i.e. any other wrapper / handler coming afterwards) and a new (partial) function, which is just your authenticate function which has already swallowed the server arg.
And then instead of just calling wrap-basic-authentication in your routes, you need to add wrap-params and wrap-basic-auth-server and you need your new auth function. E.g.
(defn authenticated? [server user pass]
... your code ...)
(def app
(-> app-routes
... potential other wrappers elided ...
(wrap-params)
(wrap-basic-auth-server authenticated?)))
As I said, I've not tested this code, but it should get you started.
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?)))