User authentication with RESTAS and Hunchentoot - common-lisp

(I use Hunchentoot and Restas, just thought that I would mention it up here too)
I don't really know how to do this stuff in general with HTTP, so I thought that maybe posting my code would be the simplest way to show my intent:
(define-route log-in ("/log-in/" :method :post)
(multiple-value-bind (username pwd) (hunchentoot:authorization)
(cond ((and (nth-value 1 (gethash username *users*)) ;; User exists
(string= pwd (gethash username *users*)))) ;; Password is correct
;; Do something to keep track of logged in user
)))
I basically just want to let a user log in, give him some sort of way to say "hey, it's me again" and some sort of way for me to say "Oh, hey! It is you again, here you go" and then serve the user a webpage.
I think that this should be done with cookies along with simply storing some value in a list that can be checked against the cookie.
How am I supposed to do this properly with Hunchentoot+Restas? Code and some explanation would be really awesome, I'm pretty lost here.

You might want to use (start-session) and then add a method like:
(defmethod handle-request :before ((acceptor acceptor) (request request))
(unless (your-check-request-matches-login-page) ; skip session check on login page
(if (null *session*)
(redirect "/log-in")
(progn
(your-check-session-validity)
(other-stuff)))))
The above method will work if you need to do authentication with a login page. But you need another way to get user and password from the user, as (authorization) will give you what the browser sent in the headers, and that is for basic-auth.
If you do really want to use basic-auth, then the browser will take care of poping-up a dialog to ask the user for credentials, so you don't need a login page. You need the following method, to intercept all requests and send the apropriate headers:
(defmethod handle-request :before ((acceptor acceptor) (request request))
(multiple-value-bind (username pwd) (hunchentoot:authorization)
(if (and (nth-value 1 (gethash username *users*)) ;; User exists
(string= pwd (gethash username *users*))) ;; Password is correct
(progn
;; Do something here
)
(require-authorization "realm"))))

Related

How do I use HTTP Basic Auth with http-client?

I want to use http-client to make an HTTP request equivalent to:
curl -u 'user:pass' 'https://api.example.net/a/b/c'
I have read the docs for http-client, as well as for intarweb and uri-common, but I am still unsure of how to achieve this.
I can see that it's possible to set a username and password with make-uri, but I'm not sure if that's the best option.
I'm open to multiple solutions if there is more than one way to do it, but an idiomatic answer is preferable.
As the docs say,
the default value is a procedure which extracts the username and password components from the URI"
So, setting the password with make-uri is definitely supported and probably the simplest way in general. This is why with-input-from-request and friends accept either a string, an URI object or a request - you pass in the simplest thing that can work.
(define user "user")
(define pass "pass")
(determine-username/password
(lambda (uri realm)
(values user pass)))
See https://wiki.call-cc.org/eggref/5/http-client#authentication-support for the technical details.

How to do HTTP 302 redirects with Noir Web framework

I'm helping to set up a Web site with Clojure's Noir framework, though I have a lot more experience with Django/Python. In Django, I'm used to URLs such as
http://site/some/url
being 302-redirected automagically to
http://site/some/url/
Noir is more picky and does not do this.
What would be the proper way to do this automatically? Since good URLs are an important way of addressing into a site, and many users will forget the trailing slash, this is basic functionality I'd like to add to my site.
EDIT: Here is what finally worked for me, based on #IvanKoblik's suggestions:
(defn wrap-slash [handler]
(fn [{:keys [uri] :as req}]
(if (and (.endsWith uri "/") (not= uri "/"))
(handler (assoc req :uri (.substring uri
0 (dec (count uri)))))
(handler req))))
I think this may be possible with a custom middleware. noir/server has public function add-middleware.
Here's a page from webnoir explaining how to do that.
Judging by the source code this custom middleware is executed first, so you'd be on your own in terms of sessions, cookies, url params, etc.
I wrote a very silly version of the middleware wrapper that checks if request URI ends with slash and if not redirects to URI with slash at the end:
(use [ring.util.response :only [redirect]])
(defn wrap-slash [handler]
(fn [{:keys [uri] :as req}]
(if (.endsWith uri "/")
(handler req)
(redirect
(str uri "/")))))
I tested it on my ring/moustache web app and it worked reasonably well.
EDIT1 (Expanding my answer after your reply to my comment.)
You could use custom middleware to either add or strip URL of trailing slash. Just do something like this to strip away trailing slash:
(use [ring.util.response :only [redirect]])
(defn add-slash [handler]
(fn [{:keys [uri] :as req}]
(if (.endsWith uri "/")
(handler (assoc req
:uri (.substring uri
0 (dec (count uri)))))
(handler req))))
I found this useful:
(defpage "" []
(response/redirect "/myapp/"))
/myapp -> /myapp/

