mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-14 21:27:23 +01:00
Add Google Signin (#7969)
* Start adding google login * fix local js issue * implement syntax suggestions * fix delete social tests * Add service for authentication alerts * fix social login tests * make suggested google sign in changes * fix accidentally deleted code * refactor social network sign in * fix incorrect find * implement suggested google sign in changes * fix(tests): Inject fake Auth module for auth controller * fix(test): prevent social service from causing page reload * fix loading user info * Use lodash's implimentation of find for IE compatibility * chore: increase test coverage around deletion route * chore: clean up social auth test * chore: Fix social login tests * remove profile from login scope * fix(api): Allow social accounts to deregister as user has auth backup * temporarily disable google login button
This commit is contained in:
committed by
Matteo Pagliazzi
parent
941000d737
commit
e3b484b29a
@@ -7,6 +7,8 @@
|
|||||||
"FACEBOOK_ANALYTICS":"1234567890123456",
|
"FACEBOOK_ANALYTICS":"1234567890123456",
|
||||||
"FACEBOOK_KEY":"123456789012345",
|
"FACEBOOK_KEY":"123456789012345",
|
||||||
"FACEBOOK_SECRET":"aaaabbbbccccddddeeeeffff00001111",
|
"FACEBOOK_SECRET":"aaaabbbbccccddddeeeeffff00001111",
|
||||||
|
"GOOGLE_CLIENT_ID":"123456789012345",
|
||||||
|
"GOOGLE_CLIENT_SECRET":"aaaabbbbccccddddeeeeffff00001111",
|
||||||
"NODE_DB_URI":"mongodb://localhost/habitrpg",
|
"NODE_DB_URI":"mongodb://localhost/habitrpg",
|
||||||
"TEST_DB_URI":"mongodb://localhost/habitrpg_test",
|
"TEST_DB_URI":"mongodb://localhost/habitrpg_test",
|
||||||
"NODE_ENV":"development",
|
"NODE_ENV":"development",
|
||||||
|
|||||||
@@ -85,6 +85,7 @@
|
|||||||
"pageres": "^4.1.1",
|
"pageres": "^4.1.1",
|
||||||
"passport": "~0.2.1",
|
"passport": "~0.2.1",
|
||||||
"passport-facebook": "2.0.0",
|
"passport-facebook": "2.0.0",
|
||||||
|
"passport-google-oauth20": "1.0.0",
|
||||||
"paypal-ipn": "3.0.0",
|
"paypal-ipn": "3.0.0",
|
||||||
"paypal-rest-sdk": "^1.2.1",
|
"paypal-rest-sdk": "^1.2.1",
|
||||||
"pretty-data": "^0.40.0",
|
"pretty-data": "^0.40.0",
|
||||||
|
|||||||
@@ -5,36 +5,94 @@ import {
|
|||||||
|
|
||||||
describe('DELETE social registration', () => {
|
describe('DELETE social registration', () => {
|
||||||
let user;
|
let user;
|
||||||
let endpoint = '/user/auth/social/facebook';
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
user = await generateUser();
|
user = await generateUser();
|
||||||
await user.update({ 'auth.facebook.id': 'some-fb-id' });
|
|
||||||
expect(user.auth.local.username).to.not.be.empty;
|
|
||||||
expect(user.auth.facebook).to.not.be.empty;
|
|
||||||
});
|
});
|
||||||
context('of NOT-FACEBOOK', () => {
|
|
||||||
|
context('NOT-SUPPORTED', () => {
|
||||||
it('is not supported', async () => {
|
it('is not supported', async () => {
|
||||||
await expect(user.del('/user/auth/social/SOME-OTHER-NETWORK')).to.eventually.be.rejected.and.eql({
|
await expect(user.del('/user/auth/social/SOME-OTHER-NETWORK')).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: t('unsupportedNetwork'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('Facebook', () => {
|
||||||
|
it('fails if user does not have an alternative registration method', async () => {
|
||||||
|
await user.update({
|
||||||
|
'auth.facebook.id': 'some-fb-id',
|
||||||
|
'auth.local': { ok: true },
|
||||||
|
});
|
||||||
|
await expect(user.del('/user/auth/social/facebook')).to.eventually.be.rejected.and.eql({
|
||||||
code: 401,
|
code: 401,
|
||||||
error: 'NotAuthorized',
|
error: 'NotAuthorized',
|
||||||
message: t('onlyFbSupported'),
|
message: t('cantDetachSocial'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('succeeds if user has a local registration', async () => {
|
||||||
|
await user.update({
|
||||||
|
'auth.facebook.id': 'some-fb-id',
|
||||||
});
|
});
|
||||||
context('of facebook', () => {
|
|
||||||
it('fails if local registration does not exist for this user', async () => {
|
let response = await user.del('/user/auth/social/facebook');
|
||||||
await user.update({ 'auth.local': { ok: true } });
|
expect(response).to.eql({});
|
||||||
await expect(user.del(endpoint)).to.eventually.be.rejected.and.eql({
|
await user.sync();
|
||||||
code: 401,
|
expect(user.auth.facebook).to.be.empty;
|
||||||
error: 'NotAuthorized',
|
|
||||||
message: t('cantDetachFb'),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('succeeds if user has a google registration', async () => {
|
||||||
|
await user.update({
|
||||||
|
'auth.facebook.id': 'some-fb-id',
|
||||||
|
'auth.google.id': 'some-google-id',
|
||||||
|
'auth.local': { ok: true },
|
||||||
});
|
});
|
||||||
it('succeeds', async () => {
|
|
||||||
let response = await user.del(endpoint);
|
let response = await user.del('/user/auth/social/facebook');
|
||||||
expect(response).to.eql({});
|
expect(response).to.eql({});
|
||||||
await user.sync();
|
await user.sync();
|
||||||
expect(user.auth.facebook).to.be.empty;
|
expect(user.auth.facebook).to.be.empty;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
context('Google', () => {
|
||||||
|
it('fails if user does not have an alternative registration method', async () => {
|
||||||
|
await user.update({
|
||||||
|
'auth.google.id': 'some-google-id',
|
||||||
|
'auth.local': { ok: true },
|
||||||
|
});
|
||||||
|
await expect(user.del('/user/auth/social/google')).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('cantDetachSocial'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('succeeds if user has a local registration', async () => {
|
||||||
|
await user.update({
|
||||||
|
'auth.google.id': 'some-google-id',
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = await user.del('/user/auth/social/google');
|
||||||
|
expect(response).to.eql({});
|
||||||
|
await user.sync();
|
||||||
|
expect(user.auth.google).to.be.empty;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('succeeds if user has a facebook registration', async () => {
|
||||||
|
await user.update({
|
||||||
|
'auth.google.id': 'some-google-id',
|
||||||
|
'auth.facebook.id': 'some-facebook-id',
|
||||||
|
'auth.local': { ok: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = await user.del('/user/auth/social/google');
|
||||||
|
expect(response).to.eql({});
|
||||||
|
await user.sync();
|
||||||
|
expect(user.auth.google).to.be.empty;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,28 +12,32 @@ describe('POST /user/auth/social', () => {
|
|||||||
let endpoint = '/user/auth/social';
|
let endpoint = '/user/auth/social';
|
||||||
let randomAccessToken = '123456';
|
let randomAccessToken = '123456';
|
||||||
let facebookId = 'facebookId';
|
let facebookId = 'facebookId';
|
||||||
let network = 'facebook';
|
let googleId = 'googleId';
|
||||||
|
let network = 'NoNetwork';
|
||||||
|
|
||||||
before(async () => {
|
beforeEach(async () => {
|
||||||
api = requester();
|
api = requester();
|
||||||
user = await generateUser();
|
user = await generateUser();
|
||||||
|
|
||||||
let expectedResult = {id: facebookId};
|
|
||||||
let passportFacebookProfile = sandbox.stub(passport._strategies.facebook, 'userProfile');
|
|
||||||
passportFacebookProfile.yields(null, expectedResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fails if network is not facebook', async () => {
|
it('fails if network is not supported', async () => {
|
||||||
await expect(api.post(endpoint, {
|
await expect(api.post(endpoint, {
|
||||||
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
|
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
|
||||||
network: 'NotFacebook',
|
network,
|
||||||
})).to.eventually.be.rejected.and.eql({
|
})).to.eventually.be.rejected.and.eql({
|
||||||
code: 401,
|
code: 400,
|
||||||
error: 'NotAuthorized',
|
error: 'BadRequest',
|
||||||
message: t('onlyFbSupported'),
|
message: t('unsupportedNetwork'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('facebook', () => {
|
||||||
|
before(async () => {
|
||||||
|
let expectedResult = {id: facebookId};
|
||||||
|
sandbox.stub(passport._strategies.facebook, 'userProfile').yields(null, expectedResult);
|
||||||
|
network = 'facebook';
|
||||||
|
});
|
||||||
|
|
||||||
it('registers a new user', async () => {
|
it('registers a new user', async () => {
|
||||||
let response = await api.post(endpoint, {
|
let response = await api.post(endpoint, {
|
||||||
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
|
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
|
||||||
@@ -45,6 +49,33 @@ describe('POST /user/auth/social', () => {
|
|||||||
expect(response.newUser).to.be.true;
|
expect(response.newUser).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('logs an existing user in', async () => {
|
||||||
|
let registerResponse = await api.post(endpoint, {
|
||||||
|
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = await api.post(endpoint, {
|
||||||
|
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.apiToken).to.eql(registerResponse.apiToken);
|
||||||
|
expect(response.id).to.eql(registerResponse.id);
|
||||||
|
expect(response.newUser).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('add social auth to an existing user', async () => {
|
||||||
|
let response = await user.post(endpoint, {
|
||||||
|
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.apiToken).to.exist;
|
||||||
|
expect(response.id).to.exist;
|
||||||
|
expect(response.newUser).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
it('enrolls a new user in an A/B test', async () => {
|
it('enrolls a new user in an A/B test', async () => {
|
||||||
await api.post(endpoint, {
|
await api.post(endpoint, {
|
||||||
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
|
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
|
||||||
@@ -53,17 +84,60 @@ describe('POST /user/auth/social', () => {
|
|||||||
|
|
||||||
await expect(getProperty('users', user._id, '_ABtest')).to.eventually.be.a('string');
|
await expect(getProperty('users', user._id, '_ABtest')).to.eventually.be.a('string');
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('google', () => {
|
||||||
|
before(async () => {
|
||||||
|
let expectedResult = {id: googleId};
|
||||||
|
sandbox.stub(passport._strategies.google, 'userProfile').yields(null, expectedResult);
|
||||||
|
network = 'google';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('registers a new user', async () => {
|
||||||
|
let response = await api.post(endpoint, {
|
||||||
|
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.apiToken).to.exist;
|
||||||
|
expect(response.id).to.exist;
|
||||||
|
expect(response.newUser).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
it('logs an existing user in', async () => {
|
it('logs an existing user in', async () => {
|
||||||
await user.update({ 'auth.facebook.id': facebookId });
|
let registerResponse = await api.post(endpoint, {
|
||||||
|
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
});
|
||||||
|
|
||||||
let response = await api.post(endpoint, {
|
let response = await api.post(endpoint, {
|
||||||
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
|
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
|
||||||
network,
|
network,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.apiToken).to.eql(user.apiToken);
|
expect(response.apiToken).to.eql(registerResponse.apiToken);
|
||||||
expect(response.id).to.eql(user._id);
|
expect(response.id).to.eql(registerResponse.id);
|
||||||
expect(response.newUser).to.be.false;
|
expect(response.newUser).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('add social auth to an existing user', async () => {
|
||||||
|
let response = await user.post(endpoint, {
|
||||||
|
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(response.apiToken).to.exist;
|
||||||
|
expect(response.id).to.exist;
|
||||||
|
expect(response.newUser).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enrolls a new user in an A/B test', async () => {
|
||||||
|
await api.post(endpoint, {
|
||||||
|
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
|
||||||
|
network,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(getProperty('users', user._id, '_ABtest')).to.eventually.be.a('string');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
describe('Auth Controller', function() {
|
describe('Auth Controller', function() {
|
||||||
var scope, ctrl, user, $httpBackend, $window, $modal;
|
var scope, ctrl, user, $httpBackend, $window, $modal, alert, Auth;
|
||||||
|
|
||||||
beforeEach(function(){
|
beforeEach(function(){
|
||||||
module(function($provide) {
|
module(function($provide) {
|
||||||
|
Auth = {
|
||||||
|
runAuth: sandbox.spy(),
|
||||||
|
};
|
||||||
$provide.value('Analytics', analyticsMock);
|
$provide.value('Analytics', analyticsMock);
|
||||||
$provide.value('Chat', { seenMessage: function() {} });
|
$provide.value('Chat', { seenMessage: function() {} });
|
||||||
|
$provide.value('Auth', Auth);
|
||||||
});
|
});
|
||||||
|
|
||||||
inject(function(_$httpBackend_, $rootScope, $controller, _$modal_) {
|
inject(function(_$httpBackend_, $rootScope, $controller, _$modal_) {
|
||||||
@@ -17,27 +21,27 @@ describe('Auth Controller', function() {
|
|||||||
$window = { location: { href: ""}, alert: sandbox.spy() };
|
$window = { location: { href: ""}, alert: sandbox.spy() };
|
||||||
$modal = _$modal_;
|
$modal = _$modal_;
|
||||||
user = { user: {}, authenticate: sandbox.spy() };
|
user = { user: {}, authenticate: sandbox.spy() };
|
||||||
|
alert = { authErrorAlert: sandbox.spy() };
|
||||||
|
|
||||||
ctrl = $controller('AuthCtrl', {$scope: scope, $window: $window, User: user});
|
ctrl = $controller('AuthCtrl', {$scope: scope, $window: $window, User: user, Alert: alert});
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('logging in', function() {
|
describe('logging in', function() {
|
||||||
|
|
||||||
it('should log in users with correct uname / pass', function() {
|
it('should log in users with correct uname / pass', function() {
|
||||||
$httpBackend.expectPOST('/api/v3/user/auth/local/login').respond({data: {id: 'abc', apiToken: 'abc'}});
|
$httpBackend.expectPOST('/api/v3/user/auth/local/login').respond({data: {id: 'abc', apiToken: 'abc'}});
|
||||||
scope.auth();
|
scope.auth();
|
||||||
$httpBackend.flush();
|
$httpBackend.flush();
|
||||||
expect(user.authenticate).to.be.calledOnce;
|
expect(Auth.runAuth).to.be.calledOnce;
|
||||||
expect($window.alert).to.not.be.called;
|
expect(alert.authErrorAlert).to.not.be.called;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not log in users with incorrect uname / pass', function() {
|
it('should not log in users with incorrect uname / pass', function() {
|
||||||
$httpBackend.expectPOST('/api/v3/user/auth/local/login').respond(404, '');
|
$httpBackend.expectPOST('/api/v3/user/auth/local/login').respond(404, '');
|
||||||
scope.auth();
|
scope.auth();
|
||||||
$httpBackend.flush();
|
$httpBackend.flush();
|
||||||
expect(user.authenticate).to.not.be.called;
|
expect(Auth.runAuth).to.not.be.called;
|
||||||
expect($window.alert).to.be.calledOnce;
|
expect(alert.authErrorAlert).to.be.calledOnce;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe('Footer Controller', function() {
|
|||||||
user: user
|
user: user
|
||||||
};
|
};
|
||||||
scope = $rootScope.$new();
|
scope = $rootScope.$new();
|
||||||
$controller('FooterCtrl', {$scope: scope, User: User});
|
$controller('FooterCtrl', {$scope: scope, User: User, Social: {}});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
context('Debug mode', function() {
|
context('Debug mode', function() {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
angular.module('habitrpg')
|
angular.module('habitrpg')
|
||||||
.controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', '$window','ApiUrl', '$modal', 'Analytics',
|
.controller("AuthCtrl", ['$scope', '$rootScope', 'User', '$http', '$location', '$window','ApiUrl', '$modal', 'Alert', 'Analytics', 'Auth',
|
||||||
function($scope, $rootScope, User, $http, $location, $window, ApiUrl, $modal, Analytics) {
|
function($scope, $rootScope, User, $http, $location, $window, ApiUrl, $modal, Alert, Analytics, Auth) {
|
||||||
$scope.Analytics = Analytics;
|
$scope.Analytics = Analytics;
|
||||||
|
|
||||||
$scope.logout = function() {
|
$scope.logout = function() {
|
||||||
@@ -14,30 +14,6 @@ angular.module('habitrpg')
|
|||||||
$window.location.href = '/logout';
|
$window.location.href = '/logout';
|
||||||
};
|
};
|
||||||
|
|
||||||
var runAuth = function(id, token) {
|
|
||||||
User.authenticate(id, token, function(err) {
|
|
||||||
if(!err) $scope.registrationInProgress = false;
|
|
||||||
Analytics.login();
|
|
||||||
Analytics.updateUser();
|
|
||||||
$window.location.href = ('/' + window.location.hash);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function errorAlert(data, status, headers, config) {
|
|
||||||
$scope.registrationInProgress = false;
|
|
||||||
if (status === 0) {
|
|
||||||
$window.alert(window.env.t('noReachServer'));
|
|
||||||
} else if (status === 400 && data.errors && _.isArray(data.errors)) { // bad requests
|
|
||||||
data.errors.forEach(function (err) {
|
|
||||||
$window.alert(err.message);
|
|
||||||
});
|
|
||||||
} else if (!!data && !!data.error) {
|
|
||||||
$window.alert(data.message);
|
|
||||||
} else {
|
|
||||||
$window.alert(window.env.t('errorUpCase') + ' ' + status);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$scope.registrationInProgress = false;
|
$scope.registrationInProgress = false;
|
||||||
|
|
||||||
$scope.register = function() {
|
$scope.register = function() {
|
||||||
@@ -60,9 +36,12 @@ angular.module('habitrpg')
|
|||||||
}
|
}
|
||||||
|
|
||||||
$http.post(url, scope.registerVals).success(function(res, status, headers, config) {
|
$http.post(url, scope.registerVals).success(function(res, status, headers, config) {
|
||||||
runAuth(res.data._id, res.data.apiToken);
|
Auth.runAuth(res.data._id, res.data.apiToken);
|
||||||
Analytics.register();
|
Analytics.register();
|
||||||
}).error(errorAlert);
|
}).error(function(data, status, headers, config) {
|
||||||
|
$scope.registrationInProgress = false;
|
||||||
|
Alert.authErrorAlert(data, status, headers, config)
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.auth = function() {
|
$scope.auth = function() {
|
||||||
@@ -73,8 +52,8 @@ angular.module('habitrpg')
|
|||||||
//@TODO: Move all the $http methods to a service
|
//@TODO: Move all the $http methods to a service
|
||||||
$http.post(ApiUrl.get() + "/api/v3/user/auth/local/login", data)
|
$http.post(ApiUrl.get() + "/api/v3/user/auth/local/login", data)
|
||||||
.success(function(res, status, headers, config) {
|
.success(function(res, status, headers, config) {
|
||||||
runAuth(res.data.id, res.data.apiToken);
|
Auth.runAuth(res.data.id, res.data.apiToken);
|
||||||
}).error(errorAlert);
|
}).error(Alert.authErrorAlert);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.playButtonClick = function() {
|
$scope.playButtonClick = function() {
|
||||||
@@ -113,8 +92,8 @@ angular.module('habitrpg')
|
|||||||
hello(network).login({scope:'email'}).then(function(auth){
|
hello(network).login({scope:'email'}).then(function(auth){
|
||||||
$http.post(ApiUrl.get() + "/api/v3/user/auth/social", auth)
|
$http.post(ApiUrl.get() + "/api/v3/user/auth/social", auth)
|
||||||
.success(function(res, status, headers, config) {
|
.success(function(res, status, headers, config) {
|
||||||
runAuth(res.data.id, res.data.apiToken);
|
Auth.runAuth(res.data.id, res.data.apiToken);
|
||||||
}).error(errorAlert);
|
}).error(Alert.authErrorAlert);
|
||||||
}, function( e ){
|
}, function( e ){
|
||||||
alert("Signin error: " + e.message );
|
alert("Signin error: " + e.message );
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,14 +2,17 @@
|
|||||||
|
|
||||||
// Make user and settings available for everyone through root scope.
|
// Make user and settings available for everyone through root scope.
|
||||||
habitrpg.controller('SettingsCtrl',
|
habitrpg.controller('SettingsCtrl',
|
||||||
['$scope', 'User', '$rootScope', '$http', 'ApiUrl', 'Guide', '$location', '$timeout', 'Content', 'Notification', 'Shared', '$compile',
|
['$scope', 'User', '$rootScope', '$http', 'ApiUrl', 'Guide', '$location', '$timeout', 'Content', 'Notification', 'Shared', 'Social', '$compile',
|
||||||
function($scope, User, $rootScope, $http, ApiUrl, Guide, $location, $timeout, Content, Notification, Shared, $compile) {
|
function($scope, User, $rootScope, $http, ApiUrl, Guide, $location, $timeout, Content, Notification, Shared, Social, $compile) {
|
||||||
var RELEASE_ANIMAL_TYPES = {
|
var RELEASE_ANIMAL_TYPES = {
|
||||||
pets: 'releasePets',
|
pets: 'releasePets',
|
||||||
mounts: 'releaseMounts',
|
mounts: 'releaseMounts',
|
||||||
both: 'releaseBoth',
|
both: 'releaseBoth',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var SOCIAL_AUTH_NETWORKS = Shared.constants.SUPPORTED_SOCIAL_NETWORKS;
|
||||||
|
$scope.SOCIAL_AUTH_NETWORKS = SOCIAL_AUTH_NETWORKS;
|
||||||
|
|
||||||
// FIXME we have this re-declared everywhere, figure which is the canonical version and delete the rest
|
// FIXME we have this re-declared everywhere, figure which is the canonical version and delete the rest
|
||||||
// $scope.auth = function (id, token) {
|
// $scope.auth = function (id, token) {
|
||||||
// User.authenticate(id, token, function (err) {
|
// User.authenticate(id, token, function (err) {
|
||||||
@@ -287,6 +290,40 @@ habitrpg.controller('SettingsCtrl',
|
|||||||
return Math.floor(numberOfHourglasses);
|
return Math.floor(numberOfHourglasses);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.hasBackupAuthOption = function(user, checkedNetworkKey) {
|
||||||
|
if (user.auth.local.username) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return _.find(SOCIAL_AUTH_NETWORKS, function (network) {
|
||||||
|
if (network.key !== checkedNetworkKey) {
|
||||||
|
if (user.auth.hasOwnProperty(network.key)) {
|
||||||
|
return user.auth[network.key].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.hasSocialAuth = function (user) {
|
||||||
|
return _.find(SOCIAL_AUTH_NETWORKS, function (network) {
|
||||||
|
if (user.auth.hasOwnProperty(network.key)) {
|
||||||
|
return user.auth[network.key].id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.deleteSocialAuth = function (networkKey) {
|
||||||
|
var network = _.find(SOCIAL_AUTH_NETWORKS, function (network) {
|
||||||
|
return network.key === networkKey;
|
||||||
|
});
|
||||||
|
|
||||||
|
$http.delete(ApiUrl.get() + "/api/v3/user/auth/social/"+networkKey).success(function(){
|
||||||
|
Notification.text(env.t("detachedSocial", {network: network.name}));
|
||||||
|
User.sync();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.socialLogin = Social.socialLogin;
|
||||||
|
|
||||||
function _calculateNextCron() {
|
function _calculateNextCron() {
|
||||||
$scope.dayStart;
|
$scope.dayStart;
|
||||||
|
|
||||||
|
|||||||
32
website/client-old/js/services/alertServices.js
Normal file
32
website/client-old/js/services/alertServices.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
(function(){
|
||||||
|
angular
|
||||||
|
.module('habitrpg')
|
||||||
|
.factory('Alert', alertFactory);
|
||||||
|
|
||||||
|
alertFactory.$inject = [
|
||||||
|
'$window'
|
||||||
|
];
|
||||||
|
|
||||||
|
function alertFactory($window) {
|
||||||
|
|
||||||
|
function authErrorAlert(data, status, headers, config) {
|
||||||
|
if (status === 0) {
|
||||||
|
$window.alert(window.env.t('noReachServer'));
|
||||||
|
} else if (status === 400 && data.errors && _.isArray(data.errors)) { // bad requests
|
||||||
|
data.errors.forEach(function (err) {
|
||||||
|
$window.alert(err.message);
|
||||||
|
});
|
||||||
|
} else if (!!data && !!data.error) {
|
||||||
|
$window.alert(data.message);
|
||||||
|
} else {
|
||||||
|
$window.alert(window.env.t('errorUpCase') + ' ' + status);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
authErrorAlert: authErrorAlert,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}());
|
||||||
28
website/client-old/js/services/authServices.js
Normal file
28
website/client-old/js/services/authServices.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
(function(){
|
||||||
|
angular
|
||||||
|
.module('habitrpg')
|
||||||
|
.factory('Auth', authFactory);
|
||||||
|
|
||||||
|
authFactory.$inject = [
|
||||||
|
'$window',
|
||||||
|
'User',
|
||||||
|
'Analytics'
|
||||||
|
];
|
||||||
|
|
||||||
|
function authFactory($window, User, Analytics) {
|
||||||
|
|
||||||
|
var runAuth = function(id, token) {
|
||||||
|
User.authenticate(id, token, function(err) {
|
||||||
|
Analytics.login();
|
||||||
|
Analytics.updateUser();
|
||||||
|
$window.location.href = ('/' + window.location.hash);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
runAuth: runAuth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}());
|
||||||
@@ -5,9 +5,11 @@
|
|||||||
.module('habitrpg')
|
.module('habitrpg')
|
||||||
.factory('Social', socialFactory);
|
.factory('Social', socialFactory);
|
||||||
|
|
||||||
socialFactory.$inject = [];
|
socialFactory.$inject = [
|
||||||
|
'$http','ApiUrl', 'Alert', 'Auth'
|
||||||
|
];
|
||||||
|
|
||||||
function socialFactory() {
|
function socialFactory($http, ApiUrl, Alert, Auth) {
|
||||||
|
|
||||||
function loadWidgets() {
|
function loadWidgets() {
|
||||||
// Facebook
|
// Facebook
|
||||||
@@ -34,8 +36,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hello.init({
|
||||||
|
facebook : window.env.FACEBOOK_KEY,
|
||||||
|
google : window.env.GOOGLE_CLIENT_ID
|
||||||
|
});
|
||||||
|
|
||||||
|
function socialLogin(network){
|
||||||
|
hello(network).login({scope:['email']}).then(function(auth){
|
||||||
|
$http.post(ApiUrl.get() + "/api/v3/user/auth/social", auth)
|
||||||
|
.success(function(res, status, headers, config) {
|
||||||
|
Auth.runAuth(res.data.id, res.data.apiToken);
|
||||||
|
}).error(Alert.authErrorAlert);
|
||||||
|
}, function( err ){
|
||||||
|
alert("Signin error: " + err.message );
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loadWidgets: loadWidgets
|
loadWidgets: loadWidgets,
|
||||||
|
socialLogin: socialLogin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}());
|
}());
|
||||||
|
|||||||
@@ -42,6 +42,8 @@
|
|||||||
"js/config.js",
|
"js/config.js",
|
||||||
|
|
||||||
"js/services/sharedServices.js",
|
"js/services/sharedServices.js",
|
||||||
|
"js/services/alertServices.js",
|
||||||
|
"js/services/authServices.js",
|
||||||
"js/services/notificationServices.js",
|
"js/services/notificationServices.js",
|
||||||
"js/directives/directives.js",
|
"js/directives/directives.js",
|
||||||
"js/services/analyticsServices.js",
|
"js/services/analyticsServices.js",
|
||||||
@@ -134,7 +136,9 @@
|
|||||||
|
|
||||||
"js/env.js",
|
"js/env.js",
|
||||||
"js/static.js",
|
"js/static.js",
|
||||||
|
"js/services/alertServices.js",
|
||||||
"js/services/analyticsServices.js",
|
"js/services/analyticsServices.js",
|
||||||
|
"js/services/authServices.js",
|
||||||
"js/services/notificationServices.js",
|
"js/services/notificationServices.js",
|
||||||
"js/services/userNotificationsService.js",
|
"js/services/userNotificationsService.js",
|
||||||
"js/services/userServices.js",
|
"js/services/userServices.js",
|
||||||
@@ -172,7 +176,9 @@
|
|||||||
|
|
||||||
"js/env.js",
|
"js/env.js",
|
||||||
"js/static.js",
|
"js/static.js",
|
||||||
|
"js/services/alertServices.js",
|
||||||
"js/services/analyticsServices.js",
|
"js/services/analyticsServices.js",
|
||||||
|
"js/services/authServices.js",
|
||||||
"js/services/notificationServices.js",
|
"js/services/notificationServices.js",
|
||||||
"js/services/sharedServices.js",
|
"js/services/sharedServices.js",
|
||||||
"js/services/socialServices.js",
|
"js/services/socialServices.js",
|
||||||
|
|||||||
@@ -95,7 +95,8 @@
|
|||||||
"leadText": "Habitica is a free habit building and productivity app that treats your real life like a game. With in-game rewards and punishments to motivate you and a strong social network to inspire you, Habitica can help you achieve your goals to become healthy, hard-working, and happy.",
|
"leadText": "Habitica is a free habit building and productivity app that treats your real life like a game. With in-game rewards and punishments to motivate you and a strong social network to inspire you, Habitica can help you achieve your goals to become healthy, hard-working, and happy.",
|
||||||
"login": "Login",
|
"login": "Login",
|
||||||
"loginAndReg": "Login / Register",
|
"loginAndReg": "Login / Register",
|
||||||
"loginFacebookAlt": "Login / Register with Facebook",
|
"loginFacebookAlt": "Sign in with Facebook",
|
||||||
|
"loginGoogleAlt": "Sign in with Google",
|
||||||
"logout": "Log Out",
|
"logout": "Log Out",
|
||||||
"marketing1Header": "Improve Your Habits By Playing A Game",
|
"marketing1Header": "Improve Your Habits By Playing A Game",
|
||||||
"marketing1Lead1": "Habitica is a video game to help you improve real life habits. It \"gamifies\" your life by turning all your tasks (habits, dailies, and to-dos) into little monsters you have to conquer. The better you are at this, the more you progress in the game. If you slip up in life, your character starts backsliding in the game.",
|
"marketing1Lead1": "Habitica is a video game to help you improve real life habits. It \"gamifies\" your life by turning all your tasks (habits, dailies, and to-dos) into little monsters you have to conquer. The better you are at this, the more you progress in the game. If you slip up in life, your character starts backsliding in the game.",
|
||||||
@@ -258,8 +259,8 @@
|
|||||||
"invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
|
"invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".",
|
||||||
"invalidCredentials": "There is no account that uses those credentials.",
|
"invalidCredentials": "There is no account that uses those credentials.",
|
||||||
"accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
|
"accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.",
|
||||||
"onlyFbSupported": "Only Facebook is supported currently.",
|
"unsupportedNetwork": "This network is not currently supported.",
|
||||||
"cantDetachFb": "Account lacks another authentication method, can't detach Facebook.",
|
"cantDetachSocial": "Account lacks another authentication method; can't detach this authentication method.",
|
||||||
"onlySocialAttachLocal": "Local authentication can be added to only a social account.",
|
"onlySocialAttachLocal": "Local authentication can be added to only a social account.",
|
||||||
"invalidReqParams": "Invalid request parameters.",
|
"invalidReqParams": "Invalid request parameters.",
|
||||||
"memberIdRequired": "\"member\" must be a valid UUID.",
|
"memberIdRequired": "\"member\" must be a valid UUID.",
|
||||||
|
|||||||
@@ -91,8 +91,8 @@
|
|||||||
"passwordSuccess": "Password successfully changed",
|
"passwordSuccess": "Password successfully changed",
|
||||||
"usernameSuccess": "Login Name successfully changed",
|
"usernameSuccess": "Login Name successfully changed",
|
||||||
"emailSuccess": "Email successfully changed",
|
"emailSuccess": "Email successfully changed",
|
||||||
"detachFacebook": "De-register Facebook",
|
"detachSocial": "De-register <%= network %>",
|
||||||
"detachedFacebook": "Successfully removed Facebook from your account",
|
"detachedSocial": "Successfully removed <%= network %> authentication from your account",
|
||||||
"addedLocalAuth": "Successfully added local authentication",
|
"addedLocalAuth": "Successfully added local authentication",
|
||||||
"data": "Data",
|
"data": "Data",
|
||||||
"exportData": "Export Data",
|
"exportData": "Export Data",
|
||||||
@@ -101,7 +101,8 @@
|
|||||||
"emailChange3": " including both your old and new email address as well as your User ID.",
|
"emailChange3": " including both your old and new email address as well as your User ID.",
|
||||||
"usernameOrEmail": "Login Name or Email",
|
"usernameOrEmail": "Login Name or Email",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"registeredWithFb": "Registered with Facebook",
|
"registerWithSocial": "Register with <%= network %>",
|
||||||
|
"registeredWithSocial": "Registered with <%= network %>",
|
||||||
"loginNameDescription1": "This is what you use to login to Habitica. Go to ",
|
"loginNameDescription1": "This is what you use to login to Habitica. Go to ",
|
||||||
"loginNameDescription2": "User->Profile",
|
"loginNameDescription2": "User->Profile",
|
||||||
"loginNameDescription3": "to change the name that appears in your avatar and chat messages.",
|
"loginNameDescription3": "to change the name that appears in your avatar and chat messages.",
|
||||||
|
|||||||
@@ -5,3 +5,8 @@ export const ATTRIBUTES = ['str', 'int', 'per', 'con'];
|
|||||||
|
|
||||||
export const TAVERN_ID = '00000000-0000-4000-A000-000000000000';
|
export const TAVERN_ID = '00000000-0000-4000-A000-000000000000';
|
||||||
export const LARGE_GROUP_COUNT_MESSAGE_CUTOFF = 5000;
|
export const LARGE_GROUP_COUNT_MESSAGE_CUTOFF = 5000;
|
||||||
|
|
||||||
|
export const SUPPORTED_SOCIAL_NETWORKS = [
|
||||||
|
{key: 'facebook', name: 'Facebook'},
|
||||||
|
{key: 'google', name: 'Google'},
|
||||||
|
];
|
||||||
|
|||||||
@@ -23,10 +23,12 @@ import {
|
|||||||
MAX_STAT_POINTS,
|
MAX_STAT_POINTS,
|
||||||
TAVERN_ID,
|
TAVERN_ID,
|
||||||
LARGE_GROUP_COUNT_MESSAGE_CUTOFF,
|
LARGE_GROUP_COUNT_MESSAGE_CUTOFF,
|
||||||
|
SUPPORTED_SOCIAL_NETWORKS,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
|
||||||
api.constants = {
|
api.constants = {
|
||||||
LARGE_GROUP_COUNT_MESSAGE_CUTOFF,
|
LARGE_GROUP_COUNT_MESSAGE_CUTOFF,
|
||||||
|
SUPPORTED_SOCIAL_NETWORKS,
|
||||||
};
|
};
|
||||||
// TODO Move these under api.constants
|
// TODO Move these under api.constants
|
||||||
api.maxLevel = MAX_LEVEL;
|
api.maxLevel = MAX_LEVEL;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
BadRequest,
|
BadRequest,
|
||||||
NotFound,
|
NotFound,
|
||||||
} from '../../libs/errors';
|
} from '../../libs/errors';
|
||||||
import Bluebird from 'bluebird';
|
|
||||||
import * as passwordUtils from '../../libs/password';
|
import * as passwordUtils from '../../libs/password';
|
||||||
import logger from '../../libs/logger';
|
import logger from '../../libs/logger';
|
||||||
import { model as User } from '../../models/user';
|
import { model as User } from '../../models/user';
|
||||||
@@ -20,6 +19,7 @@ import { sendTxn as sendTxnEmail } from '../../libs/email';
|
|||||||
import { decrypt } from '../../libs/encryption';
|
import { decrypt } from '../../libs/encryption';
|
||||||
import { send as sendEmail } from '../../libs/email';
|
import { send as sendEmail } from '../../libs/email';
|
||||||
import pusher from '../../libs/pusher';
|
import pusher from '../../libs/pusher';
|
||||||
|
import common from '../../../common';
|
||||||
|
|
||||||
let api = {};
|
let api = {};
|
||||||
|
|
||||||
@@ -50,6 +50,18 @@ async function _handleGroupInvitation (user, invite) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasBackupAuth (user, networkToRemove) {
|
||||||
|
if (user.auth.local.username) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hasAlternateNetwork = common.constants.SUPPORTED_SOCIAL_NETWORKS.find((network) => {
|
||||||
|
return network.key !== networkToRemove && user.auth[network.key].id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return hasAlternateNetwork;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {post} /api/v3/user/auth/local/register Register
|
* @api {post} /api/v3/user/auth/local/register Register
|
||||||
* @apiDescription Register a new user with email, username and password or attach local auth to a social user
|
* @apiDescription Register a new user with email, username and password or attach local auth to a social user
|
||||||
@@ -69,7 +81,7 @@ api.registerLocal = {
|
|||||||
middlewares: [authWithHeaders(true)],
|
middlewares: [authWithHeaders(true)],
|
||||||
url: '/user/auth/local/register',
|
url: '/user/auth/local/register',
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
let fbUser = res.locals.user; // If adding local auth to social user
|
let existingUser = res.locals.user; // If adding local auth to social user
|
||||||
|
|
||||||
req.checkBody({
|
req.checkBody({
|
||||||
email: {
|
email: {
|
||||||
@@ -121,10 +133,15 @@ api.registerLocal = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (fbUser) {
|
if (existingUser) {
|
||||||
if (!fbUser.auth.facebook.id) throw new NotAuthorized(res.t('onlySocialAttachLocal'));
|
let hasSocialAuth = common.constants.SUPPORTED_SOCIAL_NETWORKS.find(network => {
|
||||||
fbUser.auth.local = newUser.auth.local;
|
if (existingUser.auth.hasOwnProperty(network.key)) {
|
||||||
newUser = fbUser;
|
return existingUser.auth[network.key].id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!hasSocialAuth) throw new NotAuthorized(res.t('onlySocialAttachLocal'));
|
||||||
|
existingUser.auth.local = newUser.auth.local;
|
||||||
|
newUser = existingUser;
|
||||||
} else {
|
} else {
|
||||||
newUser = new User(newUser);
|
newUser = new User(newUser);
|
||||||
newUser.registeredThrough = req.headers['x-client']; // Not saved, used to create the correct tasks based on the device used
|
newUser.registeredThrough = req.headers['x-client']; // Not saved, used to create the correct tasks based on the device used
|
||||||
@@ -137,7 +154,7 @@ api.registerLocal = {
|
|||||||
|
|
||||||
let savedUser = await newUser.save();
|
let savedUser = await newUser.save();
|
||||||
|
|
||||||
if (savedUser.auth.facebook.id) {
|
if (existingUser) {
|
||||||
res.respond(200, savedUser.toJSON().auth.local); // We convert to toJSON to hide private fields
|
res.respond(200, savedUser.toJSON().auth.local); // We convert to toJSON to hide private fields
|
||||||
} else {
|
} else {
|
||||||
res.respond(201, savedUser);
|
res.respond(201, savedUser);
|
||||||
@@ -148,7 +165,7 @@ api.registerLocal = {
|
|||||||
.remove({email: savedUser.auth.local.email})
|
.remove({email: savedUser.auth.local.email})
|
||||||
.then(() => sendTxnEmail(savedUser, 'welcome'));
|
.then(() => sendTxnEmail(savedUser, 'welcome'));
|
||||||
|
|
||||||
if (!savedUser.auth.facebook.id) {
|
if (!existingUser) {
|
||||||
res.analytics.track('register', {
|
res.analytics.track('register', {
|
||||||
category: 'acquisition',
|
category: 'acquisition',
|
||||||
type: 'local',
|
type: 'local',
|
||||||
@@ -228,9 +245,9 @@ api.loginLocal = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function _passportFbProfile (accessToken) {
|
function _passportProfile (network, accessToken) {
|
||||||
return new Bluebird((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
passport._strategies.facebook.userProfile(accessToken, (err, profile) => {
|
passport._strategies[network].userProfile(accessToken, (err, profile) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
} else {
|
} else {
|
||||||
@@ -243,14 +260,19 @@ function _passportFbProfile (accessToken) {
|
|||||||
// Called as a callback by Facebook (or other social providers). Internal route
|
// Called as a callback by Facebook (or other social providers). Internal route
|
||||||
api.loginSocial = {
|
api.loginSocial = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
middlewares: [authWithHeaders(true)],
|
||||||
url: '/user/auth/social', // this isn't the most appropriate url but must be the same as v2
|
url: '/user/auth/social', // this isn't the most appropriate url but must be the same as v2
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
|
let existingUser = res.locals.user;
|
||||||
let accessToken = req.body.authResponse.access_token;
|
let accessToken = req.body.authResponse.access_token;
|
||||||
let network = req.body.network;
|
let network = req.body.network;
|
||||||
|
|
||||||
if (network !== 'facebook') throw new NotAuthorized(res.t('onlyFbSupported'));
|
let isSupportedNetwork = common.constants.SUPPORTED_SOCIAL_NETWORKS.find(supportedNetwork => {
|
||||||
|
return supportedNetwork.key === network;
|
||||||
|
});
|
||||||
|
if (!isSupportedNetwork) throw new BadRequest(res.t('unsupportedNetwork'));
|
||||||
|
|
||||||
let profile = await _passportFbProfile(accessToken);
|
let profile = await _passportProfile(network, accessToken);
|
||||||
|
|
||||||
let user = await User.findOne({
|
let user = await User.findOne({
|
||||||
[`auth.${network}.id`]: profile.id,
|
[`auth.${network}.id`]: profile.id,
|
||||||
@@ -260,29 +282,38 @@ api.loginSocial = {
|
|||||||
if (user) {
|
if (user) {
|
||||||
_loginRes(user, ...arguments);
|
_loginRes(user, ...arguments);
|
||||||
} else { // Create new user
|
} else { // Create new user
|
||||||
user = new User({
|
user = {
|
||||||
auth: {
|
auth: {
|
||||||
[network]: profile,
|
[network]: profile,
|
||||||
},
|
},
|
||||||
preferences: {
|
preferences: {
|
||||||
language: req.language,
|
language: req.language,
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
user.registeredThrough = req.headers['x-client'];
|
if (existingUser) {
|
||||||
|
existingUser.auth[network] = user.auth[network];
|
||||||
|
user = existingUser;
|
||||||
|
} else {
|
||||||
|
user = new User(user);
|
||||||
|
user.registeredThrough = req.headers['x-client']; // Not saved, used to create the correct tasks based on the device used
|
||||||
|
}
|
||||||
|
|
||||||
let savedUser = await user.save();
|
let savedUser = await user.save();
|
||||||
|
|
||||||
|
if (!existingUser) {
|
||||||
user.newUser = true;
|
user.newUser = true;
|
||||||
|
}
|
||||||
_loginRes(user, ...arguments);
|
_loginRes(user, ...arguments);
|
||||||
|
|
||||||
// Clean previous email preferences
|
// Clean previous email preferences
|
||||||
if (savedUser.auth[network].emails && savedUser.auth.facebook.emails[0] && savedUser.auth[network].emails[0].value) {
|
if (savedUser.auth[network].emails && savedUser.auth[network].emails[0] && savedUser.auth[network].emails[0].value) {
|
||||||
EmailUnsubscription
|
EmailUnsubscription
|
||||||
.remove({email: savedUser.auth[network].emails[0].value.toLowerCase()})
|
.remove({email: savedUser.auth[network].emails[0].value.toLowerCase()})
|
||||||
.exec()
|
.exec()
|
||||||
.then(() => sendTxnEmail(savedUser, 'welcome')); // eslint-disable-line max-nested-callbacks
|
.then(() => sendTxnEmail(savedUser, 'welcome')); // eslint-disable-line max-nested-callbacks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!existingUser) {
|
||||||
res.analytics.track('register', {
|
res.analytics.track('register', {
|
||||||
category: 'acquisition',
|
category: 'acquisition',
|
||||||
type: network,
|
type: network,
|
||||||
@@ -291,6 +322,7 @@ api.loginSocial = {
|
|||||||
headers: req.headers,
|
headers: req.headers,
|
||||||
user: savedUser,
|
user: savedUser,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -576,11 +608,15 @@ api.deleteSocial = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
let user = res.locals.user;
|
let user = res.locals.user;
|
||||||
let network = req.params.network;
|
let network = req.params.network;
|
||||||
|
let isSupportedNetwork = common.constants.SUPPORTED_SOCIAL_NETWORKS.find(supportedNetwork => {
|
||||||
if (network !== 'facebook') throw new NotAuthorized(res.t('onlyFbSupported'));
|
return supportedNetwork.key === network;
|
||||||
if (!user.auth.local.username) throw new NotAuthorized(res.t('cantDetachFb'));
|
});
|
||||||
|
if (!isSupportedNetwork) throw new BadRequest(res.t('unsupportedNetwork'));
|
||||||
await User.update({_id: user._id}, {$unset: {'auth.facebook': 1}}).exec();
|
if (!hasBackupAuth(user, network)) throw new NotAuthorized(res.t('cantDetachSocial'));
|
||||||
|
let unset = {
|
||||||
|
[`auth.${network}`]: 1,
|
||||||
|
};
|
||||||
|
await User.update({_id: user._id}, {$unset: unset}).exec();
|
||||||
|
|
||||||
res.respond(200, {});
|
res.respond(200, {});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -607,6 +607,7 @@ async function _inviteByEmail (invite, group, inviter, req, res) {
|
|||||||
let userToContact = await User.findOne({$or: [
|
let userToContact = await User.findOne({$or: [
|
||||||
{'auth.local.email': invite.email},
|
{'auth.local.email': invite.email},
|
||||||
{'auth.facebook.emails.value': invite.email},
|
{'auth.facebook.emails.value': invite.email},
|
||||||
|
{'auth.google.emails.value': invite.email},
|
||||||
]})
|
]})
|
||||||
.select({_id: true, 'preferences.emailNotifications': true})
|
.select({_id: true, 'preferences.emailNotifications': true})
|
||||||
.exec();
|
.exec();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { TAVERN_ID } from '../models/group';
|
|||||||
import { encrypt } from './encryption';
|
import { encrypt } from './encryption';
|
||||||
import request from 'request';
|
import request from 'request';
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
|
import common from '../../common';
|
||||||
|
|
||||||
const IS_PROD = nconf.get('IS_PROD');
|
const IS_PROD = nconf.get('IS_PROD');
|
||||||
const EMAIL_SERVER = {
|
const EMAIL_SERVER = {
|
||||||
@@ -47,8 +48,12 @@ export function getUserInfo (user, fields = []) {
|
|||||||
if (fields.indexOf('email') !== -1) {
|
if (fields.indexOf('email') !== -1) {
|
||||||
if (user.auth.local && user.auth.local.email) {
|
if (user.auth.local && user.auth.local.email) {
|
||||||
info.email = user.auth.local.email;
|
info.email = user.auth.local.email;
|
||||||
} else if (user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0] && user.auth.facebook.emails[0].value) {
|
} else {
|
||||||
info.email = user.auth.facebook.emails[0].value;
|
common.constants.SUPPORTED_SOCIAL_NETWORKS.forEach(network => {
|
||||||
|
if (user.auth[network.key] && user.auth[network.key].emails && user.auth[network.key].emails[0] && user.auth[network.key].emails[0].value) {
|
||||||
|
info.email = user.auth[network.key].emails[0].value;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import passport from 'passport';
|
import passport from 'passport';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import passportFacebook from 'passport-facebook';
|
import { Strategy as FacebookStrategy } from 'passport-facebook';
|
||||||
|
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
|
||||||
const FacebookStrategy = passportFacebook.Strategy;
|
|
||||||
|
|
||||||
// Passport session setup.
|
// Passport session setup.
|
||||||
// To support persistent login sessions, Passport needs to be able to
|
// To support persistent login sessions, Passport needs to be able to
|
||||||
@@ -22,3 +21,8 @@ passport.use(new FacebookStrategy({
|
|||||||
clientSecret: nconf.get('FACEBOOK_SECRET'),
|
clientSecret: nconf.get('FACEBOOK_SECRET'),
|
||||||
// callbackURL: nconf.get("BASE_URL") + "/auth/facebook/callback"
|
// callbackURL: nconf.get("BASE_URL") + "/auth/facebook/callback"
|
||||||
}, (accessToken, refreshToken, profile, done) => done(null, profile)));
|
}, (accessToken, refreshToken, profile, done) => done(null, profile)));
|
||||||
|
|
||||||
|
passport.use(new GoogleStrategy({
|
||||||
|
clientID: nconf.get('GOOGLE_CLIENT_ID'),
|
||||||
|
clientSecret: nconf.get('GOOGLE_CLIENT_SECRET'),
|
||||||
|
}, (accessToken, refreshToken, profile, done) => done(null, profile)));
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { mods } from '../models/user';
|
|||||||
// To avoid stringifying more data then we need,
|
// To avoid stringifying more data then we need,
|
||||||
// items from `env` used on the client will have to be specified in this array
|
// items from `env` used on the client will have to be specified in this array
|
||||||
const CLIENT_VARS = ['language', 'isStaticPage', 'availableLanguages', 'translations',
|
const CLIENT_VARS = ['language', 'isStaticPage', 'availableLanguages', 'translations',
|
||||||
'FACEBOOK_KEY', 'FACEBOOK_ANALYTICS', 'NODE_ENV', 'BASE_URL', 'GA_ID',
|
'FACEBOOK_KEY', 'GOOGLE_CLIENT_ID', 'FACEBOOK_ANALYTICS', 'NODE_ENV', 'BASE_URL', 'GA_ID',
|
||||||
'AMAZON_PAYMENTS', 'STRIPE_PUB_KEY', 'AMPLITUDE_KEY',
|
'AMAZON_PAYMENTS', 'STRIPE_PUB_KEY', 'AMPLITUDE_KEY',
|
||||||
'worldDmg', 'mods', 'IS_MOBILE', 'PUSHER:KEY', 'PUSHER:ENABLED'];
|
'worldDmg', 'mods', 'IS_MOBILE', 'PUSHER:KEY', 'PUSHER:ENABLED'];
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ let env = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_ANALYTICS FACEBOOK_KEY AMPLITUDE_KEY PUSHER:KEY PUSHER:ENABLED'
|
'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_ANALYTICS FACEBOOK_KEY GOOGLE_CLIENT_ID AMPLITUDE_KEY PUSHER:KEY PUSHER:ENABLED'
|
||||||
.split(' ')
|
.split(' ')
|
||||||
.forEach(key => {
|
.forEach(key => {
|
||||||
env[key] = nconf.get(key);
|
env[key] = nconf.get(key);
|
||||||
|
|||||||
@@ -103,14 +103,29 @@ function _setUpNewUser (user) {
|
|||||||
return _populateDefaultTasks(user, taskTypes);
|
return _populateDefaultTasks(user, taskTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _getFacebookName (fb) {
|
||||||
|
if (!fb) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let possibleName = fb.displayName || fb.name || fb.username;
|
||||||
|
|
||||||
|
if (possibleName) {
|
||||||
|
return possibleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fb.first_name && fb.last_name) {
|
||||||
|
return `${fb.first_name} ${fb.last_name}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function _setProfileName (user) {
|
function _setProfileName (user) {
|
||||||
let fb = user.auth.facebook;
|
let google = user.auth.google;
|
||||||
|
|
||||||
let localUsername = user.auth.local && user.auth.local.username;
|
let localUsername = user.auth.local && user.auth.local.username;
|
||||||
let facebookUsername = fb && (fb.displayName || fb.name || fb.username || `${fb.first_name && fb.first_name} ${fb.last_name}`);
|
let googleUsername = google && google.displayName;
|
||||||
let anonymous = 'Anonymous';
|
let anonymous = 'Anonymous';
|
||||||
|
|
||||||
return localUsername || facebookUsername || anonymous;
|
return localUsername || _getFacebookName(user.auth.facebook) || googleUsername || anonymous;
|
||||||
}
|
}
|
||||||
|
|
||||||
schema.pre('save', true, function preSaveUser (next, done) {
|
schema.pre('save', true, function preSaveUser (next, done) {
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ let schema = new Schema({
|
|||||||
facebook: {type: Schema.Types.Mixed, default: () => {
|
facebook: {type: Schema.Types.Mixed, default: () => {
|
||||||
return {};
|
return {};
|
||||||
}},
|
}},
|
||||||
|
google: {type: Schema.Types.Mixed, default: () => {
|
||||||
|
return {};
|
||||||
|
}},
|
||||||
local: {
|
local: {
|
||||||
email: {
|
email: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|||||||
@@ -131,9 +131,12 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
|
|||||||
.panel-heading
|
.panel-heading
|
||||||
span=env.t('registration')
|
span=env.t('registration')
|
||||||
.panel-body
|
.panel-body
|
||||||
div(ng-if='user.auth.facebook.id')
|
div
|
||||||
button.btn.btn-primary(disabled='disabled', ng-if='!user.auth.local.username')=env.t('registeredWithFb')
|
ul.list-inline
|
||||||
button.btn.btn-danger(ng-click='http("delete", "/api/v3/user/auth/social/facebook", null, "detachedFacebook")', ng-if='user.auth.local.username')=env.t('detachFacebook')
|
li(ng-repeat='network in SOCIAL_AUTH_NETWORKS')
|
||||||
|
button.btn.btn-primary(ng-if='!user.auth[network.key].id', ng-click='socialLogin(network.key, user)')=env.t('registerWithSocial', {network: '{{network.name}}'})
|
||||||
|
button.btn.btn-primary(disabled='disabled', ng-if='!hasBackupAuthOption(user, network.key) && user.auth[network.key].id')=env.t('registeredWithSocial', {network: '{{network.name}}'})
|
||||||
|
button.btn.btn-danger(ng-click='deleteSocialAuth(network.key)', ng-if='hasBackupAuthOption(user, network.key) && user.auth[network.key].id')=env.t('detachSocial', {network: '{{network.name}}'})
|
||||||
hr
|
hr
|
||||||
div(ng-if='!user.auth.local.username')
|
div(ng-if='!user.auth.local.username')
|
||||||
p=env.t('addLocalAuth')
|
p=env.t('addLocalAuth')
|
||||||
|
|||||||
@@ -26,14 +26,11 @@ script(id='modals/login.html', type='text/ng-template')
|
|||||||
button.close(type='button', ng-click='$close()') ×
|
button.close(type='button', ng-click='$close()') ×
|
||||||
h4.modal-title=env.t('loginAndReg')
|
h4.modal-title=env.t('loginAndReg')
|
||||||
.modal-body(ng-controller='AuthCtrl')
|
.modal-body(ng-controller='AuthCtrl')
|
||||||
|
ul.list-inline
|
||||||
|
li
|
||||||
a.zocial.facebook(alt=env.t('loginFacebookAlt'), ng-click='socialLogin("facebook")')=env.t('loginFacebookAlt')
|
a.zocial.facebook(alt=env.t('loginFacebookAlt'), ng-click='socialLogin("facebook")')=env.t('loginFacebookAlt')
|
||||||
//-ul.list-inline
|
// li
|
||||||
li
|
// a.zocial.google(alt="Google", ng-click='socialLogin("google")')=env.t('loginGoogleAlt')
|
||||||
a.zocial.icon.facebook(alt=env.t('loginFacebookAlt'), ng-click='socialLogin("facebook")')
|
|
||||||
li
|
|
||||||
a.zocial.icon.googleplus(alt="Google", ng-click='socialLogin("google")') Google+
|
|
||||||
li
|
|
||||||
a.zocial.icon.twitter(alt="Twitter", ng-click='socialLogin("twitter")') Twitter
|
|
||||||
hr
|
hr
|
||||||
tabset(justified='true')
|
tabset(justified='true')
|
||||||
tab(heading=env.t('login'))
|
tab(heading=env.t('login'))
|
||||||
|
|||||||
Reference in New Issue
Block a user