gui_auth.go 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. // Copyright (C) 2014 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at http://mozilla.org/MPL/2.0/.
  6. package main
  7. import (
  8. "bytes"
  9. "encoding/base64"
  10. "math/rand"
  11. "net/http"
  12. "strings"
  13. "time"
  14. "github.com/syncthing/syncthing/internal/config"
  15. "github.com/syncthing/syncthing/internal/sync"
  16. "golang.org/x/crypto/bcrypt"
  17. )
  18. var (
  19. sessions = make(map[string]bool)
  20. sessionsMut = sync.NewMutex()
  21. )
  22. func basicAuthAndSessionMiddleware(cfg config.GUIConfiguration, next http.Handler) http.Handler {
  23. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  24. if cfg.APIKey != "" && r.Header.Get("X-API-Key") == cfg.APIKey {
  25. next.ServeHTTP(w, r)
  26. return
  27. }
  28. cookie, err := r.Cookie("sessionid")
  29. if err == nil && cookie != nil {
  30. sessionsMut.Lock()
  31. _, ok := sessions[cookie.Value]
  32. sessionsMut.Unlock()
  33. if ok {
  34. next.ServeHTTP(w, r)
  35. return
  36. }
  37. }
  38. if debugHTTP {
  39. l.Debugln("Sessionless HTTP request with authentication; this is expensive.")
  40. }
  41. error := func() {
  42. time.Sleep(time.Duration(rand.Intn(100)+100) * time.Millisecond)
  43. w.Header().Set("WWW-Authenticate", "Basic realm=\"Authorization Required\"")
  44. http.Error(w, "Not Authorized", http.StatusUnauthorized)
  45. }
  46. hdr := r.Header.Get("Authorization")
  47. if !strings.HasPrefix(hdr, "Basic ") {
  48. error()
  49. return
  50. }
  51. hdr = hdr[6:]
  52. bs, err := base64.StdEncoding.DecodeString(hdr)
  53. if err != nil {
  54. error()
  55. return
  56. }
  57. fields := bytes.SplitN(bs, []byte(":"), 2)
  58. if len(fields) != 2 {
  59. error()
  60. return
  61. }
  62. if string(fields[0]) != cfg.User {
  63. error()
  64. return
  65. }
  66. if err := bcrypt.CompareHashAndPassword([]byte(cfg.Password), fields[1]); err != nil {
  67. error()
  68. return
  69. }
  70. sessionid := randomString(32)
  71. sessionsMut.Lock()
  72. sessions[sessionid] = true
  73. sessionsMut.Unlock()
  74. http.SetCookie(w, &http.Cookie{
  75. Name: "sessionid",
  76. Value: sessionid,
  77. MaxAge: 0,
  78. })
  79. next.ServeHTTP(w, r)
  80. })
  81. }