mirror of
https://github.com/duplicati/duplicati.git
synced 2025-11-28 11:30:24 +08:00
This adds a nonce to the refresh token such that each request to obtain a refresh token must now also provide a matching nonce. When using non-persisted logins, the request to the server is the same, but the "remember me" flag toggles a shorter duration for the refresh token. The FE can then store the nonce in either local storage for persisted logins or in session storage for non-persisted logins. The default is currently to always issue refresh tokens with a nonce, but this can be toggled with the JWT configuration. The ngax client does not have the non-persisted login so it stores the nonce in local storage, using a name that is compatible with ngclient so the user can swap between them without needing to re-login. The server util was updated to also store the nonce. This fixes #6451
241 lines
10 KiB
JavaScript
241 lines
10 KiB
JavaScript
backupApp.controller('AppController', function($rootScope, $scope, $cookies, $location, AppService, BrandingService, ServerStatus, SystemInfo, AppUtils, DialogService, gettextCatalog) {
|
|
$scope.brandingService = BrandingService.watch($scope);
|
|
$scope.state = ServerStatus.watch($scope);
|
|
$scope.systemInfo = SystemInfo.watch($scope);
|
|
|
|
$scope.localized = {};
|
|
$scope.location = $location;
|
|
$scope.saved_theme = $scope.active_theme = $cookies.get('current-theme') || 'default';
|
|
$scope.throttle_active = false;
|
|
|
|
// If we want the theme settings
|
|
// to be persisted on the server,
|
|
// set to "true" here
|
|
var save_theme_on_server = false;
|
|
|
|
$scope.doReconnect = function() {
|
|
ServerStatus.reconnect();
|
|
};
|
|
|
|
$scope.reload = function() {
|
|
location.reload();
|
|
};
|
|
|
|
$scope.login = function() {
|
|
location.href = '/login.html';
|
|
};
|
|
|
|
$scope.resume = function() {
|
|
ServerStatus.resume().then(function() {}, AppUtils.connectionError);
|
|
};
|
|
|
|
$scope.pause = function(duration, pauseTransfers) {
|
|
ServerStatus.pause(duration, pauseTransfers).then(function() {}, AppUtils.connectionError);
|
|
};
|
|
|
|
$scope.isLoggedIn = false;
|
|
|
|
$scope.log_out = function() {
|
|
const storedNonce = localStorage.getItem('v1:persist:duplicati:refreshNonce');
|
|
const body = storedNonce ? { Nonce: storedNonce } : undefined;
|
|
|
|
// Use a path under /auth/refresh to allow the cookie to be sent for deletion
|
|
// Calling `/auth/logout` also works, but does not revoke the token in the database
|
|
AppService.post('/auth/refresh/logout', body).then(function() {
|
|
AppService.clearAccessToken();
|
|
localStorage.removeItem('v1:persist:duplicati:refreshNonce');
|
|
location.href = '/login.html';
|
|
}, AppUtils.connectionError);
|
|
};
|
|
|
|
$scope.pauseOptions = function() {
|
|
if ($scope.state.programState != 'Running') {
|
|
$scope.resume();
|
|
} else {
|
|
DialogService.htmlDialog(
|
|
gettextCatalog.getString('Pause options'),
|
|
'templates/pause.html',
|
|
[gettextCatalog.getString('OK'), gettextCatalog.getString('Cancel')],
|
|
function(index, text, cur) {
|
|
if (index == 0 && cur != null && cur.time != null) {
|
|
var time = cur.time;
|
|
var pauseTransfers = cur.pauseTransfers;
|
|
$scope.pause(time == 'infinite' ? '' : time, pauseTransfers);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
};
|
|
|
|
$scope.throttleOptions = function() {
|
|
DialogService.htmlDialog(
|
|
gettextCatalog.getString('Throttle settings'),
|
|
'templates/throttle.html',
|
|
[gettextCatalog.getString('OK'), gettextCatalog.getString('Cancel')],
|
|
function(index, text, cur) {
|
|
if (index == 0 && cur != null && cur.uploadspeed != null && cur.downloadspeed != null) {
|
|
var patchdata = {
|
|
'max-download-speed': cur.downloadthrottleenabled ? cur.downloadspeed : '',
|
|
'max-upload-speed': cur.uploadthrottleenabled ? cur.uploadspeed : '',
|
|
};
|
|
|
|
AppService.patch('/serversettings', patchdata, {headers: {'Content-Type': 'application/json; charset=utf-8'}}).then(function(data) {
|
|
$scope.throttle_active = cur.downloadthrottleenabled || cur.uploadthrottleenabled;
|
|
}, AppUtils.connectionError);
|
|
}
|
|
}
|
|
);
|
|
};
|
|
|
|
function updateCurrentPage() {
|
|
|
|
$scope.active_theme = $scope.saved_theme;
|
|
|
|
if ($location.$$path == '/' || $location.$$path == '')
|
|
$scope.current_page = 'home';
|
|
else if ($location.$$path == '/addstart' || $location.$$path == '/add' || $location.$$path == '/import')
|
|
$scope.current_page = 'add';
|
|
else if ($location.$$path == '/restorestart' || $location.$$path == '/restore' || $location.$$path == '/restoredirect' || $location.$$path.indexOf('/restore/') == 0)
|
|
$scope.current_page = 'restore';
|
|
else if ($location.$$path == '/settings')
|
|
$scope.current_page = 'settings';
|
|
else if ($location.$$path == '/log')
|
|
$scope.current_page = 'log';
|
|
else if ($location.$$path == '/about')
|
|
$scope.current_page = 'about';
|
|
else
|
|
$scope.current_page = '';
|
|
};
|
|
|
|
$scope.$on('serverstatechanged', function() {
|
|
// Unwanted jQuery interference, but the menu is built with this
|
|
if (ServerStatus.state.programState == 'Paused') {
|
|
$('#contextmenu_pause').removeClass('open');
|
|
$('#contextmenulink_pause').removeClass('open');
|
|
}
|
|
$scope.isLoggedIn = ServerStatus.state.connectionState == 'connected';
|
|
});
|
|
|
|
//$scope.$on('$routeUpdate', updateCurrentPage);
|
|
$scope.$watch('location.$$path', updateCurrentPage);
|
|
updateCurrentPage();
|
|
|
|
function loadCurrentTheme() {
|
|
if (save_theme_on_server) {
|
|
AppService.get('/uisettings/ngax').then(
|
|
function(data) {
|
|
var theme = 'default';
|
|
if (data.data != null && (data.data['theme'] || '').trim().length > 0)
|
|
theme = data.data['theme'];
|
|
|
|
var now = new Date();
|
|
var exp = new Date(now.getFullYear()+10, now.getMonth(), now.getDate());
|
|
$cookies.put('current-theme', theme, { expires: exp });
|
|
$scope.saved_theme = $scope.active_theme = theme;
|
|
}, function() {}
|
|
);
|
|
}
|
|
};
|
|
|
|
// In case the cookie is out-of-sync
|
|
loadCurrentTheme();
|
|
|
|
$scope.$on('update_theme', function(event, args) {
|
|
var theme = 'default';
|
|
if (args != null && (args.theme || '').trim().length != 0)
|
|
theme = args.theme;
|
|
|
|
if (save_theme_on_server) {
|
|
// Set it here to avoid flickering when the page changes
|
|
$scope.saved_theme = $scope.active_theme = theme;
|
|
|
|
AppService.patch('/uisettings/ngax', { 'theme': theme }, {'headers': {'Content-Type': 'application/json'}}).then(
|
|
function(data) {
|
|
var now = new Date();
|
|
var exp = new Date(now.getFullYear()+10, now.getMonth(), now.getDate());
|
|
$cookies.put('current-theme', theme, { expires: exp });
|
|
$scope.saved_theme = $scope.active_theme = theme;
|
|
}, function() {}
|
|
);
|
|
} else {
|
|
var now = new Date();
|
|
var exp = new Date(now.getFullYear()+10, now.getMonth(), now.getDate());
|
|
$cookies.put('current-theme', theme, { expires: exp });
|
|
$scope.saved_theme = $scope.active_theme = theme;
|
|
}
|
|
|
|
loadCurrentTheme();
|
|
});
|
|
|
|
$scope.$on('preview_theme', function(event, args) {
|
|
if (args == null || (args.theme + '').trim().length == 0)
|
|
$scope.active_theme = $scope.saved_theme;
|
|
else
|
|
$scope.active_theme = args.theme || '';
|
|
});
|
|
|
|
AppService.get('/serversettings').then(function(data) {
|
|
$scope.forceActualDate = AppUtils.parseBoolString(data.data['--force-actual-date']);
|
|
|
|
var ut = data.data['max-upload-speed'];
|
|
var dt = data.data['max-download-speed'];
|
|
$scope.throttle_active = (ut != null && ut.trim().length != 0) || (dt != null && dt.trim().length != 0);
|
|
|
|
ServerStatus.state.orderBy = data.data['backup-list-sort-order'] || 'id';
|
|
$rootScope.$broadcast('sortorder_changed', { orderBy: ServerStatus.state.orderBy } );
|
|
|
|
var has_asked = AppUtils.parseBoolString(data.data['has-asked-for-password-change']);
|
|
var autogen_passphrase = AppUtils.parseBoolString(data.data['autogenerated-passphrase']);
|
|
if (!has_asked && autogen_passphrase) {
|
|
DialogService.dialog(
|
|
gettextCatalog.getString('First run setup'),
|
|
gettextCatalog.getString('Duplicati needs to be secured with a passphrase and a random passphrase has been generated for you.\nIf you open Duplicati from the tray icon, you do not need a passphrase, but if you plan to open it from another location you need to set a passphrase you know.\nDo you want to set a passphrase now?'),
|
|
[gettextCatalog.getString('Yes'), gettextCatalog.getString('No')],
|
|
function(btn) {
|
|
if (btn == 1) {
|
|
// Set the flag so we don't ask again
|
|
AppService.patch('/serversettings', { 'has-asked-for-password-change': 'true'});
|
|
}
|
|
else
|
|
{
|
|
DialogService.htmlDialog(
|
|
gettextCatalog.getString('Change server password'),
|
|
'templates/changepassword.html',
|
|
[gettextCatalog.getString('OK'), gettextCatalog.getString('Cancel')],
|
|
function(index, text, cur) {
|
|
if (index != 0)
|
|
return;
|
|
|
|
AppService.patch('/serversettings',
|
|
{
|
|
'has-asked-for-password-change': 'true',
|
|
'server-passphrase': cur.remotePassword
|
|
})
|
|
.then(function() {}, AppUtils.connectionError);
|
|
},
|
|
null,
|
|
function(index, text, cur) {
|
|
if (index != 0)
|
|
return true;
|
|
|
|
if (cur.remotePassword == null || cur.remotePassword.length == 0)
|
|
{
|
|
alert("Please enter a passphrase");
|
|
return false;
|
|
}
|
|
|
|
if(cur.remotePassword != cur.confirmPassword)
|
|
{
|
|
alert("Passwords do not match");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
}, AppUtils.connectionError);
|
|
});
|