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 2aeeb79e981fff115da70114d381cbbd937d1cd5
parent 25ccf6c90a369ab60c4bb9666e9542a0b6efd9c6
Author: Sebastian Mancke <s.mancke@tarent.de>
Date:   Sun, 20 Nov 2016 15:27:36 +0100

added caddy middleware

Diffstat:
Acaddy/README.md | 29+++++++++++++++++++++++++++++
Acaddy/cover.out | 34++++++++++++++++++++++++++++++++++
Acaddy/handler.go | 36++++++++++++++++++++++++++++++++++++
Acaddy/setup.go | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acaddy/setup_test.go | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 286 insertions(+), 0 deletions(-)

diff --git a/caddy/README.md b/caddy/README.md @@ -0,0 +1,29 @@ +# loginsrv caddy middleware + +Login plugin for caddy, based on [tarent/loginsrv](https://github.com/tarent/loginsrv). +The login is checked against a middleware and then returned as JWT token. +This middleware is designed to play together with the [caddy-jwt](https://github.com/BTBurke/caddy-jwt) plugin. + +## Configuration +To be compatible with caddy-jwt, the jwt secret is taken from the enviroment variable `JWT_SECRET` +if such a variable is set. Otherwise, a random token is generated and set as enviroment variable JWT_SECRET, +so that caddy-jwt looks up the same shared secret. + +### Basic configuration +Providing a login resource unter /login, for user bob with password secret: +``` +loginsrv / { + backend provider=simple,bob=secret +} +``` + +### Full configuration example +``` +loginsrv / { + success-url /after/login + cookie-name alternativeName + cookie-http-only true + backend provider=simple,bob=secret + backend provider=osiam,endpoint=http://localhost:8080,clientId=example-client,clientSecret=secret +} +``` diff --git a/caddy/cover.out b/caddy/cover.out @@ -0,0 +1,34 @@ +mode: set +github.com/tarent/loginsrv/caddy/handler.go:17.125,25.2 2 1 +github.com/tarent/loginsrv/caddy/handler.go:27.87,28.49 1 0 +github.com/tarent/loginsrv/caddy/handler.go:28.49,31.3 2 0 +github.com/tarent/loginsrv/caddy/handler.go:31.3,33.3 1 0 +github.com/tarent/loginsrv/caddy/setup.go:12.13,18.2 2 1 +github.com/tarent/loginsrv/caddy/setup.go:21.39,23.15 1 1 +github.com/tarent/loginsrv/caddy/setup.go:55.2,55.12 1 1 +github.com/tarent/loginsrv/caddy/setup.go:23.15,26.20 2 1 +github.com/tarent/loginsrv/caddy/setup.go:30.3,30.20 1 1 +github.com/tarent/loginsrv/caddy/setup.go:34.3,35.17 2 1 +github.com/tarent/loginsrv/caddy/setup.go:39.3,39.52 1 1 +github.com/tarent/loginsrv/caddy/setup.go:45.3,46.17 2 1 +github.com/tarent/loginsrv/caddy/setup.go:50.3,50.90 1 1 +github.com/tarent/loginsrv/caddy/setup.go:26.20,28.4 1 1 +github.com/tarent/loginsrv/caddy/setup.go:30.20,32.4 1 1 +github.com/tarent/loginsrv/caddy/setup.go:35.17,37.4 1 1 +github.com/tarent/loginsrv/caddy/setup.go:39.52,41.4 1 1 +github.com/tarent/loginsrv/caddy/setup.go:41.4,43.4 1 0 +github.com/tarent/loginsrv/caddy/setup.go:46.17,48.4 1 1 +github.com/tarent/loginsrv/caddy/setup.go:50.90,52.4 1 1 +github.com/tarent/loginsrv/caddy/setup.go:58.61,64.20 5 1 +github.com/tarent/loginsrv/caddy/setup.go:93.2,93.17 1 1 +github.com/tarent/loginsrv/caddy/setup.go:64.20,67.21 3 1 +github.com/tarent/loginsrv/caddy/setup.go:70.3,72.15 2 1 +github.com/tarent/loginsrv/caddy/setup.go:67.21,69.4 1 1 +github.com/tarent/loginsrv/caddy/setup.go:73.3,74.26 1 1 +github.com/tarent/loginsrv/caddy/setup.go:75.3,76.26 1 1 +github.com/tarent/loginsrv/caddy/setup.go:77.3,79.18 2 1 +github.com/tarent/loginsrv/caddy/setup.go:82.4,82.26 1 1 +github.com/tarent/loginsrv/caddy/setup.go:83.3,85.18 2 1 +github.com/tarent/loginsrv/caddy/setup.go:88.3,89.98 1 1 +github.com/tarent/loginsrv/caddy/setup.go:79.18,81.5 1 1 +github.com/tarent/loginsrv/caddy/setup.go:85.18,87.5 1 1 diff --git a/caddy/handler.go b/caddy/handler.go @@ -0,0 +1,36 @@ +package caddy + +import ( + "github.com/mholt/caddy/caddyhttp/httpserver" + "github.com/tarent/loginsrv/login" + _ "github.com/tarent/loginsrv/osiam" + "net/http" + "strings" +) + +type CaddyHandler struct { + next httpserver.Handler + path string + config *login.Config + loginHandler *login.Handler +} + +func NewCaddyHandler(next httpserver.Handler, path string, loginHandler *login.Handler, config *login.Config) *CaddyHandler { + h := &CaddyHandler{ + next: next, + path: path, + config: config, + loginHandler: loginHandler, + } + return h +} + +func (h *CaddyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { + if httpserver.Path(r.URL.Path).Matches(h.path) && + strings.HasSuffix(r.URL.Path, "/login") { + h.loginHandler.ServeHTTP(w, r) + return 0, nil + } else { + return h.next.ServeHTTP(w, r) + } +} diff --git a/caddy/setup.go b/caddy/setup.go @@ -0,0 +1,94 @@ +package caddy + +import ( + "fmt" + "github.com/mholt/caddy" + "github.com/mholt/caddy/caddyhttp/httpserver" + "github.com/tarent/loginsrv/login" + "os" + "strconv" +) + +func init() { + caddy.RegisterPlugin("loginsrv", caddy.Plugin{ + ServerType: "http", + Action: setup, + }) + httpserver.RegisterDevDirective("loginsrv", "jwt") +} + +// setup configures a new loginsrv instance. +func setup(c *caddy.Controller) error { + + for c.Next() { + args := c.RemainingArgs() + + if len(args) < 1 { + return fmt.Errorf("Missing path argument for loginsrv directive (%v:%v)", c.File(), c.Line()) + } + + if len(args) > 1 { + return fmt.Errorf("To many arguments for loginsrv directive %q (%v:%v)", args, c.File(), c.Line()) + } + + config, err := parseConfig(c) + if err != nil { + return err + } + + if e, isset := os.LookupEnv("JWT_SECRET"); isset { + config.JwtSecret = e + } else { + os.Setenv("JWT_SECRET", config.JwtSecret) + } + + loginHandler, err := login.NewHandler(&config) + if err != nil { + return err + } + + httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { + return NewCaddyHandler(next, args[0], loginHandler, &config) + }) + } + + return nil +} + +func parseConfig(c *caddy.Controller) (login.Config, error) { + cfg := login.DefaultConfig + cfg.Host = "" + cfg.Port = "" + cfg.LogLevel = "" + + for c.NextBlock() { + name := c.Val() + args := c.RemainingArgs() + if len(args) != 1 { + return cfg, fmt.Errorf("Wrong number of arguments for %v: %v (%v:%v)", name, args, c.File(), c.Line()) + } + value := args[0] + + switch name { + case "success-url": + cfg.SuccessUrl = value + case "cookie-name": + cfg.CookieName = value + case "cookie-http-only": + b, err := strconv.ParseBool(value) + if err != nil { + return cfg, fmt.Errorf("error parsing bool value %v: %v (%v:%v)", name, value, c.File(), c.Line()) + } + cfg.CookieHttpOnly = b + case "backend": + err := (&cfg.Backends).Set(value) + if err != nil { + return cfg, fmt.Errorf("error parsing backend configuration %v: %v (%v:%v)", name, value, c.File(), c.Line()) + } + default: + return cfg, fmt.Errorf("Unknown option within loginsrv: %v (%v:%v)", name, c.File(), c.Line()) + } + } + + return cfg, nil +} diff --git a/caddy/setup_test.go b/caddy/setup_test.go @@ -0,0 +1,93 @@ +package caddy + +import ( + "github.com/mholt/caddy" + "github.com/mholt/caddy/caddyhttp/httpserver" + "github.com/stretchr/testify/assert" + "github.com/tarent/loginsrv/login" + "os" + "testing" +) + +func TestSetup(t *testing.T) { + + os.Setenv("JWT_SECRET", "jwtsecret") + + for j, test := range []struct { + input string + shouldErr bool + path string + config login.Config + }{ + { //defaults + input: `loginsrv / { + backend provider=simple,bob=secret + }`, + shouldErr: false, + path: "/", + config: login.Config{ + JwtSecret: "jwtsecret", + SuccessUrl: "/", + CookieName: "jwt_token", + CookieHttpOnly: true, + Backends: login.BackendOptions{ + map[string]string{ + "provider": "simple", + "bob": "secret", + }, + }, + }}, + { + input: `loginsrv / { + success-url successurl + cookie-name cookiename + cookie-http-only false + backend provider=simple,bob=secret + backend provider=osiam,endpoint=http://localhost:8080,clientId=example-client,clientSecret=secret + }`, + shouldErr: false, + path: "/", + config: login.Config{ + JwtSecret: "jwtsecret", + SuccessUrl: "successurl", + CookieName: "cookiename", + CookieHttpOnly: false, + Backends: login.BackendOptions{ + map[string]string{ + "provider": "simple", + "bob": "secret", + }, + map[string]string{ + "provider": "osiam", + "endpoint": "http://localhost:8080", + "clientId": "example-client", + "clientSecret": "secret", + }, + }, + }}, + // error cases + {input: "loginsrv {\n}", shouldErr: true}, + {input: "loginsrv xx yy {\n}", shouldErr: true}, + {input: "loginsrv / {\n cookie-http-only 42 \n backend provider=simple,bob=secret \n}", shouldErr: true}, + {input: "loginsrv / {\n unknown property \n backend provider=simple,bob=secret \n}", shouldErr: true}, + {input: "loginsrv / {\n backend \n}", shouldErr: true}, + {input: "loginsrv / {\n backend provider=foo\n}", shouldErr: true}, + {input: "loginsrv / {\n backend kk\n}", shouldErr: true}, + } { + c := caddy.NewTestController("http", test.input) + err := setup(c) + if err != nil && !test.shouldErr { + t.Errorf("Test case #%d received an error of %v", j, err) + } else if test.shouldErr { + continue + } + mids := httpserver.GetConfig(c).Middleware() + if len(mids) == 0 { + t.Errorf("no middlewares created in test #%v", j) + continue + } + middleware := mids[len(mids)-1](nil).(*CaddyHandler) + assert.Equal(t, test.path, middleware.path) + assert.Equal(t, &test.config, middleware.config) + } +}