Commit 821df6df authored by ale's avatar ale

Use securecookie for long-term device ID cookie

There is no point in using the complex gorilla/sessions machinery for
storing a simple long-term cookie, just use gorilla/securecookie directly.
parent 4de18c1c
......@@ -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)
......
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
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment