Skip to content
This repository has been archived by the owner on Jul 12, 2023. It is now read-only.

Commit

Permalink
Add session idle duration
Browse files Browse the repository at this point in the history
  • Loading branch information
sethvargo committed Sep 18, 2020
1 parent a18dedf commit 53a3d31
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 8 deletions.
6 changes: 6 additions & 0 deletions cmd/server/assets/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,12 @@ <h1>Create verification code</h1>
}
},
error: function(xhr, resp, text) {
// On unauthorized, force a logout
if (xhr.status === 401 || xhr.status == 403) {
window.location.assign('/signout');
return;
}

// Show reset button
$buttonReset.removeClass('d-none');

Expand Down
2 changes: 1 addition & 1 deletion cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func realMain(ctx context.Context) error {
r.Use(requireSession)

// Create common middleware
requireAuth := middleware.RequireAuth(ctx, cacher, auth, db, h, config.SessionDuration)
requireAuth := middleware.RequireAuth(ctx, cacher, auth, db, h, config.SessionIdleTimeout, config.SessionDuration)
requireVerified := middleware.RequireVerified(ctx, auth, db, h, config.SessionDuration)
requireAdmin := middleware.RequireRealmAdmin(ctx, h)
loadCurrentRealm := middleware.LoadCurrentRealm(ctx, cacher, db, h)
Expand Down
5 changes: 3 additions & 2 deletions pkg/config/server_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ type ServerConfig struct {
Port string `env:"PORT,default=8080"`

// Login Config
SessionDuration time.Duration `env:"SESSION_DURATION, default=20h"`
RevokeCheckPeriod time.Duration `env:"REVOKE_CHECK_DURATION,default=5m"`
SessionDuration time.Duration `env:"SESSION_DURATION, default=20h"`
SessionIdleTimeout time.Duration `env:"SESSION_IDLE_TIMEOUT, default=20m"`
RevokeCheckPeriod time.Duration `env:"REVOKE_CHECK_DURATION, default=5m"`

// Password Config
PasswordRequirements PasswordRequirementsConfig
Expand Down
17 changes: 15 additions & 2 deletions pkg/controller/middleware/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import (
// RequireAuth requires a user to be logged in. It also ensures that currentUser
// is set in the template map. It fetches a user from the session and stores the
// full record in the request context.
func RequireAuth(ctx context.Context, cacher cache.Cacher, fbClient *auth.Client, db *database.Database, h *render.Renderer, ttl time.Duration) mux.MiddlewareFunc {
func RequireAuth(ctx context.Context, cacher cache.Cacher, fbClient *auth.Client, db *database.Database, h *render.Renderer, sessionIdleTTL, expiryCheckTTL time.Duration) mux.MiddlewareFunc {
logger := logging.FromContext(ctx).Named("middleware.RequireAuth")

cacheTTL := 5 * time.Minute
Expand All @@ -53,6 +53,19 @@ func RequireAuth(ctx context.Context, cacher cache.Cacher, fbClient *auth.Client

flash := controller.Flash(session)

// Check session idle timeout.
if t := controller.LastActivityFromSession(session); !t.IsZero() {
// If it's been more than the TTL since we've seen this session,
// "expire" it by creating a new empty session. Note that we don't force
// the user back to a login page or anything - other middlewares will
// handle that if needed.
if time.Since(t) > sessionIdleTTL {
logger.Debug("session is expired")
controller.Unauthorized(w, r, h)
return
}
}

firebaseCookie := controller.FirebaseCookieFromSession(session)
if firebaseCookie == "" {
logger.Debugw("firebase cookie not in session")
Expand Down Expand Up @@ -92,7 +105,7 @@ func RequireAuth(ctx context.Context, cacher cache.Cacher, fbClient *auth.Client
}

// Check if the session is still valid.
if time.Now().After(user.LastRevokeCheck.Add(ttl)) {
if time.Now().After(user.LastRevokeCheck.Add(expiryCheckTTL)) {
if _, err := fbClient.VerifySessionCookieAndCheckRevoked(ctx, firebaseCookie); err != nil {
logger.Debugw("failed to verify firebase cookie revocation", "error", err)
controller.ClearSessionFirebaseCookie(session)
Expand Down
2 changes: 2 additions & 0 deletions pkg/controller/middleware/sessions.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"
"net/http"
"sync"
"time"

"github.com/google/exposure-notifications-verification-server/pkg/controller"
"github.com/google/exposure-notifications-verification-server/pkg/render"
Expand Down Expand Up @@ -72,6 +73,7 @@ func RequireSession(ctx context.Context, store sessions.Store, h *render.Rendere
once.Do(func() {
session := controller.SessionFromContext(ctx)
if session != nil {
controller.StoreSessionLastActivity(session, time.Now())
err = session.Save(r, w)
}
})
Expand Down
39 changes: 36 additions & 3 deletions pkg/controller/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package controller

import (
"time"

"github.com/google/exposure-notifications-verification-server/pkg/database"
"github.com/gorilla/sessions"
)
Expand All @@ -25,11 +27,12 @@ import (
type sessionKey string

const (
sessionKeyFirebaseCookie = sessionKey("firebaseCookie")
sessionKeyRealmID = sessionKey("realmID")
emailVerificationPrompted = sessionKey("emailVerificationPrompted")
factorCount = sessionKey("factorCount")
lastActivity = sessionKey("lastActivity")
mfaPrompted = sessionKey("mfaPrompted")
emailVerificationPrompted = sessionKey("emailVerificationPrompted")
sessionKeyFirebaseCookie = sessionKey("firebaseCookie")
sessionKeyRealmID = sessionKey("realmID")
)

// StoreSessionFirebaseCookie stores the firebase cookie in the session. If the
Expand Down Expand Up @@ -149,6 +152,36 @@ func MFAPromptedFromSession(session *sessions.Session) bool {
return f
}

// StoreSessionLastActivity stores the last time the user did something. This is
// used to track idle session timeouts.
func StoreSessionLastActivity(session *sessions.Session, t time.Time) {
if session == nil {
return
}
session.Values[lastActivity] = t.Unix()
}

// ClearLastActivity clears the session last activity time.
func ClearLastActivity(session *sessions.Session) {
sessionClear(session, lastActivity)
}

// LastActivityFromSession extractsthe last time the user did something.
func LastActivityFromSession(session *sessions.Session) time.Time {
v := sessionGet(session, lastActivity)
if v == nil {
return time.Time{}
}

i, ok := v.(int64)
if !ok || i == 0 {
delete(session.Values, lastActivity)
return time.Time{}
}

return time.Unix(i, 0)
}

// StoreSessionEmailVerificationPrompted stores if the user was prompted for email verification.
func StoreSessionEmailVerificationPrompted(session *sessions.Session, prompted bool) {
if session == nil {
Expand Down

0 comments on commit 53a3d31

Please sign in to comment.