mirror of
https://github.com/bitwarden/desktop.git
synced 2024-11-18 10:55:48 +01:00
support user encryption key
This commit is contained in:
parent
e282966d64
commit
16098a1743
@ -11,7 +11,8 @@ var userService = new UserService(tokenService, apiService, cryptoService);
|
||||
var settingsService = new SettingsService(userService);
|
||||
var loginService = new LoginService(cryptoService, userService, apiService, settingsService);
|
||||
var folderService = new FolderService(cryptoService, userService, apiService);
|
||||
var syncService = new SyncService(loginService, folderService, userService, apiService, settingsService, cryptoService);
|
||||
var syncService = new SyncService(loginService, folderService, userService, apiService, settingsService,
|
||||
cryptoService, logout);
|
||||
var autofillService = new AutofillService();
|
||||
var passwordGenerationService = new PasswordGenerationService();
|
||||
|
||||
@ -86,7 +87,7 @@ chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
|
||||
setIcon();
|
||||
function setIcon() {
|
||||
userService.isAuthenticated(function (isAuthenticated) {
|
||||
cryptoService.getKey(function (key) {
|
||||
cryptoService.getKey().then(function (key) {
|
||||
var suffix = '';
|
||||
if (!isAuthenticated) {
|
||||
suffix = '_gray';
|
||||
@ -668,7 +669,7 @@ function logout(expired, callback) {
|
||||
settingsService.clear(function () {
|
||||
tokenService.clearToken(function () {
|
||||
cryptoService.clearKeys(function () {
|
||||
userService.clearUserIdAndEmail(function () {
|
||||
userService.clear(function () {
|
||||
loginService.clear(userId, function () {
|
||||
folderService.clear(userId, function () {
|
||||
chrome.runtime.sendMessage({
|
||||
@ -757,7 +758,7 @@ function checkLock() {
|
||||
return;
|
||||
}
|
||||
|
||||
cryptoService.getKey(function (key) {
|
||||
cryptoService.getKey().then(function (key) {
|
||||
if (!key) {
|
||||
// no key so no need to lock
|
||||
return;
|
||||
|
@ -48,11 +48,12 @@ var TokenRequest = function (email, masterPasswordHash, token, device) {
|
||||
};
|
||||
};
|
||||
|
||||
var RegisterRequest = function (email, masterPasswordHash, masterPasswordHint) {
|
||||
var RegisterRequest = function (email, masterPasswordHash, masterPasswordHint, key) {
|
||||
this.name = null;
|
||||
this.email = email;
|
||||
this.masterPasswordHash = masterPasswordHash;
|
||||
this.masterPasswordHint = masterPasswordHint ? masterPasswordHint : null;
|
||||
this.key = key;
|
||||
};
|
||||
|
||||
var PasswordHintRequest = function (email) {
|
||||
|
@ -38,6 +38,9 @@ var ProfileResponse = function (response) {
|
||||
this.masterPasswordHint = response.MasterPasswordHint;
|
||||
this.culture = response.Culture;
|
||||
this.twoFactorEnabled = response.TwoFactorEnabled;
|
||||
this.key = response.Key;
|
||||
this.privateKey = response.PrivateKey;
|
||||
this.securityStamp = response.SecurityStamp;
|
||||
|
||||
this.organizations = [];
|
||||
if (response.Organizations) {
|
||||
@ -68,6 +71,7 @@ var IdentityTokenResponse = function (response) {
|
||||
this.tokenType = response.token_type;
|
||||
|
||||
this.privateKey = response.PrivateKey;
|
||||
this.key = response.Key;
|
||||
};
|
||||
|
||||
var ListResponse = function (data) {
|
||||
|
@ -9,13 +9,8 @@ var CipherString = function () {
|
||||
var constants = chrome.extension.getBackgroundPage().constantsService;
|
||||
|
||||
if (arguments.length >= 2) {
|
||||
// ct and optional header
|
||||
if (arguments[0] === constants.encType.AesCbc256_B64) {
|
||||
this.encryptedString = arguments[1];
|
||||
}
|
||||
else {
|
||||
this.encryptedString = arguments[0] + '.' + arguments[1];
|
||||
}
|
||||
// ct and header
|
||||
this.encryptedString = arguments[0] + '.' + arguments[1];
|
||||
|
||||
// iv
|
||||
if (arguments.length > 2 && arguments[2]) {
|
||||
|
@ -2,8 +2,8 @@
|
||||
.module('bit.accounts')
|
||||
|
||||
.controller(
|
||||
'accountsRegisterController',
|
||||
function ($scope, $state, cryptoService, toastr, $q, apiService, utilsService, $analytics, i18nService) {
|
||||
'accountsRegisterController',
|
||||
function ($scope, $state, cryptoService, toastr, $q, apiService, utilsService, $analytics, i18nService) {
|
||||
$scope.i18n = i18nService;
|
||||
|
||||
$scope.model = {};
|
||||
@ -45,15 +45,17 @@
|
||||
|
||||
function registerPromise(key, masterPassword, email, hint) {
|
||||
return $q(function (resolve, reject) {
|
||||
cryptoService.hashPassword(masterPassword, key, function (hashedPassword) {
|
||||
var request = new RegisterRequest(email, hashedPassword, hint);
|
||||
apiService.postRegister(request,
|
||||
function () {
|
||||
resolve();
|
||||
},
|
||||
function (error) {
|
||||
reject(error);
|
||||
});
|
||||
cryptoService.makeEncKey(key).then(function (encKey) {
|
||||
cryptoService.hashPassword(masterPassword, key, function (hashedPassword) {
|
||||
var request = new RegisterRequest(email, hashedPassword, hint, encKey.encryptedString);
|
||||
apiService.postRegister(request,
|
||||
function () {
|
||||
resolve();
|
||||
},
|
||||
function (error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
var userService = $injector.get('userService');
|
||||
var cryptoService = $injector.get('cryptoService');
|
||||
|
||||
cryptoService.getKey(function (key) {
|
||||
cryptoService.getKey().then(function (key) {
|
||||
userService.isAuthenticated(function (isAuthenticated) {
|
||||
if (isAuthenticated) {
|
||||
if (!key) {
|
||||
|
@ -24,12 +24,9 @@
|
||||
cryptoService.setKey(key, function () {
|
||||
cryptoService.setKeyHash(hashedPassword, function () {
|
||||
userService.setUserIdAndEmail(tokenService.getUserId(), tokenService.getEmail(), function () {
|
||||
if (!response.privateKey) {
|
||||
loggedIn(deferred);
|
||||
return;
|
||||
}
|
||||
|
||||
cryptoService.setEncPrivateKey(response.privateKey).then(function () {
|
||||
cryptoService.setEncKey(response.key).then(function () {
|
||||
return cryptoService.setEncPrivateKey(response.privateKey);
|
||||
}).then(function () {
|
||||
loggedIn(deferred);
|
||||
});
|
||||
});
|
||||
|
@ -4,16 +4,16 @@ function ApiService(tokenService, appIdService, utilsService, logoutCallback) {
|
||||
//this.identityBaseUrl = 'http://localhost:33656';
|
||||
|
||||
// Desktop external
|
||||
//this.baseUrl = 'http://192.168.1.8:4000';
|
||||
//this.identityBaseUrl = 'http://192.168.1.8:33656';
|
||||
this.baseUrl = 'http://192.168.1.6:4000';
|
||||
this.identityBaseUrl = 'http://192.168.1.6:33656';
|
||||
|
||||
// Preview
|
||||
//this.baseUrl = 'https://preview-api.bitwarden.com';
|
||||
//this.identityBaseUrl = 'https://preview-identity.bitwarden.com';
|
||||
|
||||
// Production
|
||||
this.baseUrl = 'https://api.bitwarden.com';
|
||||
this.identityBaseUrl = 'https://identity.bitwarden.com';
|
||||
//this.baseUrl = 'https://api.bitwarden.com';
|
||||
//this.identityBaseUrl = 'https://identity.bitwarden.com';
|
||||
|
||||
this.tokenService = tokenService;
|
||||
this.logoutCallback = logoutCallback;
|
||||
|
@ -5,6 +5,7 @@ function CryptoService(constantsService) {
|
||||
|
||||
function initCryptoService(constantsService) {
|
||||
var _key,
|
||||
_encKey,
|
||||
_legacyEtmKey,
|
||||
_keyHash,
|
||||
_privateKey,
|
||||
@ -47,12 +48,26 @@ function initCryptoService(constantsService) {
|
||||
});
|
||||
}
|
||||
|
||||
CryptoService.prototype.setEncKey = function (encKey) {
|
||||
var deferred = Q.defer();
|
||||
|
||||
chrome.storage.local.set({
|
||||
'encKey': encKey
|
||||
}, function () {
|
||||
_encKey = null;
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
CryptoService.prototype.setEncPrivateKey = function (encPrivateKey) {
|
||||
var deferred = Q.defer();
|
||||
|
||||
chrome.storage.local.set({
|
||||
'encPrivateKey': encPrivateKey
|
||||
}, function () {
|
||||
_privateKey = null;
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
@ -76,21 +91,19 @@ function initCryptoService(constantsService) {
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
CryptoService.prototype.getKey = function (callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
CryptoService.prototype.getKey = function () {
|
||||
var deferred = Q.defer();
|
||||
|
||||
if (_key) {
|
||||
callback(_key);
|
||||
return;
|
||||
deferred.resolve(_key);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
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);
|
||||
deferred.resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -99,9 +112,11 @@ function initCryptoService(constantsService) {
|
||||
_key = new SymmetricCryptoKey(obj.key, true);
|
||||
}
|
||||
|
||||
callback(_key);
|
||||
deferred.resolve(_key);
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
CryptoService.prototype.getKeyHash = function (callback) {
|
||||
@ -123,6 +138,33 @@ function initCryptoService(constantsService) {
|
||||
});
|
||||
};
|
||||
|
||||
CryptoService.prototype.getEncKey = function () {
|
||||
var deferred = Q.defer();
|
||||
if (_encKey) {
|
||||
deferred.resolve(_encKey);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
chrome.storage.local.get('encKey', function (obj) {
|
||||
if (!obj || !obj.encKey) {
|
||||
deferred.resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
self.getKey().then(function (key) {
|
||||
return self.decrypt(new CipherString(obj.encKey), key, 'raw');
|
||||
}).then(function (encKey) {
|
||||
_encKey = new SymmetricCryptoKey(encKey);
|
||||
deferred.resolve(_encKey);
|
||||
}, function () {
|
||||
deferred.reject('Cannot get enc key. Decryption failed.');
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
CryptoService.prototype.getPrivateKey = function () {
|
||||
var deferred = Q.defer();
|
||||
if (_privateKey) {
|
||||
@ -232,8 +274,26 @@ function initCryptoService(constantsService) {
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
CryptoService.prototype.clearEncKey = function () {
|
||||
var deferred = Q.defer();
|
||||
|
||||
_encKey = null;
|
||||
chrome.storage.local.remove('encKey', function () {
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
CryptoService.prototype.clearPrivateKey = function () {
|
||||
var deferred = Q.defer();
|
||||
|
||||
_privateKey = null;
|
||||
chrome.storage.local.remove('encPrivateKey', function () {
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
CryptoService.prototype.clearOrgKeys = function (memoryOnly) {
|
||||
@ -258,8 +318,13 @@ function initCryptoService(constantsService) {
|
||||
}
|
||||
|
||||
var self = this;
|
||||
Q.all([self.clearKey(), self.clearKeyHash(), self.clearOrgKeys()]).then(function () {
|
||||
self.clearPrivateKey();
|
||||
Q.all([
|
||||
self.clearKey(),
|
||||
self.clearKeyHash(),
|
||||
self.clearOrgKeys(),
|
||||
self.clearEncKey(),
|
||||
self.clearPrivateKey()
|
||||
]).then(function () {
|
||||
callback();
|
||||
});
|
||||
};
|
||||
@ -270,7 +335,7 @@ function initCryptoService(constantsService) {
|
||||
}
|
||||
|
||||
var self = this;
|
||||
self.getKey(function (key) {
|
||||
self.getKey().then(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
|
||||
@ -299,7 +364,7 @@ function initCryptoService(constantsService) {
|
||||
};
|
||||
|
||||
CryptoService.prototype.hashPassword = function (password, key, callback) {
|
||||
this.getKey(function (storedKey) {
|
||||
this.getKey().then(function (storedKey) {
|
||||
key = key || storedKey;
|
||||
|
||||
if (!password || !key) {
|
||||
@ -311,6 +376,11 @@ function initCryptoService(constantsService) {
|
||||
});
|
||||
};
|
||||
|
||||
CryptoService.prototype.makeEncKey = function (key) {
|
||||
var bytes = forge.random.getBytesSync(512 / 8);
|
||||
return this.encrypt(bytes, key, 'raw');
|
||||
};
|
||||
|
||||
CryptoService.prototype.encrypt = function (plainValue, key, plainValueEncoding) {
|
||||
var self = this;
|
||||
var deferred = Q.defer();
|
||||
@ -319,8 +389,8 @@ function initCryptoService(constantsService) {
|
||||
deferred.resolve(null);
|
||||
}
|
||||
else {
|
||||
self.getKey(function (localKey) {
|
||||
key = key || localKey;
|
||||
getKeyForEncryption(self, key).then(function (keyToUse) {
|
||||
key = keyToUse;
|
||||
if (!key) {
|
||||
deferred.reject('Encryption key unavailable.');
|
||||
return;
|
||||
@ -356,8 +426,8 @@ function initCryptoService(constantsService) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.getKey(function (localKey) {
|
||||
key = key || localKey;
|
||||
getKeyForEncryption(self, key).then(function (keyToUse) {
|
||||
key = keyToUse;
|
||||
if (!key) {
|
||||
deferred.reject('Encryption key unavailable.');
|
||||
return;
|
||||
@ -479,6 +549,23 @@ function initCryptoService(constantsService) {
|
||||
return b64Output ? forge.util.encode64(mac.getBytes()) : mac.getBytes();
|
||||
}
|
||||
|
||||
function getKeyForEncryption(self, key) {
|
||||
var deferred = Q.defer();
|
||||
|
||||
if (key) {
|
||||
deferred.resolve(key);
|
||||
}
|
||||
else {
|
||||
self.getEncKey().then(function (encKey) {
|
||||
return encKey || self.getKey();
|
||||
}).then(function (keyToUse) {
|
||||
deferred.resolve(keyToUse);
|
||||
});
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
// Safely compare two MACs in a way that protects against timing attacks (Double HMAC Verification).
|
||||
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
|
||||
function macsEqual(macKey, mac1, mac2) {
|
||||
|
@ -68,7 +68,7 @@ function initFolderService() {
|
||||
var deferred = Q.defer();
|
||||
var self = this;
|
||||
|
||||
cryptoService.getKey(function (key) {
|
||||
cryptoService.getKey().then(function (key) {
|
||||
if (!key) {
|
||||
deferred.reject();
|
||||
return;
|
||||
|
@ -91,7 +91,7 @@ function initLoginService() {
|
||||
var deferred = Q.defer();
|
||||
var self = this;
|
||||
|
||||
cryptoService.getKey(function (key) {
|
||||
cryptoService.getKey().then(function (key) {
|
||||
if (!key) {
|
||||
deferred.reject();
|
||||
return;
|
||||
|
@ -1,11 +1,13 @@
|
||||
function SyncService(loginService, folderService, userService, apiService, settingsService, cryptoService) {
|
||||
function SyncService(loginService, folderService, userService, apiService, settingsService,
|
||||
cryptoService, logoutCallback) {
|
||||
this.loginService = loginService;
|
||||
this.folderService = folderService;
|
||||
this.userService = userService;
|
||||
this.apiService = apiService;
|
||||
this.settingsService = settingsService;
|
||||
this.cryptoService = settingsService;
|
||||
this.cryptoService = cryptoService;
|
||||
this.syncInProgress = false;
|
||||
this.logoutCallback = logoutCallback;
|
||||
|
||||
initSyncService();
|
||||
};
|
||||
@ -37,12 +39,12 @@ function initSyncService() {
|
||||
return;
|
||||
}
|
||||
|
||||
syncProfile().then(function () {
|
||||
return syncFolders(userId);
|
||||
syncProfile(self).then(function () {
|
||||
return syncFolders(self, userId);
|
||||
}).then(function () {
|
||||
return syncCiphers(userId);
|
||||
return syncCiphers(self, userId);
|
||||
}).then(function () {
|
||||
return syncSettings(userId);
|
||||
return syncSettings(self, userId);
|
||||
}).then(function () {
|
||||
self.setLastSync(now, function () {
|
||||
self.syncCompleted(true);
|
||||
@ -80,45 +82,37 @@ function initSyncService() {
|
||||
});
|
||||
}
|
||||
|
||||
function syncProfile() {
|
||||
function syncProfile(self) {
|
||||
var deferred = Q.defer();
|
||||
var self = this;
|
||||
|
||||
function setKeys(hasPrivateKey, response, d) {
|
||||
if (response.organizations && response.organizations.length && !hasPrivateKey) {
|
||||
self.apiService.getKeys(function (keysResponse) {
|
||||
if (keysResponse.privateKey) {
|
||||
self.cryptoService.setEncPrivateKey(keysResponse.privateKey).then(function () {
|
||||
return self.cryptoService.setOrgKeys(response.organizations);
|
||||
}, function () {
|
||||
d.reject();
|
||||
}).then(function () {
|
||||
d.resolve();
|
||||
}, function () {
|
||||
d.reject();
|
||||
});
|
||||
}
|
||||
else {
|
||||
d.resolve();
|
||||
}
|
||||
}, function () {
|
||||
d.reject();
|
||||
});
|
||||
}
|
||||
else {
|
||||
self.cryptoService.setOrgKeys(response.organizations).then(function () {
|
||||
d.resolve();
|
||||
}, function () {
|
||||
d.reject();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.apiService.getProfile(function (response) {
|
||||
self.cryptoService.getPrivateKey().then(function (privateKey) {
|
||||
setKeys(!!privateKey, response, deferred);
|
||||
self.userService.getSecurityStamp().then(function (stamp) {
|
||||
if (stamp && stamp != response.securityStamp) {
|
||||
if (self.logoutCallback) {
|
||||
self.logoutCallback(true, function () { });
|
||||
}
|
||||
|
||||
deferred.reject();
|
||||
return;
|
||||
}
|
||||
|
||||
return self.cryptoService.setEncKey(response.key);
|
||||
}).then(function () {
|
||||
return self.cryptoService.setEncPrivateKey(response.privateKey);
|
||||
}, function () {
|
||||
setKeys(true, response, deferred);
|
||||
deferred.reject();
|
||||
}).then(function () {
|
||||
return self.cryptoService.setOrgKeys(response.organizations);
|
||||
}, function () {
|
||||
deferred.reject();
|
||||
}).then(function () {
|
||||
return self.userService.setSecurityStamp(response.securityStamp);
|
||||
}, function () {
|
||||
deferred.reject();
|
||||
}).then(function () {
|
||||
deferred.resolve();
|
||||
}, function () {
|
||||
deferred.reject();
|
||||
});
|
||||
}, function () {
|
||||
deferred.reject();
|
||||
@ -127,9 +121,8 @@ function initSyncService() {
|
||||
return deferred.promise
|
||||
}
|
||||
|
||||
function syncFolders(userId) {
|
||||
function syncFolders(self, userId) {
|
||||
var deferred = Q.defer();
|
||||
var self = this;
|
||||
|
||||
self.apiService.getFolders(function (response) {
|
||||
var folders = {};
|
||||
@ -148,9 +141,8 @@ function initSyncService() {
|
||||
return deferred.promise
|
||||
}
|
||||
|
||||
function syncCiphers(userId) {
|
||||
function syncCiphers(self, userId) {
|
||||
var deferred = Q.defer();
|
||||
var self = this;
|
||||
|
||||
self.apiService.getCiphers(function (response) {
|
||||
var logins = {};
|
||||
@ -172,9 +164,8 @@ function initSyncService() {
|
||||
return deferred.promise
|
||||
}
|
||||
|
||||
function syncSettings(userId) {
|
||||
function syncSettings(self, userId) {
|
||||
var deferred = Q.defer();
|
||||
var self = this;
|
||||
|
||||
var ciphers = self.apiService.getIncludedDomains(function (response) {
|
||||
var eqDomains = [];
|
||||
|
@ -8,10 +8,12 @@
|
||||
|
||||
function initUserService() {
|
||||
var userIdKey = 'userId',
|
||||
userEmailKey = 'userEmail';
|
||||
userEmailKey = 'userEmail',
|
||||
stampKey = 'securityStamp';
|
||||
|
||||
var _userId = null,
|
||||
_email = null;
|
||||
_email = null,
|
||||
_stamp = null;
|
||||
|
||||
UserService.prototype.setUserIdAndEmail = function (userId, email, callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
@ -33,6 +35,20 @@ function initUserService() {
|
||||
});
|
||||
};
|
||||
|
||||
UserService.prototype.setSecurityStamp = function (stamp) {
|
||||
var deferred = Q.defer();
|
||||
|
||||
_stamp = stamp;
|
||||
var stampObj = {};
|
||||
stampObj[stampKey] = stamp;
|
||||
|
||||
chrome.storage.local.set(stampObj, function () {
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise
|
||||
};
|
||||
|
||||
UserService.prototype.getUserId = function (callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
@ -69,15 +85,35 @@ function initUserService() {
|
||||
});
|
||||
};
|
||||
|
||||
UserService.prototype.clearUserIdAndEmail = function (callback) {
|
||||
UserService.prototype.getSecurityStamp = function () {
|
||||
var deferred = Q.defer();
|
||||
|
||||
if (_stamp) {
|
||||
deferred.resolve(_stamp);
|
||||
}
|
||||
|
||||
chrome.storage.local.get(stampKey, function (obj) {
|
||||
if (obj && obj[stampKey]) {
|
||||
_stamp = obj[stampKey];
|
||||
}
|
||||
|
||||
deferred.resolve(_stamp);
|
||||
});
|
||||
|
||||
return deferred.promise
|
||||
};
|
||||
|
||||
UserService.prototype.clear = function (callback) {
|
||||
if (!callback || typeof callback !== 'function') {
|
||||
throw 'callback function required';
|
||||
}
|
||||
|
||||
_userId = _email = null;
|
||||
_userId = _email = _stamp = null;
|
||||
chrome.storage.local.remove(userIdKey, function () {
|
||||
chrome.storage.local.remove(userEmailKey, function () {
|
||||
callback();
|
||||
chrome.storage.local.remove(stampKey, function () {
|
||||
callback();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user