diff --git a/server/device/manager.go b/server/device/manager.go index d6d6fa741e36a48f9601576a80459b794bde59d7..6343e79c546e37edab43483706ccefcb54b83584 100644 --- a/server/device/manager.go +++ b/server/device/manager.go @@ -11,22 +11,33 @@ import ( "git.autistici.org/id/auth" "github.com/gorilla/securecookie" - "github.com/gorilla/sessions" "github.com/mssola/user_agent" ) -func randomDeviceID() string { +const ( + deviceIDCookieName = "_dev" + deviceIDCookieMaxAge = 10 * 365 * 86400 +) + +type DeviceID []byte + +func (d DeviceID) String() string { + return hex.EncodeToString(d) +} + +func randomDeviceID() DeviceID { b := make([]byte, 8) if _, err := io.ReadFull(rand.Reader, b[:]); err != nil { panic(err) } - return hex.EncodeToString(b) + return b } // Manager can provide DeviceInfo entries for incoming HTTP requests. type Manager struct { - store sessions.Store - geodb *geoIPDb + sc *securecookie.SecureCookie + urlPrefix string + geodb *geoIPDb } // Config stores options for the device info manager. @@ -52,26 +63,27 @@ func New(config *Config, urlPrefix string) (*Manager, error) { config.AuthKey = string(securecookie.GenerateRandomKey(64)) } + sc := securecookie.New([]byte(config.AuthKey), nil) + sc.MaxAge(deviceIDCookieMaxAge) + sc.SetSerializer(securecookie.NopEncoder{}) + return &Manager{ - geodb: geodb, - store: newStore([]byte(config.AuthKey), urlPrefix), + sc: sc, + geodb: geodb, + urlPrefix: urlPrefix, }, nil } -const deviceIDSessionName = "_dev" - // GetDeviceInfoFromRequest will retrieve or create a DeviceInfo // object for the given request. It will always return a valid object. // The ResponseWriter is needed to store the unique ID on the client // when a new device info object is created. func (m *Manager) GetDeviceInfoFromRequest(w http.ResponseWriter, req *http.Request) *auth.DeviceInfo { - session, _ := m.store.Get(req, deviceIDSessionName) - devID, ok := session.Values["id"].(string) - if !ok || devID == "" { + devID, ok := m.getDeviceCookie(req) + if !ok || len(devID) == 0 { // Generate a new Device ID and save it on the client. devID = randomDeviceID() - session.Values["id"] = devID - if err := session.Save(req, w); err != nil { + if err := m.setDeviceCookie(w, devID); err != nil { // This is likely a misconfiguration issue, so // we want to know about it. log.Printf("error saving device manager session: %v", err) @@ -82,7 +94,7 @@ func (m *Manager) GetDeviceInfoFromRequest(w http.ResponseWriter, req *http.Requ ua := user_agent.New(uaStr) browser, _ := ua.Browser() d := auth.DeviceInfo{ - ID: devID, + ID: devID.String(), UserAgent: uaStr, Mobile: ua.Mobile(), OS: ua.OS(), @@ -110,6 +122,33 @@ func (m *Manager) GetDeviceInfoFromRequest(w http.ResponseWriter, req *http.Requ return &d } +func (m *Manager) getDeviceCookie(r *http.Request) (DeviceID, bool) { + if cookie, err := r.Cookie(deviceIDCookieName); err == nil { + var value []byte + if err = m.sc.Decode(deviceIDCookieName, cookie.Value, &value); err == nil { + return DeviceID(value), true + } + } + return nil, false +} + +func (m *Manager) setDeviceCookie(w http.ResponseWriter, value DeviceID) error { + encoded, err := m.sc.Encode(deviceIDCookieName, []byte(value)) + if err != nil { + return err + } + cookie := &http.Cookie{ + Name: deviceIDCookieName, + Value: encoded, + Path: m.urlPrefix + "/", + Secure: true, + HttpOnly: true, + MaxAge: deviceIDCookieMaxAge, + } + http.SetCookie(w, cookie) + return nil +} + func getIPFromRequest(req *http.Request) net.IP { // Parse the RemoteAddr Request field, for starters. host, _, err := net.SplitHostPort(req.RemoteAddr) diff --git a/server/device/store.go b/server/device/store.go deleted file mode 100644 index 1a00725a0ede2be7eca7d0c1b8c7482f57744c19..0000000000000000000000000000000000000000 --- a/server/device/store.go +++ /dev/null @@ -1,17 +0,0 @@ -package device - -import "github.com/gorilla/sessions" - -const aVeryLongTimeInSeconds = 10 * 365 * 86400 - -func newStore(authKey []byte, urlPrefix string) sessions.Store { - // No encryption, long-term lifetime cookie. - store := sessions.NewCookieStore(authKey, nil) - store.Options = &sessions.Options{ - Path: urlPrefix + "/", - HttpOnly: true, - Secure: true, - MaxAge: aVeryLongTimeInSeconds, - } - return store -}