diff --git a/HISTORY.md b/HISTORY.md index 191f37bf..c11ebb42 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -28,6 +28,9 @@ The codebase for Dependency Injection, Internationalization and localization and ## Fixes and Improvements +- A `Context.Logout` method is added, can be used to invalidate [basicauth](https://github.com/kataras/iris/blob/master/_examples/auth/basicauth/main.go) client credentials. +- Add the ability to [share functions](https://github.com/kataras/iris/tree/master/_examples/routing/writing-a-middleware/share-funcs) between handlers chain and add an [example](https://github.com/kataras/iris/tree/master/_examples/routing/writing-a-middleware/share-services) on sharing Go structures (aka services). + - Add the new `Party.UseOnce` method to the `*Route` - Add a new `*Route.RemoveHandler(interface{}) int`, deletes a handler from begin, main and done handlers based on its name or the handler pc function. Returns the total amount of handlers removed. @@ -306,6 +309,8 @@ var dirOpts = iris.DirOptions{ ## New Context Methods +- `Context.SetLogoutFunc(fn interface{}, persistenceArgs ...interface{})` and `Logout(args ...interface{}) error` methods to allow different kind of auth middlewares to be able to set a "logout" a user/client feature with a single function, the route handler may not be aware of the implementation of the authentication used. +- `Context.SetFunc(name string, fn interface{}, persistenceArgs ...interface{})` and `Context.CallFunc(name string, args ...interface{}) ([]reflect.Value, error)` to allow middlewares to share functions dynamically when the type of the function is not predictable, see the [example](https://github.com/kataras/iris/tree/master/_examples/routing/writing-a-middleware/share-funcs) for more. - `Context.TextYAML(interface{}) error` same as `Context.YAML` but with set the Content-Type to `text/yaml` instead (Google Chrome renders it as text). - `Context.IsDebug() bool` reports whether the application is running under debug/development mode. It is a shortcut of Application.Logger().Level >= golog.DebugLevel. - `Context.IsRecovered() bool` reports whether the current request was recovered from the [recover middleware](https://github.com/kataras/iris/tree/master/middleware/recover). Also the `iris.IsErrPrivate` function and `iris.ErrPrivate` interface have been introduced. diff --git a/README.md b/README.md index 073b3061..d764dbf4 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ With your help, we can improve Open Source web development for everyone! > Donations from **China** are now accepted!
+
diff --git a/_examples/README.md b/_examples/README.md
index d6e23913..314d254c 100644
--- a/_examples/README.md
+++ b/_examples/README.md
@@ -48,6 +48,9 @@
* Middleware
* [Per Route](routing/writing-a-middleware/per-route/main.go)
* [Globally](routing/writing-a-middleware/globally/main.go)
+ * Share Values
+ * [Share Services](routing/writing-a-middleware/share-services/main.go)
+ * [Share Functions](routing/writing-a-middleware/share-funcs/main.go)
* [Handlers Execution Rule](routing/route-handlers-execution-rules/main.go)
* [Route Register Rule](routing/route-register-rule/main.go)
* Convert net/http Handlers
diff --git a/_examples/auth/basicauth/main.go b/_examples/auth/basicauth/main.go
index cfc279f0..2486efb2 100644
--- a/_examples/auth/basicauth/main.go
+++ b/_examples/auth/basicauth/main.go
@@ -37,6 +37,8 @@ func newApp() *iris.Application {
// http://localhost:8080/admin/settings
needAuth.Get("/settings", h)
+
+ needAuth.Get("/logout", logout)
}
return app
@@ -55,3 +57,11 @@ func h(ctx iris.Context) {
ctx.Writef("%s %s:%s", ctx.Path(), username, password)
}
+
+func logout(ctx iris.Context) {
+ err := ctx.Logout() // fires 401, invalidates the basic auth.
+ if err != nil {
+ ctx.Application().Logger().Errorf("Logout error: %v", err)
+ }
+ ctx.Redirect("/admin", iris.StatusTemporaryRedirect)
+}
diff --git a/_examples/routing/writing-a-middleware/share-funcs/main.go b/_examples/routing/writing-a-middleware/share-funcs/main.go
new file mode 100644
index 00000000..0b9be30d
--- /dev/null
+++ b/_examples/routing/writing-a-middleware/share-funcs/main.go
@@ -0,0 +1,109 @@
+// Package main shows how you can share a
+// function between handlers of the same chain.
+// Note that, this case is very rarely used and it exists,
+// mostly, for 3rd-party middleware creators.
+//
+// The middleware creator registers a dynamic function by Context.SetFunc and
+// the route handler just needs to call Context.CallFunc(funcName, arguments),
+// without knowning what is the specific middleware's implementation or who was the creator
+// of that function, it may be a basicauth middleware's logout or session's logout.
+//
+// See Context.SetLogoutFunc and Context.Logout methods too (these are not covered here).
+package main
+
+import (
+ "fmt"
+
+ "github.com/kataras/iris/v12"
+)
+
+func main() {
+ app := newApp()
+
+ // GET: http://localhost:8080
+ app.Listen(":8080")
+}
+
+func newApp() *iris.Application {
+ app := iris.New()
+ app.Use(middleware)
+ // OR app.UseRouter(middleware)
+ // to register it everywhere,
+ // including the HTTP errors.
+
+ app.Get("/", handler)
+ app.Get("/2", middleware2, handler2)
+ app.Get("/3", middleware3, handler3)
+
+ return app
+}
+
+// Assume: this is a middleware which does not export
+// the 'hello' function for several reasons
+// but we offer a 'greeting' optional feature to the route handler.
+func middleware(ctx iris.Context) {
+ ctx.SetFunc("greet", hello)
+ ctx.Next()
+}
+
+// Assume: this is a handler which needs to "greet" the client but
+// the function for that job is not predictable,
+// it may change - dynamically (SetFunc) - depending on
+// the middlewares registered before this route handler.
+// E.g. it may be a "Hello $name" or "Greetings $Name".
+func handler(ctx iris.Context) {
+ outputs, err := ctx.CallFunc("greet", "Gophers")
+ if err != nil {
+ ctx.StopWithError(iris.StatusInternalServerError, err)
+ return
+ }
+
+ response := outputs[0].Interface().(string)
+ ctx.WriteString(response)
+}
+
+func middleware2(ctx iris.Context) {
+ ctx.SetFunc("greet", sayHello)
+ ctx.Next()
+}
+
+func handler2(ctx iris.Context) {
+ _, err := ctx.CallFunc("greet", "Gophers [2]")
+ if err != nil {
+ ctx.StopWithError(iris.StatusInternalServerError, err)
+ return
+ }
+}
+
+func middleware3(ctx iris.Context) {
+ ctx.SetFunc("job", function3)
+ ctx.Next()
+}
+
+func handler3(ctx iris.Context) {
+ _, err := ctx.CallFunc("job")
+ if err != nil {
+ ctx.StopWithError(iris.StatusInternalServerError, err)
+ return
+ }
+
+ ctx.WriteString("OK, job was executed.\nSee the command prompt.")
+}
+
+/*
+| ------------------------ |
+| function implementations |
+| ------------------------ |
+*/
+
+func hello(name string) string {
+ return fmt.Sprintf("Hello, %s!", name)
+}
+
+func sayHello(ctx iris.Context, name string) {
+ ctx.WriteString(hello(name))
+}
+
+func function3() {
+ fmt.Printf("function3 called\n")
+}
diff --git a/_examples/routing/writing-a-middleware/share-funcs/main_test.go b/_examples/routing/writing-a-middleware/share-funcs/main_test.go
new file mode 100644
index 00000000..a6057d93
--- /dev/null
+++ b/_examples/routing/writing-a-middleware/share-funcs/main_test.go
@@ -0,0 +1,16 @@
+package main
+
+import (
+ "testing"
+
+ "github.com/kataras/iris/v12/httptest"
+)
+
+func TestShareFuncs(t *testing.T) {
+ app := newApp()
+ e := httptest.New(t, app)
+
+ e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("Hello, Gophers!")
+ e.GET("/2").Expect().Status(httptest.StatusOK).Body().Equal("Hello, Gophers [2]!")
+ e.GET("/3").Expect().Status(httptest.StatusOK).Body().Equal("OK, job was executed.\nSee the command prompt.")
+}
diff --git a/_examples/routing/writing-a-middleware/share-services/main.go b/_examples/routing/writing-a-middleware/share-services/main.go
new file mode 100644
index 00000000..9deab8a8
--- /dev/null
+++ b/_examples/routing/writing-a-middleware/share-services/main.go
@@ -0,0 +1,85 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/kataras/iris/v12"
+)
+
+func main() {
+ app := newApp()
+
+ // GET: http://localhost:8080
+ app.Listen(":8080")
+}
+
+func newApp() *iris.Application {
+ app := iris.New()
+ app.Use(middleware)
+ // OR app.UseRouter(middleware)
+ // to register it everywhere,
+ // including the HTTP errors.
+
+ app.Get("/", handler)
+
+ return app
+}
+
+func middleware(ctx iris.Context) {
+ service := &helloService{
+ Greeting: "Hello",
+ }
+ setService(ctx, service)
+
+ ctx.Next()
+}
+
+func handler(ctx iris.Context) {
+ service, ok := getService(ctx)
+ if !ok {
+ ctx.StopWithStatus(iris.StatusInternalServerError)
+ return
+ }
+
+ response := service.Greet("Gophers")
+ ctx.WriteString(response)
+}
+
+/*
+| ---------------------- |
+| service implementation |
+| ---------------------- |
+*/
+
+const serviceContextKey = "app.service"
+
+func setService(ctx iris.Context, service GreetService) {
+ ctx.Values().Set(serviceContextKey, service)
+}
+
+func getService(ctx iris.Context) (GreetService, bool) {
+ v := ctx.Values().Get(serviceContextKey)
+ if v == nil {
+ return nil, false
+ }
+
+ service, ok := v.(GreetService)
+ if !ok {
+ return nil, false
+ }
+
+ return service, true
+}
+
+// A GreetService example.
+type GreetService interface {
+ Greet(name string) string
+}
+
+type helloService struct {
+ Greeting string
+}
+
+func (m *helloService) Greet(name string) string {
+ return fmt.Sprintf("%s, %s!", m.Greeting, name)
+}
diff --git a/_examples/routing/writing-a-middleware/share-services/main_test.go b/_examples/routing/writing-a-middleware/share-services/main_test.go
new file mode 100644
index 00000000..a5b5e137
--- /dev/null
+++ b/_examples/routing/writing-a-middleware/share-services/main_test.go
@@ -0,0 +1,14 @@
+package main
+
+import (
+ "testing"
+
+ "github.com/kataras/iris/v12/httptest"
+)
+
+func TestShareServices(t *testing.T) {
+ app := newApp()
+ e := httptest.New(t, app)
+
+ e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("Hello, Gophers!")
+}
diff --git a/_examples/sessions/database/redis/main.go b/_examples/sessions/database/redis/main.go
index 2eb22e07..63860e1b 100644
--- a/_examples/sessions/database/redis/main.go
+++ b/_examples/sessions/database/redis/main.go
@@ -26,7 +26,7 @@ func main() {
Password: "",
Database: "",
Prefix: "myapp-",
- Driver: redis.GoRedis(), // defautls.
+ Driver: redis.GoRedis(), // defaults.
})
// Optionally configure the underline driver:
diff --git a/context/context.go b/context/context.go
index b9af9e3c..efe354ac 100644
--- a/context/context.go
+++ b/context/context.go
@@ -5162,6 +5162,97 @@ func (ctx *Context) IsRecovered() (*ErrPanicRecovery, bool) {
return nil, false
}
+const (
+ funcsContextPrefixKey = "iris.funcs."
+ funcLogoutContextKey = "auth.logout_func"
+)
+
+// SetFunc registers a custom function to this Request.
+// It's a helper to pass dynamic functions across handlers of the same chain.
+// For a more complete solution please use Dependency Injection instead.
+// This is just an easy to way to pass a function to the
+// next handler like ctx.Values().Set/Get does.
+// Sometimes is faster and easier to pass the object as a request value
+// and cast it when you want to use one of its methods instead of using
+// these `SetFunc` and `CallFunc` methods.
+// This implementation is suitable for functions that may change inside the
+// handler chain and the object holding the method is not predictable.
+//
+// The "name" argument is the custom name of the function,
+// you will use its name to call it later on, e.g. "auth.myfunc".
+//
+// The second, "fn" argument is the raw function/method you want
+// to pass through the next handler(s) of the chain.
+//
+// The last variadic input argument is optionally, if set
+// then its arguments are passing into the function's input arguments,
+// they should be always be the first ones to be accepted by the "fn" inputs,
+// e.g. an object, a receiver or a static service.
+//
+// See its `CallFunc` to call the "fn" on the next handler.
+//
+// Example at:
+// https://github.com/kataras/iris/tree/master/_examples/routing/writing-a-middleware/share-funcs
+func (ctx *Context) SetFunc(name string, fn interface{}, persistenceArgs ...interface{}) {
+ f := newFunc(name, fn, persistenceArgs...)
+ ctx.values.Set(funcsContextPrefixKey+name, f)
+}
+
+// GetFunc returns the context function declaration which holds
+// some information about the function registered under the given "name" by
+// the `SetFunc` method.
+func (ctx *Context) GetFunc(name string) (*Func, bool) {
+ fn := ctx.values.Get(funcsContextPrefixKey + name)
+ if fn == nil {
+ return nil, false
+ }
+
+ return fn.(*Func), true
+}
+
+// CallFunc calls the function registered by the `SetFunc`.
+// The input arguments MUST match the expected ones.
+//
+// If the registered function was just a handler
+// or a handler which returns an error
+// or a simple function
+// or a simple function which returns an error
+// then this operation will perform without any serious cost,
+// otherwise reflection will be used instead, which may slow down the overall performance.
+//
+// Retruns ErrNotFound if the function was not registered.
+//
+// For a more complete solution without limiations navigate through
+// the Iris Dependency Injection feature instead.
+func (ctx *Context) CallFunc(name string, args ...interface{}) ([]reflect.Value, error) {
+ fn, ok := ctx.GetFunc(name)
+ if !ok || fn == nil {
+ return nil, ErrNotFound
+ }
+
+ return fn.call(ctx, args...)
+}
+
+// SetLogoutFunc registers a custom logout function that will be
+// available to use inside the next handler(s). The function
+// may be registered multiple times but the last one is the valid.
+// So a logout function may start with basic authentication
+// and other middleware in the chain may change it to a custom sessions logout one.
+// This method uses the `SetFunc` method under the hoods.
+//
+// See `Logout` method too.
+func (ctx *Context) SetLogoutFunc(fn interface{}, persistenceArgs ...interface{}) {
+ ctx.SetFunc(funcLogoutContextKey, fn, persistenceArgs...)
+}
+
+// Logout calls the registered logout function.
+// Returns ErrNotFound if a logout function was not specified
+// by a prior call of `SetLogoutFunc`.
+func (ctx *Context) Logout(args ...interface{}) error {
+ _, err := ctx.CallFunc(funcLogoutContextKey, args...)
+ return err
+}
+
const idContextKey = "iris.context.id"
// SetID sets an ID, any value, to the Request Context.
diff --git a/context/context_func.go b/context/context_func.go
new file mode 100644
index 00000000..177ce1ec
--- /dev/null
+++ b/context/context_func.go
@@ -0,0 +1,203 @@
+package context
+
+import (
+ "errors"
+ "reflect"
+ "sync"
+)
+
+// ErrInvalidArgs fires when the `Context.CallFunc`
+// is called with invalid number of arguments.
+var ErrInvalidArgs = errors.New("invalid arguments")
+
+// Func represents a function registered by the Context.
+// See its `buildMeta` and `call` internal methods.
+type Func struct {
+ RegisterName string // the name of which this function is registered, for information only.
+ Raw interface{} // the Raw function, can be used for custom casting.
+ PersistenceArgs []interface{} // the persistence input arguments given on registration.
+
+ once sync.Once // guards build once, on first call.
+ // Available after the first call.
+ Meta *FuncMeta
+}
+
+func newFunc(name string, fn interface{}, persistenceArgs ...interface{}) *Func {
+ return &Func{
+ RegisterName: name,
+ Raw: fn,
+ PersistenceArgs: persistenceArgs,
+ }
+}
+
+// FuncMeta holds the necessary information about a registered
+// context function. Built once by the Func.
+type FuncMeta struct {
+ Handler Handler // when it's just a handler.
+ HandlerWithErr func(*Context) error // when it's just a handler which returns an error.
+ RawFunc func() // when it's just a func.
+ RawFuncWithErr func() error // when it's just a func which returns an error.
+ RawFuncArgs func(...interface{})
+ RawFuncArgsWithErr func(...interface{}) error
+
+ Value reflect.Value
+ Type reflect.Type
+ ExpectedArgumentsLength int
+ PersistenceInputs []reflect.Value
+ AcceptsContext bool // the Context, if exists should be always first argument.
+ ReturnsError bool // when the function's last output argument is error.
+}
+
+func (f *Func) buildMeta() {
+ switch fn := f.Raw.(type) {
+ case Handler:
+ f.Meta = &FuncMeta{Handler: fn}
+ return
+ case func(*Context):
+ f.Meta = &FuncMeta{Handler: fn}
+ return
+ case func(*Context) error:
+ f.Meta = &FuncMeta{HandlerWithErr: fn}
+ return
+ case func():
+ f.Meta = &FuncMeta{RawFunc: fn}
+ return
+ case func() error:
+ f.Meta = &FuncMeta{RawFuncWithErr: fn}
+ return
+ case func(...interface{}):
+ f.Meta = &FuncMeta{RawFuncArgs: fn}
+ return
+ case func(...interface{}) error:
+ f.Meta = &FuncMeta{RawFuncArgsWithErr: fn}
+ return
+ }
+
+ fn := f.Raw
+
+ meta := FuncMeta{}
+ if val, ok := fn.(reflect.Value); ok {
+ meta.Value = val
+ } else {
+ meta.Value = reflect.ValueOf(fn)
+ }
+
+ meta.Type = meta.Value.Type()
+
+ if meta.Type.Kind() != reflect.Func {
+ return
+ }
+
+ meta.ExpectedArgumentsLength = meta.Type.NumIn()
+
+ skipInputs := len(meta.PersistenceInputs)
+ if meta.ExpectedArgumentsLength > skipInputs {
+ meta.AcceptsContext = isContext(meta.Type.In(skipInputs))
+ }
+
+ if numOut := meta.Type.NumOut(); numOut > 0 {
+ // error should be the last output.
+ if isError(meta.Type.Out(numOut - 1)) {
+ meta.ReturnsError = true
+ }
+ }
+
+ persistenceArgs := f.PersistenceArgs
+ if len(persistenceArgs) > 0 {
+ inputs := make([]reflect.Value, 0, len(persistenceArgs))
+ for _, arg := range persistenceArgs {
+ if in, ok := arg.(reflect.Value); ok {
+ inputs = append(inputs, in)
+ } else {
+ inputs = append(inputs, reflect.ValueOf(in))
+ }
+ }
+
+ meta.PersistenceInputs = inputs
+ }
+
+ f.Meta = &meta
+}
+
+func (f *Func) call(ctx *Context, args ...interface{}) ([]reflect.Value, error) {
+ f.once.Do(f.buildMeta)
+ meta := f.Meta
+
+ if meta.Handler != nil {
+ meta.Handler(ctx)
+ return nil, nil
+ }
+
+ if meta.HandlerWithErr != nil {
+ return nil, meta.HandlerWithErr(ctx)
+ }
+
+ if meta.RawFunc != nil {
+ meta.RawFunc()
+ return nil, nil
+ }
+
+ if meta.RawFuncWithErr != nil {
+ return nil, meta.RawFuncWithErr()
+ }
+
+ if meta.RawFuncArgs != nil {
+ meta.RawFuncArgs(args...)
+ return nil, nil
+ }
+
+ if meta.RawFuncArgsWithErr != nil {
+ return nil, meta.RawFuncArgsWithErr(args...)
+ }
+
+ inputs := make([]reflect.Value, 0, f.Meta.ExpectedArgumentsLength)
+ inputs = append(inputs, f.Meta.PersistenceInputs...)
+ if f.Meta.AcceptsContext {
+ inputs = append(inputs, reflect.ValueOf(ctx))
+ }
+
+ for _, arg := range args {
+ if in, ok := arg.(reflect.Value); ok {
+ inputs = append(inputs, in)
+ } else {
+ inputs = append(inputs, reflect.ValueOf(arg))
+ }
+ }
+
+ // keep it here, the inptus may contain the context.
+ if f.Meta.ExpectedArgumentsLength != len(inputs) {
+ return nil, ErrInvalidArgs
+ }
+
+ outputs := f.Meta.Value.Call(inputs)
+ return outputs, getError(outputs)
+}
+
+var contextType = reflect.TypeOf((*Context)(nil))
+
+// isContext returns true if the "typ" is a type of Context.
+func isContext(typ reflect.Type) bool {
+ return typ == contextType
+}
+
+var errTyp = reflect.TypeOf((*error)(nil)).Elem()
+
+// isError returns true if "typ" is type of `error`.
+func isError(typ reflect.Type) bool {
+ return typ.Implements(errTyp)
+}
+
+func getError(outputs []reflect.Value) error {
+ if n := len(outputs); n > 0 {
+ lastOut := outputs[n-1]
+ if isError(lastOut.Type()) {
+ if lastOut.IsNil() {
+ return nil
+ }
+
+ return lastOut.Interface().(error)
+ }
+ }
+
+ return nil
+}
diff --git a/middleware/basicauth/basicauth.go b/middleware/basicauth/basicauth.go
index fc90eba3..5b71ae8f 100644
--- a/middleware/basicauth/basicauth.go
+++ b/middleware/basicauth/basicauth.go
@@ -21,15 +21,15 @@ type (
HeaderValue string
Username string
logged bool
+ forceLogout bool // in order to be able to invalidate and use a redirect response.
expires time.Time
mu sync.RWMutex
}
- encodedUsers []*encodedUser
basicAuthMiddleware struct {
- config Config
+ config *Config
// these are filled from the config.Users map at the startup
- auth encodedUsers
+ auth []*encodedUser
realmHeaderValue string
// The below can be removed but they are here because on the future we may add dynamic options for those two fields,
@@ -54,7 +54,7 @@ func New(c Config) context.Handler {
config.Expires = c.Expires
config.OnAsk = c.OnAsk
- b := &basicAuthMiddleware{config: config}
+ b := &basicAuthMiddleware{config: &config}
b.init()
return b.Serve
}
@@ -71,7 +71,7 @@ func Default(users map[string]string) context.Handler {
func (b *basicAuthMiddleware) init() {
// pass the encoded users from the user's config's Users value
- b.auth = make(encodedUsers, 0, len(b.config.Users))
+ b.auth = make([]*encodedUser, 0, len(b.config.Users))
for k, v := range b.config.Users {
fullUser := k + ":" + v
@@ -109,12 +109,19 @@ func (b *basicAuthMiddleware) askForCredentials(ctx *context.Context) {
// Serve the actual middleware
func (b *basicAuthMiddleware) Serve(ctx *context.Context) {
auth, found := b.findAuth(ctx.GetHeader("Authorization"))
- if !found {
+ if !found || auth.forceLogout {
+ if auth != nil {
+ auth.mu.Lock()
+ auth.forceLogout = false
+ auth.mu.Unlock()
+ }
+
b.askForCredentials(ctx)
ctx.StopExecution()
return
// don't continue to the next handler
}
+
// all ok
if b.expireEnabled {
if !auth.logged {
@@ -136,5 +143,26 @@ func (b *basicAuthMiddleware) Serve(ctx *context.Context) {
return
}
}
+
+ if !b.config.DisableLogoutFunc {
+ ctx.SetLogoutFunc(b.Logout)
+ }
+
ctx.Next() // continue
}
+
+// Logout sends a 401 so the browser/client can invalidate the
+// Basic Authentication and also sets the underline user's logged field to false,
+// so its expiration resets when re-ask for credentials.
+//
+// End-developers should call the `Context.Logout()` method
+// to fire this method as this structure is hidden.
+func (b *basicAuthMiddleware) Logout(ctx *context.Context) {
+ ctx.StatusCode(401)
+ if auth, found := b.findAuth(ctx.GetHeader("Authorization")); found {
+ auth.mu.Lock()
+ auth.logged = false
+ auth.forceLogout = true
+ auth.mu.Unlock()
+ }
+}
diff --git a/middleware/basicauth/config.go b/middleware/basicauth/config.go
index 2a4758c5..ddd76316 100644
--- a/middleware/basicauth/config.go
+++ b/middleware/basicauth/config.go
@@ -42,11 +42,14 @@ type Config struct {
//
// Defaults to nil.
OnAsk context.Handler
+
+ // DisableLogoutFunc disables the registration of the custom basicauth Context.Logout.
+ DisableLogoutFunc bool
}
// DefaultConfig returns the default configs for the BasicAuth middleware
func DefaultConfig() Config {
- return Config{make(map[string]string), DefaultBasicAuthRealm, 0, nil}
+ return Config{make(map[string]string), DefaultBasicAuthRealm, 0, nil, false}
}
// User returns the user from context key same as ctx.Request().BasicAuth().