mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Better interface for OAuth2 login services + more cleanup
This commit is contained in:
committed by
Nick Martin
parent
0d886d6c24
commit
a73715491b
@@ -1,7 +1,7 @@
|
||||
(function () {
|
||||
Meteor.loginWithFacebook = function () {
|
||||
if (!Meteor.accounts.facebook._appId || !Meteor.accounts.facebook._appUrl)
|
||||
throw new Meteor.accounts.facebook.SetupError("Need to call Meteor.accounts.facebook.setup first");
|
||||
throw new Meteor.accounts.ConfigError("Need to call Meteor.accounts.facebook.setup first");
|
||||
|
||||
var state = Meteor.uuid();
|
||||
// XXX I think there's a smaller popup. Replace with appropriate URL.
|
||||
|
||||
@@ -2,11 +2,9 @@ if (!Meteor.accounts.facebook) {
|
||||
Meteor.accounts.facebook = {};
|
||||
}
|
||||
|
||||
Meteor.accounts.facebook.setup = function(appId, appUrl) {
|
||||
Meteor.accounts.facebook.config = function(appId, appUrl) {
|
||||
Meteor.accounts.facebook._appId = appId;
|
||||
Meteor.accounts.facebook._appUrl = appUrl;
|
||||
};
|
||||
|
||||
Meteor.accounts.facebook.SetupError = function(description) {
|
||||
this.message = description;
|
||||
};
|
||||
|
||||
|
||||
@@ -4,37 +4,30 @@
|
||||
Meteor.accounts.facebook._secret = secret;
|
||||
};
|
||||
|
||||
// register the facebook identity provider
|
||||
Meteor.accounts.oauth2.providers.facebook = {
|
||||
userIdForOauthReq: function(req) {
|
||||
if (!Meteor.accounts.facebook._appId || !Meteor.accounts.facebook._appUrl)
|
||||
throw new Meteor.accounts.facebook.SetupError("Need to call Meteor.accounts.facebook.setup first");
|
||||
if (!Meteor.accounts.facebook._secret)
|
||||
throw new Meteor.accounts.facebook.SetupError("Need to call Meteor.accounts.facebook.setSecret first");
|
||||
|
||||
var accessToken = getAccessToken(req);
|
||||
// If the user didn't authorize the login, either explicitly
|
||||
// or by closing the popup window, return null
|
||||
if (!accessToken)
|
||||
return null;
|
||||
|
||||
// Fetch user's facebook identity
|
||||
var identity = Meteor.http.get("https://graph.facebook.com/me", {
|
||||
params: {access_token: accessToken}}).data;
|
||||
|
||||
return Meteor.accounts.updateOrCreateUser(
|
||||
identity.email, {name: identity.name},
|
||||
'facebook', identity.id, {accessToken: accessToken});
|
||||
}
|
||||
};
|
||||
|
||||
// @returns {String} Facebook access token
|
||||
var getAccessToken = function (req) {
|
||||
if (req.query.error) {
|
||||
Meteor.accounts.oauth2.registerService('facebook', function(query) {
|
||||
if (query.error) {
|
||||
// The user didn't authorize access
|
||||
// XXX can/should we generalize this into the oauth abstration?
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!Meteor.accounts.facebook._appId || !Meteor.accounts.facebook._appUrl)
|
||||
throw new Meteor.accounts.ConfigError("Need to call Meteor.accounts.facebook.setup first");
|
||||
if (!Meteor.accounts.facebook._secret)
|
||||
throw new Meteor.accounts.ConfigError("Need to call Meteor.accounts.facebook.setSecret first");
|
||||
|
||||
var accessToken = getAccessToken(query);
|
||||
var identity = getIdentity(accessToken);
|
||||
|
||||
return {
|
||||
email: identity.email,
|
||||
userData: {name: identity.name},
|
||||
serviceUserId: identity.id,
|
||||
serviceData: {accessToken: accessToken}
|
||||
};
|
||||
});
|
||||
|
||||
var getAccessToken = function (query) {
|
||||
// Request an access token
|
||||
var response = Meteor.http.get(
|
||||
"https://graph.facebook.com/oauth/access_token", {
|
||||
@@ -42,7 +35,7 @@
|
||||
client_id: Meteor.accounts.facebook._appId,
|
||||
redirect_uri: Meteor.accounts.facebook._appUrl + "/_oauth/facebook?close",
|
||||
client_secret: Meteor.accounts.facebook._secret,
|
||||
code: req.query.code
|
||||
code: query.code
|
||||
}
|
||||
}).content;
|
||||
|
||||
@@ -76,4 +69,13 @@
|
||||
return fbAccessToken;
|
||||
}
|
||||
};
|
||||
|
||||
var getIdentity = function (accessToken) {
|
||||
var result = Meteor.http.get("https://graph.facebook.com/me", {
|
||||
params: {access_token: accessToken}});
|
||||
|
||||
if (result.error)
|
||||
throw result.error;
|
||||
return result.data;
|
||||
};
|
||||
}) ();
|
||||
@@ -4,7 +4,7 @@
|
||||
throw new Meteor.accounts.google.SetupError("Need to call Meteor.accounts.google.setup first");
|
||||
|
||||
var state = Meteor.uuid();
|
||||
// XXX need to support configuring access_type and scopy
|
||||
// XXX need to support configuring access_type and scope
|
||||
var loginUrl =
|
||||
'https://accounts.google.com/o/oauth2/auth' +
|
||||
'?response_type=code' +
|
||||
|
||||
@@ -2,11 +2,7 @@ if (!Meteor.accounts.google) {
|
||||
Meteor.accounts.google = {};
|
||||
}
|
||||
|
||||
Meteor.accounts.google.setup = function(clientId, appUrl) {
|
||||
Meteor.accounts.google.config = function(clientId, appUrl) {
|
||||
Meteor.accounts.google._clientId = clientId;
|
||||
Meteor.accounts.google._appUrl = appUrl;
|
||||
};
|
||||
|
||||
Meteor.accounts.google.SetupError = function(description) {
|
||||
this.message = description;
|
||||
};
|
||||
|
||||
@@ -1,45 +1,56 @@
|
||||
(function () {
|
||||
|
||||
Meteor.accounts.google.setSecret = function (secret) {
|
||||
Meteor.accounts.google._secret = secret;
|
||||
};
|
||||
|
||||
Meteor.accounts.oauth2.providers.google = {
|
||||
userIdForOauthReq: function(req) {
|
||||
var accessToken = getAccessToken(req);
|
||||
|
||||
// XXX can we generalize this flow into the oauth abstraction?
|
||||
if (!accessToken)
|
||||
return null;
|
||||
|
||||
var identity = Meteor.http.get(
|
||||
"https://www.googleapis.com/oauth2/v1/userinfo",
|
||||
{params: {access_token: accessToken}}).data;
|
||||
|
||||
return Meteor.accounts.updateOrCreateUser(
|
||||
identity.email, {name: identity.name},
|
||||
'google', identity.id, {accessToken: accessToken});
|
||||
}
|
||||
};
|
||||
|
||||
var getAccessToken = function (req) {
|
||||
if (req.query.error) {
|
||||
Meteor.accounts.oauth2.registerService('google', function(query) {
|
||||
if (query.error) {
|
||||
// The user didn't authorize access
|
||||
// XXX can we generalize this into the oauth abstration?
|
||||
// XXX can/should we generalize this into the oauth abstration?
|
||||
return null;
|
||||
}
|
||||
|
||||
var response = Meteor.http.post(
|
||||
if (!Meteor.accounts.google._clientId || !Meteor.accounts.google._appUrl)
|
||||
throw new Meteor.accounts.ConfigError("Need to call Meteor.accounts.google.config first");
|
||||
if (!Meteor.accounts.google._secret)
|
||||
throw new Meteor.accounts.ConfigError("Need to call Meteor.accounts.google.setSecret first");
|
||||
|
||||
var accessToken = getAccessToken(query);
|
||||
var identity = getIdentity(accessToken);
|
||||
|
||||
return {
|
||||
email: identity.email,
|
||||
userData: {name: identity.name},
|
||||
serviceUserId: identity.id,
|
||||
serviceData: {accessToken: accessToken}
|
||||
};
|
||||
});
|
||||
|
||||
var getAccessToken = function (query) {
|
||||
var result = Meteor.http.post(
|
||||
"https://accounts.google.com/o/oauth2/token", {params: {
|
||||
code: req.query.code,
|
||||
code: query.code,
|
||||
client_id: Meteor.accounts.google._clientId,
|
||||
client_secret: Meteor.accounts.google._secret,
|
||||
redirect_uri: Meteor.accounts.google._appUrl + "/_oauth/google?close",
|
||||
grant_type: 'authorization_code'
|
||||
}}).data;
|
||||
}});
|
||||
|
||||
if (response.error)
|
||||
throw response;
|
||||
if (result.error) // if the http response was an error
|
||||
throw result.error;
|
||||
if (result.data.error) // if the http response was a json object with an error attribute
|
||||
throw result.data;
|
||||
return result.data.access_token;
|
||||
};
|
||||
|
||||
return response.access_token;
|
||||
var getIdentity = function (accessToken) {
|
||||
var result = Meteor.http.get(
|
||||
"https://www.googleapis.com/oauth2/v1/userinfo",
|
||||
{params: {access_token: accessToken}});
|
||||
|
||||
if (result.error)
|
||||
throw result.error;
|
||||
return result.data;
|
||||
};
|
||||
})();
|
||||
@@ -5,8 +5,8 @@
|
||||
try {
|
||||
Meteor.loginWithFacebook();
|
||||
} catch (e) {
|
||||
if (e instanceof Meteor.accounts.facebook.SetupError)
|
||||
alert("Facebook API key not set. Configure app details with Meteor.accounts.facebook.setup()");
|
||||
if (e instanceof Meteor.accounts.ConfigError)
|
||||
alert("Facebook API key not set. Configure app details with Meteor.accounts.facebook.config()");
|
||||
else
|
||||
throw e;
|
||||
}
|
||||
@@ -16,8 +16,8 @@
|
||||
try {
|
||||
Meteor.loginWithGoogle();
|
||||
} catch (e) {
|
||||
if (e instanceof Meteor.accounts.google.SetupError)
|
||||
alert("Google API key not set. Configure app details with Meteor.accounts.google.setup()");
|
||||
if (e instanceof Meteor.accounts.ConfigError)
|
||||
alert("Google API key not set. Configure app details with Meteor.accounts.google.config()");
|
||||
else
|
||||
throw e;
|
||||
};
|
||||
|
||||
@@ -16,3 +16,8 @@ Meteor.users = new Meteor.Collection(
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -1,13 +1,38 @@
|
||||
(function () {
|
||||
var connect = __meteor_bootstrap__.require("connect");
|
||||
|
||||
Meteor.accounts.oauth2.providers = {};
|
||||
Meteor.accounts.oauth2._services = {};
|
||||
|
||||
// Register a handler for an OAuth2 service. The handler will be called
|
||||
// when we get an incoming http request on /_oauth/{serviceName}. This
|
||||
// handler should use that information to fetch data about the user
|
||||
// logging in.
|
||||
//
|
||||
// @param name {String} e.g. "google", "facebook"
|
||||
// @param handleOauthRequest {Function(query): userInfo}
|
||||
// - query is an object with the parameters passed in the query string
|
||||
// - userInfo {Object} with following keys:
|
||||
// - email {String}
|
||||
// - userData {Object} attributes to store directly on the user object,
|
||||
// such as "name"
|
||||
// - serviceUserId {?} The logging in user's id in the login service
|
||||
// - serviceData {Object} attributes to store on the user record's
|
||||
// specific login service's subobject, such as
|
||||
// "accessToken"
|
||||
Meteor.accounts.oauth2.registerService = function (name, handleOauthRequest) {
|
||||
if (Meteor.accounts.oauth2._services[name])
|
||||
throw new Meteor.Error("Already registered the " + name + " OAuth2 service");
|
||||
Meteor.accounts.oauth2._services[name] = {
|
||||
handleOauthRequest: handleOauthRequest
|
||||
};
|
||||
};
|
||||
|
||||
// Listen to calls to `login` with an oauth option set
|
||||
Meteor.accounts.registerLoginHandler(function (options) {
|
||||
if (!options.oauth)
|
||||
return undefined; // don't handle
|
||||
|
||||
var result = Meteor.accounts.oauth2.loginResultForState[options.oauth.state];
|
||||
var result = Meteor.accounts.oauth2._loginResultForState[options.oauth.state];
|
||||
if (result === undefined) // not using `!result` since can be null
|
||||
// We weren't notified of the user authorizing the login.
|
||||
return null;
|
||||
@@ -20,7 +45,7 @@
|
||||
// The results are stored in this map which is then read when the
|
||||
// login method is called. Maps {oauthState} --> return value of
|
||||
// `login`
|
||||
Meteor.accounts.oauth2.loginResultForState = {};
|
||||
Meteor.accounts.oauth2._loginResultForState = {};
|
||||
|
||||
// Listen on /_oauth/*
|
||||
__meteor_bootstrap__.app
|
||||
@@ -40,14 +65,19 @@
|
||||
// This way the subsequent call to the `login` method will be
|
||||
// immediate.
|
||||
|
||||
var providerName = splitUrl[2];
|
||||
var provider = Meteor.accounts.oauth2.providers[providerName];
|
||||
var serviceName = splitUrl[2];
|
||||
var service = Meteor.accounts.oauth2._services[serviceName];
|
||||
|
||||
// Get or create user id
|
||||
var userId = provider.userIdForOauthReq(req);
|
||||
var userInfo = service.handleOauthRequest(req.query);
|
||||
var userId = Meteor.accounts.updateOrCreateUser(
|
||||
userInfo.email, userInfo.userData, serviceName,
|
||||
userInfo.serviceUserId, userInfo.serviceData);
|
||||
|
||||
// Generate and store a login token for reconnect
|
||||
var loginToken = Meteor.accounts._loginTokens.insert({userId: userId});
|
||||
// Store results to subsequent call to `login`
|
||||
Meteor.accounts.oauth2.loginResultForState[req.query.state] =
|
||||
Meteor.accounts.oauth2._loginResultForState[req.query.state] =
|
||||
{token: loginToken, id: userId};
|
||||
|
||||
// We support /_oauth?close, /_oauth?redirect=URL. Any other /_oauth request
|
||||
|
||||
Reference in New Issue
Block a user