loginsrv

Unnamed repository; edit this file 'description' to name the repository.
git clone git@jamesshield.xyz:repos/loginsrv.git
Log | Files | Refs | README | LICENSE

commit a271f57da6670cd8166d76c652b6610e14129de7
parent 26e57b06cdbfd0c913ae632bdc6381ef3f2b25c2
Author: Sebastian Mancke <s.mancke@tarent.de>
Date:   Mon, 14 Nov 2016 22:44:21 +0100

fixed doku examples

Diffstat:
MREADME.md | 33+++++++++++++++++++++++++--------
Mlogin/handler.go | 36+++++++++++++++++++++++++++++-------
Mlogin/handler_test.go | 15+++++++++++++++
3 files changed, 69 insertions(+), 15 deletions(-)

diff --git a/README.md b/README.md @@ -56,18 +56,35 @@ Does the login and returns the JWT. Depending on the content-type, and parameter Hint: The status `401 Unauthorized` is not used as a return code to not conflict with an Http BasicAuth Authentication. -#### Example for classical REST call +#### Example: +Default is to return the token as Content-Type application/jwt within the body. ``` -curl -I -X POST -H "Accept: application/jwt" -H "Content-Type: application/json" --data '{"username": "bob", "password": "secret"}' http://example.com/login +curl -i --data "username=bob&password=secret" http://127.0.0.1:6789/login HTTP/1.1 200 OK +Content-Type: application/jwt +Date: Mon, 14 Nov 2016 21:35:42 GMT +Content-Length: 100 -xxxxx.yyyyy.zzzzz +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJib2IifQ.-51G5JQmpJleARHp8rIljBczPFanWT93d_N_7LQGUXU ``` -#### Example for form based web flow +#### Example: Credentials as JSON +The Credentials also could be send as JSON encoded. ``` -curl -I -X POST --data "username=bob&password=secret" http://example.com/login -HTTP/1.1 303 Moved Temporary -Set-Cookie: jwt_token=xxxxx.yyyyy.zzzzz -Location: /startpage +curl -i -H 'Content-Type: application/json' --data '{"username": "bob", "password": "secret"}' http://127.0.0.1:6789/login +HTTP/1.1 200 OK +Content-Type: application/jwt +Date: Mon, 14 Nov 2016 21:35:42 GMT +Content-Length: 100 + +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJib2IifQ.-51G5JQmpJleARHp8rIljBczPFanWT93d_N_7LQGUXU +``` + +#### Example: web based flow with 'Accept: text/html' +Sets the jwt token as cookie and redirects to a web page. +``` +curl -i -H 'Accept: text/html' --data "username=bob&password=secret" http://127.0.0.1:6789/login +HTTP/1.1 303 See Other +Location: / +Set-Cookie: jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJib2IifQ.-51G5JQmpJleARHp8rIljBczPFanWT93d_N_7LQGUXU; HttpOnly ``` diff --git a/login/handler.go b/login/handler.go @@ -1,10 +1,12 @@ package login import ( + "encoding/json" "errors" "fmt" "github.com/dgrijalva/jwt-go" "github.com/tarent/lib-compose/logging" + "io/ioutil" "net/http" "strings" ) @@ -63,8 +65,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { (strings.HasPrefix(contentType, "application/json") || strings.HasPrefix(contentType, "application/x-www-form-urlencoded") || strings.HasPrefix(contentType, "multipart/form-data")))) { - w.WriteHeader(400) - fmt.Fprintf(w, "Bad Request: Method or content-type not supported") + h.respondBadRequest(w, r) return } @@ -79,7 +80,11 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } if r.Method == "POST" { - username, password := getCredentials(r) + username, password, err := getCredentials(r) + if err != nil { + h.respondBadRequest(w, r) + return + } authenticated, userInfo, err := h.authenticate(username, password) if err != nil { logging.Application(r.Header).WithError(err).Error() @@ -130,7 +135,7 @@ func (h *Handler) createToken(userInfo UserInfo) (string, error) { func (h *Handler) respondError(w http.ResponseWriter, r *http.Request) { if wantHtml(r) { - username, _ := getCredentials(r) + username, _, _ := getCredentials(r) writeLoginForm(w, map[string]interface{}{ "path": r.URL.Path, @@ -144,9 +149,14 @@ func (h *Handler) respondError(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Internal Server Error") } +func (h *Handler) respondBadRequest(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(400) + fmt.Fprintf(w, "Bad Request: Method or content-type not supported") +} + func (h *Handler) respondAuthFailure(w http.ResponseWriter, r *http.Request) { if wantHtml(r) { - username, _ := getCredentials(r) + username, _, _ := getCredentials(r) writeLoginForm(w, map[string]interface{}{ "path": r.URL.Path, @@ -165,6 +175,18 @@ func wantHtml(r *http.Request) bool { return strings.Contains(r.Header.Get("Accept"), "text/html") } -func getCredentials(r *http.Request) (string, string) { - return r.PostForm.Get("username"), r.PostForm.Get("password") +func getCredentials(r *http.Request) (string, string, error) { + if r.Header.Get("Content-Type") == "application/json" { + m := map[string]string{} + body, err := ioutil.ReadAll(r.Body) + if err != nil { + return "", "", err + } + err = json.Unmarshal(body, &m) + if err != nil { + return "", "", err + } + return m["username"], m["password"], nil + } + return r.PostForm.Get("username"), r.PostForm.Get("password"), nil } diff --git a/login/handler_test.go b/login/handler_test.go @@ -74,6 +74,21 @@ func TestHandler_HEAD(t *testing.T) { assert.Equal(t, recorder.Code, 400) } +func TestHandler_LoginJson(t *testing.T) { + // success + recorder := call(req("POST", "/context/login", `{"username": "bob", "password": "secret"}`, TypeJson, AcceptJwt)) + assert.Equal(t, 200, recorder.Code) + assert.Equal(t, recorder.Header().Get("Content-Type"), "application/jwt") + assert.True(t, recorder.Body.Len() > 30) + + // TODO: verify the jwt token + + // wrong credentials + recorder = call(req("POST", "/context/login", `{"username": "bob", "password": "FOOOBAR"}`, TypeJson, AcceptJwt)) + assert.Equal(t, 403, recorder.Code) + assert.Equal(t, "Wrong credentials", recorder.Body.String()) +} + func TestHandler_LoginWeb(t *testing.T) { // redirectSuccess recorder := call(req("POST", "/context/login", "username=bob&password=secret", TypeForm, AcceptHtml))