/** * Copyright© 2016, Oracle and/or its affiliates. All rights reserved. */ /** * Class used to authorize a user against Facebook and use the OAuth token from Facebook * to authenicate against Oracle Mobile Cloud Service. Callers should use * MobileBackend's [FacebookAuthorization()]{@link MobileBackend#authorization} property. * Derives from {@link Authorization}. * @constructor * @global */ function FacebookAuthorization(config, backend, appKey, utils, platform, logger) { var HEADERS = utils.HEADERS; Authorization.call(this, backend, appKey, utils, platform, logger); var _backendId = utils.validateConfiguration(config.backendId); var _anonymousToken = utils.validateConfiguration(config.anonymousToken); var _facebookAppId = utils.validateConfiguration(config.facebookAppId); var expiredTime = null; var _this = this; /** * Returns the Facebook Application Id token for the current backend. */ this.getFacebookAppId = function () { return _facebookAppId; }; /** * Callback invoked after successfully authenticating. * @callback Authorization~authenticateSuccessCallback * @param statusCode {Number} Any HTTP status code returned from the server, if available. * @param message {String} The HTTP payload from the server, if available, or an error message. */ /** * Callback invoked on error while authenticating. * @callback Authorization~authenticateErrorCallback * @param statusCode {Number} Any HTTP status code returned from the server, if available. * @param message {String} The HTTP payload from the server, if available, or an error message. */ /** * Authenticates a user with the given credentials against Facebook. The user remains logged in until logout() is called. * In the Facebook Developer console you must define the domain that the application will use. * in the Facebook Developer UI, When you add a platform for the application, you choose Website and set the site URL to http://localhost/. * @param [successCallback] {Authorization~authenticateSuccessCallback} Optional callback invoked on success (deprecated use promises instead). * @param [errorCallback] {Authorization~authenticateErrorCallback} Optional callback invoked on failure (deprecated use promises instead). * @return {Promise.<NetworkResponse|NetworkResponse>} */ this.authenticate = function (successCallback, errorCallback) { this.logout(); if (window.cordova) { var metadata = cordova.require('cordova/plugin_list').metadata; if (IsInAppBrowserInstalled(metadata) !== true) { if (errorCallback != null) { errorCallback(100, 'Could not find InAppBrowser plugin, use command "cordova plugin add cordova-plugin-inappbrowser"'); return undefined; } else { return Promise.reject(new NetworkResponse(100, 'Could not find InAppBrowser plugin, use command "cordova plugin add cordova-plugin-inappbrowser"')); } } else { return authenticateInvoke(successCallback, errorCallback); } } else { if (errorCallback != null) { errorCallback(400, 'Bad Request - This method require Cordova framework'); return undefined; } else { return Promise.reject(new NetworkResponse(400, 'Bad Request - This method require Cordova framework')); } } }; function authenticateInvoke (successCallback, errorCallback){ return new Promise(invoke) .then(invokeSuccess) .catch(invokeError); function invoke(resolve, reject){ var clientId = _this.getFacebookAppId(); var redirect_uri = 'http://localhost/callback'; var flowUrl = 'https://www.facebook.com/dialog/oauth?client_id=' + clientId + '&redirect_uri=' + redirect_uri + '&response_type=token&scope=' + 'public_profile'; var browserRef = window.open(flowUrl, '_blank', 'location=no,clearsessioncache=yes,clearcache=yes'); browserRef.show(); logger.info('Opening InAppBrowser to url: ' + flowUrl); browserRef.addEventListener('loadstart', function loadStart(event) { if ((event.url).indexOf(redirect_uri) === 0) { browserRef.close(); var callbackResponse = (event.url).split('#')[1]; var responseParameters = (callbackResponse).split('&'); var social_token = {}; for (var i = 0; i < responseParameters.length; i++) { social_token[responseParameters[i].split('=')[0]] = responseParameters[i].split('=')[1]; } if (social_token.access_token) { expiredTime = Date.now() + social_token.expires_in * 1000; resolve(new NetworkResponse(200, social_token)); } else { if ((event.url).indexOf('error_code=100') !== 0 && !_this.isAuthorized) { reject(new NetworkResponse(100, 'Cannot authenticate via a web browser')); } } } }); browserRef.addEventListener('exit', function () { if (!_this._getIsAuthorized()) { reject(new NetworkResponse(100, 'Cannot authenticate via a web browser')); } }) } function invokeSuccess(response){ _this._authenticateSuccess(response, response.data.access_token); expiredTime = Date.now() + response.data.expires_in * 1000; if (successCallback) { successCallback(response.statusCode, response.data); } return response; } function invokeError(response){ _this._authenticateError(response); if (errorCallback) { errorCallback(response.statusCode, response.data); } else { return Promise.reject(response); } } } /** * Authenticates an anonymous user against the service. The user remains logged in until logout() is called. * @param successCallback {Authorization~authenticateSuccessCallback} Optional callback invoked on success (deprecated use promises instead). * @param errorCallback {Authorization~authenticateErrorCallback} Optional callback invoked on failure (deprecated use promises instead). * @return {Promise.<NetworkResponse|NetworkResponse>} */ this.authenticateAnonymous = function (successCallback, errorCallback) { var authorizationToken = 'Basic ' + _anonymousToken; var headers = {}; headers[HEADERS.ORACLE_MOBILE_BACKEND_ID] = _backendId; return this._authenticateAnonymousInvoke(authorizationToken, headers, backend.getPlatformUrl("users/login"), utils.HTTP_METHODS.GET) .then(invokeServiceSuccess, invokeServiceError); function invokeServiceSuccess(response) { if (successCallback) { successCallback(response.statusCode, response.data); } return response; } function invokeServiceError(response) { if(errorCallback) { errorCallback(response.statusCode, response.data); } else { return Promise.reject(response); } } }; this._anonymousTokenResponseConverter = function(response){ return { orgResponse: response.orgResponse, anonymousAccessToken: response.authorizationToken }; }; /** * Checks to see if the OAuth token is null, undefined, NaN, AN empty string (''), 0, or false. It also checks the timestamp * for when the token was first retrieved to see if it was still valid. * @returns {Boolean} */ this.isTokenValid = function () { if (this.getAccessToken() || this._getAnonymousAccessToken()) { logger.verbose('Token is not null or empty'); var currentTime = Date.now(); if (currentTime >= expiredTime) { logger.info('Token has expired or user has not been authenticate with the service/Facebook'); return false; } else { logger.verbose('Token is still valid'); return true; } } else { return false; } }; /** * Refreshes the authentication token if it has expired from Facebook. The authentication scheme should support refresh. * @param successCallback {Authorization~authenticateSuccessCallback} Optional callback invoked on success (deprecated use promises instead). * @param errorCallback {Authorization~authenticateErrorCallback} Optional callback invoked on failure (deprecated use promises instead). * @return {Promise.<NetworkResponse|NetworkResponse>} */ this.refreshToken = function(successCallback,errorCallback) { var isTokenValid = _this.isTokenValid(); if (isTokenValid && this.getAccessToken() == null && this._getIsAnonymous()) { if (successCallback) { logger.error('Anonymous token is valid, you do not need to refresh.'); successCallback(200, this._getAnonymousAccessToken()); } return Promise.resolve(new NetworkResponse(200, this._getAnonymousAccessToken())); } else if (isTokenValid && this._getAnonymousAccessToken() && !this._getIsAnonymous()) { if (successCallback) { logger.error('Authenticated token is valid, you do not need to refresh.'); successCallback(200, this.getAccessToken()); } return Promise.resolve(new NetworkResponse(200, this._getAnonymousAccessToken())); } else { logger.error('Token is not valid and has expired, refreshing token from Facebook.'); return this.authenticate(successCallback, errorCallback); } }; /** * Logs out the current user and clears credentials and tokens. */ this.logout = function() { this._clearState(); expiredTime = Date.now() * 1000; }; this._getHttpHeaders = function(headers) { if (this.getAccessToken() != null && typeof this.getAccessToken() == 'string') { headers[HEADERS.AUTHORIZATION] = 'Basic ' + _anonymousToken; headers[HEADERS.ORACLE_MOBILE_SOCIAL_ACCESS_TOKEN] = this.getAccessToken(); headers[HEADERS.ORACLE_MOBILE_SOCIAL_IDENTITY_PROVIDER] = 'facebook'; } headers[HEADERS.ORACLE_MOBILE_BACKEND_ID] = _backendId; headers[HEADERS.ORACLE_MOBILE_APPLICATION_KEY] = this._getApplicationKey(); }; this._getAnonymousHttpHeaders = function (headers) { if (this._getAnonymousAccessToken() && typeof this._getAnonymousAccessToken() == 'string') { headers[HEADERS.AUTHORIZATION] = this._getAnonymousAccessToken(); } headers[HEADERS.ORACLE_MOBILE_BACKEND_ID] = _backendId; headers[HEADERS.ORACLE_MOBILE_APPLICATION_KEY] = this._getApplicationKey(); } } /** * Checks to see if the correct plugin is installed into the application. * @return {boolean} */ var IsInAppBrowserInstalled = function (metadata) { var inAppBrowserNames = ['cordova-plugin-inappbrowser', 'org.apache.cordova.inappbrowser']; return inAppBrowserNames.some(function (name) { return metadata.hasOwnProperty(name); }); }; FacebookAuthorization.prototype = Object.create(Authorization.prototype); FacebookAuthorization.prototype.constructor = FacebookAuthorization;