Permalink
Browse files

Merge branch 'perenecabuto-master'

  • Loading branch information...
smancke committed Dec 27, 2018
2 parents dfc3994 + 85e698f commit 15423135b3c55ecbe4ca31d4247e4b022e313ecc
Showing with 82 additions and 7 deletions.
  1. +7 −1 README.md
  2. +28 −6 login/handler.go
  3. +47 −0 login/handler_test.go
@@ -100,11 +100,17 @@ $ docker run -d -p 8080:8080 -e LOGINSRV_JWT_SECRET=my_secret -e LOGINSRV_BACKEN

### GET /login

Returns a simple bootstrap styled login form.
Per default, it returns a simple bootstrap styled login form for unauthenticated requests an a small page with user info authenticated requests.
When the call accepts a JSON output, the json content of the token is returned authenticated requests.

The returned HTML follows the UI composition conventions from (lib-compose)[https://github.com/tarent/lib-compose],
so it can be embedded into an existing layout.

| Parameter-Type | Parameter | Description | |
| ------------------|--------------------------------------------------|-------------------------------------------------------------------|--------------|
| Http-Header | Accept: text/html | Return the login form or user html. | default |
| Http-Header | Accept: application/json | Return the user Object as json, or 403 if not authenticated. | |

### GET /login/<provider>

Starts the OAuth Web Flow with the configured provider. E.g. `GET /login/github` redirects to the GitHub login form.
@@ -17,6 +17,7 @@ import (

const contentTypeHTML = "text/html; charset=utf-8"
const contentTypeJWT = "application/jwt"
const contentTypeJSON = "application/json"
const contentTypePlain = "text/plain"

type userClaimsFunc func(userInfo model.UserInfo) (jwt.Claims, error)
@@ -122,7 +123,7 @@ func (h *Handler) handleLogin(w http.ResponseWriter, r *http.Request) {
contentType := r.Header.Get("Content-Type")
if !(r.Method == "GET" || r.Method == "DELETE" ||
(r.Method == "POST" &&
(strings.HasPrefix(contentType, "application/json") ||
(strings.HasPrefix(contentType, contentTypeJSON) ||
strings.HasPrefix(contentType, "application/x-www-form-urlencoded") ||
strings.HasPrefix(contentType, "multipart/form-data") ||
contentType == ""))) {
@@ -147,6 +148,16 @@ func (h *Handler) handleLogin(w http.ResponseWriter, r *http.Request) {

if r.Method == "GET" {
userInfo, valid := h.GetToken(r)
if wantJSON(r) {
if valid {
w.Header().Set("Content-Type", contentTypeJSON)
enc := json.NewEncoder(w)
enc.Encode(userInfo) // ignore error of encoding
} else {
h.respondAuthFailure(w, r)
}
return
}
writeLoginForm(w,
loginFormData{
Config: h.config,
@@ -176,7 +187,7 @@ func (h *Handler) handleLogin(w http.ResponseWriter, r *http.Request) {
h.respondAuthFailure(w, r)
return
}

h.respondBadRequest(w, r)
return
}
@@ -375,17 +386,28 @@ func (h *Handler) respondAuthFailure(w http.ResponseWriter, r *http.Request) {
return
}

w.Header().Set("Content-Type", contentTypePlain)
w.WriteHeader(403)
fmt.Fprintf(w, "Wrong credentials")
if wantJSON(r) {
w.Header().Set("Content-Type", contentTypeJSON)
w.WriteHeader(403)
fmt.Fprintf(w, `{"error": "Wrong credentials"}`)
} else {
w.Header().Set("Content-Type", contentTypePlain)
w.WriteHeader(403)
fmt.Fprintf(w, "Wrong credentials")
}

}

func wantHTML(r *http.Request) bool {
return strings.Contains(r.Header.Get("Accept"), "text/html")
}

func wantJSON(r *http.Request) bool {
return strings.Contains(r.Header.Get("Accept"), contentTypeJSON)
}

func getCredentials(r *http.Request) (string, string, error) {
if r.Header.Get("Content-Type") == "application/json" {
if r.Header.Get("Content-Type") == contentTypeJSON {
m := map[string]string{}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
@@ -1,10 +1,12 @@
package login

import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"strings"
"testing"
@@ -414,6 +416,51 @@ func TestHandler_getToken_Valid(t *testing.T) {
Equal(t, input, userInfo)
}

func TestHandler_ReturnUserInfoJSON(t *testing.T) {
h := testHandler()
input := model.UserInfo{Sub: "marvin", Expiry: time.Now().Add(time.Second).Unix()}
token, err := h.createToken(input)
NoError(t, err)
url, _ := url.Parse("/context/login")
r := &http.Request{
Method: "GET",
URL: url,
Header: http.Header{
"Cookie": {h.config.CookieName + "=" + token + ";"},
"Accept": {"application/json"},
},
}

recorder := call(r)
Equal(t, 200, recorder.Code)
Equal(t, "application/json", recorder.Header().Get("Content-Type"))

output := model.UserInfo{}
json.Unmarshal(recorder.Body.Bytes(), &output)

Equal(t, input, output)
}

func TestHandler_ReturnUserInfoJSON_InvalidToken(t *testing.T) {
h := testHandler()
url, _ := url.Parse("/context/login")
r := &http.Request{
Method: "GET",
URL: url,
Header: http.Header{
"Cookie": {h.config.CookieName + "= 123;"},
"Accept": {"application/json"},
},
}

recorder := call(r)
Equal(t, 403, recorder.Code)
Equal(t, "application/json", recorder.Header().Get("Content-Type"))
output := map[string]interface{}{}
json.Unmarshal(recorder.Body.Bytes(), &output)
Equal(t, map[string]interface{}{"error": "Wrong credentials"}, output)
}

func TestHandler_signAndVerify_ES256(t *testing.T) {
h := testHandler()
h.config.JwtAlgo = "ES256"

0 comments on commit 1542313

Please sign in to comment.