diff --git a/packages/accounts-base/accounts_client.js b/packages/accounts-base/accounts_client.js
index ffbecd6938..fa7854b9e2 100644
--- a/packages/accounts-base/accounts_client.js
+++ b/packages/accounts-base/accounts_client.js
@@ -35,4 +35,30 @@
return Meteor.user();
});
}
+
+ // XXX this can be simplified if we merge in
+ // https://github.com/meteor/meteor/pull/273
+ var loginServicesConfigured = false;
+ var loginServicesConfiguredListeners = {}; // context.id -> context
+ Meteor.subscribe("loginServiceConfiguration", function () {
+ loginServicesConfigured = true;
+ _.each(loginServicesConfiguredListeners, function(context) {
+ context.invalidate();
+ });
+ });
+
+ // A reactive function returning whether the
+ // loginServiceConfiguration subscription is ready. Used by
+ // accounts-ui to hide the login button until we have all the
+ // configuration loaded
+ Meteor.accounts.loginServicesConfigured = function () {
+ if (loginServicesConfigured)
+ return true;
+
+ // not yet complete, save the context for invalidation once we are.
+ var context = Meteor.deps.Context.current;
+ if (context)
+ loginServicesConfiguredListeners[context.id] = context;
+ return false;
+ };
})();
diff --git a/packages/accounts-base/accounts_common.js b/packages/accounts-base/accounts_common.js
index 2ce6c2fb96..3aaf9a7062 100644
--- a/packages/accounts-base/accounts_common.js
+++ b/packages/accounts-base/accounts_common.js
@@ -31,7 +31,17 @@ Meteor.users = new Meteor.Collection(
null /*driver*/,
true /*preventAutopublish*/);
+// Table containing documents with configuration options for each
+// login service
+Meteor.accounts.configuration = new Meteor.Collection(
+ "accounts._loginServiceConfiguration",
+ null /*manager*/,
+ null /*driver*/,
+ true /*preventAutopublish*/);
+
// Thrown when trying to use a login service which is not configured
Meteor.accounts.ConfigError = function(description) {
this.message = description;
};
+Meteor.accounts.ConfigError.prototype = new Error();
+Meteor.accounts.ConfigError.prototype.name = 'Meteor.accounts.ConfigError';
\ No newline at end of file
diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js
index 50b8bd0d81..f6519fefd3 100644
--- a/packages/accounts-base/accounts_server.js
+++ b/packages/accounts-base/accounts_server.js
@@ -193,7 +193,7 @@
///
- /// PUBLISHING USER OBJECTS
+ /// PUBLISHING DATA
///
// Always publish the current user's record to the client.
@@ -214,6 +214,23 @@
Meteor.default_server.publish(null, handler, {is_auto: true});
});
+ // Publish all login service configuration fields other than secret.
+ Meteor.publish("loginServiceConfiguration", function () {
+ return Meteor.accounts.configuration.find({}, {fields: {secret: 0}});
+ });
+
+ // Allow a one-time configuration for a login service.
+ Meteor.accounts.configuration.allow({}); // disallow mutators
+ Meteor.methods({
+ "configureLoginService": function(options) {
+ if (!Meteor.accounts.configuration.findOne({service: options.service}))
+ Meteor.accounts.configuration.insert(options);
+ else
+ throw new Meteor.Error(403, "Service " + options.service + " already configured");
+ }
+ });
+
+
///
/// RESTRICTING WRITES TO USER OBJECTS
///
diff --git a/packages/accounts-facebook/facebook_client.js b/packages/accounts-facebook/facebook_client.js
index 3d34aac30e..d3901c310a 100644
--- a/packages/accounts-facebook/facebook_client.js
+++ b/packages/accounts-facebook/facebook_client.js
@@ -1,7 +1,8 @@
(function () {
Meteor.loginWithFacebook = function () {
- if (!Meteor.accounts.facebook._appId || !Meteor.accounts.facebook._appUrl)
- throw new Meteor.accounts.ConfigError("Need to call Meteor.accounts.facebook.config first");
+ var config = Meteor.accounts.configuration.findOne({service: 'facebook'});
+ if (!config)
+ throw new Meteor.accounts.ConfigError("Service not configured");
var state = Meteor.uuid();
var mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);
@@ -13,8 +14,8 @@
scope = Meteor.accounts.facebook._options.scope.join(',');
var loginUrl =
- 'https://www.facebook.com/dialog/oauth?client_id=' + Meteor.accounts.facebook._appId +
- '&redirect_uri=' + Meteor.accounts.facebook._appUrl + '/_oauth/facebook?close' +
+ 'https://www.facebook.com/dialog/oauth?client_id=' + config.appId +
+ '&redirect_uri=' + Meteor.absoluteUrl('_oauth/facebook?close') +
'&display=' + display + '&scope=' + scope + '&state=' + state;
Meteor.accounts.oauth.initiateLogin(state, loginUrl);
diff --git a/packages/accounts-facebook/facebook_common.js b/packages/accounts-facebook/facebook_common.js
index d3ba6e0771..2c1fca99f9 100644
--- a/packages/accounts-facebook/facebook_common.js
+++ b/packages/accounts-facebook/facebook_common.js
@@ -1,12 +1,7 @@
if (!Meteor.accounts.facebook) {
Meteor.accounts.facebook = {};
- Meteor.accounts.facebook._requireConfigs = ['_appId', '_appUrl'];
}
-Meteor.accounts.facebook.config = function(appId, appUrl, options) {
- Meteor.accounts.facebook._appId = appId;
- Meteor.accounts.facebook._appUrl = appUrl;
+Meteor.accounts.facebook.config = function(options) {
Meteor.accounts.facebook._options = options;
};
-
-
diff --git a/packages/accounts-facebook/facebook_configure.html b/packages/accounts-facebook/facebook_configure.html
new file mode 100644
index 0000000000..c24b24b823
--- /dev/null
+++ b/packages/accounts-facebook/facebook_configure.html
@@ -0,0 +1,19 @@
+
+
+ First, you'll need to register your app on Facebook. Follow these steps:
+
+ First, you'll need to get a Google Client ID. Follow these steps:
+
+ First, you'll need to register your app on Twitter. Follow these steps:
+
+
+
diff --git a/packages/accounts-facebook/facebook_configure.js b/packages/accounts-facebook/facebook_configure.js
new file mode 100644
index 0000000000..26eee7761f
--- /dev/null
+++ b/packages/accounts-facebook/facebook_configure.js
@@ -0,0 +1,10 @@
+Template.configureLoginServicesDialogForFacebook.siteUrl = function () {
+ return Meteor.absoluteUrl();
+};
+
+Template.configureLoginServicesDialogForFacebook.fields = function () {
+ return [
+ {property: 'appId', label: 'App ID'},
+ {property: 'secret', label: 'App Secret'}
+ ];
+};
\ No newline at end of file
diff --git a/packages/accounts-facebook/facebook_server.js b/packages/accounts-facebook/facebook_server.js
index b24d745e51..0ce7db31c4 100644
--- a/packages/accounts-facebook/facebook_server.js
+++ b/packages/accounts-facebook/facebook_server.js
@@ -1,9 +1,5 @@
(function () {
- Meteor.accounts.facebook.setSecret = function (secret) {
- Meteor.accounts.facebook._secret = secret;
- };
-
Meteor.accounts.oauth.registerService('facebook', 2, function(query) {
var accessToken = getAccessToken(query);
@@ -22,13 +18,17 @@
});
var getAccessToken = function (query) {
+ var config = Meteor.accounts.configuration.findOne({service: 'facebook'});
+ if (!config)
+ throw new Meteor.accounts.ConfigError("Service not configured");
+
// Request an access token
var result = Meteor.http.get(
"https://graph.facebook.com/oauth/access_token", {
params: {
- client_id: Meteor.accounts.facebook._appId,
- redirect_uri: Meteor.accounts.facebook._appUrl + "/_oauth/facebook?close",
- client_secret: Meteor.accounts.facebook._secret,
+ client_id: config.appId,
+ redirect_uri: Meteor.absoluteUrl("_oauth/facebook?close"),
+ client_secret: config.secret,
code: query.code
}
});
diff --git a/packages/accounts-facebook/package.js b/packages/accounts-facebook/package.js
index c8ab9ecc19..9b63589120 100644
--- a/packages/accounts-facebook/package.js
+++ b/packages/accounts-facebook/package.js
@@ -6,6 +6,11 @@ Package.on_use(function(api) {
api.use('accounts-base', ['client', 'server']);
api.use('accounts-oauth2-helper', ['client', 'server']);
api.use('http', ['client', 'server']);
+ api.use('templating', 'client');
+
+ api.add_files(
+ ['facebook_configure.html', 'facebook_configure.js'],
+ 'client');
api.add_files('facebook_common.js', ['client', 'server']);
api.add_files('facebook_server.js', 'server');
diff --git a/packages/accounts-google/google_client.js b/packages/accounts-google/google_client.js
index 431b853fe9..97bd9cb7bb 100644
--- a/packages/accounts-google/google_client.js
+++ b/packages/accounts-google/google_client.js
@@ -1,7 +1,8 @@
(function () {
Meteor.loginWithGoogle = function () {
- if (!Meteor.accounts.google._clientId || !Meteor.accounts.google._appUrl)
- throw new Meteor.accounts.ConfigError("Need to call Meteor.accounts.google.config first");
+ var config = Meteor.accounts.configuration.findOne({service: 'google'});
+ if (!config)
+ throw new Meteor.accounts.ConfigError("Service not configured");
var state = Meteor.uuid();
@@ -20,9 +21,9 @@
var loginUrl =
'https://accounts.google.com/o/oauth2/auth' +
'?response_type=code' +
- '&client_id=' + Meteor.accounts.google._clientId +
+ '&client_id=' + config.clientId +
'&scope=' + flat_scope +
- '&redirect_uri=' + Meteor.accounts.google._appUrl + '/_oauth/google?close' +
+ '&redirect_uri=' + Meteor.absoluteUrl('_oauth/google?close') +
'&state=' + state;
Meteor.accounts.oauth.initiateLogin(state, loginUrl);
diff --git a/packages/accounts-google/google_common.js b/packages/accounts-google/google_common.js
index e5071871bc..b68429d14b 100644
--- a/packages/accounts-google/google_common.js
+++ b/packages/accounts-google/google_common.js
@@ -1,10 +1,7 @@
if (!Meteor.accounts.google) {
Meteor.accounts.google = {};
- Meteor.accounts.google._requireConfigs = ['_clientId', '_appUrl'];
}
-Meteor.accounts.google.config = function(clientId, appUrl, options) {
- Meteor.accounts.google._clientId = clientId;
- Meteor.accounts.google._appUrl = appUrl;
+Meteor.accounts.google.config = function(options) {
Meteor.accounts.google._options = options;
};
diff --git a/packages/accounts-google/google_configure.html b/packages/accounts-google/google_configure.html
new file mode 100644
index 0000000000..c2d97de2c1
--- /dev/null
+++ b/packages/accounts-google/google_configure.html
@@ -0,0 +1,28 @@
+
+
+
+
diff --git a/packages/accounts-google/google_configure.js b/packages/accounts-google/google_configure.js
new file mode 100644
index 0000000000..3ab531e1b6
--- /dev/null
+++ b/packages/accounts-google/google_configure.js
@@ -0,0 +1,10 @@
+Template.configureLoginServicesDialogForGoogle.siteUrl = function () {
+ return Meteor.absoluteUrl();
+};
+
+Template.configureLoginServicesDialogForGoogle.fields = function () {
+ return [
+ {property: 'clientId', label: 'Client ID'},
+ {property: 'secret', label: 'Client secret'}
+ ];
+};
\ No newline at end of file
diff --git a/packages/accounts-google/google_server.js b/packages/accounts-google/google_server.js
index 295010ab38..85903a10be 100644
--- a/packages/accounts-google/google_server.js
+++ b/packages/accounts-google/google_server.js
@@ -22,12 +22,16 @@
});
var getAccessToken = function (query) {
+ var config = Meteor.accounts.configuration.findOne({service: 'google'});
+ if (!config)
+ throw new Meteor.accounts.ConfigError("Service not configured");
+
var result = Meteor.http.post(
"https://accounts.google.com/o/oauth2/token", {params: {
code: query.code,
- client_id: Meteor.accounts.google._clientId,
- client_secret: Meteor.accounts.google._secret,
- redirect_uri: Meteor.accounts.google._appUrl + "/_oauth/google?close",
+ client_id: config.clientId,
+ client_secret: config.secret,
+ redirect_uri: Meteor.absoluteUrl("_oauth/google?close"),
grant_type: 'authorization_code'
}});
diff --git a/packages/accounts-google/package.js b/packages/accounts-google/package.js
index 4db58deaaf..e6484baadb 100644
--- a/packages/accounts-google/package.js
+++ b/packages/accounts-google/package.js
@@ -6,6 +6,11 @@ Package.on_use(function(api) {
api.use('accounts-base', ['client', 'server']);
api.use('accounts-oauth2-helper', ['client', 'server']);
api.use('http', ['client', 'server']);
+ api.use('templating', 'client');
+
+ api.add_files(
+ ['google_configure.html', 'google_configure.js'],
+ 'client');
api.add_files('google_common.js', ['client', 'server']);
api.add_files('google_server.js', 'server');
diff --git a/packages/accounts-oauth-helper/oauth_server.js b/packages/accounts-oauth-helper/oauth_server.js
index c6f221afe5..15f8e2e6a5 100644
--- a/packages/accounts-oauth-helper/oauth_server.js
+++ b/packages/accounts-oauth-helper/oauth_server.js
@@ -142,17 +142,9 @@
// Make sure we're configured
var ensureConfigured = function(serviceName) {
- var service = Meteor.accounts[serviceName];
-
- _.each(service._requireConfigs, function(key) {
- if (!service[key])
- throw new Meteor.accounts.ConfigError(
- "Need to call Meteor.accounts." + serviceName + ".config first");
- });
-
- if (Meteor.isServer && !service._secret)
- throw new Meteor.accounts.ConfigError(
- "Need to call Meteor.accounts." + serviceName + ".setSecret first");
+ if (!Meteor.accounts.configuration.findOne({service: serviceName})) {
+ throw new Meteor.accounts.ConfigError("Service not configured");
+ };
};
Meteor.accounts.oauth._renderOauthResults = function(res, query) {
diff --git a/packages/accounts-oauth1-helper/oauth1_binding.js b/packages/accounts-oauth1-helper/oauth1_binding.js
index 8f03ef8451..835ca74cc1 100644
--- a/packages/accounts-oauth1-helper/oauth1_binding.js
+++ b/packages/accounts-oauth1-helper/oauth1_binding.js
@@ -116,7 +116,7 @@ OAuth1Binding.prototype._call = function(method, url, headers, params) {
});
if (response.error) {
- Meteor._debug('Error sending OAuth1 HTTP call', method, url, params, authString);
+ Meteor._debug('Error sending OAuth1 HTTP call', response.content, method, url, params, authString);
throw response.error;
}
diff --git a/packages/accounts-oauth1-helper/oauth1_server.js b/packages/accounts-oauth1-helper/oauth1_server.js
index 3203f08955..4025862d4d 100644
--- a/packages/accounts-oauth1-helper/oauth1_server.js
+++ b/packages/accounts-oauth1-helper/oauth1_server.js
@@ -7,8 +7,14 @@
// connect middleware
Meteor.accounts.oauth1._handleRequest = function (service, query, res) {
- var config = Meteor.accounts[service.serviceName];
- var oauthBinding = new OAuth1Binding(config._consumerKey, config._secret, config._urls);
+ var config = Meteor.accounts.configuration.findOne({service: service.serviceName});
+ if (!config) {
+ throw new Meteor.accounts.ConfigError("Service " + service.serviceName + " not configured");
+ }
+
+ var urls = Meteor.accounts[service.serviceName]._urls;
+ var oauthBinding = new OAuth1Binding(
+ config.consumerKey, config.secret, urls);
if (query.requestTokenAndRedirect) {
// step 1 - get and store a request token
@@ -20,7 +26,7 @@
Meteor.accounts.oauth1._requestTokens[query.state] = oauthBinding.requestToken;
// redirect to provider login, which will redirect back to "step 2" below
- var redirectUrl = config._urls.authenticate + '?oauth_token=' + oauthBinding.requestToken;
+ var redirectUrl = urls.authenticate + '?oauth_token=' + oauthBinding.requestToken;
res.writeHead(302, {'Location': redirectUrl});
res.end();
diff --git a/packages/accounts-oauth1-helper/oauth1_tests.js b/packages/accounts-oauth1-helper/oauth1_tests.js
index e7a004cc7a..e081ed7c1c 100644
--- a/packages/accounts-oauth1-helper/oauth1_tests.js
+++ b/packages/accounts-oauth1-helper/oauth1_tests.js
@@ -18,9 +18,9 @@ Tinytest.add("oauth1 - loginResultForState is stored", function (test) {
Meteor.accounts.oauth._loginResultForState = {};
Meteor.accounts.oauth._services = {};
+ if (!Meteor.accounts.configuration.findOne({service: 'twitterfoo'}))
+ Meteor.accounts.configuration.insert({service: 'twitterfoo'});
Meteor.accounts.twitterfoo = {};
- Meteor.accounts.twitterfoo._requireConfigs = [];
- Meteor.accounts.twitterfoo._secret = 'XXX';
// register a fake login service - twitterfoo
Meteor.accounts.oauth.registerService("twitterfoo", 1, function (query) {
@@ -78,9 +78,9 @@ Tinytest.add("oauth1 - error in user creation", function (test) {
var twitterfailAccessToken = Meteor.uuid();
var twitterfailAccessTokenSecret = Meteor.uuid();
+ if (!Meteor.accounts.configuration.findOne({service: 'twitterfail'}))
+ Meteor.accounts.configuration.insert({service: 'twitterfail'});
Meteor.accounts.twitterfail = {};
- Meteor.accounts.twitterfail._requireConfigs = [];
- Meteor.accounts.twitterfail._secret = 'XXX';
// Wire up access token so that verification passes
Meteor.accounts.oauth1._requestTokens[state] = twitterfailAccessToken;
diff --git a/packages/accounts-oauth2-helper/oauth2_tests.js b/packages/accounts-oauth2-helper/oauth2_tests.js
index 8584926d69..56171fec8b 100644
--- a/packages/accounts-oauth2-helper/oauth2_tests.js
+++ b/packages/accounts-oauth2-helper/oauth2_tests.js
@@ -8,9 +8,9 @@ Tinytest.add("oauth2 - loginResultForState is stored", function (test) {
Meteor.accounts.oauth._loginResultForState = {};
Meteor.accounts.oauth._services = {};
+ if (!Meteor.accounts.configuration.findOne({service: 'foobook'}))
+ Meteor.accounts.configuration.insert({service: 'foobook'});
Meteor.accounts.foobook = {};
- Meteor.accounts.foobook._requireConfigs = [];
- Meteor.accounts.foobook._secret = 'XXX';
// register a fake login service - foobook
Meteor.accounts.oauth.registerService("foobook", 2, function (query) {
@@ -49,9 +49,9 @@ Tinytest.add("oauth2 - error in user creation", function (test) {
var state = Meteor.uuid();
var failbookId = Meteor.uuid();
+ if (!Meteor.accounts.configuration.findOne({service: 'failbook'}))
+ Meteor.accounts.configuration.insert({service: 'failbook'});
Meteor.accounts.failbook = {};
- Meteor.accounts.failbook._requireConfigs = [];
- Meteor.accounts.failbook._secret = 'XXX';
// register a failing login service
Meteor.accounts.oauth.registerService("failbook", 2, function (query) {
diff --git a/packages/accounts-twitter/package.js b/packages/accounts-twitter/package.js
index a144429e23..bae64bbd53 100644
--- a/packages/accounts-twitter/package.js
+++ b/packages/accounts-twitter/package.js
@@ -6,6 +6,11 @@ Package.on_use(function(api) {
api.use('accounts-base', ['client', 'server']);
api.use('accounts-oauth1-helper', ['client', 'server']);
api.use('http', ['client', 'server']);
+ api.use('templating', 'client');
+
+ api.add_files(
+ ['twitter_configure.html', 'twitter_configure.js'],
+ 'client');
api.add_files('twitter_common.js', ['client', 'server']);
api.add_files('twitter_server.js', 'server');
diff --git a/packages/accounts-twitter/twitter_client.js b/packages/accounts-twitter/twitter_client.js
index b75dcf378e..4e64ed5050 100644
--- a/packages/accounts-twitter/twitter_client.js
+++ b/packages/accounts-twitter/twitter_client.js
@@ -1,7 +1,8 @@
(function () {
Meteor.loginWithTwitter = function () {
- if (!Meteor.accounts.twitter._appUrl)
- throw new Meteor.accounts.ConfigError("Need to call Meteor.accounts.twitter.config first");
+ var config = Meteor.accounts.configuration.findOne({service: 'twitter'});
+ if (!config)
+ throw new Meteor.accounts.ConfigError("Service not configured");
var state = Meteor.uuid();
// We need to keep state across the next two 'steps' so we're adding
@@ -10,7 +11,7 @@
// url back to app, enters "step 2" as described in
// packages/accounts-oauth1-helper/oauth1_server.js
- var callbackUrl = Meteor.accounts.twitter._appUrl + '/_oauth/twitter?close&state=' + state;
+ var callbackUrl = Meteor.absoluteUrl('_oauth/twitter?close&state=' + state);
// url to app, enters "step 1" as described in
// packages/accounts-oauth1-helper/oauth1_server.js
diff --git a/packages/accounts-twitter/twitter_common.js b/packages/accounts-twitter/twitter_common.js
index 19fea5f6cb..72d57689fa 100644
--- a/packages/accounts-twitter/twitter_common.js
+++ b/packages/accounts-twitter/twitter_common.js
@@ -1,13 +1,7 @@
if (!Meteor.accounts.twitter) {
Meteor.accounts.twitter = {};
- Meteor.accounts.twitter._requireConfigs = ['_consumerKey', '_appUrl'];
}
-Meteor.accounts.twitter.config = function(consumerKey, appUrl) {
- Meteor.accounts.twitter._consumerKey = consumerKey;
- Meteor.accounts.twitter._appUrl = appUrl;
-};
-
Meteor.accounts.twitter._urls = {
requestToken: "https://api.twitter.com/oauth/request_token",
authorize: "https://api.twitter.com/oauth/authorize",
diff --git a/packages/accounts-twitter/twitter_configure.html b/packages/accounts-twitter/twitter_configure.html
new file mode 100644
index 0000000000..a8720a8fc2
--- /dev/null
+++ b/packages/accounts-twitter/twitter_configure.html
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/packages/accounts-twitter/twitter_configure.js b/packages/accounts-twitter/twitter_configure.js
new file mode 100644
index 0000000000..e5e7f05e72
--- /dev/null
+++ b/packages/accounts-twitter/twitter_configure.js
@@ -0,0 +1,11 @@
+Template.configureLoginServicesDialogForTwitter.siteUrl = function () {
+ // Twitter doesn't recognize localhost as a domain name
+ return Meteor.absoluteUrl({replaceLocalhost: true});
+};
+
+Template.configureLoginServicesDialogForTwitter.fields = function () {
+ return [
+ {property: 'consumerKey', label: 'Consumer key'},
+ {property: 'secret', label: 'Consumer secret'}
+ ];
+};
\ No newline at end of file
diff --git a/packages/accounts-twitter/twitter_server.js b/packages/accounts-twitter/twitter_server.js
index ea3d557982..900331d31b 100644
--- a/packages/accounts-twitter/twitter_server.js
+++ b/packages/accounts-twitter/twitter_server.js
@@ -1,9 +1,5 @@
(function () {
- Meteor.accounts.twitter.setSecret = function (consumerSecret) {
- Meteor.accounts.twitter._secret = consumerSecret;
- };
-
Meteor.accounts.oauth.registerService('twitter', 1, function(oauthBinding) {
var identity = oauthBinding.get('https://api.twitter.com/1/account/verify_credentials.json');
diff --git a/packages/accounts-ui/login_buttons.html b/packages/accounts-ui/login_buttons.html
index 5dc344b2ae..5538f45028 100644
--- a/packages/accounts-ui/login_buttons.html
+++ b/packages/accounts-ui/login_buttons.html
@@ -11,10 +11,12 @@
+ Now, copy over some details. +
++
| + + | ++ + | +
+ First, you'll need to register your app on Weibo. Follow these steps: +
+