Log in to access your vault.
+ \ No newline at end of file diff --git a/src/Vault/wwwroot/app/accounts/views/accountsLoginTwoFactor.html b/src/Vault/wwwroot/app/accounts/views/accountsLoginTwoFactor.html new file mode 100644 index 00000000000..db7fed6e7df --- /dev/null +++ b/src/Vault/wwwroot/app/accounts/views/accountsLoginTwoFactor.html @@ -0,0 +1,22 @@ +Enter your two-step verification code.
+ \ No newline at end of file diff --git a/src/Vault/wwwroot/app/accounts/views/accountsPasswordHint.html b/src/Vault/wwwroot/app/accounts/views/accountsPasswordHint.html new file mode 100644 index 00000000000..d4f7f05d0cd --- /dev/null +++ b/src/Vault/wwwroot/app/accounts/views/accountsPasswordHint.html @@ -0,0 +1,37 @@ +Get your master password hint.
+Register for a new account.
+Confirm your master password.
+You may now log in to your new account.
+Finalize the creation of your new account.
+ diff --git a/src/Vault/wwwroot/app/apiInterceptor.js b/src/Vault/wwwroot/app/apiInterceptor.js new file mode 100644 index 00000000000..5040b83ea65 --- /dev/null +++ b/src/Vault/wwwroot/app/apiInterceptor.js @@ -0,0 +1,29 @@ +angular + .module('bit') + + .factory('apiInterceptor', function ($injector, $q, toastr) { + return { + request: function (config) { + return config; + }, + response: function (response) { + if (response.status === 401 || response.status == 403) { + $injector.get('authService').logOut(); + $injector.get('$state').go('frontend.login.info').then(function () { + toastr.warning('Your login session has expired.', 'Logged out'); + }); + } + + return response || $q.when(response); + }, + responseError: function (rejection) { + if (rejection.status === 401 || rejection.status == 403) { + $injector.get('authService').logOut(); + $injector.get('$state').go('frontend.login.info').then(function () { + toastr.warning('Your login session has expired.', 'Logged out'); + }); + } + return $q.reject(rejection); + } + }; + }); \ No newline at end of file diff --git a/src/Vault/wwwroot/app/app.js b/src/Vault/wwwroot/app/app.js new file mode 100644 index 00000000000..b27f39d254a --- /dev/null +++ b/src/Vault/wwwroot/app/app.js @@ -0,0 +1,18 @@ +angular + .module('bit', [ + 'ui.router', + 'ngMessages', + 'angular-jwt', + 'angular-md5', + 'ui.bootstrap.showErrors', + 'toastr', + + 'bit.directives', + 'bit.services', + + 'bit.global', + 'bit.accounts', + 'bit.vault', + 'bit.settings', + 'bit.tools' + ]); diff --git a/src/Vault/wwwroot/app/config.js b/src/Vault/wwwroot/app/config.js new file mode 100644 index 00000000000..acedfd38f9c --- /dev/null +++ b/src/Vault/wwwroot/app/config.js @@ -0,0 +1,157 @@ +angular + .module('bit') + + .config(function ($stateProvider, $urlRouterProvider, $httpProvider, jwtInterceptorProvider, $uibTooltipProvider, toastrConfig) { + jwtInterceptorProvider.tokenGetter = /*@ngInject*/ function (config, appSettings, tokenService) { + if (config.url.indexOf(appSettings.apiUri) === 0) { + return tokenService.getToken(); + } + }; + + angular.extend(toastrConfig, { + closeButton: true, + progressBar: true, + showMethod: 'slideDown', + target: '.toast-target' + }); + + $uibTooltipProvider.options({ + popupDelay: 600 + }); + + if (!$httpProvider.defaults.headers.get) { + $httpProvider.defaults.headers.get = {}; + } + + $httpProvider.defaults.headers.get['If-Modified-Since'] = 'Mon, 26 Jul 1997 05:00:00 GMT'; + $httpProvider.defaults.headers.get['Cache-Control'] = 'no-cache'; + $httpProvider.defaults.headers.get.Pragma = 'no-cache'; + + $httpProvider.interceptors.push('apiInterceptor'); + $httpProvider.interceptors.push('jwtInterceptor'); + + $urlRouterProvider.otherwise('/'); + + $stateProvider + // Backend + .state('backend', { + templateUrl: 'app/views/backendLayout.html', + abstract: true, + data: { + authorize: true + } + }) + .state('backend.vault', { + url: '^/', + templateUrl: 'app/vault/views/vault.html', + controller: 'vaultController', + data: { pageTitle: 'My Vault' } + }) + .state('backend.settings', { + url: '^/settings', + templateUrl: 'app/settings/views/settings.html', + controller: 'settingsController', + data: { pageTitle: 'Settings' } + }) + .state('backend.tools', { + url: '^/tools', + templateUrl: 'app/tools/views/tools.html', + controller: 'toolsController', + data: { pageTitle: 'Tools' } + }) + + // Frontend + .state('frontend', { + templateUrl: 'app/views/frontendLayout.html', + abstract: true, + data: { + authorize: false + } + }) + .state('frontend.login', { + templateUrl: 'app/accounts/views/accountsLogin.html', + controller: 'accountsLoginController', + data: { + bodyClass: 'login-page' + } + }) + .state('frontend.login.info', { + url: '^/login', + templateUrl: 'app/accounts/views/accountsLoginInfo.html', + data: { + pageTitle: 'Log In' + } + }) + .state('frontend.login.twoFactor', { + url: '^/login/two-factor', + templateUrl: 'app/accounts/views/accountsLoginTwoFactor.html', + data: { + pageTitle: 'Log In (Two Factor)', + authorizeTwoFactor: true + } + }) + .state('frontend.logout', { + url: '^/logout', + controller: 'accountsLogoutController', + data: { + authorize: true + } + }) + .state('frontend.passwordHint', { + url: '^/password-hint', + templateUrl: 'app/accounts/views/accountsPasswordHint.html', + controller: 'accountsPasswordHintController', + data: { + pageTitle: 'Master Password Hint', + bodyClass: 'login-page' + } + }) + .state('frontend.register', { + url: '^/register', + templateUrl: 'app/accounts/views/accountsRegister.html', + controller: 'accountsRegisterController', + data: { + pageTitle: 'Register', + bodyClass: 'register-page' + } + }) + .state('frontend.registerFinalize', { + controller: 'accountsRegisterFinalizeController', + templateUrl: 'app/accounts/views/accountsRegisterFinalize.html', + data: { + bodyClass: 'register-page' + } + }) + .state('frontend.registerFinalize.info', { + url: '^/register/finalize', + templateUrl: 'app/accounts/views/accountsRegisterFinalizeInfo.html', + data: { + pageTitle: 'Finalize Registration' + } + }) + .state('frontend.registerFinalize.confirm', { + url: '^/register/finalize/confirm', + templateUrl: 'app/accounts/views/accountsRegisterFinalizeConfirm.html', + data: { + pageTitle: 'Finalize Registration (Confirm)' + } + }); + }) + .run(function ($rootScope, authService, jwtHelper, tokenService, $state) { + $rootScope.$on('$stateChangeStart', function (event, toState, toParams) { + if (!toState.data || !toState.data.authorize) { + if (authService.isAuthenticated() && !jwtHelper.isTokenExpired(tokenService.getToken())) { + event.preventDefault(); + $state.go('backend.vault'); + } + + return; + } + + if (!authService.isAuthenticated() || jwtHelper.isTokenExpired(tokenService.getToken())) { + event.preventDefault(); + authService.logOut(); + $state.go('frontend.login.info'); + } + }); + }); \ No newline at end of file diff --git a/src/Vault/wwwroot/app/directives/apiFieldDirective.js b/src/Vault/wwwroot/app/directives/apiFieldDirective.js new file mode 100644 index 00000000000..a053712a5a3 --- /dev/null +++ b/src/Vault/wwwroot/app/directives/apiFieldDirective.js @@ -0,0 +1,30 @@ +angular + .module('bit.directives') + + .directive('apiField', function () { + var linkFn = function (scope, element, attrs, ngModel) { + ngModel.$registerApiError = registerApiError; + ngModel.$validators.apiValidate = apiValidator; + + function apiValidator() { + ngModel.$setValidity('api', true); + return true; + } + + function registerApiError() { + ngModel.$setValidity('api', false); + } + }; + + return { + require: 'ngModel', + restrict: 'A', + compile: function (elem, attrs) { + if (!attrs.name || attrs.name === '') { + throw 'api-field element does not have a valid name attribute'; + } + + return linkFn; + } + }; + }); \ No newline at end of file diff --git a/src/Vault/wwwroot/app/directives/apiFormDirective.js b/src/Vault/wwwroot/app/directives/apiFormDirective.js new file mode 100644 index 00000000000..f240ab53c2c --- /dev/null +++ b/src/Vault/wwwroot/app/directives/apiFormDirective.js @@ -0,0 +1,35 @@ +angular + .module('bit.directives') + + .directive('apiForm', function ($rootScope, validationService) { + return { + require: 'form', + restrict: 'A', + link: function (scope, element, attrs, formCtrl) { + var watchPromise = attrs.apiForm || null; + if (watchPromise !== void 0) { + scope.$watch(watchPromise, formSubmitted.bind(null, formCtrl, scope)); + } + } + }; + + function formSubmitted(form, scope, promise) { + if (!promise || !promise.then) { + return; + } + + // reset errors + form.$errors = null; + + // start loading + form.$loading = true; + + promise.then(function success(response) { + form.$loading = false; + }, function failure(reason) { + form.$loading = false; + validationService.addErrors(form, reason); + scope.$broadcast('show-errors-check-validity'); + }); + } + }); \ No newline at end of file diff --git a/src/Vault/wwwroot/app/directives/directivesModule.js b/src/Vault/wwwroot/app/directives/directivesModule.js new file mode 100644 index 00000000000..ad4865f131d --- /dev/null +++ b/src/Vault/wwwroot/app/directives/directivesModule.js @@ -0,0 +1,2 @@ +angular + .module('bit.directives', []); diff --git a/src/Vault/wwwroot/app/directives/masterPasswordDirective.js b/src/Vault/wwwroot/app/directives/masterPasswordDirective.js new file mode 100644 index 00000000000..7c5eb8fe36d --- /dev/null +++ b/src/Vault/wwwroot/app/directives/masterPasswordDirective.js @@ -0,0 +1,40 @@ +angular + .module('bit.directives') + + .directive('masterPassword', function (cryptoService, authService) { + return { + require: 'ngModel', + restrict: 'A', + link: function (scope, elem, attr, ngModel) { + var profile = authService.getUserProfile(); + if (!profile) { + return; + } + + // For DOM -> model validation + ngModel.$parsers.unshift(function (value) { + if (!value) { + return undefined; + } + + var key = cryptoService.makeKey(value, profile.email, true); + var valid = key == cryptoService.getKey(true); + ngModel.$setValidity('masterPassword', valid); + return valid ? value : undefined; + }); + + // For model -> DOM validation + ngModel.$formatters.unshift(function (value) { + if (!value) { + return undefined; + } + + var key = cryptoService.makeKey(value, profile.email, true); + var valid = key == cryptoService.getKey(true); + + ngModel.$setValidity('masterPassword', valid); + return value; + }); + } + }; + }); \ No newline at end of file diff --git a/src/Vault/wwwroot/app/directives/pageTitleDirective.js b/src/Vault/wwwroot/app/directives/pageTitleDirective.js new file mode 100644 index 00000000000..8385b3f8987 --- /dev/null +++ b/src/Vault/wwwroot/app/directives/pageTitleDirective.js @@ -0,0 +1,22 @@ +angular + .module('bit.directives') + + .directive('pageTitle', function ($rootScope, $timeout, appSettings) { + return { + link: function (scope, element) { + var listener = function (event, toState, toParams, fromState, fromParams) { + // Default title + var title = 'bitwarden Password Manager'; + if (toState.data && toState.data.pageTitle) { + title = toState.data.pageTitle + ' - bitwarden Password Manager'; + } + + $timeout(function () { + element.text(title); + }); + }; + + $rootScope.$on('$stateChangeStart', listener); + } + }; + }); \ No newline at end of file diff --git a/src/Vault/wwwroot/app/directives/passwordMeterDirective.js b/src/Vault/wwwroot/app/directives/passwordMeterDirective.js new file mode 100644 index 00000000000..4c9b1fcbade --- /dev/null +++ b/src/Vault/wwwroot/app/directives/passwordMeterDirective.js @@ -0,0 +1,73 @@ +angular + .module('bit.directives') + + .directive('passwordMeter', function () { + return { + template: '+
Please wait. We are now changing your email and reencrypting all of your data. Do not close this window. You will be automatically logged out when this process has finished.
++
Please wait. We are now changing your password and reencrypting all of your data. Do not close this window. You will be automatically logged out when this process has finished.
++
Please wait. We are now exporting all of your data to a .csv file.
No sites in this folder.
+ +| Site | +Username | ++ |
|---|---|---|
| {{site.name}} | +{{site.username}} | ++ + + | +