diff --git a/packages/accounts-google/google.js b/packages/accounts-google/google.js index f6e092f930..ab5c4c97ea 100644 --- a/packages/accounts-google/google.js +++ b/packages/accounts-google/google.js @@ -8,6 +8,15 @@ if (Meteor.isClient) { options = null; } + if (Meteor.isCordova && + Google.signIn) { + // After 20 April 2017, Google OAuth login will no longer work from + // a WebView, so Cordova apps must use Google Sign-In instead. + // https://github.com/meteor/meteor/issues/8253 + Google.signIn(options, callback); + return; + } + // Use Google's domain-specific login page if we want to restrict creation to // a particular email domain. (Don't use it if restrictCreationByEmailDomain // is a function.) Note that all this does is change Google's UI --- diff --git a/packages/accounts-google/package.js b/packages/accounts-google/package.js index 4b398a98df..6c624c9e08 100644 --- a/packages/accounts-google/package.js +++ b/packages/accounts-google/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Login service for Google accounts", - version: "1.1.1" + version: "1.1.2" }); Package.onUse(function(api) { diff --git a/packages/google-oauth/google_client.js b/packages/google-oauth/google_client.js index b8af26fee4..c7b54dccfb 100644 --- a/packages/google-oauth/google_client.js +++ b/packages/google-oauth/google_client.js @@ -1,4 +1,4 @@ -Google = {}; +var Google = require("./namespace.js"); // Request Google credentials for the user // @param options {optional} diff --git a/packages/google-oauth/google_server.js b/packages/google-oauth/google_server.js index 33bdac656b..4b647a37d5 100644 --- a/packages/google-oauth/google_server.js +++ b/packages/google-oauth/google_server.js @@ -1,12 +1,38 @@ -Google = {}; +var Google = require("./namespace.js"); +var Accounts = require("meteor/accounts-base").Accounts; // https://developers.google.com/accounts/docs/OAuth2Login#userinfocall Google.whitelistedFields = ['id', 'email', 'verified_email', 'name', 'given_name', 'family_name', 'picture', 'locale', 'timezone', 'gender']; +Accounts.registerLoginHandler(function (request) { + if (request.googleSignIn !== true) { + return; + } + + var res = HTTP.get( + "https://www.googleapis.com/oauth2/v3/tokeninfo", + { headers: { "User-Agent": "Meteor/1.0" }, + params: { id_token: request.idToken }} + ); + + if (res.error) { + throw res.error; + } + + if (res.statusCode === 200 && + res.data.sub === request.userId) { + return Accounts.updateOrCreateUserFromExternalService("google", { + id: request.userId, + idToken: request.idToken, + accessToken: request.accessToken, + email: request.email, + picture: request.imageUrl + }); + } +}); OAuth.registerService('google', 2, null, function(query) { - var response = getTokens(query); var expiresAt = (+new Date) + (1000 * parseInt(response.expiresIn, 10)); var accessToken = response.accessToken; diff --git a/packages/google-oauth/google_sign-in.js b/packages/google-oauth/google_sign-in.js new file mode 100644 index 0000000000..4d21403264 --- /dev/null +++ b/packages/google-oauth/google_sign-in.js @@ -0,0 +1,84 @@ +var Google = require("./namespace.js"); + +var gplusPromise = new Promise(function (resolve, reject) { + if (! Meteor.isCordova) { + reject(new Error("plugins.googleplus requires Cordova")); + return; + } + + Meteor.startup(function () { + var plugins = global.plugins; + var gplus = plugins && plugins.googleplus; + if (gplus) { + resolve(gplus); + } else { + reject(new Error("plugins.googleplus not defined")); + } + }); +}); + +function tolerateUnhandledRejection() {} +gplusPromise.catch(tolerateUnhandledRejection); + +// After 20 April 2017, Google OAuth login will no longer work from a +// WebView, so Cordova apps must use Google Sign-In instead. +// https://github.com/meteor/meteor/issues/8253 +exports.signIn = Google.signIn = function (options, callback) { + // support a callback without options + if (! callback && typeof options === "function") { + callback = options; + options = null; + } + + gplusPromise.then(function (gplus) { + var config = ServiceConfiguration.configurations.findOne({ + service: "google" + }); + + if (! config) { + throw new ServiceConfiguration.ConfigError(); + } + + options = Object.assign(Object.create(null), options); + + gplus.login({ + scopes: getScopes(options).join(" "), + webClientId: config.clientId, + offline: true + }, function (response) { + Accounts.callLoginMethod({ + methodArguments: [Object.assign({ + googleSignIn: true + }, response)], + userCallback: callback + }); + }, callback); + + }).catch(callback); +}; + +function getScopes(options) { + // we need the email scope to get user id from google. + var requiredScopes = ['email']; + var scopes = ['profile']; + if (options && options.requestPermissions) { + scopes = options.requestPermissions; + } + return _.union(scopes, requiredScopes); +} + +exports.signOut = Google.signOut = function () { + return gplusPromise.then(function (gplus) { + return new Promise(function (resolve) { + gplus.logout(resolve); + }); + }); +}; + +// Make sure we don't stay logged in with Google Sign-In after the client +// calls Meteor.logout(). +Meteor.startup(function () { + Accounts.onLogout(function () { + Google.signOut().catch(tolerateUnhandledRejection); + }); +}); diff --git a/packages/google-oauth/namespace.js b/packages/google-oauth/namespace.js new file mode 100644 index 0000000000..ba7feea718 --- /dev/null +++ b/packages/google-oauth/namespace.js @@ -0,0 +1,6 @@ +// The module.exports object of this module becomes the Google namespace +// for other modules in this package. +Google = module.exports; + +// So that api.export finds the "Google" property. +Google.Google = Google; diff --git a/packages/google-oauth/package.js b/packages/google-oauth/package.js index 7b8f76d401..8193e069b2 100644 --- a/packages/google-oauth/package.js +++ b/packages/google-oauth/package.js @@ -1,9 +1,15 @@ Package.describe({ summary: "Google OAuth flow", - version: "1.2.0" + version: "1.2.1" +}); + +Cordova.depends({ + "cordova-plugin-googleplus": "5.1.1" }); Package.onUse(function(api) { + api.use("modules"); + api.use("promise"); api.use('oauth2', ['client', 'server']); api.use('oauth', ['client', 'server']); api.use('http', ['server']); @@ -12,6 +18,9 @@ Package.onUse(function(api) { api.addFiles('google_server.js', 'server'); api.addFiles('google_client.js', 'client'); + api.addFiles('google_sign-in.js', 'web.cordova'); + + api.mainModule('namespace.js'); api.export('Google'); }); diff --git a/tools/cordova/README.md b/tools/cordova/README.md index 192745b27f..eeab6c8e8c 100644 --- a/tools/cordova/README.md +++ b/tools/cordova/README.md @@ -111,7 +111,7 @@ app bundle (`pluginVersionsFromStarManifest`, a combination of `.meteor/cordova-plugins` for stand-alone plugin installs and the plugins added as dependencies of packages through `Cordova.depends`). The `pluginsConfiguration` comes from `App.configurePlugin` calls in -`meteor-config.js`. +`mobile-config.js`. Uses methods `CordovaProject#listInstalledPluginVersions()`, `CordovaProject#addPlugin(name, version, config)`,