Add HTML login form (fixes #4137) (#8757)

This commit is contained in:
Emil Lundberg
2023-10-06 13:00:58 +02:00
committed by GitHub
parent ac2e444a97
commit 8294870ffc
12 changed files with 683 additions and 244 deletions
+46 -12
View File
@@ -72,7 +72,7 @@
<img class="logo hidden-xs" src="assets/img/logo-horizontal.svg" height="32" width="117" alt=""/>
<img class="logo hidden visible-xs" src="assets/img/favicon-default.png" height="32" alt=""/>
</span>
<p class="navbar-text hidden-xs" ng-class="{'hidden-sm':upgradeInfo && upgradeInfo.newer}">{{thisDeviceName()}}</p>
<p ng-if="authenticated" class="navbar-text hidden-xs" ng-class="{'hidden-sm':upgradeInfo && upgradeInfo.newer}">{{thisDeviceName()}}</p>
<ul class="nav navbar-nav navbar-right">
<li ng-if="upgradeInfo && upgradeInfo.newer" class="upgrade-newer">
<button type="button" class="btn navbar-btn btn-primary btn-sm" data-toggle="modal" data-target="#upgrade">
@@ -109,21 +109,25 @@
<li><a href="" ng-click="about.show()"><span class="fa fa-fw fa-heart"></span>&nbsp;<span translate>About</span></a></li>
</ul>
</li>
<li class="dropdown action-menu">
<li ng-if="authenticated || config.gui.debugging" class="dropdown action-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
<span class="fa fa-cog"></span>
<span class="hidden-xs" translate>Actions</span>
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li><a href="" ng-click="showSettings()"><span class="fa fa-fw fa-cog"></span>&nbsp;<span translate>Settings</span></a></li>
<li><a href="" ng-click="showDeviceIdentification(thisDevice())"><span class="fa fa-fw fa-qrcode"></span>&nbsp;<span translate>Show ID</span></a></li>
<li class="divider" aria-hidden="true"></li>
<li><a href="" ng-click="shutdown()"><span class="fa fa-fw fa-power-off"></span>&nbsp;<span translate>Shutdown</span></a></li>
<li><a href="" ng-click="restart()"><span class="fa fa-fw fa-refresh"></span>&nbsp;<span translate>Restart</span></a></li>
<li class="divider" aria-hidden="true"></li>
<li><a href="" ng-click="advanced()"><span class="fa fa-fw fa-cogs"></span>&nbsp;<span translate>Advanced</span></a></li>
<li><a href="" ng-click="logging.show()"><span class="fa fa-fw fa-wrench"></span>&nbsp;<span translate>Logs</span></a></li>
<li ng-if="authenticated"><a href="" ng-click="showSettings()"><span class="fa fa-fw fa-cog"></span>&nbsp;<span translate>Settings</span></a></li>
<li ng-if="authenticated"><a href="" ng-click="showDeviceIdentification(thisDevice())"><span class="fa fa-fw fa-qrcode"></span>&nbsp;<span translate>Show ID</span></a></li>
<li ng-if="authenticated" class="divider" aria-hidden="true"></li>
<li ng-if="authenticated"><a href="" ng-click="shutdown()"><span class="fa fa-fw fa-power-off"></span>&nbsp;<span translate>Shutdown</span></a></li>
<li ng-if="authenticated"><a href="" ng-click="restart()"><span class="fa fa-fw fa-refresh"></span>&nbsp;<span translate>Restart</span></a></li>
<li ng-if="authenticated" class="divider" aria-hidden="true"></li>
<li ng-if="authenticated"><a href="" ng-click="advanced()"><span class="fa fa-fw fa-cogs"></span>&nbsp;<span translate>Advanced</span></a></li>
<li ng-if="authenticated"><a href="" ng-click="logging.show()"><span class="fa fa-fw fa-wrench"></span>&nbsp;<span translate>Logs</span></a></li>
<li ng-if="authenticated"><a href="" ng-click="logout()"><span class="far fa-fw fa-ban"></span>&nbsp;<span translate>Log Out</span></a></li>
<li class="divider" aria-hidden="true" ng-if="config.gui.debugging"></li>
<li><a href="/rest/debug/support" target="_blank" ng-if="config.gui.debugging"><span class="fa fa-fw fa-user-md"></span>&nbsp;<span translate>Support Bundle</span></a></li>
</ul>
@@ -338,9 +342,39 @@
</div>
</div>
<!-- First regular row -->
<!-- Login form -->
<div ng-if="!authenticated" class="center-block">
<h3 translate>Authentication Required</h3>
<div class="row">
<form ng-submit="authenticatePassword()">
<div class="form-group">
<label for="user" translate>User</label>
<input id="user" class="form-control" type="text" name="user" ng-model="login.username" autofocus required autocomplete="username" />
</div>
<div class="form-group">
<label for="password" translate>Password</label>
<input id="password" class="form-control" type="password" name="password" ng-model="login.password" ng-trim="false" autocomplete="current-password" />
</div>
<div class="row">
<div class="col-md-9 login-form-messages">
<p ng-if="login.errors.badLogin" class="text-danger" translate>
Incorrect user name or password.
</p>
<p ng-if="login.errors.failed" class="text-danger" translate>
Login failed, see Syncthing logs for details.
</p>
</div>
<div class="col-md-3 text-right">
<button type="submit" class="btn btn-default" ng-disabled="login.inProgress" translate>Log In</button>
</div>
</div>
</form>
</div>
<!-- First regular row -->
<div ng-if="authenticated" class="row">
<!-- Folder list (top left) -->
+13 -5
View File
@@ -16,13 +16,9 @@ var syncthing = angular.module('syncthing', [
]);
var urlbase = 'rest';
var authUrlbase = urlbase + '/noauth/auth';
syncthing.config(function ($httpProvider, $translateProvider, LocaleServiceProvider) {
var deviceIDShort = metadata.deviceID.substr(0, 5);
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token-' + deviceIDShort;
$httpProvider.defaults.xsrfCookieName = 'CSRF-Token-' + deviceIDShort;
$httpProvider.useApplyAsync(true);
// language and localisation
$translateProvider.useSanitizeValueStrategy('escape');
@@ -33,6 +29,18 @@ syncthing.config(function ($httpProvider, $translateProvider, LocaleServiceProvi
LocaleServiceProvider.setAvailableLocales(validLangs);
LocaleServiceProvider.setDefaultLocale('en');
$httpProvider.useApplyAsync(true);
if (!window.metadata) {
// Most likely we're not authenticated yet, in which case we can't proceed with the rest of the setup.
// Do nothing and wait for the page reload on successful login.
return;
}
var deviceIDShort = metadata.deviceID.substr(0, 5);
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token-' + deviceIDShort;
$httpProvider.defaults.xsrfCookieName = 'CSRF-Token-' + deviceIDShort;
});
// @TODO: extract global level functions into separate service(s)
+10 -3
View File
@@ -2,17 +2,21 @@
<div class="modal-body">
<h1 class="text-center">
<img alt="Syncthing" src="assets/img/logo-horizontal.svg" style="max-width: 366px; vertical-align: -16px" />
<br />
</h1>
<h1 ng-if="version.version" class="text-center">
<small>{{versionString()}}</small>
<br />
<small><i>"{{version.codename}}"</i></small>
</h1>
<p class="text-center">
<p ng-if="version.version" class="text-center">
Build {{version.date | date:"yyyy-MM-dd"}}
<span ng-if="version.tags.length">({{version.tags.join(", ")}})</span>
<br />
Copyright &copy; 2014-{{version.date | date:"yyyy"}} the Syncthing Authors.
</p>
<p ng-if="!version.version" class="text-center" translate>
Log in to see version information.
</p>
<p class="text-center" translate>Syncthing is Free and Open Source Software licensed as MPL v2.0.</p>
<ul class="nav nav-tabs">
@@ -81,7 +85,10 @@ Jakob Borg, Audrius Butkevicius, Jesse Lucas, Simon Frei, Tomasz Wilczyński, Al
</div>
<div id="about-paths" class="tab-pane">
<table class="table table-striped table-auto">
<p ng-if="!authenticated" translate>
Log in to see paths information.
</p>
<table ng-if="authenticated" class="table table-striped table-auto">
<caption><label translate>Internally used paths:</label></caption>
<tbody>
<tr>
+7 -1
View File
@@ -38,7 +38,13 @@ angular.module('syncthing.core')
.error(errorFn);
}
function errorFn(dummy) {
function errorFn(statusString, status) {
if (status === 403) {
// Auth error - reload login page
location.reload();
return;
}
$rootScope.$broadcast(self.OFFLINE);
$timeout(function () {
@@ -14,12 +14,26 @@ angular.module('syncthing.core')
function initController() {
LocaleService.autoConfigLocale();
if (!$scope.authenticated) {
// Can't proceed yet - wait for the page reload after successful login.
return;
}
setInterval($scope.refresh, 10000);
Events.start();
}
// public/scope definitions
// window.metadata is set in /meta.js which requires authentication
$scope.authenticated = window.metadata && window.metadata.authenticated;
$scope.login = {
username: '',
password: '',
errors: {},
};
$scope.completion = {};
$scope.config = {};
$scope.configInSync = true;
@@ -83,6 +97,35 @@ angular.module('syncthing.core')
files: 0
};
$scope.authenticatePassword = function () {
$scope.login.inProgress = true;
$scope.login.errors = {};
$http.post(authUrlbase + '/password', {
username: $scope.login.username,
password: $scope.login.password,
}).then(function () {
location.reload();
}).catch(function (response) {
if (response.status === 403) {
$scope.login.errors.badLogin = true;
} else {
$scope.login.errors.failed = true;
console.log('Password authentication failed:', response);
}
}).finally(function () {
$scope.login.inProgress = false;
});
};
$scope.logout = function() {
$http.post(authUrlbase + '/logout', {})
.then(function () {
location.reload();
}).catch(function (response) {
console.log('Failed to log out:', response);
});
};
$(window).bind('beforeunload', function () {
navigatingAway = true;
});
@@ -183,6 +226,9 @@ angular.module('syncthing.core')
if (arg.status === 0) {
// A network error, not an HTTP error
$scope.$emit(Events.OFFLINE);
} else if (arg.status === 403) {
// Auth error - reload login page
location.reload();
} else if (arg.status >= 400 && arg.status <= 599 && arg.status != 501) {
// A genuine HTTP error. 501/NotImplemented is considered intentional
// and not an error which we need to act upon.
@@ -3119,6 +3165,9 @@ angular.module('syncthing.core')
$scope.docsURL = function (path) {
var url = 'https://docs.syncthing.net';
if (!$scope.versionBase()) {
return url;
}
if (!path) {
// Undefined or null should become a valid string.
path = '';
@@ -128,14 +128,14 @@
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label translate for="User">GUI Authentication User</label>
<input id="User" class="form-control" type="text" ng-model="tmpGUI.user" autocomplete="off" />
<label translate for="user">GUI Authentication User</label>
<input id="user" class="form-control" type="text" name="user" ng-model="tmpGUI.user" autocomplete="username" />
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label translate for="Password">GUI Authentication Password</label>
<input id="Password" class="form-control" type="password" ng-model="tmpGUI.password" ng-trim="false" autocomplete="new-password" />
<label translate for="password">GUI Authentication Password</label>
<input id="password" class="form-control" type="password" name="password" ng-model="tmpGUI.password" ng-trim="false" autocomplete="new-password" />
</div>
</div>
</div>