diff --git a/home/auth.go b/home/auth.go index 0bcf0ed3..b5241c77 100644 --- a/home/auth.go +++ b/home/auth.go @@ -18,7 +18,6 @@ import ( ) const cookieTTL = 365 * 24 // in hours -const expireTime = 30 * 24 // in hours type session struct { userName string @@ -56,10 +55,11 @@ func (s *session) deserialize(data []byte) bool { // Auth - global object type Auth struct { - db *bbolt.DB - sessions map[string]*session // session name -> session data - lock sync.Mutex - users []User + db *bbolt.DB + sessions map[string]*session // session name -> session data + lock sync.Mutex + users []User + sessionTTL uint32 // in seconds } // User object @@ -69,8 +69,9 @@ type User struct { } // InitAuth - create a global object -func InitAuth(dbFilename string, users []User) *Auth { +func InitAuth(dbFilename string, users []User, sessionTTL uint32) *Auth { a := Auth{} + a.sessionTTL = sessionTTL a.sessions = make(map[string]*session) rand.Seed(time.Now().UTC().Unix()) var err error @@ -145,18 +146,21 @@ func (a *Auth) loadSessions() { // store session data in file func (a *Auth) addSession(data []byte, s *session) { + name := hex.EncodeToString(data) a.lock.Lock() - a.sessions[hex.EncodeToString(data)] = s + a.sessions[name] = s a.lock.Unlock() - a.storeSession(data, s) + if a.storeSession(data, s) { + log.Info("Auth: created session %s: expire=%d", name, s.expire) + } } // store session data in file -func (a *Auth) storeSession(data []byte, s *session) { +func (a *Auth) storeSession(data []byte, s *session) bool { tx, err := a.db.Begin(true) if err != nil { log.Error("Auth: bbolt.Begin: %s", err) - return + return false } defer func() { _ = tx.Rollback() @@ -165,21 +169,20 @@ func (a *Auth) storeSession(data []byte, s *session) { bkt, err := tx.CreateBucketIfNotExists(bucketName()) if err != nil { log.Error("Auth: bbolt.CreateBucketIfNotExists: %s", err) - return + return false } err = bkt.Put(data, s.serialize()) if err != nil { log.Error("Auth: bbolt.Put: %s", err) - return + return false } err = tx.Commit() if err != nil { log.Error("Auth: bbolt.Commit: %s", err) - return + return false } - - log.Debug("Auth: stored session in DB") + return true } // remove session from file @@ -233,7 +236,7 @@ func (a *Auth) CheckSession(sess string) int { return 1 } - newExpire := now + expireTime*60*60 + newExpire := now + a.sessionTTL if s.expire/(24*60*60) != newExpire/(24*60*60) { // update expiration time once a day update = true @@ -244,7 +247,9 @@ func (a *Auth) CheckSession(sess string) int { if update { key, _ := hex.DecodeString(sess) - a.storeSession(key, s) + if a.storeSession(key, s) { + log.Debug("Auth: updated session %s: expire=%d", sess, s.expire) + } } return 0 @@ -270,8 +275,8 @@ func getSession(u *User) []byte { return hash[:] } -func httpCookie(req loginJSON) string { - u := config.auth.UserFind(req.Name, req.Password) +func (a *Auth) httpCookie(req loginJSON) string { + u := a.UserFind(req.Name, req.Password) if len(u.Name) == 0 { return "" } @@ -286,8 +291,8 @@ func httpCookie(req loginJSON) string { s := session{} s.userName = u.Name - s.expire = uint32(now.Unix()) + expireTime*60*60 - config.auth.addSession(sess, &s) + s.expire = uint32(now.Unix()) + a.sessionTTL + a.addSession(sess, &s) return fmt.Sprintf("session=%s; Path=/; HttpOnly; Expires=%s", hex.EncodeToString(sess), expstr) } @@ -300,10 +305,11 @@ func handleLogin(w http.ResponseWriter, r *http.Request) { return } - cookie := httpCookie(req) + cookie := config.auth.httpCookie(req) if len(cookie) == 0 { + log.Info("Auth: invalid user name or password: name='%s'", req.Name) time.Sleep(1 * time.Second) - httpError(w, http.StatusBadRequest, "invalid login or password") + http.Error(w, "invalid user name or password", http.StatusBadRequest) return } @@ -365,7 +371,7 @@ func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.Re w.WriteHeader(http.StatusFound) return } else if r < 0 { - log.Debug("Auth: invalid cookie value: %s", cookie) + log.Info("Auth: invalid cookie value: %s", cookie) } } @@ -382,7 +388,7 @@ func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.Re if r == 0 { ok = true } else if r < 0 { - log.Debug("Auth: invalid cookie value: %s", cookie) + log.Info("Auth: invalid cookie value: %s", cookie) } } else { // there's no Cookie, check Basic authentication @@ -391,6 +397,8 @@ func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.Re u := config.auth.UserFind(user, pass) if len(u.Name) != 0 { ok = true + } else { + log.Info("Auth: invalid Basic Authorization value") } } } diff --git a/home/auth_test.go b/home/auth_test.go index ed2c3e6a..19cd5001 100644 --- a/home/auth_test.go +++ b/home/auth_test.go @@ -27,7 +27,7 @@ func TestAuth(t *testing.T) { users := []User{ User{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"}, } - a := InitAuth(fn, nil) + a := InitAuth(fn, nil, 60) s := session{} user := User{Name: "name"} @@ -54,7 +54,7 @@ func TestAuth(t *testing.T) { a.Close() // load saved session - a = InitAuth(fn, users) + a = InitAuth(fn, users, 60) // the session is still alive assert.True(t, a.CheckSession(sessStr) == 0) @@ -69,7 +69,7 @@ func TestAuth(t *testing.T) { time.Sleep(3 * time.Second) // load and remove expired sessions - a = InitAuth(fn, users) + a = InitAuth(fn, users, 60) assert.True(t, a.CheckSession(sessStr) == -1) a.Close() @@ -100,7 +100,7 @@ func TestAuthHTTP(t *testing.T) { users := []User{ User{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"}, } - config.auth = InitAuth(fn, users) + config.auth = InitAuth(fn, users, 60) handlerCalled := false handler := func(w http.ResponseWriter, r *http.Request) { @@ -129,7 +129,7 @@ func TestAuthHTTP(t *testing.T) { assert.True(t, handlerCalled) // perform login - cookie := httpCookie(loginJSON{Name: "name", Password: "password"}) + cookie := config.auth.httpCookie(loginJSON{Name: "name", Password: "password"}) assert.True(t, cookie != "") // get / diff --git a/home/config.go b/home/config.go index fcd5e3dc..8c53574a 100644 --- a/home/config.go +++ b/home/config.go @@ -91,6 +91,10 @@ type configuration struct { Language string `yaml:"language"` // two-letter ISO 639-1 language code RlimitNoFile uint `yaml:"rlimit_nofile"` // Maximum number of opened fd's per process (0: default) + // TTL for a web session (in hours) + // An active session is automatically refreshed once a day. + WebSessionTTLHours uint32 `yaml:"web_session_ttl"` + DNS dnsConfig `yaml:"dns"` TLS tlsConfig `yaml:"tls"` Filters []filter `yaml:"filters"` @@ -210,6 +214,8 @@ func initConfig() { Transport: config.transport, } + config.WebSessionTTLHours = 30 * 24 + if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" { // Use plain DNS on MIPS, encryption is too slow defaultDNS = []string{"1.1.1.1", "1.0.0.1"} diff --git a/home/dns.go b/home/dns.go index 4d722fa2..7b81a89e 100644 --- a/home/dns.go +++ b/home/dns.go @@ -68,7 +68,7 @@ func initDNSServer() { config.dnsServer = dnsforward.NewServer(config.dnsFilter, config.stats, config.queryLog) sessFilename := filepath.Join(baseDir, "sessions.db") - config.auth = InitAuth(sessFilename, config.Users) + config.auth = InitAuth(sessFilename, config.Users, config.WebSessionTTLHours*60*60) config.Users = nil config.dnsctx.rdns = InitRDNS(&config.clients)