Browse Source

Implement API keys

Jakob Borg 11 years ago
parent
commit
20a018db2e
6 changed files with 34 additions and 0 deletions
  1. 0 0
      auto/gui.files.go
  2. 8 0
      cmd/syncthing/gui.go
  3. 3 0
      cmd/syncthing/gui_csrf.go
  4. 1 0
      config/config.go
  5. 17 0
      gui/app.js
  6. 5 0
      gui/index.html

File diff suppressed because it is too large
+ 0 - 0
auto/gui.files.go


+ 8 - 0
cmd/syncthing/gui.go

@@ -321,6 +321,10 @@ func getQR(w http.ResponseWriter, params martini.Params) {
 
 func basic(username string, passhash string) http.HandlerFunc {
 	return func(res http.ResponseWriter, req *http.Request) {
+		if validAPIKey(req.Header.Get("X-API-Key")) {
+			return
+		}
+
 		error := func() {
 			time.Sleep(time.Duration(rand.Intn(100)+100) * time.Millisecond)
 			res.Header().Set("WWW-Authenticate", "Basic realm=\"Authorization Required\"")
@@ -358,6 +362,10 @@ func basic(username string, passhash string) http.HandlerFunc {
 	}
 }
 
+func validAPIKey(k string) bool {
+	return len(cfg.GUI.APIKey) > 0 && k == cfg.GUI.APIKey
+}
+
 func embeddedStatic() func(http.ResponseWriter, *http.Request, *log.Logger) {
 	var modt = time.Now().UTC().Format(http.TimeFormat)
 

+ 3 - 0
cmd/syncthing/gui_csrf.go

@@ -22,6 +22,9 @@ var csrfMut sync.Mutex
 // the request with 403. For / and /index.html, set a new CSRF cookie if none
 // is currently set.
 func csrfMiddleware(w http.ResponseWriter, r *http.Request) {
+	if validAPIKey(r.Header.Get("X-API-Key")) {
+		return
+	}
 	if strings.HasPrefix(r.URL.Path, "/rest/") {
 		token := r.Header.Get("X-CSRF-Token")
 		if !validCsrfToken(token) {

+ 1 - 0
config/config.go

@@ -123,6 +123,7 @@ type GUIConfiguration struct {
 	User     string `xml:"user,omitempty"`
 	Password string `xml:"password,omitempty"`
 	UseTLS   bool   `xml:"tls,attr"`
+	APIKey   string `xml:"apikey,omitempty"`
 }
 
 func setDefaults(data interface{}) error {

+ 17 - 0
gui/app.js

@@ -52,6 +52,7 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
     {id: 'User', descr: 'GUI Authentication User', type: 'text', restart: true},
     {id: 'Password', descr: 'GUI Authentication Password', type: 'password', restart: true},
     {id: 'UseTLS', descr: 'Use HTTPS for GUI', type: 'bool', restart: true},
+    {id: 'APIKey', descr: 'API Key', type: 'apikey'},
     ];
 
     function getSucceeded() {
@@ -514,6 +515,10 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http) {
         $http.post(urlbase + '/config', JSON.stringify($scope.config), {headers: {'Content-Type': 'application/json'}});
     };
 
+    $scope.setAPIKey = function (cfg) {
+        cfg.APIKey = randomString(30, 32);
+    };
+
     $scope.init = function() {
         $http.get(urlbase + '/version').success(function (data) {
             $scope.version = data;
@@ -593,6 +598,18 @@ function decimals(val, num) {
     return decs;
 }
 
+function randomString(len, bits)
+{
+    bits = bits || 36;
+    var outStr = "", newStr;
+    while (outStr.length < len)
+    {
+        newStr = Math.random().toString(bits).slice(2);
+        outStr += newStr.slice(0, Math.min(newStr.length, (len - outStr.length)));
+    }
+    return outStr.toUpperCase();
+}
+
 syncthing.filter('natural', function () {
     return function (input, valid) {
         return input.toFixed(decimals(input, valid));

+ 5 - 0
gui/index.html

@@ -595,6 +595,11 @@ found in the LICENSE file.
                       {{setting.descr}} <input id="{{setting.id}}" type="checkbox" ng-model="config.workingGUI[setting.id]"></input>
                     </label>
                   </div>
+                  <div ng-if="setting.type == 'apikey'">
+                    <label>{{setting.descr}} (<a href="http://discourse.syncthing.net/t/v0-8-14-api-keys/335">Usage</a>)</label>
+                    <div class="well well-sm text-monospace">{{config.workingGUI[setting.id] || "-"}}</div>
+                    <button type="button" class="btn btn-sm btn-default" ng-click="setAPIKey(config.workingGUI)">Generate</button>
+                  </div>
                 </div>
               </div>
             </div>

Some files were not shown because too many files changed in this diff