mirror of
https://github.com/octoleo/syncthing.git
synced 2024-12-22 10:58:57 +00:00
This adds our short device ID to the basic auth realm. This has at least two consequences: - It is different from what's presented by another device on the same address (e.g., if I use SSH forwards to different dives on the same local address), preventing credentials for one from being sent to another. - It is different from what we did previously, meaning we avoid cached credentials from old versions interfering with the new login flow. I don't *think* there should be things that depend on our precise realm string, so this shouldn't break any existing setups... Sneakily this also changes the session cookie and CSRF name, because I think `id.Short().String()` is nicer than `id.String()[:5]` and the short ID is two characters longer. That's also not a problem...
This commit is contained in:
parent
53123c0b01
commit
b014a9ebc2
@ -38,9 +38,8 @@ syncthing.config(function ($httpProvider, $translateProvider, LocaleServiceProvi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var deviceIDShort = metadata.deviceID.substr(0, 5);
|
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token-' + metadata.deviceIDShort;
|
||||||
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token-' + deviceIDShort;
|
$httpProvider.defaults.xsrfCookieName = 'CSRF-Token-' + metadata.deviceIDShort;
|
||||||
$httpProvider.defaults.xsrfCookieName = 'CSRF-Token-' + deviceIDShort;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// @TODO: extract global level functions into separate service(s)
|
// @TODO: extract global level functions into separate service(s)
|
||||||
|
@ -365,15 +365,15 @@ func (s *service) Serve(ctx context.Context) error {
|
|||||||
|
|
||||||
// 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.
|
||||||
var handler http.Handler = newCsrfManager(s.id.String()[:5], "/rest", guiCfg, mux, locations.Get(locations.CsrfTokens))
|
var handler http.Handler = newCsrfManager(s.id.Short().String(), "/rest", guiCfg, mux, locations.Get(locations.CsrfTokens))
|
||||||
|
|
||||||
// Add our version and ID as a header to responses
|
// Add our version and ID as a header to responses
|
||||||
handler = withDetailsMiddleware(s.id, 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 guiCfg.IsAuthEnabled() {
|
if guiCfg.IsAuthEnabled() {
|
||||||
sessionCookieName := "sessionid-" + s.id.String()[:5]
|
sessionCookieName := "sessionid-" + s.id.Short().String()
|
||||||
handler = basicAuthAndSessionMiddleware(sessionCookieName, guiCfg, s.cfg.LDAP(), handler, s.evLogger)
|
handler = basicAuthAndSessionMiddleware(sessionCookieName, s.id.Short().String(), guiCfg, s.cfg.LDAP(), handler, s.evLogger)
|
||||||
handlePasswordAuth := passwordAuthHandler(sessionCookieName, guiCfg, s.cfg.LDAP(), s.evLogger)
|
handlePasswordAuth := passwordAuthHandler(sessionCookieName, guiCfg, s.cfg.LDAP(), s.evLogger)
|
||||||
restMux.Handler(http.MethodPost, "/rest/noauth/auth/password", handlePasswordAuth)
|
restMux.Handler(http.MethodPost, "/rest/noauth/auth/password", handlePasswordAuth)
|
||||||
|
|
||||||
@ -719,6 +719,7 @@ func (*service) getSystemPaths(w http.ResponseWriter, _ *http.Request) {
|
|||||||
func (s *service) getJSMetadata(w http.ResponseWriter, _ *http.Request) {
|
func (s *service) getJSMetadata(w http.ResponseWriter, _ *http.Request) {
|
||||||
meta, _ := json.Marshal(map[string]interface{}{
|
meta, _ := json.Marshal(map[string]interface{}{
|
||||||
"deviceID": s.id.String(),
|
"deviceID": s.id.String(),
|
||||||
|
"deviceIDShort": s.id.Short().String(),
|
||||||
"authenticated": true,
|
"authenticated": true,
|
||||||
})
|
})
|
||||||
w.Header().Set("Content-Type", "application/javascript")
|
w.Header().Set("Content-Type", "application/javascript")
|
||||||
|
@ -42,8 +42,8 @@ func antiBruteForceSleep() {
|
|||||||
time.Sleep(time.Duration(rand.Intn(100)+100) * time.Millisecond)
|
time.Sleep(time.Duration(rand.Intn(100)+100) * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
func unauthorized(w http.ResponseWriter) {
|
func unauthorized(w http.ResponseWriter, shortID string) {
|
||||||
w.Header().Set("WWW-Authenticate", "Basic realm=\"Authorization Required\"")
|
w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm="Authorization Required (%s)"`, shortID))
|
||||||
http.Error(w, "Not Authorized", http.StatusUnauthorized)
|
http.Error(w, "Not Authorized", http.StatusUnauthorized)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ func isNoAuthPath(path string) bool {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func basicAuthAndSessionMiddleware(cookieName string, guiCfg config.GUIConfiguration, ldapCfg config.LDAPConfiguration, next http.Handler, evLogger events.Logger) http.Handler {
|
func basicAuthAndSessionMiddleware(cookieName, shortID string, guiCfg config.GUIConfiguration, ldapCfg config.LDAPConfiguration, next http.Handler, evLogger events.Logger) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if hasValidAPIKeyHeader(r, guiCfg) {
|
if hasValidAPIKeyHeader(r, guiCfg) {
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
@ -117,7 +117,7 @@ func basicAuthAndSessionMiddleware(cookieName string, guiCfg config.GUIConfigura
|
|||||||
// Some browsers don't send the Authorization request header unless prompted by a 401 response.
|
// Some browsers don't send the Authorization request header unless prompted by a 401 response.
|
||||||
// This enables https://user:pass@localhost style URLs to keep working.
|
// This enables https://user:pass@localhost style URLs to keep working.
|
||||||
if guiCfg.SendBasicAuthPrompt {
|
if guiCfg.SendBasicAuthPrompt {
|
||||||
unauthorized(w)
|
unauthorized(w, shortID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,10 +13,15 @@ import (
|
|||||||
"github.com/syncthing/syncthing/lib/sha256"
|
"github.com/syncthing/syncthing/lib/sha256"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DeviceIDLength = 32
|
const (
|
||||||
|
DeviceIDLength = 32
|
||||||
|
ShortIDStringLength = 7
|
||||||
|
)
|
||||||
|
|
||||||
type DeviceID [DeviceIDLength]byte
|
type (
|
||||||
type ShortID uint64
|
DeviceID [DeviceIDLength]byte
|
||||||
|
ShortID uint64
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
LocalDeviceID = repeatedDeviceID(0xff)
|
LocalDeviceID = repeatedDeviceID(0xff)
|
||||||
@ -94,7 +99,7 @@ func (s ShortID) String() string {
|
|||||||
}
|
}
|
||||||
var bs [8]byte
|
var bs [8]byte
|
||||||
binary.BigEndian.PutUint64(bs[:], uint64(s))
|
binary.BigEndian.PutUint64(bs[:], uint64(s))
|
||||||
return base32.StdEncoding.EncodeToString(bs[:])[:7]
|
return base32.StdEncoding.EncodeToString(bs[:])[:ShortIDStringLength]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *DeviceID) UnmarshalText(bs []byte) error {
|
func (n *DeviceID) UnmarshalText(bs []byte) error {
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { environment } from '../environments/environment'
|
import { environment } from '../environments/environment'
|
||||||
|
|
||||||
export const deviceID = (): String => {
|
export const deviceID = (): String => {
|
||||||
const dID: String = environment.production ? globalThis.metadata['deviceID'] : '12345';
|
return environment.production ? globalThis.metadata['deviceIDShort'] : '1234567';
|
||||||
return dID.substring(0, 5)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const apiURL: String = '/'
|
export const apiURL: String = '/'
|
||||||
|
@ -173,7 +173,7 @@ func TestHTTPPOSTWithoutCSRF(t *testing.T) {
|
|||||||
}
|
}
|
||||||
res.Body.Close()
|
res.Body.Close()
|
||||||
hdr := res.Header.Get("Set-Cookie")
|
hdr := res.Header.Get("Set-Cookie")
|
||||||
id := res.Header.Get("X-Syncthing-ID")[:5]
|
id := res.Header.Get("X-Syncthing-ID")[:protocol.ShortIDStringLength]
|
||||||
if !strings.Contains(hdr, "CSRF-Token") {
|
if !strings.Contains(hdr, "CSRF-Token") {
|
||||||
t.Error("Missing CSRF-Token in", hdr)
|
t.Error("Missing CSRF-Token in", hdr)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user