diff --git a/src/browser_action/app/accounts/accountsLoginController.js b/src/browser_action/app/accounts/accountsLoginController.js new file mode 100644 index 00000000..9c6bf4eb --- /dev/null +++ b/src/browser_action/app/accounts/accountsLoginController.js @@ -0,0 +1,12 @@ +angular + .module('bit.accounts') + + .controller('accountsLoginController', function ($scope, $state) { + $scope.login = function (model) { + $state.go('tabs.current'); + }; + + $scope.twoFactor = function (model) { + $state.go('tabs.current'); + }; + }); diff --git a/src/browser_action/app/accounts/accountsModule.js b/src/browser_action/app/accounts/accountsModule.js new file mode 100644 index 00000000..633e094a --- /dev/null +++ b/src/browser_action/app/accounts/accountsModule.js @@ -0,0 +1,2 @@ +angular + .module('bit.accounts', []); diff --git a/src/browser_action/app/accounts/views/accountsLogin.html b/src/browser_action/app/accounts/views/accountsLogin.html new file mode 100644 index 00000000..d5639886 --- /dev/null +++ b/src/browser_action/app/accounts/views/accountsLogin.html @@ -0,0 +1,22 @@ + + +
+ + +
+
+ +

+ Get master password hint +

+
+
+
diff --git a/src/browser_action/app/accounts/views/accountsLoginTwoFactor.html b/src/browser_action/app/accounts/views/accountsLoginTwoFactor.html new file mode 100644 index 00000000..04e55bf0 --- /dev/null +++ b/src/browser_action/app/accounts/views/accountsLoginTwoFactor.html @@ -0,0 +1,7 @@ + + +

+ Some content for your login. +

