Documentation
¶
Overview ¶
Package middleware provides two session middlewares.
StartSession loads the session at the start of a request, puts it on the context where Session reads it back out, and writes it -- and its cookie -- before the first byte of the response. AuthenticateSession ends a session whose password has changed underneath it, which is what makes "change my password" sign out the other browsers.
Both return pipeline.Middleware[http.Handler], which is what http.Middleware is an alias of, so they compose with everything else without this package importing the HTTP layer.
The two collaborators neither of them may import -- the lock store and the authentication guard -- are declared here as LockFactory and Guard, with the smallest surface each of them uses. hesape/cache and hesape/auth are what an application wires behind them.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Session ¶
Session returns the session StartSession put on the request, and false when there is none. The session travels on the context, because Go's request has no methods to hang one on, and this is the one way back out of it.
The false case is not hypothetical and must not be ignored: a route mounted without this middleware, a request answered by the error handler before it ran, and a console binary all reach a handler with no session on the context. A caller that dereferences without checking panics on exactly those paths.
func WithSession ¶
WithSession puts a session on a context, because Go's request has no field to hang one on.
It is what StartSession does before calling the handler, and what a test does to run a handler without the middleware in front of it.
Types ¶
type AuthenticateSession ¶
type AuthenticateSession struct {
// contains filtered or unexported fields
}
AuthenticateSession ends a session whose password has changed underneath it. It is the reason "change my password" signs out the other browsers. The mechanism is small: the session records the password hash it started with, and every request compares it with the one in the database. A password change rewrites the hash, so every session that recorded the old one stops on its next request -- including the one belonging to whoever forced the change, which is the whole point.
It runs after StartSession and after whatever authenticates the request.
func NewAuthenticateSession ¶
func NewAuthenticateSession(auth Guard) *AuthenticateSession
NewAuthenticateSession returns a middleware over an authentication guard.
func (*AuthenticateSession) Handle ¶
func (m *AuthenticateSession) Handle(next http.Handler) http.Handler
Handle compares the session's recorded password hash against the guard's current one on every request, ending the session on a mismatch.
There is no separate check for a remembered session: remember-me lengthens the session itself, see session.Remember, so a remembered session is checked by the same session key as any other, and there is no second copy of the hash to disagree with it.
func (*AuthenticateSession) RedirectUsing ¶
func (m *AuthenticateSession) RedirectUsing(callback func(r *http.Request) string) *AuthenticateSession
RedirectUsing sets what the middleware sends somebody to when their session is ended. Returning "" answers 401 instead of redirecting. See the field it sets for why this is an instance method and not a package-level function.
type Guard ¶
type Guard interface {
// GetDefaultDriver names the guard. It is what the session key is suffixed
// with, so two guards on one application do not invalidate each other's
// sessions.
GetDefaultDriver() string
// GetAuthPassword returns the signed-in subject's stored password hash, and
// false when nobody is signed in.
//
// The HASH and not the password, and not a "has it changed" answer: the
// comparison has to happen here, against what the session recorded when it
// started, and a guard that answered the question itself would be trusting
// the thing being checked.
GetAuthPassword(r *http.Request) (string, bool)
// LogoutCurrentDevice ends the session on this device and no other.
LogoutCurrentDevice(r *http.Request) error
}
Guard is the part of an authentication guard AuthenticateSession uses.
It is declared here, minimally, rather than imported, because this package sits below the one that authenticates and importing it would be the cycle. hesape/auth is what an application wires behind it.
type LockFactory ¶
type LockFactory interface {
// Lock takes the named lock, waiting up to wait for it, and returns the
// release. It returns false when the wait ran out, which is a request that
// must not proceed: proceeding is the lost write the lock exists to stop.
Lock(ctx context.Context, name string, hold, wait time.Duration) (release func(), ok bool)
}
LockFactory hands out the lock that makes requests of one session wait for each other.
It is declared here, minimally, rather than imported: the lock lives in the cache and this package must not depend on it. hesape/cache's locks are what an application wires behind it.
type StartSession ¶
type StartSession struct {
// contains filtered or unexported fields
}
StartSession loads the session at the start of a request and writes it back at the end. It is what makes old input, validation errors and the CSRF token work: the session it puts on the context is what a handler reads with Session, and the flash it ages on the way out is what makes a message survive exactly one redirect.
The order it does things in, and why ¶
The session cookie and the session record have to be written BEFORE the handler writes the response body, because a header set after the first byte is a header that never reaches the browser. This wraps the ResponseWriter and does the work on the first write, or after the handler returns when it wrote nothing, and it is invisible from a handler.
func NewStartSession ¶
func NewStartSession(manager *session.SessionManager, locks LockFactory) *StartSession
NewStartSession returns a middleware over a session manager and a lock factory.
It takes the LockFactory itself as an argument, because there is no container here to resolve one from at the moment it is needed.
locks may be nil. When it is and the configuration asks for blocking, StartSession.Handle refuses the request rather than serving it unlocked: running unprotected is the lost write that blocking was turned on to stop, and doing it silently means nobody finds out until a customer reports data that reverted.
func (*StartSession) GetSession ¶
GetSession returns the session for this request, with the id the browser sent on it. It is exported so that a test and a handler outside the pipeline can build the same session the middleware would have.
func (*StartSession) Handle ¶
func (m *StartSession) Handle(next http.Handler) http.Handler
Handle loads the session, optionally taking the session lock, and runs next with it on the context.
It returns pipeline.Middleware[http.Handler], which is what http.Middleware is an alias of -- so this composes with everything else without this package importing the HTTP layer.
Blocking is configuration-wide here rather than per-route: every route gets the same lock hold and wait settings. And a request with no route bound at all is still blocked, because the lock is named from the session id and needs nothing else.