In Lisp how can I check stream to see if it's empty without modifying it?

How can I check if the stream is empty without modifying it? At the moment I'm using peek-char to see if there a character, but it appears to wait for the user to enter something if nothing new is in the stream. Also, I don't know how to compare to an eof character... #\Space clearly won't work. Help please?
(loop while (equal (peek-char) '#\Space)
do (print 'testword))
You just need to read the manual:
LISTEN checks if there is input available.
PEEK-CHAR can either signal an error at eof or return an eof value. You can also tell it what eof value to return.

Can I override asp:CreateUserWizard to essentially not require a password?

Got this site with UN/PW set via the Createuserwizard control.
Client considers PW too large of a barrier to entry and wants to get rid of the password requirement but still have accounts so users simply log back in with emaill address only.
I want the quickest fix possible where I use the same provider and control but just use the same static PW for all users on signup, then sort of enter it for them when they "log in" if they return. Works like a cookie basically but has an actual login.
Problem is the Createuserwizard.Password property is read only. Can I do AutoGeneratePassword= true and force it to generate the exact same password every time?
For what it's worth, this is a simple, no secure data, basically not much stored kind of site. Lets not get into whether the req makes sense though, and the implications of this - I probably agree with you :)
Bla, bla, bla lots of stuff you probably agree with... and now to the point:
Just ditch the CreateUserWizard and call MembershipProvider.CreateUser directly. You will have to throw in a few textboxes for the email and stuff instead of the createuserwizard but it should be a walk in the park. For the login, just drop the login control too and add a textbox for the email and a "login" button. Then in code-behind call MembershipProvider.ValidateUser with the email and hardcoded password, and if it returns true (meaning the user exists) you just call FormsAuthentication.SetAuthCookie followed by FormsAuthentication.RedirectFromLoginPage and the user is logged in.

Redirect to Error page when user clicks the Browser Refresh button

i need to check whether the user clicking the browser Refresh button and redirect to error page. Can we do this in javascript or any server side methods in ASP.net
If you give each link you present a unique ID (e.g. a GUID) in the URL as a parameter, then you can keep track of all the requests you've processed. (You could clear out "old" requests if you don't mind the mechanism not working if someone leaves a browser open for a few days and then hitting refresh.) The first time you see a GUID, write it into the table. If you see it again, redirect to an error page.
It's pretty ugly though, and users could just edit the URL to change the GUID slightly. (You could fix this last flaw by recording the GUID when you generate it, and update the table to indicate when it's been used.)
In general, users expect to be able to refresh the page though - particularly for GET requests (even though most users wouldn't know what that means). Why do you want to do this?
Well, you can use a very famous tecnique called "Syncronizing Token" or something like that =D, mostly used to send forms.
This will work like this:
Create a function to provide a pseudo-random string token.
For every request to you page, check if a variable in Session, ex: Session["synctoken"] if present. If no, then it is the first time, generate a token and store it there.
Every link request, ex: "mypage.aspx" put a get called synctoken with another token, diferent from the one you have stored in the Session, it goes like "mypage.aspx?synctoken=2iO02-3S23d".
Then, comming back to (2), in a request, if a token is present in Session check if the GET is present (Request.QueryString["synctoken"] != null). If no, send Error. If yes check whether the Tokens (Session and GET) are different. If they are different, it is ok, store the GET into your Session (Session["synctoken"] = Request.QueryString["synctoken"]) and go to step (2). If no, then the user refreshed the page, there goes your error.
It goes like:
if (Session["synctoken"] != null) {
if (Request.QueryString["synctoken"] != null) {
if (Request.QueryString["synctoken"].ToString().Equals(Session["synctoken"].ToString())) {
// Refresh! Goto Error!
MyUtil.GotoError();
}
else {
// It is ok, store the token and go on!
Session["synctoken"] = Request.QueryString["synctoken"];
}
}
else {
MyUtil.GotoErrorPage();
}
}
else {
Session["synctoken"] = MyUtil.GenerateToken();
}
Sorry if I could not be more clear.. good luck!
You can do that, but I'm sure you shouldn't. The user is in control of the browser, and if she feels like refreshing, it your job to make sure the page refreshes. Returning an error page is the wrong answer.
you can use client side hidden variable to store a counter or you can put counter in session. Well I would suggest you to expire the page on refresh there are ways you can achieve this disable cache etc [like all banks website do].

Resources