mirror of
https://github.com/bitwarden/desktop.git
synced 2024-11-17 10:45:41 +01:00
Added setting to not store key via lock options, only keeping it in memory. Fixed some i18n and created constants service
This commit is contained in:
parent
25fef2d826
commit
80ed37ada6
@ -382,5 +382,61 @@
|
||||
"browserNotSupportClipboard": {
|
||||
"message": "Your web browser does not support easy clipboard copying. Copy it manually instead.",
|
||||
"description": "Your web browser does not support easy clipboard copying. Copy it manually instead."
|
||||
},
|
||||
"verifyMasterPassword": {
|
||||
"message": "Verify Master Password",
|
||||
"description": "Verify Master Password"
|
||||
},
|
||||
"invalidMasterPassword": {
|
||||
"message": "Invalid master password",
|
||||
"description": "Invalid master password"
|
||||
},
|
||||
"errorsHaveOccurred": {
|
||||
"message": "Invalid master password",
|
||||
"description": "Invalid master password"
|
||||
},
|
||||
"lockOptions": {
|
||||
"message": "Lock Options",
|
||||
"description": "Lock Options"
|
||||
},
|
||||
"immediately": {
|
||||
"message": "Immediately",
|
||||
"description": "Immediately"
|
||||
},
|
||||
"oneMinute": {
|
||||
"message": "1 minute",
|
||||
"description": "1 minute"
|
||||
},
|
||||
"fiveMinutes": {
|
||||
"message": "5 minutes",
|
||||
"description": "5 minutes"
|
||||
},
|
||||
"fifteenMinutes": {
|
||||
"message": "15 minutes",
|
||||
"description": "15 minutes"
|
||||
},
|
||||
"thirtyMinutes": {
|
||||
"message": "30 minutes",
|
||||
"description": "30 minutes"
|
||||
},
|
||||
"oneHour": {
|
||||
"message": "1 hour",
|
||||
"description": "1 hour"
|
||||
},
|
||||
"fourHours": {
|
||||
"message": "4 hours",
|
||||
"description": "4 hours"
|
||||
},
|
||||
"onRestart": {
|
||||
"message": "On Restart",
|
||||
"description": "On Restart"
|
||||
},
|
||||
"never": {
|
||||
"message": "Never",
|
||||
"description": "Never"
|
||||
},
|
||||
"security": {
|
||||
"message": "Security",
|
||||
"description": "Security"
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
var isBackground = true;
|
||||
var i18nService = new i18nService();
|
||||
var constantsService = new ConstantsService();
|
||||
var utilsService = new UtilsService();
|
||||
var cryptoService = new CryptoService();
|
||||
var cryptoService = new CryptoService(constantsService);
|
||||
var tokenService = new TokenService();
|
||||
var apiService = new ApiService(tokenService);
|
||||
var userService = new UserService(tokenService, apiService, cryptoService);
|
||||
|
@ -33,6 +33,7 @@
|
||||
"models/dataModels.js",
|
||||
"models/domainModels.js",
|
||||
"services/i18nService.js",
|
||||
"services/constantsService.js",
|
||||
"services/utilsService.js",
|
||||
"services/cryptoService.js",
|
||||
"services/tokenService.js",
|
||||
|
@ -7,7 +7,7 @@
|
||||
<button type="submit" class="btn btn-link" ng-show="!theForm.$loading">{{i18n.login}}</button>
|
||||
<i class="fa fa-spinner fa-lg" ng-show="theForm.$loading" ng-class="{'fa-spin' : theForm.$loading}"></i>
|
||||
</div>
|
||||
<div class="title">{{i18n.bitwarden}}</div>
|
||||
<div class="title">{{i18n.appName}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list">
|
||||
|
@ -14,5 +14,6 @@
|
||||
'bit.current',
|
||||
'bit.vault',
|
||||
'bit.settings',
|
||||
'bit.tools'
|
||||
'bit.tools',
|
||||
'bit.lock'
|
||||
]);
|
||||
|
@ -163,17 +163,30 @@
|
||||
controller: 'settingsEditFolderController',
|
||||
data: { authorize: true },
|
||||
params: { animation: null }
|
||||
})
|
||||
.state('lock', {
|
||||
url: '/lock',
|
||||
templateUrl: 'app/lock/views/lock.html',
|
||||
controller: 'lockController',
|
||||
data: { authorize: true },
|
||||
params: { animation: null }
|
||||
});
|
||||
})
|
||||
.run(function ($rootScope, userService, loginService, tokenService, $state) {
|
||||
.run(function ($rootScope, userService, loginService, cryptoService, tokenService, $state) {
|
||||
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
|
||||
cryptoService.getKey(false, function (key) {
|
||||
tokenService.getToken(function (token) {
|
||||
userService.isAuthenticated(function (isAuthenticated) {
|
||||
if (!toState.data || !toState.data.authorize) {
|
||||
if (isAuthenticated && !tokenService.isTokenExpired(token)) {
|
||||
event.preventDefault();
|
||||
if (!key) {
|
||||
$state.go('lock');
|
||||
}
|
||||
else {
|
||||
$state.go('tabs.current');
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@ -188,3 +201,4 @@
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
45
src/popup/app/lock/lockController.js
Normal file
45
src/popup/app/lock/lockController.js
Normal file
@ -0,0 +1,45 @@
|
||||
angular
|
||||
.module('bit.lock')
|
||||
|
||||
.controller('lockController', function ($scope, $state, $analytics, i18nService, loginService, cryptoService, toastr,
|
||||
userService, SweetAlert) {
|
||||
$scope.i18n = i18nService;
|
||||
$('#master-password').focus();
|
||||
|
||||
$scope.logOut = function () {
|
||||
loginService.logOut(function () {
|
||||
SweetAlert.swal({
|
||||
title: 'Log Out',
|
||||
text: 'Are you sure you want to log out?',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Yes',
|
||||
cancelButtonText: 'Cancel'
|
||||
}, function (confirmed) {
|
||||
if (confirmed) {
|
||||
loginService.logOut(function () {
|
||||
$analytics.eventTrack('Logged Out');
|
||||
$state.go('home');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.submit = function () {
|
||||
userService.getEmail(function (email) {
|
||||
var key = cryptoService.makeKey($scope.masterPassword, email);
|
||||
cryptoService.hashPassword($scope.masterPassword, key, function (keyHash) {
|
||||
cryptoService.getKeyHash(true, function (storedKeyHash) {
|
||||
if (storedKeyHash && keyHash && storedKeyHash === keyHash) {
|
||||
cryptoService.setKey(key, function () {
|
||||
$state.go('tabs.current');
|
||||
});
|
||||
}
|
||||
else {
|
||||
toastr.error(i18nService.invalidMasterPassword, i18nService.errorsHaveOccurred);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
2
src/popup/app/lock/lockModule.js
Normal file
2
src/popup/app/lock/lockModule.js
Normal file
@ -0,0 +1,2 @@
|
||||
angular
|
||||
.module('bit.lock', ['ngAnimate', 'toastr']);
|
25
src/popup/app/lock/views/lock.html
Normal file
25
src/popup/app/lock/views/lock.html
Normal file
@ -0,0 +1,25 @@
|
||||
<form name="theForm" ng-submit="submit()" bit-form="submitPromise">
|
||||
<div class="header">
|
||||
<div class="right">
|
||||
<button type="submit" class="btn btn-link">{{i18n.submit}}</button>
|
||||
</div>
|
||||
<div class="title">{{i18n.verifyMasterPassword}}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item list-section-item-icon-input">
|
||||
<i class="fa fa-lock fa-lg fa-fw"></i>
|
||||
<label for="master-password" class="sr-only">{{i18n.masterPass}}</label>
|
||||
<input id="master-password" type="password" name="MasterPassword" placeholder="{{i18n.masterPass}}"
|
||||
ng-model="masterPassword">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-center text-accent">
|
||||
<a ng-click="logOut()" href="">{{i18n.logOut}}</a>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
@ -39,4 +39,7 @@
|
||||
})
|
||||
.factory('i18nService', function () {
|
||||
return chrome.extension.getBackgroundPage().i18nService;
|
||||
})
|
||||
.factory('constantsService', function () {
|
||||
return chrome.extension.getBackgroundPage().constantsService;
|
||||
});
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
tokenService.setToken(response.token, function () {
|
||||
cryptoService.setKey(key, function () {
|
||||
cryptoService.setKeyHash(hashedPassword, function () {
|
||||
if (response.profile) {
|
||||
userService.setUserId(response.profile.id, function () {
|
||||
userService.setEmail(response.profile.email, function () {
|
||||
@ -32,6 +33,7 @@
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}, function (error) {
|
||||
deferred.reject(error);
|
||||
});
|
||||
@ -68,6 +70,7 @@
|
||||
userService.getUserId(function (userId) {
|
||||
tokenService.clearToken(function () {
|
||||
cryptoService.clearKey(function () {
|
||||
cryptoService.clearKeyHash(function () {
|
||||
userService.clearUserId(function () {
|
||||
userService.clearEmail(function () {
|
||||
siteService.clear(userId, function () {
|
||||
@ -83,6 +86,7 @@
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return _service;
|
||||
|
@ -2,19 +2,42 @@
|
||||
.module('bit.settings')
|
||||
|
||||
.controller('settingsController', function ($scope, loginService, $state, SweetAlert, utilsService, $analytics,
|
||||
i18nService) {
|
||||
var gaKey = 'disableGa';
|
||||
|
||||
i18nService, constantsService, cryptoService) {
|
||||
utilsService.initListSectionItemListeners($(document), angular);
|
||||
$scope.disableGa = false;
|
||||
$scope.lockOption = '';
|
||||
$scope.i18n = i18nService;
|
||||
|
||||
chrome.storage.local.get(gaKey, function (obj) {
|
||||
if (obj && obj[gaKey]) {
|
||||
chrome.storage.local.get(constantsService.disableGaKey, function (obj) {
|
||||
if (obj && obj[constantsService.disableGaKey]) {
|
||||
$scope.disableGa = true;
|
||||
}
|
||||
else {
|
||||
$scope.disableGa = false;
|
||||
}
|
||||
});
|
||||
|
||||
chrome.storage.local.get(constantsService.lockOptionKey, function (obj) {
|
||||
if (obj && (obj[constantsService.lockOptionKey] || obj[constantsService.lockOptionKey] === 0)) {
|
||||
$scope.lockOption = obj[constantsService.lockOptionKey].toString();
|
||||
}
|
||||
else {
|
||||
$scope.lockOption = '';
|
||||
}
|
||||
});
|
||||
|
||||
$scope.changeLockOption = function () {
|
||||
var obj = {};
|
||||
obj[constantsService.lockOptionKey] = null;
|
||||
if ($scope.lockOption && $scope.lockOption !== '') {
|
||||
obj[constantsService.lockOptionKey] = parseInt($scope.lockOption);
|
||||
}
|
||||
|
||||
chrome.storage.local.set(obj, function () {
|
||||
cryptoService.toggleKey(function () { });
|
||||
});
|
||||
};
|
||||
|
||||
$scope.logOut = function () {
|
||||
SweetAlert.swal({
|
||||
title: 'Log Out',
|
||||
@ -82,20 +105,20 @@
|
||||
}
|
||||
|
||||
$scope.updateGa = function () {
|
||||
chrome.storage.local.get(gaKey, function (obj) {
|
||||
if (obj[gaKey]) {
|
||||
chrome.storage.local.get(constantsService.disableGaKey, function (obj) {
|
||||
if (obj[constantsService.disableGaKey]) {
|
||||
// enable
|
||||
obj[gaKey] = false;
|
||||
obj[constantsService.disableGaKey] = false;
|
||||
}
|
||||
else {
|
||||
// disable
|
||||
$analytics.eventTrack('Disabled Google Analytics');
|
||||
obj[gaKey] = true;
|
||||
obj[constantsService.disableGaKey] = true;
|
||||
}
|
||||
|
||||
chrome.storage.local.set(obj, function () {
|
||||
$scope.disableGa = obj[gaKey];
|
||||
if (!obj[gaKey]) {
|
||||
$scope.disableGa = obj[constantsService.disableGaKey];
|
||||
if (!obj[constantsService.disableGaKey]) {
|
||||
$analytics.eventTrack('Enabled Google Analytics');
|
||||
}
|
||||
});
|
||||
|
@ -3,6 +3,31 @@
|
||||
</div>
|
||||
<div class="content content-tabs">
|
||||
<div class="list">
|
||||
<div class="list-section">
|
||||
<div class="list-section-header">
|
||||
{{i18n.security}}
|
||||
</div>
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item">
|
||||
<label for="lock-option" class="item-label">{{i18n.lockOptions}}</label>
|
||||
<select id="lock-option" name="LockOption" ng-model="lockOption" ng-change="changeLockOption()">
|
||||
<option value="0">{{i18n.immediately}}</option>
|
||||
<option value="1">{{i18n.oneMinute}}</option>
|
||||
<option value="5">{{i18n.fiveMinutes}}</option>
|
||||
<option value="15">{{i18n.fifteedMinutes}}</option>
|
||||
<option value="30">{{i18n.thirtyMinutes}}</option>
|
||||
<option value="60">{{i18n.oneHour}}</option>
|
||||
<option value="240">{{i18n.fourHours}}</option>
|
||||
<option value="-1">{{i18n.onRestart}}</option>
|
||||
<option value="">{{i18n.never}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<a class="list-section-item" href="" ng-click="twoStep()">
|
||||
{{i18n.twoStepLogin}}
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-section">
|
||||
<div class="list-section-header">
|
||||
{{i18n.account}}
|
||||
@ -16,10 +41,6 @@
|
||||
{{i18n.changeEmail}}
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
<a class="list-section-item" href="" ng-click="twoStep()">
|
||||
{{i18n.twoStepLogin}}
|
||||
<i class="fa fa-chevron-right fa-lg"></i>
|
||||
</a>
|
||||
<a class="list-section-item" href="" ng-click="logOut()">
|
||||
{{i18n.logOut}}
|
||||
</a>
|
||||
|
@ -41,7 +41,7 @@
|
||||
<div class="list-section">
|
||||
<div class="list-section-items">
|
||||
<div class="list-section-item">
|
||||
<label for="folder" class="item-label">Folder</label>
|
||||
<label for="folder" class="item-label">{{i18n.folder}}</label>
|
||||
<select id="folder" name="FolderId" ng-model="site.folderId">
|
||||
<option ng-repeat="folder in folders | orderBy: ['name']" value="{{folder.id}}">
|
||||
{{folder.name}}
|
||||
|
@ -76,6 +76,9 @@
|
||||
<script src="app/tools/toolsModule.js"></script>
|
||||
<script src="app/tools/toolsController.js"></script>
|
||||
<script src="app/tools/toolsPasswordGeneratorController.js"></script>
|
||||
|
||||
<script src="app/lock/lockModule.js"></script>
|
||||
<script src="app/lock/lockController.js"></script>
|
||||
</head>
|
||||
<body ng-controller="mainController as main" class="{{main.animation}}">
|
||||
<div ui-view class="main-view"></div>
|
||||
|
6
src/services/constantsService.js
Normal file
6
src/services/constantsService.js
Normal file
@ -0,0 +1,6 @@
|
||||
function ConstantsService() {
|
||||
return {
|
||||
disableGaKey: 'disableGa',
|
||||
lockOptionKey: 'lockOption'
|
||||
};
|
||||
};
|
@ -1,10 +1,13 @@
|
||||
function CryptoService() {
|
||||
function CryptoService(constantsService) {
|
||||
this.constantsService = constantsService;
|
||||
initCryptoService();
|
||||
};
|
||||
|
||||
function initCryptoService() {
|
||||
var _key,
|
||||
_b64Key,
|
||||
_keyHash,
|
||||
_b64KeyHash,
|
||||
_aes;
|
||||
|
||||
sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."]();
|
||||
@ -14,12 +17,36 @@ function initCryptoService() {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
var self = this;
|
||||
_key = key;
|
||||
|
||||
chrome.storage.local.get(self.constantsService.lockOptionKey, function (obj) {
|
||||
if (obj && (obj[self.constantsService.lockOptionKey] || obj[self.constantsService.lockOptionKey] === 0)) {
|
||||
// if we have a lock option set, we do not store the key
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
chrome.storage.local.set({
|
||||
'key': sjcl.codec.base64.fromBits(key)
|
||||
}, function () {
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
CryptoService.prototype.setKeyHash = function (keyHash, callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
_keyHash = sjcl.codec.base64.toBits(keyHash);
|
||||
|
||||
chrome.storage.local.set({
|
||||
'keyHash': keyHash
|
||||
}, function () {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
CryptoService.prototype.getKey = function (b64, callback) {
|
||||
@ -28,23 +55,73 @@ function initCryptoService() {
|
||||
}
|
||||
|
||||
if (b64 && b64 === true && _b64Key) {
|
||||
return callback(_b64Key);
|
||||
callback(_b64Key);
|
||||
return;
|
||||
}
|
||||
else if (!b64 && _key) {
|
||||
return callback(_key);
|
||||
callback(_key);
|
||||
return;
|
||||
}
|
||||
else if (b64 && b64 === true && _key && !_b64Key) {
|
||||
_b64Key = sjcl.codec.base64.fromBits(_key);
|
||||
callback(_b64Key);
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
chrome.storage.local.get(self.constantsService.lockOptionKey, function (obj) {
|
||||
if (obj && (obj[self.constantsService.lockOptionKey] || obj[self.constantsService.lockOptionKey] === 0)) {
|
||||
// if we have a lock option set, we do not try to fetch the storage key since it should not even be there
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
chrome.storage.local.get('key', function (obj) {
|
||||
if (obj && obj.key) {
|
||||
_key = sjcl.codec.base64.toBits(obj.key);
|
||||
}
|
||||
|
||||
if (b64 && b64 === true) {
|
||||
_b64Key = sjcl.codec.base64.fromBits(_key);
|
||||
return callback(_b64Key);
|
||||
_b64Key = obj.key;
|
||||
callback(_b64Key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return callback(_key);
|
||||
callback(_key);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
CryptoService.prototype.getKeyHash = function (b64, callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
if (b64 && b64 === true && _b64KeyHash) {
|
||||
callback(_b64KeyHash);
|
||||
}
|
||||
else if (!b64 && _keyHash) {
|
||||
callback(_keyHash);
|
||||
return;
|
||||
}
|
||||
else if (b64 && b64 === true && _keyHash && !_b64KeyHash) {
|
||||
_b64KeyHash = sjcl.codec.base64.fromBits(_keyHash);
|
||||
callback(_b64KeyHash);
|
||||
return;
|
||||
}
|
||||
|
||||
chrome.storage.local.get('keyHash', function (obj) {
|
||||
if (obj && obj.keyHash) {
|
||||
_keyHash = sjcl.codec.base64.toBits(obj.keyHash);
|
||||
|
||||
if (b64 && b64 === true) {
|
||||
_b64KeyHash = obj.keyHash;
|
||||
callback(_b64KeyHash);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
callback(_keyHash);
|
||||
});
|
||||
};
|
||||
|
||||
@ -59,6 +136,44 @@ function initCryptoService() {
|
||||
});
|
||||
};
|
||||
|
||||
CryptoService.prototype.clearKeyHash = function (callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
_keyHash = _b64KeyHash = null;
|
||||
chrome.storage.local.remove('keyHash', function () {
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
CryptoService.prototype.toggleKey = function (callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
var self = this;
|
||||
self.getKey(false, function (key) {
|
||||
chrome.storage.local.get(self.constantsService.lockOptionKey, function (obj) {
|
||||
if (obj && (obj[self.constantsService.lockOptionKey] || obj[self.constantsService.lockOptionKey] === 0)) {
|
||||
// if we have a lock option set, clear the key
|
||||
self.clearKey(function () {
|
||||
_key = key;
|
||||
callback();
|
||||
return;
|
||||
});
|
||||
}
|
||||
else {
|
||||
// no lock option, so store the current key
|
||||
self.setKey(key, function () {
|
||||
callback();
|
||||
return;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
CryptoService.prototype.makeKey = function (password, salt, b64) {
|
||||
var key = sjcl.misc.pbkdf2(password, salt, 5000, 256, null);
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
function i18nService() {
|
||||
return {
|
||||
appName: chrome.i18n.getMessage('appName'),
|
||||
loginOrCreateNewAccount: chrome.i18n.getMessage('loginOrCreateNewAccount'),
|
||||
createAccount: chrome.i18n.getMessage('createAccount'),
|
||||
login: chrome.i18n.getMessage('login'),
|
||||
@ -91,6 +92,20 @@ function i18nService() {
|
||||
disableGa: chrome.i18n.getMessage('disableGa'),
|
||||
rateExtension: chrome.i18n.getMessage('rateExtension'),
|
||||
rateExtensionDesc: chrome.i18n.getMessage('rateExtensionDesc'),
|
||||
browserNotSupportClipboard: chrome.i18n.getMessage('browserNotSupportClipboard')
|
||||
browserNotSupportClipboard: chrome.i18n.getMessage('browserNotSupportClipboard'),
|
||||
verifyMasterPassword: chrome.i18n.getMessage('verifyMasterPassword'),
|
||||
invalidMasterPassword: chrome.i18n.getMessage('invalidMasterPassword'),
|
||||
errorsHaveOccurred: chrome.i18n.getMessage('errorsHaveOccurred'),
|
||||
lockOptions: chrome.i18n.getMessage('lockOptions'),
|
||||
immediately: chrome.i18n.getMessage('immediately'),
|
||||
oneMinute: chrome.i18n.getMessage('oneMinute'),
|
||||
fiveMinutes: chrome.i18n.getMessage('fiveMinutes'),
|
||||
fifteenMinutes: chrome.i18n.getMessage('fifteenMinutes'),
|
||||
thirtyMinutes: chrome.i18n.getMessage('thirtyMinutes'),
|
||||
oneHour: chrome.i18n.getMessage('oneHour'),
|
||||
fourHours: chrome.i18n.getMessage('fourHours'),
|
||||
onRestart: chrome.i18n.getMessage('onRestart'),
|
||||
never: chrome.i18n.getMessage('never'),
|
||||
security: chrome.i18n.getMessage('security')
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user