+
+
diff --git a/src/browser_action/app/app.js b/src/browser_action/app/app.js index 7e3fbbab..e6b4c6b9 100644 --- a/src/browser_action/app/app.js +++ b/src/browser_action/app/app.js @@ -2,6 +2,7 @@ .module('bit', [ 'ionic', + 'bit.accounts', 'bit.current', 'bit.vault', 'bit.settings', diff --git a/src/browser_action/app/config.js b/src/browser_action/app/config.js index 05eecef3..3fd194ba 100644 --- a/src/browser_action/app/config.js +++ b/src/browser_action/app/config.js @@ -4,6 +4,16 @@ .config(function ($stateProvider, $urlRouterProvider) { $stateProvider + .state('login', { + url: "/login", + controller: 'accountsLoginController', + templateUrl: "app/accounts/views/accountsLogin.html" + }) + .state('login.twoFactor', { + url: "/two-factor", + controller: 'accountsLoginController', + templateUrl: "app/accounts/views/accountsLoginTwoFactor.html" + }) .state('tabs', { url: "/tab", abstract: true, @@ -47,5 +57,5 @@ }); - $urlRouterProvider.otherwise("/tab/current"); + $urlRouterProvider.otherwise("/login"); }); diff --git a/src/browser_action/index.html b/src/browser_action/index.html index e3979050..032fd33b 100644 --- a/src/browser_action/index.html +++ b/src/browser_action/index.html @@ -8,9 +8,20 @@ + + + + + + + + + + + diff --git a/src/manifest.json b/src/manifest.json index c28db0f9..a5f5c3b5 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -21,5 +21,9 @@ }, "default_title": "bitwarden", "default_popup": "browser_action/index.html" - } + }, + "permissions": [ + "storage", + "unlimitedStorage" + ] } \ No newline at end of file diff --git a/src/package.json b/src/package.json index afcd1a18..a700d5b9 100644 --- a/src/package.json +++ b/src/package.json @@ -2,6 +2,7 @@ "name": "bitwarden", "version": "0.0.1", "devDependencies": { - "ionic-framework-v1": "1.3.1" + "ionic-framework-v1": "1.3.1", + "sjcl": "1.0.3" } } diff --git a/src/services/authService.js b/src/services/authService.js new file mode 100644 index 00000000..2dbcb53d --- /dev/null +++ b/src/services/authService.js @@ -0,0 +1,78 @@ +var g_authService = function () { + var _service = {}, _userProfile = null; + + _service.logIn = function (email, masterPassword) { + return; + }; + + _service.logInTwoFactor = function (code, provider) { + return; + }; + + _service.logOut = function (callback) { + g_tokenService.clearToken(function () { + g_cryptoService.clearKey(function () { + _userProfile = null; + callback(); + }); + }); + }; + + _service.getUserProfile = function (callback) { + if (!_userProfile) { + _service.setUserProfile(null, function () { + callback(_userProfile); + }); + } + + return callback(_userProfile); + }; + + _service.setUserProfile = function (profile, callback) { + g_tokenService.getToken(function (token) { + if (!token) { + return; + } + + var decodedToken = jwtHelper.decodeToken(token); + var twoFactor = decodedToken.authmethod === "TwoFactor"; + + _userProfile = { + id: decodedToken.nameid, + email: decodedToken.email, + twoFactor: twoFactor + }; + + if (!twoFactor && profile) { + loadProfile(profile); + } + else if (!twoFactor && !profile) { + apiService.accounts.getProfile({}, loadProfile); + } + + callback(); + }); + }; + + function loadProfile(profile) { + _userProfile.extended = { + name: profile.Name, + twoFactorEnabled: profile.TwoFactorEnabled, + culture: profile.Culture + }; + } + + _service.isAuthenticated = function (callback) { + callback(_service.getUserProfile(function (profile) { + return profile !== null && !profile.twoFactor; + })); + }; + + _service.isTwoFactorAuthenticated = function (callback) { + callback(_service.getUserProfile(function (profile) { + return profile !== null && profile.twoFactor; + })); + }; + + return _service; +}; diff --git a/src/services/cryptoService.js b/src/services/cryptoService.js new file mode 100644 index 00000000..12de9f4e --- /dev/null +++ b/src/services/cryptoService.js @@ -0,0 +1,128 @@ +var g_cryptoService = function () { + var _service = {}, _key, _b64Key, _aes; + + sjcl.beware['CBC mode is dangerous because it doesn\'t protect message integrity.'](); + + _service.setKey = function (key, callback) { + if (!callback || typeof callback !== 'function') { + throw 'callback function required'; + } + + _key = key; + chrome.storage.local.set({ + 'key': sjcl.codec.base64.fromBits(key) + }, function () { + callback(); + }); + }; + + _service.getKey = function (b64, callback) { + if (!callback || typeof callback !== 'function') { + throw 'callback function required'; + } + + if (b64 && b64 === true && _b64Key) { + return callback(_b64Key); + } + else if (!b64 && _key) { + return callback(_key); + } + + chrome.storage.local.get('key', function (key) { + if (key) { + _key = sjcl.codec.base64.toBits(key); + } + + if (b64 && b64 === true) { + _b64Key = sjcl.codec.base64.fromBits(_key); + return callback(_b64Key); + } + + return callback(_key); + }); + }; + + _service.clearKey = function (callback) { + if (!callback || typeof callback !== 'function') { + throw 'callback function required'; + } + + _key = _b64Key = _aes = null; + chrome.storage.local.remove('key', function () { + callback(); + }); + }; + + _service.makeKey = function (password, salt, b64) { + var key = sjcl.misc.pbkdf2(password, salt, 5000, 256, null); + + if (b64 && b64 === true) { + return sjcl.codec.base64.fromBits(key); + } + + return key; + }; + + _service.hashPassword = function (password, key) { + if (!key) { + key = _service.getKey(); + } + + if (!password || !key) { + throw 'Invalid parameters.'; + } + + var hashBits = sjcl.misc.pbkdf2(key, password, 1, 256, null); + return sjcl.codec.base64.fromBits(hashBits); + }; + + _service.getAes = function () { + if (!_aes && _service.getKey()) { + _aes = new sjcl.cipher.aes(_service.getKey()); + } + + return _aes; + }; + + _service.encrypt = function (plaintextValue, key) { + if (!_service.getKey() && !key) { + throw 'Encryption key unavailable.'; + } + + if (!key) { + key = _service.getKey(); + } + + var response = {}; + var params = { + mode: "cbc", + iv: sjcl.random.randomWords(4, 0) + }; + + var ctJson = sjcl.encrypt(key, plaintextValue, params, response); + + var ct = ctJson.match(/"ct":"([^"]*)"/)[1]; + var iv = sjcl.codec.base64.fromBits(response.iv); + + return iv + "|" + ct; + }; + + _service.decrypt = function (encValue) { + if (!_service.getAes()) { + throw 'AES encryption unavailable.'; + } + + var encPieces = encValue.split('|'); + if (encPieces.length !== 2) { + return ''; + } + + var ivBits = sjcl.codec.base64.toBits(encPieces[0]); + var ctBits = sjcl.codec.base64.toBits(encPieces[1]); + + var decBits = sjcl.mode.cbc.decrypt(_service.getAes(), ctBits, ivBits, null); + return sjcl.codec.utf8String.fromBits(decBits); + }; + + return _service; +}; diff --git a/src/services/tokenService.js b/src/services/tokenService.js new file mode 100644 index 00000000..77446d46 --- /dev/null +++ b/src/services/tokenService.js @@ -0,0 +1,35 @@ +var g_tokenService = function () { + var _service = {}, _token; + + _service.setToken = function (token, callback) { + _token = token; + chrome.storage.local.set({ + 'authBearer': token + }, function () { + callback(); + }); + }; + + _service.getToken = function (callback) { + if (_token) { + return callback(_token); + } + + chrome.storage.local.get('authBearer', function (authBearer) { + if (authBearer) { + _token = authBearer; + } + + return callback(_token); + }); + }; + + _service.clearToken = function (callback) { + _token = null; + chrome.storage.local.remove('authBearer', function () { + callback(); + }); + }; + + return _service; +};