Skip to content
Snippets Groups Projects

Refactor the login handler

Merged ale requested to merge better-login into master
2 files
+ 54
32
Compare changes
  • Side-by-side
  • Inline
Files
2
+ 62
16
@@ -10,21 +10,33 @@ import (
"strings"
"git.autistici.org/id/auth"
"github.com/gorilla/sessions"
"github.com/gorilla/securecookie"
"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
sc *securecookie.SecureCookie
urlPrefix string
geodb *geoIPDb
}
@@ -35,7 +47,7 @@ type Config struct {
}
// New returns a new Manager with the given configuration.
func New(config *Config) (*Manager, error) {
func New(config *Config, urlPrefix string) (*Manager, error) {
if config == nil {
config = &Config{}
}
@@ -45,26 +57,33 @@ func New(config *Config) (*Manager, error) {
log.Printf("Warning: GeoIP disabled: %v", err)
}
// This should only happen in tests.
if config.AuthKey == "" {
log.Printf("Warning: device_manager.auth_key unset, generating temporary random secrets")
config.AuthKey = string(securecookie.GenerateRandomKey(64))
}
sc := securecookie.New([]byte(config.AuthKey), nil)
sc.MaxAge(deviceIDCookieMaxAge)
sc.SetSerializer(securecookie.NopEncoder{})
return &Manager{
sc: sc,
geodb: geodb,
store: newStore([]byte(config.AuthKey)),
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)
@@ -75,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(),
@@ -103,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)
Loading