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 { type apiSvc struct {
id protocol.DeviceID
cfg config.GUIConfiguration cfg config.GUIConfiguration
assetDir string assetDir string
model *model.Model model *model.Model
@ -62,8 +63,9 @@ type apiSvc struct {
eventSub *events.BufferedSubscription 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{ svc := &apiSvc{
id: id,
cfg: cfg, cfg: cfg,
assetDir: assetDir, assetDir: assetDir,
model: m, model: m,
@ -188,14 +190,14 @@ func (s *apiSvc) Serve() {
// Wrap everything in CSRF protection. The /rest prefix should be // Wrap everything in CSRF protection. The /rest prefix should be
// protected, other requests will grant cookies. // 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 // Add our version and ID as a header to responses
handler = withVersionMiddleware(handler) handler = withDetailsMiddleware(s.id, handler)
// Wrap everything in basic auth, if user/password is set. // Wrap everything in basic auth, if user/password is set.
if len(s.cfg.User) > 0 && len(s.cfg.Password) > 0 { 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 // 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) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Syncthing-Version", Version) w.Header().Set("X-Syncthing-Version", Version)
w.Header().Set("X-Syncthing-ID", id.String())
h.ServeHTTP(w, r) h.ServeHTTP(w, r)
}) })
} }

View File

@ -24,14 +24,15 @@ var (
sessionsMut = sync.NewMutex() 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) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if cfg.APIKey != "" && r.Header.Get("X-API-Key") == cfg.APIKey { if cfg.APIKey != "" && r.Header.Get("X-API-Key") == cfg.APIKey {
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
return return
} }
cookie, err := r.Cookie("sessionid") cookie, err := r.Cookie(cookieName)
if err == nil && cookie != nil { if err == nil && cookie != nil {
sessionsMut.Lock() sessionsMut.Lock()
_, ok := sessions[cookie.Value] _, ok := sessions[cookie.Value]
@ -86,7 +87,7 @@ func basicAuthAndSessionMiddleware(cfg config.GUIConfiguration, next http.Handle
sessions[sessionid] = true sessions[sessionid] = true
sessionsMut.Unlock() sessionsMut.Unlock()
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: "sessionid", Name: cookieName,
Value: sessionid, Value: sessionid,
MaxAge: 0, 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 // 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 // the request with 403. For / and /index.html, set a new CSRF cookie if none
// is currently set. // 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() loadCsrfTokens()
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Allow requests carrying a valid API key // 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. // 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) { 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) { if err != nil || !validCsrfToken(cookie.Value) {
cookie = &http.Cookie{ cookie = &http.Cookie{
Name: "CSRF-Token", Name: "CSRF-Token-" + unique,
Value: newCsrfToken(), Value: newCsrfToken(),
} }
http.SetCookie(w, cookie) http.SetCookie(w, cookie)
@ -54,7 +54,7 @@ func csrfMiddleware(prefix, apiKey string, next http.Handler) http.Handler {
} }
// Verify the CSRF token // Verify the CSRF token
token := r.Header.Get("X-CSRF-Token") token := r.Header.Get("X-CSRF-Token-" + unique)
if !validCsrfToken(token) { if !validCsrfToken(token) {
http.Error(w, "CSRF Error", 403) http.Error(w, "CSRF Error", 403)
return 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))) urlShow := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostShow, strconv.Itoa(addr.Port)))
l.Infoln("Starting web GUI on", urlShow) 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 { if err != nil {
l.Fatalln("Cannot start GUI:", err) l.Fatalln("Cannot start GUI:", err)
} }

View File

@ -17,10 +17,9 @@ var syncthing = angular.module('syncthing', [
var urlbase = 'rest'; var urlbase = 'rest';
var guiVersion = null; var guiVersion = null;
var deviceId = null;
syncthing.config(function ($httpProvider, $translateProvider, LocaleServiceProvider) { syncthing.config(function ($httpProvider, $translateProvider, LocaleServiceProvider) {
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token';
$httpProvider.defaults.xsrfCookieName = 'CSRF-Token';
$httpProvider.interceptors.push(function () { $httpProvider.interceptors.push(function () {
return { return {
response: function (response) { response: function (response) {
@ -30,6 +29,14 @@ syncthing.config(function ($httpProvider, $translateProvider, LocaleServiceProvi
} else if (guiVersion != responseVersion) { } else if (guiVersion != responseVersion) {
document.location.reload(true); 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; return response;
} }
}; };

File diff suppressed because one or more lines are too long