Use different session cookies per device

This commit is contained in:
Audrius Butkevicius 2015-06-22 16:57:08 +01:00
parent 0450d48f89
commit 089fca2319
6 changed files with 29 additions and 18 deletions

View File

@ -52,6 +52,7 @@ var (
)
type apiSvc struct {
id protocol.DeviceID
cfg config.GUIConfiguration
assetDir string
model *model.Model
@ -62,8 +63,9 @@ type apiSvc struct {
eventSub *events.BufferedSubscription
}
func newAPISvc(cfg config.GUIConfiguration, assetDir string, m *model.Model, eventSub *events.BufferedSubscription) (*apiSvc, error) {
func newAPISvc(id protocol.DeviceID, cfg config.GUIConfiguration, assetDir string, m *model.Model, eventSub *events.BufferedSubscription) (*apiSvc, error) {
svc := &apiSvc{
id: id,
cfg: cfg,
assetDir: assetDir,
model: m,
@ -188,14 +190,14 @@ func (s *apiSvc) Serve() {
// Wrap everything in CSRF protection. The /rest prefix should be
// protected, other requests will grant cookies.
handler := csrfMiddleware("/rest", s.cfg.APIKey, mux)
handler := csrfMiddleware(s.id.String()[:5], "/rest", s.cfg.APIKey, mux)
// Add our version as a header to responses
handler = withVersionMiddleware(handler)
// Add our version and ID as a header to responses
handler = withDetailsMiddleware(s.id, handler)
// Wrap everything in basic auth, if user/password is set.
if len(s.cfg.User) > 0 && len(s.cfg.Password) > 0 {
handler = basicAuthAndSessionMiddleware(s.cfg, handler)
handler = basicAuthAndSessionMiddleware("sessionid-"+s.id.String()[:5], s.cfg, handler)
}
// Redirect to HTTPS if we are supposed to
@ -334,9 +336,10 @@ func noCacheMiddleware(h http.Handler) http.Handler {
})
}
func withVersionMiddleware(h http.Handler) http.Handler {
func withDetailsMiddleware(id protocol.DeviceID, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Syncthing-Version", Version)
w.Header().Set("X-Syncthing-ID", id.String())
h.ServeHTTP(w, r)
})
}

View File

@ -24,14 +24,15 @@ var (
sessionsMut = sync.NewMutex()
)
func basicAuthAndSessionMiddleware(cfg config.GUIConfiguration, next http.Handler) http.Handler {
func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguration, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if cfg.APIKey != "" && r.Header.Get("X-API-Key") == cfg.APIKey {
next.ServeHTTP(w, r)
return
}
cookie, err := r.Cookie("sessionid")
cookie, err := r.Cookie(cookieName)
if err == nil && cookie != nil {
sessionsMut.Lock()
_, ok := sessions[cookie.Value]
@ -86,7 +87,7 @@ func basicAuthAndSessionMiddleware(cfg config.GUIConfiguration, next http.Handle
sessions[sessionid] = true
sessionsMut.Unlock()
http.SetCookie(w, &http.Cookie{
Name: "sessionid",
Name: cookieName,
Value: sessionid,
MaxAge: 0,
})

View File

@ -24,7 +24,7 @@ var csrfMut = sync.NewMutex()
// Check for CSRF token on /rest/ URLs. If a correct one is not given, reject
// the request with 403. For / and /index.html, set a new CSRF cookie if none
// is currently set.
func csrfMiddleware(prefix, apiKey string, next http.Handler) http.Handler {
func csrfMiddleware(unique, prefix, apiKey string, next http.Handler) http.Handler {
loadCsrfTokens()
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Allow requests carrying a valid API key
@ -35,10 +35,10 @@ func csrfMiddleware(prefix, apiKey string, next http.Handler) http.Handler {
// Allow requests for the front page, and set a CSRF cookie if there isn't already a valid one.
if !strings.HasPrefix(r.URL.Path, prefix) {
cookie, err := r.Cookie("CSRF-Token")
cookie, err := r.Cookie("CSRF-Token-" + unique)
if err != nil || !validCsrfToken(cookie.Value) {
cookie = &http.Cookie{
Name: "CSRF-Token",
Name: "CSRF-Token-" + unique,
Value: newCsrfToken(),
}
http.SetCookie(w, cookie)
@ -54,7 +54,7 @@ func csrfMiddleware(prefix, apiKey string, next http.Handler) http.Handler {
}
// Verify the CSRF token
token := r.Header.Get("X-CSRF-Token")
token := r.Header.Get("X-CSRF-Token-" + unique)
if !validCsrfToken(token) {
http.Error(w, "CSRF Error", 403)
return

View File

@ -808,7 +808,7 @@ func setupGUI(mainSvc *suture.Supervisor, cfg *config.Wrapper, m *model.Model, a
urlShow := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostShow, strconv.Itoa(addr.Port)))
l.Infoln("Starting web GUI on", urlShow)
api, err := newAPISvc(guiCfg, guiAssets, m, apiSub)
api, err := newAPISvc(myID, guiCfg, guiAssets, m, apiSub)
if err != nil {
l.Fatalln("Cannot start GUI:", err)
}

View File

@ -17,10 +17,9 @@ var syncthing = angular.module('syncthing', [
var urlbase = 'rest';
var guiVersion = null;
var deviceId = null;
syncthing.config(function ($httpProvider, $translateProvider, LocaleServiceProvider) {
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token';
$httpProvider.defaults.xsrfCookieName = 'CSRF-Token';
$httpProvider.interceptors.push(function () {
return {
response: function (response) {
@ -30,6 +29,14 @@ syncthing.config(function ($httpProvider, $translateProvider, LocaleServiceProvi
} else if (guiVersion != responseVersion) {
document.location.reload(true);
}
if (!deviceId) {
deviceId = response.headers()['x-syncthing-id'];
if (deviceId) {
var deviceIdShort = deviceId.substring(0, 5);
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token-' + deviceIdShort;
$httpProvider.defaults.xsrfCookieName = 'CSRF-Token-' + deviceIdShort;
}
}
return response;
}
};

File diff suppressed because one or more lines are too long