diff --git a/src/models/api/requestModels.js b/src/models/api/requestModels.js index f83082a4aa..c975511f63 100644 --- a/src/models/api/requestModels.js +++ b/src/models/api/requestModels.js @@ -15,10 +15,29 @@ var FolderRequest = function (folder) { var TokenRequest = function (email, masterPasswordHash, device) { this.email = email; this.masterPasswordHash = masterPasswordHash; + this.device = null; if (device) { this.device = new DeviceRequest(device); } - this.device = null; + + this.toIdentityToken = function () { + var obj = { + grant_type: 'password', + username: this.email, + password: this.masterPasswordHash, + scope: 'api offline_access', + client_id: 'browser' + }; + + if (this.device) { + obj.deviceType = this.device.type; + obj.deviceIdentifier = this.device.identifier; + obj.deviceName = this.device.name; + obj.devicePushToken = this.device.pushToken; + } + + return obj; + }; }; var RegisterRequest = function (email, masterPasswordHash, masterPasswordHint) { diff --git a/src/models/api/responseModels.js b/src/models/api/responseModels.js index 6609303fe8..63c097d8ac 100644 --- a/src/models/api/responseModels.js +++ b/src/models/api/responseModels.js @@ -46,6 +46,15 @@ var TokenResponse = function (response) { } }; +var IdentityTokenResponse = function (response) { + this.accessToken = response.access_token; + this.expiresIn = response.expires_in; + this.refreshToken = response.refresh_token; + this.tokenType = response.token_type; + + // TODO: extras +}; + var ListResponse = function (data) { this.data = data; }; diff --git a/src/popup/app/config.js b/src/popup/app/config.js index e95bf59126..03a700c9e6 100644 --- a/src/popup/app/config.js +++ b/src/popup/app/config.js @@ -197,35 +197,33 @@ } cryptoService.getKey(false, function (key) { - tokenService.getToken(function (token) { - userService.isAuthenticated(function (isAuthenticated) { - if (isAuthenticated) { - var obj = {}; - obj[constantsService.lastActiveKey] = (new Date()).getTime(); - chrome.storage.local.set(obj, function () { }); - } + userService.isAuthenticated(function (isAuthenticated) { + if (isAuthenticated) { + var obj = {}; + obj[constantsService.lastActiveKey] = (new Date()).getTime(); + chrome.storage.local.set(obj, function () { }); + } - if (!toState.data || !toState.data.authorize) { - if (isAuthenticated && !tokenService.isTokenExpired(token)) { - event.preventDefault(); - if (!key) { - $state.go('lock'); - } - else { - $state.go('tabs.current'); - } - } - - return; - } - - if (!isAuthenticated || tokenService.isTokenExpired(token)) { + if (!toState.data || !toState.data.authorize) { + if (isAuthenticated && !tokenService.isTokenExpired()) { event.preventDefault(); - authService.logOut(function () { - $state.go('home'); - }); + if (!key) { + $state.go('lock'); + } + else { + $state.go('tabs.current'); + } } - }); + + return; + } + + if (!isAuthenticated || tokenService.isTokenExpired()) { + event.preventDefault(); + authService.logOut(function () { + $state.go('home'); + }); + } }); }); }); diff --git a/src/popup/app/services/authService.js b/src/popup/app/services/authService.js index d3b707b4a3..282e6f8351 100644 --- a/src/popup/app/services/authService.js +++ b/src/popup/app/services/authService.js @@ -12,25 +12,25 @@ cryptoService.hashPassword(masterPassword, key, function (hashedPassword) { var request = new TokenRequest(email, hashedPassword); - apiService.postToken(request, function (response) { - if (!response || !response.token) { + apiService.postIdentityToken(request, function (response) { + if (!response || !response.accessToken) { return; } - tokenService.setToken(response.token, function () { + tokenService.setTokens(response.accessToken, response.refreshToken, function () { cryptoService.setKey(key, function () { cryptoService.setKeyHash(hashedPassword, function () { - if (response.profile) { - userService.setUserId(response.profile.id, function () { - userService.setEmail(response.profile.email, function () { + if (tokenService.isTwoFactorScheme()) { + deferred.resolve(response); + } + else { + userService.setUserId(tokenService.getUserId(), function () { + userService.setEmail(tokenService.getEmail(), function () { chrome.runtime.sendMessage({ command: 'loggedIn' }); deferred.resolve(response); }); }); } - else { - deferred.resolve(response); - } }); }); }); diff --git a/src/services/apiService.js b/src/services/apiService.js index efe7011a2f..b534351d8e 100644 --- a/src/services/apiService.js +++ b/src/services/apiService.js @@ -8,29 +8,12 @@ function ApiService(tokenService) { function initApiService() { // Auth APIs - ApiService.prototype.postToken = function (tokenRequest, success, error) { - var self = this; - $.ajax({ - type: 'POST', - url: self.baseUrl + '/auth/token', - data: JSON.stringify(tokenRequest), - contentType: 'application/json; charset=utf-8', - dataType: 'json', - success: function (response) { - success(new TokenResponse(response)); - }, - error: function (jqXHR, textStatus, errorThrown) { - handleError(error, jqXHR, textStatus, errorThrown); - } - }); - }; - ApiService.prototype.postTokenTwoFactor = function (twoFactorTokenRequest, success, error) { var self = this; this.tokenService.getToken(function (token) { $.ajax({ type: 'POST', - url: self.baseUrl + '/auth/token/two-factor?access_token=' + token, + url: self.baseUrl + '/auth/token/two-factor?access_token2=' + token, data: JSON.stringify(twoFactorTokenRequest), contentType: 'application/json; charset=utf-8', dataType: 'json', @@ -44,6 +27,24 @@ function initApiService() { }); }; + ApiService.prototype.postIdentityToken = function (tokenRequest, success, error) { + var self = this; + + $.ajax({ + type: 'POST', + url: self.baseUrl + '/connect/token', + data: tokenRequest.toIdentityToken(), + contentType: 'application/x-www-form-urlencoded; charset=utf-8', + dataType: 'json', + success: function (response) { + success(new IdentityTokenResponse(response)); + }, + error: function (jqXHR, textStatus, errorThrown) { + handleError(error, jqXHR, textStatus, errorThrown); + } + }); + }; + // Account APIs ApiService.prototype.getAccountRevisionDate = function (success, error) { @@ -51,7 +52,7 @@ function initApiService() { this.tokenService.getToken(function (token) { $.ajax({ type: 'GET', - url: self.baseUrl + '/accounts/revision-date?access_token=' + token, + url: self.baseUrl + '/accounts/revision-date?access_token2=' + token, dataType: 'json', success: function (response) { success(response); @@ -68,7 +69,7 @@ function initApiService() { this.tokenService.getToken(function (token) { $.ajax({ type: 'GET', - url: self.baseUrl + '/accounts/profile?access_token=' + token, + url: self.baseUrl + '/accounts/profile?access_token2=' + token, dataType: 'json', success: function (response) { success(new ProfileResponse(response)); @@ -121,7 +122,7 @@ function initApiService() { this.tokenService.getToken(function (token) { $.ajax({ type: 'GET', - url: self.baseUrl + '/settings/domains?excluded=false&access_token=' + token, + url: self.baseUrl + '/settings/domains?excluded=false&access_token2=' + token, dataType: 'json', success: function (response) { success(new DomainsResponse(response)); @@ -140,7 +141,7 @@ function initApiService() { this.tokenService.getToken(function (token) { $.ajax({ type: 'GET', - url: self.baseUrl + '/sites/' + id + '?access_token=' + token, + url: self.baseUrl + '/sites/' + id + '?access_token2=' + token, dataType: 'json', success: function (response) { success(new LoginResponse(response)); @@ -157,7 +158,7 @@ function initApiService() { this.tokenService.getToken(function (token) { $.ajax({ type: 'POST', - url: self.baseUrl + '/sites?access_token=' + token, + url: self.baseUrl + '/sites?access_token2=' + token, data: JSON.stringify(loginRequest), contentType: 'application/json; charset=utf-8', dataType: 'json', @@ -176,7 +177,7 @@ function initApiService() { this.tokenService.getToken(function (token) { $.ajax({ type: 'POST', - url: self.baseUrl + '/sites/' + id + '?access_token=' + token, + url: self.baseUrl + '/sites/' + id + '?access_token2=' + token, data: JSON.stringify(loginRequest), contentType: 'application/json; charset=utf-8', dataType: 'json', @@ -197,7 +198,7 @@ function initApiService() { this.tokenService.getToken(function (token) { $.ajax({ type: 'GET', - url: self.baseUrl + '/folders/' + id + '?access_token=' + token, + url: self.baseUrl + '/folders/' + id + '?access_token2=' + token, dataType: 'json', success: function (response) { success(new FolderResponse(response)); @@ -214,7 +215,7 @@ function initApiService() { this.tokenService.getToken(function (token) { $.ajax({ type: 'POST', - url: self.baseUrl + '/folders?access_token=' + token, + url: self.baseUrl + '/folders?access_token2=' + token, data: JSON.stringify(folderRequest), contentType: 'application/json; charset=utf-8', dataType: 'json', @@ -233,7 +234,7 @@ function initApiService() { this.tokenService.getToken(function (token) { $.ajax({ type: 'POST', - url: self.baseUrl + '/folders/' + id + '?access_token=' + token, + url: self.baseUrl + '/folders/' + id + '?access_token2=' + token, data: JSON.stringify(folderRequest), contentType: 'application/json; charset=utf-8', dataType: 'json', @@ -254,7 +255,7 @@ function initApiService() { this.tokenService.getToken(function (token) { $.ajax({ type: 'GET', - url: self.baseUrl + '/ciphers/' + id + '?access_token=' + token, + url: self.baseUrl + '/ciphers/' + id + '?access_token2=' + token, dataType: 'json', success: function (response) { success(new CipherResponse(response)); @@ -271,7 +272,7 @@ function initApiService() { this.tokenService.getToken(function (token) { $.ajax({ type: 'GET', - url: self.baseUrl + '/ciphers?access_token=' + token, + url: self.baseUrl + '/ciphers?access_token2=' + token, dataType: 'json', success: function (response) { var data = []; @@ -293,7 +294,7 @@ function initApiService() { this.tokenService.getToken(function (token) { $.ajax({ type: 'POST', - url: self.baseUrl + '/ciphers/' + id + '/delete?access_token=' + token, + url: self.baseUrl + '/ciphers/' + id + '/delete?access_token2=' + token, dataType: 'text', success: function (response) { success(); diff --git a/src/services/tokenService.js b/src/services/tokenService.js index 9353050db6..70e7087095 100644 --- a/src/services/tokenService.js +++ b/src/services/tokenService.js @@ -3,7 +3,22 @@ }; function initTokenService() { - var _token; + var _token, + _decodedToken, + _refreshToken; + + TokenService.prototype.setTokens = function (accessToken, refreshToken, callback) { + if (!callback || typeof callback !== 'function') { + throw 'callback function required'; + } + + var self = this; + self.setToken(accessToken, function () { + self.setRefreshToken(refreshToken, function () { + callback(); + }); + }); + }; TokenService.prototype.setToken = function (token, callback) { if (!callback || typeof callback !== 'function') { @@ -11,8 +26,9 @@ function initTokenService() { } _token = token; + _decodedToken = null; chrome.storage.local.set({ - 'authBearer': token + 'accessToken': token }, function () { callback(); }); @@ -27,48 +43,88 @@ function initTokenService() { return callback(_token); } - chrome.storage.local.get('authBearer', function (obj) { - if (obj && obj.authBearer) { - _token = obj.authBearer; + chrome.storage.local.get('accessToken', function (obj) { + if (obj && obj.accessToken) { + _token = obj.accessToken; } return callback(_token); }); }; + TokenService.prototype.setRefreshToken = function (refreshToken, callback) { + if (!callback || typeof callback !== 'function') { + throw 'callback function required'; + } + + _refreshToken = refreshToken; + chrome.storage.local.set({ + 'refreshToken': refreshToken + }, function () { + callback(); + }); + }; + + TokenService.prototype.getRefreshToken = function (callback) { + if (!callback || typeof callback !== 'function') { + throw 'callback function required'; + } + + if (_refreshToken) { + return callback(_refreshToken); + } + + chrome.storage.local.get('refreshToken', function (obj) { + if (obj && obj.refreshToken) { + _refreshToken = obj.refreshToken; + } + + return callback(_refreshToken); + }); + }; + TokenService.prototype.clearToken = function (callback) { if (!callback || typeof callback !== 'function') { throw 'callback function required'; } - _token = null; - chrome.storage.local.remove('authBearer', function () { - callback(); + _token = _decodedToken = _refreshToken = null; + chrome.storage.local.remove('accessToken', function () { + chrome.storage.local.remove('refreshToken', function () { + callback(); + }); }); }; // jwthelper methods // ref https://github.com/auth0/angular-jwt/blob/master/src/angularJwt/services/jwt.js - TokenService.prototype.decodeToken = function (token) { - var parts = token.split('.'); + TokenService.prototype.decodeToken = function () { + if (_decodedToken) { + return _decodedToken; + } + if (!_token) { + throw 'Token not found.'; + } + + var parts = _token.split('.'); if (parts.length !== 3) { - throw new Error('JWT must have 3 parts'); + throw 'JWT must have 3 parts'; } var decoded = urlBase64Decode(parts[1]); if (!decoded) { - throw new Error('Cannot decode the token'); + throw 'Cannot decode the token'; } return JSON.parse(decoded); }; - TokenService.prototype.getTokenExpirationDate = function (token) { - var decoded = this.decodeToken(token); + TokenService.prototype.getTokenExpirationDate = function () { + var decoded = this.decodeToken(); - if (typeof decoded.exp === "undefined") { + if (typeof decoded.exp === 'undefined') { return null; } @@ -78,8 +134,8 @@ function initTokenService() { return d; }; - TokenService.prototype.isTokenExpired = function (token, offsetSeconds) { - var d = this.getTokenExpirationDate(token); + TokenService.prototype.isTokenExpired = function (offsetSeconds) { + var d = this.getTokenExpirationDate(); offsetSeconds = offsetSeconds || 0; if (d === null) { return false; @@ -89,6 +145,42 @@ function initTokenService() { return !(d.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000))); }; + TokenService.prototype.isTwoFactorScheme = function () { + return this.getScheme() !== 'Application'; + }; + + TokenService.prototype.getScheme = function () { + var decoded = this.decodeToken(); + + if (typeof decoded.amr === 'undefined' || !decoded.amr.length) { + throw 'No scheme found'; + } + + return decoded.amr[0]; + }; + + TokenService.prototype.getUserId = function () { + var decoded = this.decodeToken(); + + if (typeof decoded.sub === 'undefined') { + throw 'No user id found'; + } + + return decoded.sub; + }; + + TokenService.prototype.getEmail = function () { + var decoded = this.decodeToken(); + + var email = decoded['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress']; + + if (typeof email === 'undefined') { + throw 'No email found'; + } + + return email; + }; + function urlBase64Decode(str) { var output = str.replace(/-/g, '+').replace(/_/g, '/'); switch (output.length % 4) {