diff --git a/bower.json b/bower.json index 35c2b965..b9b03063 100644 --- a/bower.json +++ b/bower.json @@ -40,7 +40,8 @@ "mno-ui-elements": "maestrano/mno-ui-elements#master", "textAngular": "^1.5.16", "json-formatter": "^0.6.0", - "es6-shim": "^0.35.3" + "es6-shim": "^0.35.3", + "AngularDevise": "angular-devise#^1.3.0" }, "devDependencies": { "angular-mocks": "~1.6.0" diff --git a/src/app/components/mno-errors-handler/mno-errors-handler.svc.coffee b/src/app/components/mno-errors-handler/mno-errors-handler.svc.coffee index fa0ec236..ed2181c8 100644 --- a/src/app/components/mno-errors-handler/mno-errors-handler.svc.coffee +++ b/src/app/components/mno-errors-handler/mno-errors-handler.svc.coffee @@ -6,29 +6,32 @@ angular.module 'mnoEnterpriseAngular' errorCache = null mnoErrorHandler.processServerError = (serverError, form) -> $log.error('An error occurred:', serverError) + return unless form + return if _.startsWith(serverError.data, '') if 400 <= serverError.status <= 499 # Error in the request # Save the errors object in the scope - errorCache = serverError + errorCache = serverError.data.errors # Set each error fields as not valid - _.each errorCache.data, (errors, key) -> - _.each errors, -> - if form[key]? - form[key].$setValidity 'server', false - else - $log.error('MnoErrorsHandler: cannot find field:' + key) + if form? && errorCache + _.each errorCache, (errors, key) -> + _.each errors, -> + if form[key]? + form[key].$setValidity 'server', false + else + $log.error('MnoErrorsHandler: cannot find field:' + key) mnoErrorHandler.errorMessage = (name) -> result = [] if errorCache? - _.each errorCache.data[name], (msg) -> - result.push msg + _.each errorCache[name], (msg) -> + result.push [ _.capitalize(name), msg ].join ' ' result.join ', ' mnoErrorHandler.resetErrors = (form) -> if errorCache? - _.each errorCache.data, (errors, key) -> + _.each errorCache, (errors, key) -> _.each errors, -> if form[key]? form[key].$setValidity 'server', null diff --git a/src/app/components/mno-locale-config/mno-locale-config.svc.coffee b/src/app/components/mno-locale-config/mno-locale-config.svc.coffee index 29163ded..9f2bf37d 100644 --- a/src/app/components/mno-locale-config/mno-locale-config.svc.coffee +++ b/src/app/components/mno-locale-config/mno-locale-config.svc.coffee @@ -37,7 +37,7 @@ angular.module 'mnoEnterpriseAngular' # Find the locale from the User#settings localeFromUser = -> - MnoeCurrentUser.get().then( + MnoeCurrentUser.get(skip_login_check: true).then( (response) -> response.settings?.locale ) diff --git a/src/app/components/mno-password-strength/mno-password-strength.coffee b/src/app/components/mno-password-strength/mno-password-strength.coffee new file mode 100644 index 00000000..205d0755 --- /dev/null +++ b/src/app/components/mno-password-strength/mno-password-strength.coffee @@ -0,0 +1,149 @@ +angular.module 'mnoEnterpriseAngular' + .directive 'mnoPasswordStrength', (MnoErrorsHandler) -> + require: "ngModel" + restrict: 'A' + scope: + passwordScore: '=' #outject score + + link: (scope, element, attrs, ctrl) -> + measureStrength = (p) -> + matches = + pos: {} + neg: {} + + counts = + pos: {} + neg: + seqLetter: 0 + seqNumber: 0 + seqSymbol: 0 + + tmp = undefined + strength = 0 + letters = "abcdefghijklmnopqrstuvwxyz" + numbers = "01234567890" + symbols = "\\!@#$%&/()=?¿" + back = undefined + forth = undefined + + if p + # Benefits + matches.pos.lower = p.match(/[a-z]/g) + matches.pos.upper = p.match(/[A-Z]/g) + matches.pos.numbers = p.match(/\d/g) + matches.pos.symbols = p.match(/[$-/:-?{-~!^_`\[\]]/g) + matches.pos.middleNumber = p.slice(1, -1).match(/\d/g) + matches.pos.middleSymbol = p.slice(1, -1).match(/[$-/:-?{-~!^_`\[\]]/g) + counts.pos.lower = (if matches.pos.lower then matches.pos.lower.length else 0) + counts.pos.upper = (if matches.pos.upper then matches.pos.upper.length else 0) + counts.pos.numbers = (if matches.pos.numbers then matches.pos.numbers.length else 0) + counts.pos.symbols = (if matches.pos.symbols then matches.pos.symbols.length else 0) + tmp = _.reduce(counts.pos, (memo, val) -> + # if has count will add 1 + memo + Math.min(1, val) + , 0) + counts.pos.numChars = p.length + tmp += (if (counts.pos.numChars >= 8) then 1 else 0) + counts.pos.requirements = (if (tmp >= 3) then tmp else 0) + counts.pos.middleNumber = (if matches.pos.middleNumber then matches.pos.middleNumber.length else 0) + counts.pos.middleSymbol = (if matches.pos.middleSymbol then matches.pos.middleSymbol.length else 0) + + # Deductions + matches.neg.consecLower = p.match(/(?=([a-z]{2}))/g) + matches.neg.consecUpper = p.match(/(?=([A-Z]{2}))/g) + matches.neg.consecNumbers = p.match(/(?=(\d{2}))/g) + matches.neg.onlyNumbers = p.match(/^[0-9]*$/g) + matches.neg.onlyLetters = p.match(/^([a-z]|[A-Z])*$/g) + counts.neg.consecLower = (if matches.neg.consecLower then matches.neg.consecLower.length else 0) + counts.neg.consecUpper = (if matches.neg.consecUpper then matches.neg.consecUpper.length else 0) + counts.neg.consecNumbers = (if matches.neg.consecNumbers then matches.neg.consecNumbers.length else 0) + + # sequential letters (back and forth) + i = 0 + while i < letters.length - 2 + p2 = p.toLowerCase() + forth = letters.substring(i, parseInt(i + 3)) + back = _(forth).split("").reverse() + counts.neg.seqLetter++ if p2.indexOf(forth) isnt -1 or p2.indexOf(back) isnt -1 + i++ + + # sequential numbers (back and forth) + i = 0 + while i < numbers.length - 2 + forth = numbers.substring(i, parseInt(i + 3)) + back = _(forth).split("").reverse() + counts.neg.seqNumber++ if p.indexOf(forth) isnt -1 or p.toLowerCase().indexOf(back) isnt -1 + i++ + + # sequential symbols (back and forth) + i = 0 + while i < symbols.length - 2 + forth = symbols.substring(i, parseInt(i + 3)) + back = _(forth).split("").reverse() + counts.neg.seqSymbol++ if p.indexOf(forth) isnt -1 or p.toLowerCase().indexOf(back) isnt -1 + i++ + + # repeated chars + counts.neg.repeated = _.chain(p.toLowerCase().split("")).countBy((val) -> + val + ).reject((val) -> + val is 1 + ).reduce((memo, val) -> + memo + val + , 0).value() + + # Calculations + strength += counts.pos.numChars * 4 + strength += (counts.pos.numChars - counts.pos.upper) * 2 if counts.pos.upper + strength += (counts.pos.numChars - counts.pos.lower) * 2 if counts.pos.lower + strength += counts.pos.numbers * 4 if counts.pos.upper or counts.pos.lower + strength += counts.pos.symbols * 6 + strength += (counts.pos.middleSymbol + counts.pos.middleNumber) * 2 + strength += counts.pos.requirements * 2 + strength -= counts.neg.consecLower * 2 + strength -= counts.neg.consecUpper * 2 + strength -= counts.neg.consecNumbers * 2 + strength -= counts.neg.seqNumber * 3 + strength -= counts.neg.seqLetter * 3 + strength -= counts.neg.seqSymbol * 3 + strength -= counts.pos.numChars if matches.neg.onlyNumbers + strength -= counts.pos.numChars if matches.neg.onlyLetters + strength -= (counts.neg.repeated / counts.pos.numChars) * 10 if counts.neg.repeated + Math.max 0, Math.min(100, Math.round(strength)) + + getPwStrength = (s) -> + switch Math.round(s / 20) + when 0, 1 + "weak" + when 2,3 + "good" + when 4,5 + "secure" + + getClass = (s) -> + switch getPwStrength(s) + when 'weak' + "danger" + when 'good' + "warning" + when 'secure' + "success" + + isPwStrong = (s) -> + switch getPwStrength(s) + when 'weak' + false + else + true + + scope.$watch (-> ctrl.$modelValue), -> + scope.value = measureStrength(ctrl.$modelValue) + scope.pwStrength = getPwStrength(scope.value) + ctrl.$setValidity('password-strength', isPwStrong(scope.value)) + if scope.passwordScore? + scope.passwordScore.value = scope.pwStrength + scope.passwordScore.class = getClass(scope.value) + scope.passwordScore.showTip = (ctrl.$modelValue? && ctrl.$modelValue != '' && !isPwStrong(scope.value)) + + + return diff --git a/src/app/components/mnoe-api/auth.svc.coffee b/src/app/components/mnoe-api/auth.svc.coffee new file mode 100644 index 00000000..3ed837f1 --- /dev/null +++ b/src/app/components/mnoe-api/auth.svc.coffee @@ -0,0 +1,6 @@ +angular.module 'mnoEnterpriseAngular' + .factory 'MnoeAuthSvc', (Restangular, inflector) -> + return Restangular.withConfig((RestangularProvider) -> + RestangularProvider.setBaseUrl('/mnoe/auth') + RestangularProvider.setDefaultHeaders({Accept: "application/json"}) + ) diff --git a/src/app/components/mnoe-api/current-user.svc.coffee b/src/app/components/mnoe-api/current-user.svc.coffee index 85c0217a..992112a5 100644 --- a/src/app/components/mnoe-api/current-user.svc.coffee +++ b/src/app/components/mnoe-api/current-user.svc.coffee @@ -13,7 +13,7 @@ # => PUT /mnoe/jpi/v1/current_user/update_password angular.module 'mnoEnterpriseAngular' - .service 'MnoeCurrentUser', (MnoeApiSvc, $window, $state, $rootScope, URI, MnoeConfig) -> + .service 'MnoeCurrentUser', (MnoeApiSvc, $window, $state, $q, $timeout, $rootScope, URI, Auth, MnoeConfig) -> _self = @ # Store the current_user promise @@ -23,12 +23,34 @@ angular.module 'mnoEnterpriseAngular' # Save the current user in variable to be able to reference it directly @user = {} + # Redirect if user is already logged in + @skipIfLoggedIn = -> + Auth.currentUser().then( + (response) -> + $timeout( -> $state.go('home.impac') ) + $q.reject() + -> + $q.resolve() + ) + + @loginRequired = -> + Auth.currentUser().catch( + -> + $timeout( -> $state.go('login') ) + $q.reject() + ) + # Get the current user - @get = -> + @get = (opts = {})-> return userPromise if userPromise? + userPromise = MnoeApiSvc.one('current_user').get().then( (response) -> response = response.plain() + + if ! (opts.skip_login_check || response.logged_in) + $state.go('login') + angular.copy(response, _self.user) response ) @@ -55,15 +77,4 @@ angular.module 'mnoEnterpriseAngular' @updatePassword = (passwordData) -> MnoeApiSvc.all('/current_user').doPUT({user: passwordData}, 'update_password') - # Ensure user is logged in - @loginRequired = -> - _self.get().then( - (response) -> - unless response.logged_in - if MnoeConfig.arePublicApplicationsEnabled() - $state.go('public.landing') - else - $window.location = URI.login - ) - return @ diff --git a/src/app/index.config.coffee b/src/app/index.config.coffee index fca99d6e..5ce35ad4 100644 --- a/src/app/index.config.coffee +++ b/src/app/index.config.coffee @@ -25,13 +25,15 @@ angular.module 'mnoEnterpriseAngular' ) .config ($httpProvider) -> - $httpProvider.interceptors.push ($q, $window) -> + $httpProvider.interceptors.push ($q, $window, $injector, $log) -> { responseError: (rejection) -> if rejection.status == 401 # Redirect to login page - console.log "User is not connected!" - $window.location.href = '/' + toastr = $injector.get('toastr') + + toastr.error('User is not connected!') + $log.error('User is not connected!') $q.reject rejection } @@ -84,3 +86,22 @@ angular.module 'mnoEnterpriseAngular' # TODO: Activate in "developer mode" only (spams the console and makes the application lag) # $translateProvider.useMissingTranslationHandlerLog() ) + # Configure auth routes + .config((AuthProvider, AuthInterceptProvider) -> + AuthProvider.loginPath('/mnoe/auth/users/sign_in') + AuthProvider.logoutPath('/mnoe/auth/users/sign_out') + AuthProvider.registerPath('/mnoe/auth/users/') + AuthProvider.sendResetPasswordInstructionsPath('/mnoe/auth/users/password') + AuthProvider.resetPasswordPath('/mnoe/auth/users/password') + AuthInterceptProvider.interceptAuth(true) + ) + + + + + + + + + + diff --git a/src/app/index.constants.coffee b/src/app/index.constants.coffee index 6e7b03ef..834f3220 100644 --- a/src/app/index.constants.coffee +++ b/src/app/index.constants.coffee @@ -1,10 +1,10 @@ angular.module 'mnoEnterpriseAngular' .constant('URI', { - login: '/mnoe/auth/users/sign_in', + login: '/login', dashboard: '/dashboard/', - logout: '/mnoe/auth/users/sign_out', signup: '/mnoe/auth/users/sign_up', - api_root: '/mnoe/jpi/v1' + api_root: '/mnoe/jpi/v1', + logout: '/logout' }) .constant('DOC_LINKS', { connecDoc: 'https://maestrano.atlassian.net/wiki/x/BIHLAQ' diff --git a/src/app/index.default-config.coffee b/src/app/index.default-config.coffee index 373c9cb4..0850ebf1 100644 --- a/src/app/index.default-config.coffee +++ b/src/app/index.default-config.coffee @@ -11,3 +11,4 @@ angular.module('mnoEnterprise.defaultConfiguration', []) .constant('APP_NAME', null) .constant('INTERCOM_ID', null) .constant('URL_CONFIG', {}) + .constant('DEVISE_CONFIG', {}) diff --git a/src/app/index.less b/src/app/index.less index 4d6007a9..169db2c4 100644 --- a/src/app/index.less +++ b/src/app/index.less @@ -39,7 +39,7 @@ body { } } -.myspace { +.myspace, .page-wrapper { background-color: @dashboard-bg-color; min-height:100%; } diff --git a/src/app/index.module.coffee b/src/app/index.module.coffee index dce3a08e..98c7bdaf 100644 --- a/src/app/index.module.coffee +++ b/src/app/index.module.coffee @@ -22,5 +22,7 @@ angular.module 'mnoEnterpriseAngular', [ 'schemaForm', 'angular.filter', 'textAngular', - 'jsonFormatter' + 'jsonFormatter', + 'mnoUiElements', + 'Devise' ] diff --git a/src/app/index.route.coffee b/src/app/index.route.coffee index 93aecce1..a86e5ddf 100644 --- a/src/app/index.route.coffee +++ b/src/app/index.route.coffee @@ -28,6 +28,92 @@ angular.module 'mnoEnterpriseAngular' controller: 'LandingProductCtrl' controllerAs: 'vm' public: true + .state 'login', + data: + pageTitle: 'Login' + url: '/login' + templateUrl: 'app/views/auth/login/login.html' + controller: 'AuthLoginCtrl' + controllerAs: 'vm' + resolve: + # Redirect the user to the platform if he is already logged in + skipIfLoggedIn: (MnoeCurrentUser) -> + MnoeCurrentUser.skipIfLoggedIn() + .state 'confirmation_lounge', + data: + pageTitle: 'Lounge' + params: { + email: null + } + url: '/confirmation/lounge' + templateUrl: 'app/views/auth/confirmation/lounge.html' + controller: 'AuthLoungeCtrl' + controllerAs: 'vm' + resolve: + # Redirect the user to the platform if he is already logged in + skipIfLoggedIn: (MnoeCurrentUser) -> + MnoeCurrentUser.skipIfLoggedIn() + .state 'confirmation', + data: + pageTitle: 'Confirmation' + url: '/confirmation?:confirmation_token' + templateUrl: 'app/views/auth/confirmation/confirm.html' + controller: 'AuthConfirmCtrl' + controllerAs: 'vm' + public: true + .state 'confirmation_recovery', + data: + pageTitle: 'ConfirmationRecovery' + url: '/confirmation/new' + templateUrl: 'app/views/auth/confirmation/new.html' + controller: 'AuthConfirmRecoveryCtrl' + controllerAs: 'vm' + public: true + .state 'unlock_recovery', + data: + pageTitle: "UnlockRecovery" + url: '/unlock/new', + templateUrl: 'app/views/auth/unlock/unlock.html' + controller: 'AuthUnlockRecoveryCtrl' + controllerAs: 'vm' + public: true + .state 'password_recovery', + data: + pageTitle: 'PasswordRecovery' + url: '/password/new' + templateUrl: 'app/views/auth/password/recovery.html' + controller: 'PasswordRecoveryCtrl' + controllerAs: 'vm' + resolve: + skipIfLoggedIn: (MnoeCurrentUser) -> + MnoeCurrentUser.skipIfLoggedIn() + .state 'password_reset', + data: + pageTitle: 'PasswordReset' + url: '/password/reset' + templateUrl: 'app/views/auth/password/reset.html' + controller: 'PasswordResetCtrl' + controllerAs: 'vm' + resolve: + skipIfLoggedIn: (MnoeCurrentUser) -> + MnoeCurrentUser.skipIfLoggedIn() + .state 'signup', + data: + pageTitle: 'SignUp' + url: '/signup' + templateUrl: 'app/views/auth/signup/signup.html' + controller: 'AuthSignUpCtrl' + controllerAs: 'vm' + resolve: + skipIfLoggedIn: (MnoeCurrentUser) -> + MnoeCurrentUser.skipIfLoggedIn() + .state 'authorize', + data: + pageTitle: 'Authorization' + url: '/authorize?:redirect_path' + templateUrl: 'app/views/apps/authorize.html' + controller: 'AppAuthController' + controllerAs: 'vm' .state 'home', data: pageTitle:'Home' @@ -45,6 +131,9 @@ angular.module 'mnoEnterpriseAngular' url: '/apps' templateUrl: 'app/views/apps/dashboard-apps-list.html' controller: 'DashboardAppsListCtrl' + resolve: + resourcesLoaded: ($rootScope) -> + $rootScope.resourcesLoaded .state 'home.impac', data: pageTitle:'Impac' @@ -68,22 +157,14 @@ angular.module 'mnoEnterpriseAngular' controllerAs: 'vm' .state 'logout', url: '/logout' - controller: ($window, $http, $translate, AnalyticsSvc, URL_CONFIG) -> + controller: ($state, Auth, toastr, AnalyticsSvc, MnoeCurrentUser) -> 'ngInject' # Logout and redirect the user - $http.delete(URI.logout).then( -> + Auth.logout().then(-> AnalyticsSvc.logOut() - - logout_url = URL_CONFIG.after_sign_out_url || URI.login - - if I18N_CONFIG.enabled - if URL_CONFIG.after_sign_out_url - $window.location.href = logout_url - else - $window.location.href = "/#{$translate.use()}#{URI.login}" - else - $window.location.href = logout_url + toastr.info('You have been logged out.') + $state.go('login') ) if MnoeConfigProvider.$get().isOnboardingWizardEnabled() @@ -128,6 +209,9 @@ angular.module 'mnoEnterpriseAngular' templateUrl: 'app/views/marketplace/marketplace.html' controller: 'DashboardMarketplaceCtrl' controllerAs: 'vm' + resolve: + resourcesLoaded: ($rootScope) -> + $rootScope.resourcesLoaded .state 'home.marketplace.app', data: pageTitle:'Marketplace' @@ -211,16 +295,15 @@ angular.module 'mnoEnterpriseAngular' MnoeCurrentUser = $injector.get('MnoeCurrentUser') MnoeOrganizations = $injector.get('MnoeOrganizations') MnoeAppInstances = $injector.get('MnoeAppInstances') - $window = $injector.get('$window') - MnoeCurrentUser.get().then( + MnoeCurrentUser.get(skip_login_check: true).then( (response) -> # Same as MnoeCurrentUser.loginRequired unless response.logged_in if MnoeConfig.arePublicApplicationsEnabled() $location.url('/landing') else - $window.location = URI.login + $location.url('/login') else if MnoeConfig.isOnboardingWizardEnabled() MnoeOrganizations.getCurrentOrganisation().then( diff --git a/src/app/index.run.coffee b/src/app/index.run.coffee index b7bcf91d..5533a539 100644 --- a/src/app/index.run.coffee +++ b/src/app/index.run.coffee @@ -49,54 +49,3 @@ angular.module 'mnoEnterpriseAngular' toastr[message.type](message.msg, _.capitalize(message.type), timeout: 10000) $location.search('flash', null) # remove the flash from url ) - - .run(($rootScope, $timeout, AnalyticsSvc) -> - $timeout ( -> AnalyticsSvc.init() ) - - $rootScope.$on('$stateChangeSuccess', -> - AnalyticsSvc.update() - ) - ) - - # App initialization: Retrieve current user and current organization, then preload marketplace - .run(($rootScope, $q, $location, $stateParams, MnoeCurrentUser, MnoeOrganizations, MnoeMarketplace, MnoeAppInstances, MnoeConfig) -> - - _self = this - - # Hide the layout with a loader - $rootScope.isLoggedIn = false - - # Load the current user - userPromise = MnoeCurrentUser.get() - - # Load the current organization if defined (url param, cookie or first) - _self.appInstancesDeferred = $q.defer() - orgPromise = MnoeOrganizations.getCurrentOrganisation().then( - (response) -> - # Pre-load the marketplace, the products and the local products - MnoeMarketplace.getApps() - MnoeMarketplace.getProducts() if MnoeConfig.isProvisioningEnabled() - MnoeMarketplace.getLocalProducts() if MnoeConfig.areLocalProductsEnabled() - - # App instances needs to be run after fetching the organization (At least the first call) - MnoeAppInstances.getAppInstances().then( - (appInstances) -> - _self.appInstancesDeferred.resolve(appInstances) - ) - - response - ) - - $q.all([userPromise, orgPromise, _self.appInstancesDeferred.promise]).then( - -> - # Display the layout - $rootScope.isLoggedIn = true - ).catch( - -> - # Display the layout - $rootScope.isLoggedIn = true - - # Display no organisation message - $rootScope.hasNoOrganisations = true - ) - ) diff --git a/src/app/stylesheets/variables.less b/src/app/stylesheets/variables.less index 67efd19c..325b79cc 100644 --- a/src/app/stylesheets/variables.less +++ b/src/app/stylesheets/variables.less @@ -23,12 +23,13 @@ @public-page-header-bg-color: @bg-inverse-color; @public-page-header-padding: 10px; +@public-page-header-logo-img: "../images/main-logo.png"; @public-page-header-logo: { min-height: 61px; max-width: 160px; max-height: 130px; margin: 17px auto 5px auto; }; /*-----------------------------------------------------------------------*/ /* Login Page */ /*-----------------------------------------------------------------------*/ -@login-bg-img: "mno_enterprise/login-background.jpg"; +@login-bg-img: "../images/login-background.jpg"; @login-bg-color: @bg-main-color; @login-box-grid-position: { margin-top: 80px; .make-sm-column(4); .make-sm-column-offset(4); }; @@ -39,10 +40,11 @@ @login-box-bg-color: @bg-inverse-color; @login-box-padding: 20px; -@login-box-brand-logo: { max-width: 300px; max-height: 200px; margin: 16px auto 16px auto; }; +@login-box-brand-logo-img: "../images/main-logo.png"; +@login-box-brand-logo: { min-height: 61px; max-width: 160px; max-height: 130px; margin: 17px auto 5px auto; }; @login-box-btn-login: { width: 100%; }; -@login-box-sso-intuit-logo: "mno_enterprise/logo-intuit.png"; +@login-box-sso-intuit-logo: "../images/logo-intuit.png"; @login-box-oauth-text-color: #fff; @login-box-oauth-bg-color-facebook: #3b5998; @login-box-oauth-bg-color-google: #dd4b39; diff --git a/src/app/views/apps/authorize.controller.coffee b/src/app/views/apps/authorize.controller.coffee new file mode 100644 index 00000000..175aa8a9 --- /dev/null +++ b/src/app/views/apps/authorize.controller.coffee @@ -0,0 +1,11 @@ +angular.module 'mnoEnterpriseAngular' + .controller('AppAuthController', + ($state, $window, $timeout, $stateParams, MnoErrorsHandler) -> + + $timeout( + -> + $window.location.href = $stateParams.redirect_path + , 3000) + + return + ) diff --git a/src/app/views/apps/authorize.html b/src/app/views/apps/authorize.html new file mode 100644 index 00000000..04709c5a --- /dev/null +++ b/src/app/views/apps/authorize.html @@ -0,0 +1,15 @@ +
diff --git a/src/app/views/auth/auth.less b/src/app/views/auth/auth.less new file mode 100644 index 00000000..023b3f38 --- /dev/null +++ b/src/app/views/auth/auth.less @@ -0,0 +1,420 @@ +//--------------------------------------------------------------------------- +// Material Style +//--------------------------------------------------------------------------- +.registration-material { + height: 90%; + width: 100%; + position: absolute; + background-color: @login-bg-color; + text-align: center; + + .action-box { + height: 90%; + } + + .action-box md-input-container { + text-align: left; + } + + .action-box md-checkbox { + text-align: left; + } +} + +// Default style + +.registration when (isstring(@login-bg-img)) { + background-image: url(@login-bg-img); + background-size: cover; +} + +.registration { + height: 100%; + width: 100%; + position: absolute; + background-color: @login-bg-color; + + .login-box-wrapper { + text-align: center; + @login-box-grid-position(); + } + + .login-box-wrapper.large { + text-align: center; + margin-top: 80px; + width: 100%; + max-width: 400px; + } + + .login-box-wrapper md-input-container { + text-align: left; + } + + .login-box-wrapper .login-box-title { + color: @login-box-title-color; + margin-bottom: 15px; + @login-box-title(); + } + + .login-box-wrapper .login-box { + padding: @login-box-padding; + background-color: @login-box-bg-color; + border-radius: @login-box-corners; + + .loader { + content: url(../images/loader-32x32-bg-inverse.gif) + } + } + + .login-box-wrapper .login-box .brand-logo when (isstring(@login-box-brand-logo-img)){ + min-height: 80px; + background-repeat: no-repeat; + background-image: url(@login-box-brand-logo-img); + background-size: 100%; + @login-box-brand-logo(); + } + + .login-box-wrapper .login-box .btn-login { + @login-box-btn-login(); + } + + + .login-box-wrapper .login-box:before when (@login-box-title-display-box-arrow = true){ + .fa; + color: @login-box-bg-color; + display: block; + content: @fa-var-caret-up; + margin-top: -27px; + font-size: 24px; + line-height: 8px; + max-width: 60%; + margin-left: auto; + margin-right: auto; + } + + .login-box .sign-in-using { + color: @decorator-main-color; + margin-top: 5px; + } + .login-box .oauth-sso { + display: flex; + flex-direction: row-reverse; + justify-content: center; + align-items: center; + margin-top: 5px; + + .omniauth i { + display: inline-block; + border-radius: 3px; + color: @login-box-oauth-text-color; + width: 25px; + height: 25px; + margin: 0 5px; + line-height: 25px; + text-align: center; + + &.fa-facebook { + background: @login-box-oauth-bg-color-facebook; + } + &.fa-google { + background: @login-box-oauth-bg-color-google; + } + &.fa-linkedin { + background: @login-box-oauth-bg-color-linkedin; + } + } + .omniauth.intuit { + background: @login-box-oauth-bg-color-intuit; + height: 25px; + width: 50px; + display: inline-block; + margin: 0 5px; + border-radius: 3px; + background-position: 50%; + background-repeat: no-repeat; + background-size: 40px auto; + background-image: url(@login-box-sso-intuit-logo); + } + } + + .login-box h3, .login-box h5 { + color: @text-inverse-strong-color; + } + + .checkbox-section label { + font-weight: normal; + } + + .login-box-wrapper .login-box .phone select.form-control { + height: 34px; + } +} + +input.fluroblue[type=radio] { + width : 18px; + margin : 0; + padding : 0; + opacity : 0; +} + +input.fluroblue[type=radio] + label{ + display : inline-block; + margin-left : -12px; + padding-left : 25px; + background : image-url('icons/radio-fluroblue.png') no-repeat 0 -18px; + line-height : 18px; +} + +input.fluroblue[type=radio]:checked + label{ + background-position : 0 1px; +} + +form label { + color: @input-label-color; +} + +.input-group-addon.select select { + background-color: @input-bg; +} + + +select { + text-indent: 1px; + text-overflow: ''; +} + +// Remove dropdown arrow (Chrome, Firefox and IE) +select.unstyled { + -webkit-appearance: none; + -moz-appearance: none; +} + +select::-ms-expand { + display: none; +} + +//---------------------------------------- +// Styling for phone: select + input +//---------------------------------------- +form .phone { + select.form-control { + width:30%; + .border-right-radius(0px); + } + + input.form-control[type='text'] { + margin-left:23%; + margin-top:-34px; + width:77%; + .border-left-radius(0px); + } +} + +form select.form-control { + height:44px; + -webkit-appearance: none; +} + +form .select.fa { + margin: -28px 7% 0px 0px; + float: right; +} + +//---------------------------------------- +// Styling for password checker +//---------------------------------------- +form li.checked { + color: lighten(@decorator-inverse-color,30%); +} + +form li.unchecked { + color: lighten(@text-strong-color,70%); +} + +//---------------------------------------- +// Style for forms on inverse backgrounds +//---------------------------------------- +form.inverse { + select { + background-color: @bg-inverse-color; + .font(16px, 300, @decorator-inverse-color); + border:solid 1px #111c21; + + .border-radius(5px); + height:44px; + -webkit-appearance: none; + padding-left:15px; + width:100%; + + // Placeholder + .placeholder(@decorator-inverse-color); + } + + .select.fa { + .font(16px,300,@decorator-inverse-color); + margin: -28px 7% 0px 0px; + float: right; + } + + .form-control { + // Placeholder + .placeholder(@decorator-inverse-color); + } +} + +//------------------------------------ +// Dark forms styles +//------------------------------------ + +form.dark { + input[type='text'], input[type='email'], textarea { + .border-radius(5px); + .font(16px, 300, @bg-main-color); + background-color: @bg-inverse-color; + border: solid 1px @bg-inverse-color; + line-height: 25px; + margin-bottom: 0px; + width: 100%; + min-height: 42px; + padding: 5px 5px 5px 15px; + } + + *::-webkit-input-placeholder { + color: @bg-main-color; + } + *:-moz-placeholder { + /* FF 4-18 */ + color: @bg-main-color; + } + *::-moz-placeholder { + /* FF 19+ */ + color: @bg-main-color; + } + *:-ms-input-placeholder { + /* IE 10+ */ + color: @bg-main-color; + } + + label { + color: @bg-main-color; + } +} + +.dark { + .bootstrap-select { + select { + .font(16px, 300, @bg-main-color); + background-color: @bg-inverse-color; + } + + .dropdown-toggle { + height: 42px; + } + + .dropdown-menu { + .font(16px, 300, @bg-main-color); + background-color: @bg-inverse-color; + } + .dropdown-menu > li > a { + .font(16px, 300, @bg-main-color); + } + + .dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus { + .font(16px, 300, @decorator-inverse-color); + background-color: @bg-inverse-color; + } + } +} + +// Put a dropdown inside an input-group-addon +.input-group-addon.select { + padding:0px; + select { + height:100%; + border:0px; + } +} + +//------------------------------------ +// Invalid inputs (AngularJS) +//------------------------------------ +input.ng-dirty.ng-invalid { + border-color: @brand-danger; +} + +// Mixins + +html, body { + height: 100%; +} + +// .border-radius doesn't exist anymore in bootstrap3 +.border-radius(@radius) { + .border-top-radius(@radius); + .border-bottom-radius(@radius); +} + + +// Thoses mixins don't exist anymore in bootstrap 3. I just did a dummy +// implementation of them to prevent errors +.muted { +} + +.rgba(@color,@alpha) { + background-color:@color; + background-color:rgba(red(@color),green(@color),blue(@color),@alpha); +} + +.font(@size,@weight,@color) { + font-weight:@weight; + font-size:@size; + color:@color; +} + +.buttonBackground(@a,@b) { +} + +.border-top-left-radius(@a) { +} + +.border-top-right-radius(@a) { +} + +.border-bottom-right-radius(@a) { +} + +.border-bottom-left-radius(@a) { +} + +.inherit-all { + .font(inherit,inherit,inherit); + background-color:inherit; + text-align:inherit; +} + +// Fontawesome icon mixing +// E.g.: +// .my-class { +// .icon-fa(exchange); +// } +// +// E.g.: +// .my-class { +// .fa-lg(); +// .icon-fa(exchange); +// } +// +.icon-fa(@icon) { + .fa; + @fa-icon-variable: "fa-var-@{icon}"; + &:before { content: @@fa-icon-variable; } +}; + +// Replace glyphicon by fontawesome +.glyphicon.glyphicon-chevron-right { + .icon-fa(chevron-right) +} +.glyphicon.glyphicon-chevron-left { + .icon-fa(chevron-left) +} + diff --git a/src/app/views/auth/confirmation/confirm.controller.coffee b/src/app/views/auth/confirmation/confirm.controller.coffee new file mode 100644 index 00000000..f2affb2a --- /dev/null +++ b/src/app/views/auth/confirmation/confirm.controller.coffee @@ -0,0 +1,36 @@ +angular.module 'mnoEnterpriseAngular' + .controller('AuthConfirmCtrl', + ($state, $stateParams, MnoeAuthSvc, toastr, MnoErrorsHandler) -> + vm = @ + vm.user = { phone_country_code: "US", confirmation_token: $stateParams.confirmation_token} + vm.$pwdScore = {} + vm.phoneRequired = "required" + + MnoeAuthSvc.one("/users/confirmation").get({confirmation_token: vm.user.confirmation_token}).then( + (response) -> + if response.attributes.new_email_confirmed + toastr.success('Email confirmed successfully') + $state.go('home.impac') + else if response.attributes.confirmed_at + toastr.info('User already confirmed') + $state.go('login') + else if response.attributes.no_phone_required + vm.phoneRequired = '' + (error) -> + toastr.error("Confirmation token is invalid") + $state.go('login') + ) + + vm.confirmUser = -> + delete vm.user.$pwdScore + MnoeAuthSvc.one("/users/confirmation/finalize").patch({user: vm.user}).then( + -> + toastr.success('User confirmed successfully') + $state.go('home.impac') + (error) -> + toastr.error('Confirmation unsuccessful, please try again') + vm.hasClicked = false + ) + + return + ) diff --git a/src/app/views/auth/confirmation/confirm.html b/src/app/views/auth/confirmation/confirm.html new file mode 100644 index 00000000..06b8d4c3 --- /dev/null +++ b/src/app/views/auth/confirmation/confirm.html @@ -0,0 +1,306 @@ +