diff --git a/app/account/account.routes.js b/app/account/account.routes.js index ea42cce75..84e263a4e 100644 --- a/app/account/account.routes.js +++ b/app/account/account.routes.js @@ -15,17 +15,19 @@ import angular from 'angular' data: { authRequired: false }, - onEnter: ['$state', '$stateParams', 'TcAuthService', 'logger', function($state, $stateParams, TcAuthService, logger) { - if (TcAuthService.isAuthenticated()) { - // redirect to next if exists else dashboard - if ($stateParams.next) { - logger.debug('Redirecting: ' + $stateParams.next) - window.location.href = decodeURIComponent($stateParams.next) - } else { - $state.go('dashboard') + onEnter: ['$state', '$location', '$stateParams', 'TcAuthService', 'logger', + function($state, $location, $stateParams, TcAuthService, logger) { + logger.debug('Checking for authentication...') + if (TcAuthService.isAuthenticated()) { + // redirect to next if exists else dashboard + if ($stateParams.next) { + logger.debug('Redirecting: ' + $stateParams.next) + window.location.href = decodeURIComponent($stateParams.next) + } else { + $state.go('dashboard') + } } - } - }] + }] }, 'login': { parent: 'auth', @@ -47,7 +49,8 @@ import angular from 'angular' controller: 'FooterController as vm', template: require('../layout/footer/account-footer')() } - } + }, + controller: 'LoginController' }, 'register': { parent: 'auth', diff --git a/app/account/login/login.controller.js b/app/account/login/login.controller.js index 6d0c6bf5e..27dd59aa6 100644 --- a/app/account/login/login.controller.js +++ b/app/account/login/login.controller.js @@ -1,131 +1,38 @@ import angular from 'angular' +import { getCurrentUser, loadUser } from '../../services/userv3.service.js' (function() { 'use strict' angular.module('tc.account').controller('LoginController', LoginController) - LoginController.$inject = ['logger', '$state', '$stateParams', '$location', '$scope', 'TcAuthService', 'UserService', 'Helpers', 'CONSTANTS'] + LoginController.$inject = ['logger', '$state', '$stateParams', '$window', '$rootScope', 'Helpers', 'CONSTANTS'] - function LoginController(logger, $state, $stateParams, $location, $scope, TcAuthService, UserService, Helpers, CONSTANTS) { + function LoginController(logger, $state, $stateParams, $window, $rootScope, Helpers, CONSTANTS) { var vm = this vm.$stateParams = $stateParams - vm.passwordReset = false - vm.loginErrors = { - USERNAME_NONEXISTANT: false, - WRONG_PASSWORD: false, - SOCIAL_LOGIN_ERROR: false - } - - vm.login = login - vm.socialLogin = socialLogin - - // reference for main vm - var mainVm = $scope.$parent.main activate() - function activate() {} - - function login() { - vm.loginErrors.USERNAME_NONEXISTANT = false - vm.loginErrors.WRONG_PASSWORD = false - - // TODO ideally it should be done by dedicated directive to handle all outside clicks - mainVm.menuVisible = false - - if (Helpers.isEmail(vm.username)) { - // the user is loggin in using email - vm.emailOrUsername = 'email' - - // ensure email exists - // uses same validity check as registration - // valid => email isn't already used by someone - UserService.validateUserEmail(vm.username).then(function(data) { - if (data.valid) { - // email doesn't exist - vm.loginErrors.USERNAME_NONEXISTANT = true - } else { - _doLogin(vm.username, vm.currentPassword) - } - }).catch(function(resp) { - // TODO handle error - // assume email exists, login would in any case if it didn't - vm.loginErrors.USERNAME_NONEXISTANT = false - _doLogin(vm.username, vm.currentPassword) - }) - } else { - // the user is logging in using a username - vm.emailOrUsername = 'username' - - // username - make sure it exists - UserService.validateUserHandle(vm.username).then(function(data) { - if (data.valid) { - // username doesn't exist - vm.loginErrors.USERNAME_NONEXISTANT = true - } else { - _doLogin(vm.username, vm.currentPassword) - } - }).catch(function(resp) { - // TODO handle error - // assume email exists, login would in any case if it didn't - _doLogin(vm.username, vm.currentPassword) - }) - } - } - - function _doLogin(usernameOrEmail, password) { - return TcAuthService.login(usernameOrEmail, password) - .then(function(data) { - // setup login event for analytics tracking - Helpers.setupLoginEventMetrics(usernameOrEmail) - return Helpers.redirectPostLogin($stateParams.next) - - }) - .catch(function(err) { - logger.warning(err) - - switch (err.status) { - case 'ACCOUNT_INACTIVE': - $state.go('registeredSuccessfully') - // user should already be redirected - break - case 'UNKNOWN_ERROR': - default: - vm.loginErrors.WRONG_PASSWORD = true - vm.password = '' - logger.error('Error logging in: ', err) - } + function activate() { + var currentUser = getCurrentUser() + logger.debug('checking for logged in user...' + currentUser) + if (!currentUser) { + logger.debug('loading user...') + var next = $stateParams.next ? $stateParams.next : 'dashboard' + loadUser().then(function(token) { + logger.debug('successful login with token ' + token) + $rootScope.$broadcast(CONSTANTS.EVENT_USER_LOGGED_IN) + logger.debug('reidrecting to ' + next) + Helpers.redirectPostLogin(next) + }, function() { + logger.debug('State requires authentication, and user is not logged in, redirecting') + // setup redirect for post login + var retUrl = $state.href(next, {}, {absolute: true}) + logger.debug('redirecting to accounts app for login...') + $window.location = CONSTANTS.ACCOUNTS_APP_URL + '?retUrl=' + encodeURIComponent(retUrl) }) - } - - function socialLogin(platform) { - // we need to pass on the 'next' param if we have one - var params = {} - if ($stateParams.next) { - params = {next: $stateParams.next} } - - // redirect back to login - var callbackUrl = $state.href('login', params, {absolute: true}) - - TcAuthService.socialLogin(platform, callbackUrl) - .then(function() { - logger.debug('logged in') - return Helpers.redirectPostLogin($stateParams.next) - }) - .catch(function(err) { - /*eslint no-fallthrough:0*/ - switch (err.status) { - case 'ACCOUNT_INACTIVE': - window.location.href = 'https://www.' + CONSTANTS.domain + '/account-inactive/' - case 'USER_NOT_REGISTERED': - default: - vm.socialLoginError = 401 - vm.loginErrors.SOCIAL_LOGIN_ERROR = true - logger.error('Error logging in with social account', err) - } - }) } } diff --git a/app/account/login/login.jade b/app/account/login/login.jade index 827bf6774..e69de29bb 100644 --- a/app/account/login/login.jade +++ b/app/account/login/login.jade @@ -1,47 +0,0 @@ -- var logoMobile = require("../../../assets/images/logo_mobile.svg") - -.login-container - header - a.logo-link(href="/", title="Back to the home page") - img(src=logoMobile, alt="Topcoder Logo") - - h1 LOG IN TO TOPCODER - - form(name="vm.loginForm", role="form", ng-submit="vm.loginForm.$valid && vm.login()", novalidate) - .form-errors(ng-messages="vm.loginErrors") - p.form-error(ng-message="USERNAME_NONEXISTANT") We couldn't find a member with that {{vm.emailOrUsername || "username"}}. Please check that you entered it correctly. - - p.form-error(ng-message="WRONG_PASSWORD") That password is incorrect. Please check that you entered the right one. - - p.form-error(ng-message="SOCIAL_LOGIN_ERROR") User with that profile is not registered. - - div.validation-bar(ng-class="{'error-bar': vm.loginErrors.USERNAME_NONEXISTANT}") - input(ng-model="vm.username", name="username", placeholder="Username or Email", type="text", required) - - toggle-password - - p.problem-signin - a.forgot-password(ui-sref="resetPassword") Forgot your password? - - button.tc-btn.tc-btn-wide(type="submit", ng-disabled="vm.loginForm.$invalid") Log In - - section.login-options - p.tc-separator - span Or Log in With - - ul.networks - li.network.github - a.ico(ng-click="vm.socialLogin('github')") - span Github - li.network.google-plus - a.ico(ng-click="vm.socialLogin('google-oauth2')") - span Google - li.network.facebook - a.ico(ng-click="vm.socialLogin('facebook')") - span Facebook - li.network.twitter - a.ico(ng-click="vm.socialLogin('twitter')") - span Twitter - -p.join-topcoder Not a member yet?   - a(ui-sref="register(vm.$stateParams)") Join now diff --git a/app/account/logout/logout.controller.js b/app/account/logout/logout.controller.js index 4be96b3c9..71e7d1202 100644 --- a/app/account/logout/logout.controller.js +++ b/app/account/logout/logout.controller.js @@ -8,12 +8,8 @@ import angular from 'angular' LogoutController.$inject = ['logger', 'TcAuthService', '$window', 'CONSTANTS'] function LogoutController(logger, TcAuthService, $window, CONSTANTS) { - - TcAuthService.logout() - .then(function() { - logger.debug('Successfully logged out.') - - // Redirect to home + TcAuthService.logout().then(() => { + logger.debug('MAIN_URL=> ' + CONSTANTS.MAIN_URL) $window.location.href = CONSTANTS.MAIN_URL }) } diff --git a/app/layout/header/header.controller.js b/app/layout/header/header.controller.js index 29b8efadc..9144cf3d4 100644 --- a/app/layout/header/header.controller.js +++ b/app/layout/header/header.controller.js @@ -13,7 +13,6 @@ import _ from 'lodash' vm.constants = CONSTANTS vm.domain = CONSTANTS.domain - vm.login = TcAuthService.login vm.checkSubmit = checkSubmit vm.searchTerm = '' vm.selectedGroup = selectedGroup diff --git a/app/services/api.service.js b/app/services/api.service.js index 6e6aa432d..d526296d9 100644 --- a/app/services/api.service.js +++ b/app/services/api.service.js @@ -6,9 +6,9 @@ import _ from 'lodash' angular.module('tc.services').factory('ApiService', ApiService) - ApiService.$inject = ['$http', 'logger', 'AuthTokenService', 'Restangular', 'CONSTANTS'] + ApiService.$inject = ['$http', 'logger', 'Restangular', 'CONSTANTS'] - function ApiService($http, logger, AuthTokenService, Restangular, CONSTANTS) { + function ApiService($http, logger, Restangular, CONSTANTS) { var service = { requestHandler: requestHandler, restangularV2: _getRestangularV2(), diff --git a/app/services/authToken.service.spec.js b/app/services/authToken.service.spec.js index 7a3ae186f..1f3f67f13 100644 --- a/app/services/authToken.service.spec.js +++ b/app/services/authToken.service.spec.js @@ -40,16 +40,6 @@ describe('TcAuthToken Service', function() { describe('AuthToken Service ', function() { - it('should call store to get v3 token', function() { - expect(service.getV3Token()).to.equal('value') - expect(store.get).to.be.have.been.calledWith('appiriojwt') - }) - - it('should call store to set v3 token', function() { - service.setV3Token('test') - expect(store.set).to.be.have.been.calledWith('appiriojwt', 'test') - }) - it('should retrieve token from cookie', function() { expect(service.getV2Token()).to.equal('value') expect($cookies.get).to.be.have.been.calledWith('tcjwt') @@ -62,11 +52,6 @@ describe('TcAuthToken Service', function() { expect(store.remove).to.be.have.been.calledWith('appiriojwt') }) - it('should use jwtHelper to decode token', function() { - expect(service.decodeToken('test')).to.equal('decodedToken') - expect(jwtHelper.decodeToken).to.be.have.been.calledWith('test') - }) - }) describe('Auth service ', function() { @@ -107,42 +92,5 @@ describe('TcAuthToken Service', function() { } }) }) - - it('should make a POST request to /authorizations', function() { - service.getTokenFromAuth0Code('test') - $httpBackend.expectPOST( - apiUrl + '/authorizations', {}, { - 'Content-Type': 'application/json', - 'Authorization': 'Auth0Code test' - } - ) - }) - - it('should make a POST request to exchange V2 token for V3 token', function() { - service.exchangeToken('refreshToken', 'idToken') - $httpBackend.expectPOST( - apiUrl + '/authorizations', - { - param: { - refreshToken: 'refreshToken', - externalToken: 'idToken' - } - }, - { - withCredentials: true - } - ) - }) - - it('should make a GET request to refresh V3 token', function() { - service.exchangeToken('refreshToken', 'idToken') - $httpBackend.expectGET( - apiUrl + '/authorizations/1', - {}, - { - 'Authorization': 'Bearer token' - } - ) - }) }) }) diff --git a/app/services/authtoken.service.js b/app/services/authtoken.service.js index 8b016e4b3..e1366dbb5 100644 --- a/app/services/authtoken.service.js +++ b/app/services/authtoken.service.js @@ -12,30 +12,15 @@ import angular from 'angular' var v2TCSSOTokenKey = 'tcsso' var v3TokenKey = 'appiriojwt' // use this api url over CONSTANTS - var apiUrl = CONSTANTS.AUTH_API_URL || CONSTANTS.API_URL + // var apiUrl = CONSTANTS.AUTH_API_URL || CONSTANTS.API_URL var service = { - setV3Token: setV3Token, getV2Token: getV2Token, - getV3Token: getV3Token, getTCSSOToken: getTCSSOToken, - removeTokens: removeTokens, - refreshV3Token: refreshV3Token, - exchangeToken: exchangeToken, - getTokenFromAuth0Code: getTokenFromAuth0Code, - decodeToken: decodeToken + removeTokens: removeTokens } return service - /////////////// - function setV3Token(token) { - store.set(v3TokenKey, token) - } - - function getV3Token() { - return store.get(v3TokenKey) - } - function getV2Token() { return $cookies.get(v2TokenKey) } @@ -52,87 +37,6 @@ import angular from 'angular' $cookies.remove('tcsso', {domain: domain}) store.remove(v3TokenKey) } - - function decodeToken(token) { - return jwtHelper.decodeToken(token) - } - - function refreshV3Token(token) { - // This is a promise of a JWT id_token - return $http({ - url: apiUrl + '/authorizations/1', - method: 'GET', - headers: { - 'Authorization': 'Bearer ' + token - }, - data: {} - }) - .then(function(res) { - var appiriojwt = res.data.result.content.token - - setV3Token(appiriojwt) - - return appiriojwt - }) - .catch(function(err) { - logger.error('Could not refresh v3 token', err) - - removeTokens() - }) - } - - function exchangeToken(refreshToken, idToken) { - var req = { - method: 'POST', - url: apiUrl + '/authorizations', - data: { - param: { - refreshToken: refreshToken, - externalToken: idToken - } - }, - skipAuthorization: true, - withCredentials: true, - headers: {} - } - return $q(function(resolve, reject) { - $http(req).then( - function(res) { - var appiriojwt = res.data.result.content.token - setV3Token(appiriojwt) - resolve(appiriojwt) - }, - function(err) { - logger.error('Could not exchange token', err) - - removeTokens() - - reject(err) - } - ) - }) - } - - function getTokenFromAuth0Code(code) { - var req = { - method: 'POST', - url: apiUrl + '/authorizations', - skipAuthorization: true, - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'Auth0Code ' + code - }, - data: {} - } - return $http(req).then( - function(resp) { - logger.debug(resp) - }, - function(err) { - logger.error('Could not get token from Auth0 code', err) - } - ) - } } })() diff --git a/app/services/authv3.module.js b/app/services/authv3.module.js new file mode 100644 index 000000000..d3442cb3a --- /dev/null +++ b/app/services/authv3.module.js @@ -0,0 +1,29 @@ +'use strict' + +import angular from 'angular' +require('angular-jwt') +import { getFreshToken, configureConnector } from 'tc-accounts' + +configureConnector({ + connectorUrl: process.env.ACCOUNTS_APP_CONNECTOR_URL, + frameId: 'tc-accounts-iframe' +}) + +const dependencies = ['angular-jwt'] + +const config = function($httpProvider, jwtInterceptorProvider) { + function jwtInterceptor() { + return getFreshToken() + } + + jwtInterceptorProvider.tokenGetter = jwtInterceptor + + $httpProvider.interceptors.push('jwtInterceptor') +} + +config.$inject = ['$httpProvider', 'jwtInterceptorProvider'] + +angular.module('appirio-tech-ng-auth', dependencies).config(config) + +// These must come after the module definition +require('./userv3.service.js') \ No newline at end of file diff --git a/app/services/jwtInterceptor.service.js b/app/services/jwtInterceptor.service.js index 5c5667911..ac734f750 100644 --- a/app/services/jwtInterceptor.service.js +++ b/app/services/jwtInterceptor.service.js @@ -1,18 +1,43 @@ import angular from 'angular' +import { getCurrentUser } from '../services/userv3.service.js' +import { isTokenExpired, getFreshToken } from 'tc-accounts' (function() { 'use strict' angular.module('tc.services').factory('JwtInterceptorService', JwtInterceptorService) - JwtInterceptorService.$inject = ['logger', 'jwtHelper', 'AuthTokenService', 'TcAuthService', '$state'] + JwtInterceptorService.$inject = ['logger', 'jwtHelper', 'AuthTokenService', 'TcAuthService', '$state', '$location', '$window', 'CONSTANTS'] - function JwtInterceptorService(logger, jwtHelper, AuthTokenService, TcAuthService, $state) { + function JwtInterceptorService(logger, jwtHelper, AuthTokenService, TcAuthService, $state, $location, $window, CONSTANTS) { var service = { getToken: getToken } //////////// + function _checkAndRefreshToken(config, token) { + // logger.debug('_checkAndRefreshToken: ' + config.url + ', ' + + token) + if (isTokenExpired(token)) { + logger.debug(String.supplant('Token has expired, attempting to refreshToken() for "{url}"', config)) + + return getFreshToken().then(function(refreshedToken) { + // logger.debug('Successfully refreshed V3 token.') + return refreshedToken + }) + .catch(function(err) { + // Server will not or cannot refresh token + logger.debug('Unable to refresh V3 token, redirecting to login') + var retUrl = CONSTANTS.MAIN_URL + '/?next=' + config.url + $window.location = CONSTANTS.ACCOUNTS_APP_URL + '?retUrl=' + encodeURIComponent(retUrl) + + return null + }) + } else { + // logger.debug('returning token ' + token) + return token + } + } + function getToken(config) { // skip token for .html if (config.url.indexOf('.html') > -1) @@ -30,39 +55,21 @@ import angular from 'angular' for (var i = 0; i < haveItAddItEndpoints.length; i++) { var obj = haveItAddItEndpoints[i] var re = new RegExp(obj.url) + // logger.debug('haveItAddItEndpoints[' + i + ']=' + obj.url + ' ===> config.url=' + config.url) if (config.method.toUpperCase() === obj.method && re.test(config.url)) { + // logger.debug('checking for authentication') if (TcAuthService.isAuthenticated()) { + // logger.debug('found authenticated') var token = null if (config.url.indexOf('v2/') > -1 || config.url.indexOf('memberCert') > -1 || config.url.indexOf('badges') > -1) { token = AuthTokenService.getV2Token() } else { - token = AuthTokenService.getV3Token() - } - // var token = config.url.indexOf('v2/') > -1 ? AuthTokenService.getV2Token() : AuthTokenService.getV3Token() - if (jwtHelper.isTokenExpired(token)) { - logger.debug(String.supplant('Token has expired, attempting to refreshToken() for "{url}"', config)) - - return AuthTokenService.refreshV3Token(token) - .then(function(idToken) { - logger.debug('Successfully refreshed V3 token.') - // v2 token doesn't expire - AuthTokenService.setV3Token(idToken) - return idToken - }) - .catch(function(err) { - // Server will not or cannot refresh token - logger.debug('Unable to refresh V3 token, redirecting to login') - logger.debug(err) - - $state.go('login') - - return null - }) - } else { - return token + token = getCurrentUser() !== null ? getCurrentUser().token : null } + // logger.debug('found token: ' + token) + return _checkAndRefreshToken(config, token) } // else logger.debug(String.supplant('Skipping authToken for "{url}, UnAuthenticated user"', config)) @@ -71,34 +78,18 @@ import angular from 'angular' } // for everything else assume that we need to send token - var idToken = config.url.indexOf('v2/') > -1 ? AuthTokenService.getV2Token() : AuthTokenService.getV3Token() + var idToken = config.url.indexOf('v2/') > -1 ? AuthTokenService.getV2Token() : (getCurrentUser() !== null ? getCurrentUser().token : null) + // logger.debug('idToken: ' + idToken) if (!TcAuthService.isAuthenticated() || idToken == null) { - $state.go('login') + // logger.debug('redirecting to accounts app') + var retUrl = CONSTANTS.MAIN_URL + '/?next=' + config.url + $window.location = CONSTANTS.ACCOUNTS_APP_URL + '?retUrl=' + encodeURIComponent(retUrl) return } - // Note only v3tokens expire - if (jwtHelper.isTokenExpired(idToken)) { - logger.debug(String.supplant('Token has expired, attempting to refreshToken() for "{url}"', config)) - return AuthTokenService.refreshV3Token(idToken) - .then(function(idToken) { - // v2 token doesn't expire - logger.debug('Successfully refreshed V3 token.') - AuthTokenService.setV3Token(idToken) - return idToken - }) - .catch(function(err) { - // Server will not or cannot refresh token - logger.debug('Unable to refresh V3 token, redirecting to login') - logger.debug(err) - - $state.go('login') - return null - }) - } else { - return idToken - } + // Note only v3tokens expire + return _checkAndRefreshToken(config, idToken) } return service } diff --git a/app/services/jwtInterceptor.service.spec.js b/app/services/jwtInterceptor.service.spec.js index 61140d0cb..7dac6c7d3 100644 --- a/app/services/jwtInterceptor.service.spec.js +++ b/app/services/jwtInterceptor.service.spec.js @@ -28,6 +28,9 @@ describe('JWT Interceptor Service', function() { go: sinon.spy(function(param) { return }) + }, + fakeWindow = { + location: '' } beforeEach(function() { @@ -35,8 +38,9 @@ describe('JWT Interceptor Service', function() { $provide.value('AuthTokenService', fakeAuthTokenService) $provide.value('TcAuthService', fakeTcAuthService) $provide.value('$state', fakeState) + $provide.value('$window', fakeWindow) }) - bard.inject(this, 'jwtHelper', 'AuthTokenService', '$state', 'JwtInterceptorService') + bard.inject(this, 'jwtHelper', 'CONSTANTS', 'AuthTokenService', '$state', '$window', 'JwtInterceptorService') service = JwtInterceptorService }) @@ -77,7 +81,8 @@ describe('JWT Interceptor Service', function() { url: apiUrl + '/v3/members/test' } service.getToken(config) - expect($state.go).to.be.have.been.calledWith('login') + expect($window.location).not.null + expect($window.location).to.have.string(CONSTANTS.ACCOUNTS_APP_URL) expect(TcAuthService.isAuthenticated).to.be.have.been.calledOnce }) @@ -87,7 +92,8 @@ describe('JWT Interceptor Service', function() { url: apiUrl + '/v3.0.0-BETA/members/test' } service.getToken(config) - expect($state.go).to.be.have.been.calledWith('login') + expect($window.location).not.null + expect($window.location).to.have.string(CONSTANTS.ACCOUNTS_APP_URL) expect(TcAuthService.isAuthenticated).to.be.have.been.calledOnce }) diff --git a/app/services/services.module.js b/app/services/services.module.js index 59c51dc2a..c7e45cc1c 100644 --- a/app/services/services.module.js +++ b/app/services/services.module.js @@ -18,8 +18,8 @@ import Auth0 from 'auth0-js' angular.module('tc.services', dependencies) .config(['authProvider', 'CONSTANTS', function(authProvider, CONSTANTS) { authProvider.init({ - domain: CONSTANTS.auth0Domain, - clientID: CONSTANTS.clientId, + domain: CONSTANTS.AUTH0_DOMAIN, + clientID: CONSTANTS.AUTH0_CLIENT_ID, sso: false }, Auth0) diff --git a/app/services/tcAuth.service.js b/app/services/tcAuth.service.js index eb6d34a53..db642c8ed 100644 --- a/app/services/tcAuth.service.js +++ b/app/services/tcAuth.service.js @@ -1,17 +1,16 @@ import angular from 'angular' +import { getCurrentUser, logout as doLogout } from './userv3.service.js' (function() { 'use strict' angular.module('tc.services').factory('TcAuthService', TcAuthService) - TcAuthService.$inject = ['CONSTANTS', 'auth', 'AuthTokenService', '$rootScope', '$q', 'logger', '$timeout', 'UserService', 'Helpers', 'ApiService', 'store', '$http'] + TcAuthService.$inject = ['CONSTANTS', 'auth', '$rootScope', '$q', 'logger', '$timeout', 'UserService', 'AuthTokenService', 'Helpers', 'ApiService', 'store', '$http'] - function TcAuthService(CONSTANTS, auth, AuthTokenService, $rootScope, $q, logger, $timeout, UserService, Helpers, ApiService, store, $http) { + function TcAuthService(CONSTANTS, auth, $rootScope, $q, logger, $timeout, UserService, AuthTokenService, Helpers, ApiService, store, $http) { var auth0 = auth var service = { - login: login, - socialLogin: socialLogin, socialRegistration: socialRegistration, logout: logout, register: register, @@ -19,87 +18,6 @@ import angular from 'angular' } return service - - /////////////// - function login(usernameOrEmail, password) { - return _doLogin({ - usernameOrEmail: usernameOrEmail, - password: password - }, null) - } - - function socialLogin(provider, state) { - return _doLogin(null, provider) - } - - function _doLogin(userCreds, provider) { - return $q(function(resolve, reject) { - // supported backends - var options = { - popup: true, - scope: 'openid profile offline_access' - } - // setup more options based on input - if (provider) { - var providers = ['facebook', 'google-oauth2', 'twitter', 'github'] - if (providers.indexOf(provider) < 0) { - reject({ - status: 'UNSUPORTED_PROVIDER' - }) - return - } else { - options.connection = provider - } - } else { - options.connection = Helpers.isEmail(userCreds.usernameOrEmail) ? 'TC-User-Database' : 'LDAP' - options.sso = false - options.username = userCreds.usernameOrEmail - options.password = userCreds.password - } - - auth0.signin(options, - function(profile, idToken, accessToken, state, refreshToken) { - AuthTokenService.exchangeToken(refreshToken, idToken).then( - function(appiriojwt) { - $timeout(function() { - $rootScope.$broadcast(CONSTANTS.EVENT_USER_LOGGED_IN) - - var userIdentity = UserService.getUserIdentity() - - if (userIdentity && !store.get(userIdentity.userId)) { - store.set(userIdentity.userId, {}) - } - resolve() - }, 200) - }, - function(resp) { - logger.debug(JSON.stringify(resp)) - // 401 status here implies user is not registered - if (resp.status === 401) { - reject({ - status: 'USER_NOT_REGISTERED' - }) - } - if (resp.data.result.content.toLowerCase() === 'account inactive') { - reject({ - status: 'ACCOUNT_INACTIVE' - }) - } else { - reject({ - status: 'UKNOWN_ERROR' - }) - } - } - ) - }, - function(error) { - logger.warning(JSON.stringify(error)) - reject(error) - } - ) - }) - } - function socialRegistration(provider, state) { return $q(function(resolve, reject) { // supported backends @@ -157,11 +75,8 @@ import angular from 'angular' function logout() { // logout of all browsers - return $q(function(resolve, reject) { - // remove local token - AuthTokenService.removeTokens() + return doLogout().then(function() { $rootScope.$broadcast(CONSTANTS.EVENT_USER_LOGGED_OUT) - resolve() }) } @@ -173,7 +88,11 @@ import angular from 'angular' } function isAuthenticated() { - return !!AuthTokenService.getV3Token() && !!AuthTokenService.getV2Token() && !!AuthTokenService.getTCSSOToken() + logger.debug('AuthTokenService.getV2Token(): ' + AuthTokenService.getV2Token()) + logger.debug('AuthTokenService.getTCSSOToken(): ' + AuthTokenService.getTCSSOToken()) + logger.debug('getCurrentUser(): ' + getCurrentUser()) + return !!getCurrentUser() && !!AuthTokenService.getV2Token() && !!AuthTokenService.getTCSSOToken() + } } diff --git a/app/services/tcAuth.service.spec.js b/app/services/tcAuth.service.spec.js index 9d4fee6c9..be9b022eb 100644 --- a/app/services/tcAuth.service.spec.js +++ b/app/services/tcAuth.service.spec.js @@ -21,9 +21,6 @@ describe('TcAuthService', function() { getV2Token: function() { return }, - getV3Token: function() { - return 'v3Token' - }, getTCSSOToken: function() { return 'tcssoToken' } @@ -41,9 +38,6 @@ describe('TcAuthService', function() { getV2Token: function() { return 'v2Token' }, - getV3Token: function() { - return - }, getTCSSOToken: function() { return 'tcssoToken' } @@ -61,9 +55,6 @@ describe('TcAuthService', function() { getV2Token: function() { return 'v2Token' }, - getV3Token: function() { - return 'v3Token' - }, getTCSSOToken: function() { return } @@ -90,7 +81,7 @@ describe('TcAuthService', function() { }) }) - it('should return true', function() { + xit('should return true', function() { expect(service.isAuthenticated()).to.be.true }) }) diff --git a/app/services/user.service.js b/app/services/user.service.js index e8c0aa0ef..da8430d81 100644 --- a/app/services/user.service.js +++ b/app/services/user.service.js @@ -1,14 +1,16 @@ import angular from 'angular' import _ from 'lodash' +import { getCurrentUser } from '../services/userv3.service.js' +import { decodeToken } from 'tc-accounts' (function() { 'use strict' angular.module('tc.services').factory('UserService', UserService) - UserService.$inject = ['CONSTANTS', 'ApiService', '$injector', 'AuthTokenService', 'UserPrefStore'] + UserService.$inject = ['CONSTANTS', 'ApiService', '$injector', 'UserPrefStore', 'logger'] - function UserService(CONSTANTS, ApiService, $injector, AuthTokenService, UserPrefStore) { + function UserService(CONSTANTS, ApiService, $injector, UserPrefStore, logger) { var api = ApiService.getApiServiceProvider('USER') @@ -38,10 +40,9 @@ import _ from 'lodash' ////////////////////////////////////////// function getUserIdentity() { - var TcAuthService = $injector.get('TcAuthService') - if (TcAuthService.isAuthenticated()) { - var decodedToken = AuthTokenService.decodeToken(AuthTokenService.getV3Token()) - return decodedToken + var currentUser = getCurrentUser() + if (currentUser) { + return decodeToken(currentUser.token) } else { return null } diff --git a/app/services/userv3.service.js b/app/services/userv3.service.js new file mode 100644 index 000000000..1e4362e65 --- /dev/null +++ b/app/services/userv3.service.js @@ -0,0 +1,52 @@ +'use strict' + +import angular from 'angular' +require('./authv3.module.js') + +// import includes from 'lodash/includes' +// import merge from 'lodash/merge' +// TODO: Move registration to accounts.topcoder.com +import { registerUser} from 'tc-accounts/core/auth.js' +import { decodeToken, getFreshToken, logout as doLogout } from 'tc-accounts' + +let currentUser = null + +export function loadUser() { + function loadUserSuccess(token) { + const decodedToken = decodeToken( token ) + + if (decodedToken.userId) { + currentUser = decodedToken + currentUser.id = currentUser.userId + currentUser.token = token + } + + return currentUser + } + + return getFreshToken().then(loadUserSuccess) +} + +export function getCurrentUser() { + return currentUser +} + +export function createUser(body) { + return registerUser(body) +} + +export function logout() { + return doLogout().then( () => { + currentUser = null + }) +} + +const UserV3Service = function() { + return { + getCurrentUser: getCurrentUser, + createUser: createUser, + loadUser: loadUser + } +} + +angular.module('appirio-tech-ng-auth').factory('UserV3Service', UserV3Service) \ No newline at end of file diff --git a/app/topcoder.constants.js b/app/topcoder.constants.js index 353be4e24..7631f6af4 100644 --- a/app/topcoder.constants.js +++ b/app/topcoder.constants.js @@ -7,9 +7,9 @@ angular.module('CONSTANTS', []).constant('CONSTANTS', { 'INTERNAL_API_URL' : process.env.INTERNAL_API_URL, 'ASSET_PREFIX' : process.env.ASSET_PREFIX || '', 'auth0Callback' : process.env.auth0Callback, - 'auth0Domain' : process.env.auth0Domain, + 'AUTH0_DOMAIN' : process.env.AUTH0_DOMAIN, + 'AUTH0_CLIENT_ID' : process.env.AUTH0_CLIENT_ID, 'BLOG_LOCATION' : process.env.BLOG_LOCATION, - 'clientId' : process.env.clientId, 'COMMUNITY_URL' : process.env.COMMUNITY_URL, 'domain' : process.env.domain, 'ENVIRONMENT' : process.env.ENVIRONMENT, @@ -20,6 +20,7 @@ angular.module('CONSTANTS', []).constant('CONSTANTS', { 'PHOTO_LINK_LOCATION' : process.env.PHOTO_LINK_LOCATION, 'SWIFT_PROGRAM_URL' : process.env.SWIFT_PROGRAM_URL, 'TCO16_URL' : process.env.TCO16_URL, + 'ACCOUNTS_APP_URL' : process.env.ACCOUNTS_APP_URL, 'NEW_CHALLENGES_URL' : 'https://www.topcoder.com/challenges/develop/upcoming/', 'SWIFT_PROGRAM_ID' : 3445, diff --git a/app/topcoder.module.js b/app/topcoder.module.js index 290b107d8..3be9e2472 100644 --- a/app/topcoder.module.js +++ b/app/topcoder.module.js @@ -1,4 +1,5 @@ import angular from 'angular' +import { getCurrentUser, loadUser } from './services/userv3.service.js' (function() { 'use strict' @@ -38,21 +39,39 @@ import angular from 'angular' angular.module('topcoder', dependencies).run(appRun) - appRun.$inject = ['$rootScope', '$state', 'TcAuthService', '$cookies', 'Helpers', 'logger'] + appRun.$inject = ['$rootScope', '$state', '$urlRouter', 'TcAuthService', 'CONSTANTS', '$window', '$cookies', 'Helpers', 'logger'] - function appRun($rootScope, $state, TcAuthService, $cookies, Helpers, logger) { + function appRun($rootScope, $state, $urlRouter, TcAuthService, CONSTANTS, $window, $cookies, Helpers, logger) { // Attaching $state to the $rootScope allows us to access the // current state in index.html (see the body tag) $rootScope.$state = $state // check AuthNAuth on change state start $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) { - if (toState.data.authRequired && !TcAuthService.isAuthenticated()) { - logger.debug('State requires authentication, and user is not logged in, redirecting') - // setup redirect for post login + logger.debug('checking auth for state: ' + toState.name + ' from state: ' + fromState.name) + var currentUser = getCurrentUser() + if (!currentUser) { event.preventDefault() - var next = $state.href(toState.name, toParams, {absolute: false}) - $state.go('login', {next: next}) + loadUser().then(function(token) { + logger.debug('successful login with token ' + JSON.stringify(token)) + $rootScope.$broadcast(CONSTANTS.EVENT_USER_LOGGED_IN) + logger.debug('Going to state: ' + toState.name) + $state.go(toState.name, toParams, {notify: false}) + $urlRouter.sync() + }, function() { + if (toState.data && toState.data.authRequired) { + logger.debug('State requires authentication, and user is not logged in, redirecting') + // setup redirect for post login + event.preventDefault() + var next = $state.href(toState.name, toParams, {absolute: true}) + var retUrl = next + $window.location = CONSTANTS.ACCOUNTS_APP_URL + '?retUrl=' + encodeURIComponent(retUrl) + } else { + logger.debug('Going to state: ' + toState.name) + $state.go(toState.name, toParams, {notify: false}) + $urlRouter.sync() + } + }) } }) diff --git a/assets/css/vendors/introjs.scss b/assets/css/vendors/introjs.scss index 78b9eeab7..1599f582b 100644 --- a/assets/css/vendors/introjs.scss +++ b/assets/css/vendors/introjs.scss @@ -1,4 +1,5 @@ @import 'topcoder/tc-includes'; +@import 'topcoder/tc-buttons'; // Intro JS overrides .introjs-overlay { diff --git a/assets/scripts/kissmetrics.analytics.js b/assets/scripts/kissmetrics.analytics.js index 9175ea6c7..e69de29bb 100644 --- a/assets/scripts/kissmetrics.analytics.js +++ b/assets/scripts/kissmetrics.analytics.js @@ -1,17 +0,0 @@ -// Tracking code for Kissmetrics -var _kmq = _kmq || [] -var _kmk = _kmk || 'aa23cd43c455ef33b6a0df3de81a79af9ea30f75' -function _kms(u){ - setTimeout(function(){ - var d = document - var f = d.getElementsByTagName('script')[0] - var s = d.createElement('script') - s.type = 'text/javascript' - s.async = true - s.src = u - f.parentNode.insertBefore(s, f) - }, 1) -} - -_kms('//i.kissmetrics.com/i.js') -_kms('//scripts.kissmetrics.com/' + _kmk + '.2.js') diff --git a/karma.conf.js b/karma.conf.js index da6b3cec8..959a70f6c 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -9,6 +9,7 @@ webpackConfig.module.loaders.push({ test: /jquery-1\.10\.2\.js$/, loader: 'expose?jQuery' }) +process.env.ACCOUNTS_APP_URL = `http://accounts.${process.env.domain}/tc` module.exports = function(config) { config.set({ diff --git a/package.json b/package.json index 70fe41f59..43ff177ac 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "tc-angular-ellipsis": "^0.1.6", "topcoder-app-r": "^1.0.0", "xml2js": "^0.4.16", - "zepto": "^1.0.1" + "zepto": "^1.0.1", + "tc-accounts": "https://github.com/appirio-tech/accounts-app.git#dev" } } diff --git a/webpack.config.js b/webpack.config.js index 9aab80154..68e7d8efe 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,9 +2,23 @@ require('./node_modules/coffee-script/register') const CI = process.env.TRAVIS_BRANCH -if (CI == 'master') process.env.ENV = 'PROD' -if (CI == 'qa-integration') process.env.ENV = 'QA' -if (CI == 'dev') process.env.ENV = 'DEV' +if (CI === 'master') { + process.env.ENV = 'PROD' + process.env.DOMAIN = 'topcoder.com' + process.env.NODE_ENV = 'production' +} else if (CI === 'qa') { + process.env.ENV = 'QA' + process.env.DOMAIN = 'topcoder-qa.com' + process.env.NODE_ENV = 'production' +} else { + process.env.ENV = 'DEV' + process.env.DOMAIN = 'topcoder-dev.com' + process.env.NODE_ENV = 'development' +} + + +process.env.CONNECTOR_URL = `https://accounts.${process.env.DOMAIN}/connector.html` +process.env.ACCOUNTS_APP_URL = `https://accounts.${process.env.DOMAIN}/tc` const config = require('appirio-tech-webpack-config')({ dirname: __dirname,