From 88b458b585482e32bc9de3d2d3edc2b07fe5b902 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Thu, 26 Jan 2017 10:48:04 -0200 Subject: [PATCH] Convert serrver/configuration and server/lib to JS --- .eslintrc | 2 +- server/configuration/accounts_meld.coffee | 35 ---- server/configuration/accounts_meld.js | 47 +++++ server/lib/accounts.coffee | 171 ----------------- server/lib/accounts.js | 204 ++++++++++++++++++++ server/lib/cordova.coffee | 188 ------------------- server/lib/cordova.js | 215 ++++++++++++++++++++++ server/lib/cordova/facebook-login.coffee | 29 --- server/lib/cordova/facebook-login.js | 38 ++++ 9 files changed, 505 insertions(+), 424 deletions(-) delete mode 100644 server/configuration/accounts_meld.coffee create mode 100644 server/configuration/accounts_meld.js delete mode 100644 server/lib/accounts.coffee create mode 100644 server/lib/accounts.js delete mode 100644 server/lib/cordova.coffee create mode 100644 server/lib/cordova.js delete mode 100644 server/lib/cordova/facebook-login.coffee create mode 100644 server/lib/cordova/facebook-login.js diff --git a/.eslintrc b/.eslintrc index 388ad0f391a..051f36924b2 100644 --- a/.eslintrc +++ b/.eslintrc @@ -67,7 +67,7 @@ "curly": [2, "all"], "eqeqeq": [2, "allow-null"], "new-cap": [2, { - "capIsNewExceptions": ["Match.Optional", "Match.Maybe", "Match.ObjectIncluding"] + "capIsNewExceptions": ["Match.Optional", "Match.Maybe", "Match.ObjectIncluding", "Push.Configure"] }], "use-isnan": 2, "valid-typeof": 2, diff --git a/server/configuration/accounts_meld.coffee b/server/configuration/accounts_meld.coffee deleted file mode 100644 index c1c2ca1952e..00000000000 --- a/server/configuration/accounts_meld.coffee +++ /dev/null @@ -1,35 +0,0 @@ -orig_updateOrCreateUserFromExternalService = Accounts.updateOrCreateUserFromExternalService -Accounts.updateOrCreateUserFromExternalService = (serviceName, serviceData, options) -> - - if serviceName not in ['facebook', 'github', 'gitlab', 'google', 'meteor-developer', 'linkedin', 'twitter', 'sandstorm'] and serviceData._OAuthCustom isnt true - return - - if serviceName is 'meteor-developer' - if _.isArray serviceData?.emails - serviceData.emails.sort (a, b) -> - return a.primary isnt true - - for email in serviceData.emails - if email.verified is true - serviceData.email = email.address - break - - if serviceName is 'linkedin' - serviceData.email = serviceData.emailAddress - - if serviceData.email - - # Find user with given email - user = RocketChat.models.Users.findOneByEmailAddress serviceData.email - if user? - # If email is not verified, reset password and require password change - if not _.findWhere user.emails, { address: serviceData.email, verified: true } - RocketChat.models.Users.resetPasswordAndSetRequirePasswordChange(user._id, true, 'This_email_has_already_been_used_and_has_not_been_verified__Please_change_your_password') - - # Merge accounts - RocketChat.models.Users.setServiceId user._id, serviceName, serviceData.id - - # Validate email - RocketChat.models.Users.setEmailVerified user._id, serviceData.email - - return orig_updateOrCreateUserFromExternalService.apply(this, arguments) diff --git a/server/configuration/accounts_meld.js b/server/configuration/accounts_meld.js new file mode 100644 index 00000000000..1848635c4a5 --- /dev/null +++ b/server/configuration/accounts_meld.js @@ -0,0 +1,47 @@ +const orig_updateOrCreateUserFromExternalService = Accounts.updateOrCreateUserFromExternalService; + +Accounts.updateOrCreateUserFromExternalService = function(serviceName, serviceData = {} /*, options*/) { + const services = [ + 'facebook', + 'github', + 'gitlab', + 'google', + 'meteor-developer', + 'linkedin', + 'twitter', + 'sandstorm' + ]; + + if (services.includes(serviceName) === false && serviceData._OAuthCustom !== true) { + return; + } + + if (serviceName === 'meteor-developer') { + if (Array.isArray(serviceData.emails)) { + serviceData.email = serviceData.emails.sort(a => a.primary !== true).filter(item => item.verified === true)[0]; + } + } + + if (serviceName === 'linkedin') { + serviceData.email = serviceData.emailAddress; + } + + if (serviceData.email) { + const user = RocketChat.models.Users.findOneByEmailAddress(serviceData.email); + if (user != null) { + const findQuery = { + address: serviceData.email, + verified: true + }; + + if (!_.findWhere(user.emails, findQuery)) { + RocketChat.models.Users.resetPasswordAndSetRequirePasswordChange(user._id, true, 'This_email_has_already_been_used_and_has_not_been_verified__Please_change_your_password'); + } + + RocketChat.models.Users.setServiceId(user._id, serviceName, serviceData.id); + RocketChat.models.Users.setEmailVerified(user._id, serviceData.email); + } + } + + return orig_updateOrCreateUserFromExternalService.apply(this, arguments); +}; diff --git a/server/lib/accounts.coffee b/server/lib/accounts.coffee deleted file mode 100644 index 647eaf2b9fe..00000000000 --- a/server/lib/accounts.coffee +++ /dev/null @@ -1,171 +0,0 @@ -# Deny Account.createUser in client and set Meteor.loginTokenExpires -accountsConfig = { forbidClientAccountCreation: true, loginExpirationInDays: RocketChat.settings.get 'Accounts_LoginExpiration' } -Accounts.config accountsConfig - -Accounts.emailTemplates.siteName = RocketChat.settings.get 'Site_Name'; -Accounts.emailTemplates.from = "#{RocketChat.settings.get 'Site_Name'} <#{RocketChat.settings.get 'From_Email'}>"; - -verifyEmailHtml = Accounts.emailTemplates.verifyEmail.text -Accounts.emailTemplates.verifyEmail.html = (user, url) -> - url = url.replace Meteor.absoluteUrl(), Meteor.absoluteUrl() + 'login/' - verifyEmailHtml user, url - -resetPasswordHtml = Accounts.emailTemplates.resetPassword.text -Accounts.emailTemplates.resetPassword.html = (user, url) -> - url = url.replace /\/#\//, '/' - resetPasswordHtml user, url - -Accounts.emailTemplates.enrollAccount.subject = (user) -> - if RocketChat.settings.get 'Accounts_Enrollment_Customized' - subject = RocketChat.settings.get 'Accounts_Enrollment_Email_Subject' - else - subject = TAPi18n.__('Accounts_Enrollment_Email_Subject_Default', { lng: user?.language || RocketChat.settings.get('language') || 'en' }) - - return RocketChat.placeholders.replace(subject); - -Accounts.emailTemplates.enrollAccount.html = (user, url) -> - - if RocketChat.settings.get 'Accounts_Enrollment_Customized' - html = RocketChat.settings.get 'Accounts_Enrollment_Email' - else - html = TAPi18n.__('Accounts_Enrollment_Email_Default', { lng: user?.language || RocketChat.settings.get('language') || 'en' }) - - header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || "") - footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || "") - html = RocketChat.placeholders.replace(html, { - name: user.name, - email: user.emails?[0]?.address - }); - - return header + html + footer; - -Accounts.onCreateUser (options, user) -> - # console.log 'onCreateUser ->',JSON.stringify arguments, null, ' ' - # console.log 'options ->',JSON.stringify options, null, ' ' - # console.log 'user ->',JSON.stringify user, null, ' ' - - RocketChat.callbacks.run 'beforeCreateUser', options, user - - user.status = 'offline' - user.active = not RocketChat.settings.get 'Accounts_ManuallyApproveNewUsers' - - if not user?.name? or user.name is '' - if options.profile?.name? - user.name = options.profile?.name - - if user.services? - for serviceName, service of user.services - if not user?.name? or user.name is '' - if service.name? - user.name = service.name - else if service.username? - user.name = service.username - - if not user.emails? and service.email? - user.emails = [ - address: service.email - verified: true - ] - - return user - -# Wrap insertUserDoc to allow executing code after Accounts.insertUserDoc is run -Accounts.insertUserDoc = _.wrap Accounts.insertUserDoc, (insertUserDoc, options, user) -> - roles = [] - if Match.test(user.globalRoles, [String]) and user.globalRoles.length > 0 - roles = roles.concat user.globalRoles - - delete user.globalRoles - - user.type ?= 'user' - - _id = insertUserDoc.call(Accounts, options, user) - - # Get user from db again to get all fields added by middlewares like - # validateNewUser and others. - user = Meteor.users.findOne({_id: _id}); - - # Add user to default channels - if user.username? and options.joinDefaultChannels isnt false and user.joinDefaultChannels isnt false - Meteor.runAsUser _id, -> - Meteor.call('joinDefaultChannels', options.joinDefaultChannelsSilenced) - - if roles.length is 0 - # when inserting first user give them admin privileges otherwise make a regular user - hasAdmin = RocketChat.models.Users.findOne({ roles: 'admin' }, {fields: {_id: 1}}) - if hasAdmin? - roles.push 'user' - else - roles.push 'admin' - - RocketChat.authz.addUserRoles(_id, roles) - - Meteor.defer -> - RocketChat.callbacks.run 'afterCreateUser', options, user - - return _id - -Accounts.validateLoginAttempt (login) -> - login = RocketChat.callbacks.run 'beforeValidateLogin', login - - if login.allowed isnt true - return login.allowed - - # bypass for livechat users - if login.user.type is 'visitor' - return true - - if !!login.user?.active isnt true - throw new Meteor.Error 'error-user-is-not-activated', 'User is not activated', { function: 'Accounts.validateLoginAttempt' } - return false - - # If user is admin, no need to check if email is verified - if 'admin' not in login.user?.roles and login.type is 'password' and RocketChat.settings.get('Accounts_EmailVerification') is true - validEmail = login.user.emails.filter (email) -> - return email.verified is true - - if validEmail.length is 0 - throw new Meteor.Error 'error-invalid-email', 'Invalid email __email__' - return false - - RocketChat.models.Users.updateLastLoginById login.user._id - - Meteor.defer -> - RocketChat.callbacks.run 'afterValidateLogin', login - - return true - -Accounts.validateNewUser (user) -> - # bypass for livechat users - if user.type is 'visitor' - return true - - if RocketChat.settings.get('Accounts_Registration_AuthenticationServices_Enabled') is false and RocketChat.settings.get('LDAP_Enable') is false and not user.services?.password? - throw new Meteor.Error 'registration-disabled-authentication-services', 'User registration is disabled for authentication services' - return true - - -Accounts.validateNewUser (user) -> - # bypass for livechat users - if user.type is 'visitor' - return true - - domainWhiteList = RocketChat.settings.get('Accounts_AllowedDomainsList') - - if _.isEmpty s.trim(domainWhiteList) - return true - - domainWhiteList = _.map(domainWhiteList.split(','), (domain) -> domain.trim()) - - if user.emails?.length > 0 - ret = false - email = user.emails[0].address - for domain in domainWhiteList - if email.match('@' + RegExp.escape(domain) + '$') - ret = true - break - - if not ret - throw new Meteor.Error 'error-invalid-domain' - - return true diff --git a/server/lib/accounts.js b/server/lib/accounts.js new file mode 100644 index 00000000000..cec25477d69 --- /dev/null +++ b/server/lib/accounts.js @@ -0,0 +1,204 @@ +const accountsConfig = { + forbidClientAccountCreation: true, + loginExpirationInDays: RocketChat.settings.get('Accounts_LoginExpiration') +}; + +Accounts.config(accountsConfig); + +Accounts.emailTemplates.siteName = RocketChat.settings.get('Site_Name'); + +Accounts.emailTemplates.from = `${RocketChat.settings.get('Site_Name')} <${RocketChat.settings.get('From_Email')}>`; + +const verifyEmailHtml = Accounts.emailTemplates.verifyEmail.text; + +Accounts.emailTemplates.verifyEmail.html = function(user, url) { + url = url.replace(Meteor.absoluteUrl(), `${Meteor.absoluteUrl()}login/`); + return verifyEmailHtml(user, url); +}; + +const resetPasswordHtml = Accounts.emailTemplates.resetPassword.text; + +Accounts.emailTemplates.resetPassword.html = function(user, url) { + url = url.replace(/\/#\//, '/'); + return resetPasswordHtml(user, url); +}; + +Accounts.emailTemplates.enrollAccount.subject = function(user = {}) { + let subject; + if (RocketChat.settings.get('Accounts_Enrollment_Customized')) { + subject = RocketChat.settings.get('Accounts_Enrollment_Email_Subject'); + } else { + subject = TAPi18n.__('Accounts_Enrollment_Email_Subject_Default', { + lng: user.language || RocketChat.settings.get('language') || 'en' + }); + } + return RocketChat.placeholders.replace(subject); +}; + +Accounts.emailTemplates.enrollAccount.html = function(user = {}/*, url*/) { + let html; + if (RocketChat.settings.get('Accounts_Enrollment_Customized')) { + html = RocketChat.settings.get('Accounts_Enrollment_Email'); + } else { + html = TAPi18n.__('Accounts_Enrollment_Email_Default', { + lng: user.language || RocketChat.settings.get('language') || 'en' + }); + } + + const header = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Header') || ''); + const footer = RocketChat.placeholders.replace(RocketChat.settings.get('Email_Footer') || ''); + + html = RocketChat.placeholders.replace(html, { + name: user.name, + email: user.emails && user.emails[0] && user.emails[0].address + }); + + return header + html + footer; +}; + +Accounts.onCreateUser(function(options, user = {}) { + RocketChat.callbacks.run('beforeCreateUser', options, user); + + user.status = 'offline'; + user.active = !RocketChat.settings.get('Accounts_ManuallyApproveNewUsers'); + + if (!user.name) { + if (options.profile && options.profile.name) { + user.name = options.profile.name; + } + } + + if (user.services) { + for (const service of Object.values(user.services)) { + if (!user.name) { + user.name = service.name || service.username; + } + + if (!user.emails && service.email) { + user.emails = [{ + address: service.email, + verified: true + }]; + } + } + } + + return user; +}); + +Accounts.insertUserDoc = _.wrap(Accounts.insertUserDoc, function(insertUserDoc, options, user) { + let roles = []; + + if (Match.test(user.globalRoles, [String]) && user.globalRoles.length > 0) { + roles = roles.concat(user.globalRoles); + } + + delete user.globalRoles; + + if (!user.type) { + user.type = 'user'; + } + + const _id = insertUserDoc.call(Accounts, options, user); + + user = Meteor.users.findOne({ + _id: _id + }); + + if (user.username && options.joinDefaultChannels !== false && user.joinDefaultChannels !== false) { + Meteor.runAsUser(_id, function() { + return Meteor.call('joinDefaultChannels', options.joinDefaultChannelsSilenced); + }); + } + + if (roles.length === 0) { + const hasAdmin = RocketChat.models.Users.findOne({ + roles: 'admin' + }, { + fields: { + _id: 1 + } + }); + + if (hasAdmin) { + roles.push('user'); + } else { + roles.push('admin'); + } + } + + RocketChat.authz.addUserRoles(_id, roles); + Meteor.defer(function() { + return RocketChat.callbacks.run('afterCreateUser', options, user); + }); + + return _id; +}); + +Accounts.validateLoginAttempt(function(login) { + login = RocketChat.callbacks.run('beforeValidateLogin', login); + + if (login.allowed !== true) { + return login.allowed; + } + + if (login.user.type === 'visitor') { + return true; + } + + if (!!login.user.active !== true) { + throw new Meteor.Error('error-user-is-not-activated', 'User is not activated', { + 'function': 'Accounts.validateLoginAttempt' + }); + } + + if (login.user.roles.includes('admin') === false && login.type === 'password' && RocketChat.settings.get('Accounts_EmailVerification') === true) { + const validEmail = login.user.emails.filter(email => email.verified === true); + if (validEmail.length === 0) { + throw new Meteor.Error('error-invalid-email', 'Invalid email __email__'); + } + } + + RocketChat.models.Users.updateLastLoginById(login.user._id); + Meteor.defer(function() { + return RocketChat.callbacks.run('afterValidateLogin', login); + }); + + return true; +}); + +Accounts.validateNewUser(function(user) { + if (user.type === 'visitor') { + return true; + } + + if (RocketChat.settings.get('Accounts_Registration_AuthenticationServices_Enabled') === false && RocketChat.settings.get('LDAP_Enable') === false && !(user.services && user.services.password)) { + throw new Meteor.Error('registration-disabled-authentication-services', 'User registration is disabled for authentication services'); + } + + return true; +}); + +Accounts.validateNewUser(function(user) { + if (user.type === 'visitor') { + return true; + } + + let domainWhiteList = RocketChat.settings.get('Accounts_AllowedDomainsList'); + if (_.isEmpty(s.trim(domainWhiteList))) { + return true; + } + + domainWhiteList = domainWhiteList.split(',').map(domain => domain.trim()); + + if (user.emails && user.emails.length > 0) { + const email = user.emails[0].address; + const inWhiteList = domainWhiteList.some(domain => email.match('@' + RegExp.escape(domain) + '$')); + + if (inWhiteList === false) { + throw new Meteor.Error('error-invalid-domain'); + } + } + + return true; +}); diff --git a/server/lib/cordova.coffee b/server/lib/cordova.coffee deleted file mode 100644 index dd5f74aab66..00000000000 --- a/server/lib/cordova.coffee +++ /dev/null @@ -1,188 +0,0 @@ -Meteor.methods - log: -> - console.log.apply console, arguments - - push_test: -> - user = Meteor.user() - if not user? - throw new Meteor.Error 'error-not-allowed', 'Not allowed', { method: 'push_test' } - - if not RocketChat.authz.hasRole(user._id, 'admin') - throw new Meteor.Error 'error-not-allowed', 'Not allowed', { method: 'push_test' } - - if Push.enabled isnt true - throw new Meteor.Error 'error-push-disabled', 'Push is disabled', { method: 'push_test' } - - query = - $and: [ - userId: user._id - { - $or: [ - { 'token.apn': { $exists: true } } - { 'token.gcm': { $exists: true } } - ] - } - ] - - tokens = Push.appCollection.find(query).count() - - if tokens is 0 - throw new Meteor.Error 'error-no-tokens-for-this-user', "There are no tokens for this user", { method: 'push_test' } - - Push.send - from: 'push' - title: "@#{user.username}" - text: TAPi18n.__ "This_is_a_push_test_messsage" - apn: - text: "@#{user.username}:\n" + TAPi18n.__ "This_is_a_push_test_messsage" - sound: 'default' - query: - userId: user._id - - return {} = - message: "Your_push_was_sent_to_s_devices" - params: [tokens] - - -configurePush = -> - if RocketChat.settings.get 'Push_debug' - Push.debug = true - console.log 'Push: configuring...' - - if RocketChat.settings.get('Push_enable') is true - Push.allow - send: (userId, notification) -> - return RocketChat.authz.hasRole(userId, 'admin') - - apn = undefined - gcm = undefined - - if RocketChat.settings.get('Push_enable_gateway') is false - gcm = - apiKey: RocketChat.settings.get 'Push_gcm_api_key' - projectNumber: RocketChat.settings.get 'Push_gcm_project_number' - - apn = - passphrase: RocketChat.settings.get 'Push_apn_passphrase' - keyData: RocketChat.settings.get 'Push_apn_key' - certData: RocketChat.settings.get 'Push_apn_cert' - - if RocketChat.settings.get('Push_production') isnt true - apn = - passphrase: RocketChat.settings.get 'Push_apn_dev_passphrase' - keyData: RocketChat.settings.get 'Push_apn_dev_key' - certData: RocketChat.settings.get 'Push_apn_dev_cert' - gateway: 'gateway.sandbox.push.apple.com' - - if not apn.keyData? or apn.keyData.trim() is '' or not apn.keyData? or apn.keyData.trim() is '' - apn = undefined - - if not gcm.apiKey? or gcm.apiKey.trim() is '' or not gcm.projectNumber? or gcm.projectNumber.trim() is '' - gcm = undefined - - Push.Configure - apn: apn - gcm: gcm - production: RocketChat.settings.get 'Push_production' - sendInterval: 1000 - sendBatchSize: 10 - - if RocketChat.settings.get('Push_enable_gateway') is true - Push.serverSend = (options) -> - options = options or { badge: 0 } - query = undefined - - if options.from isnt ''+options.from - throw new Error('Push.send: option "from" not a string') - - if options.title isnt ''+options.title - throw new Error('Push.send: option "title" not a string') - - if options.text isnt ''+options.text - throw new Error('Push.send: option "text" not a string') - - if RocketChat.settings.get 'Push_debug' - console.log('Push: send message "' + options.title + '" via query', options.query) - - query = - $and: [ - options.query - { - $or: [ - { 'token.apn': { $exists: true } } - { 'token.gcm': { $exists: true } } - ] - } - ] - - Push.appCollection.find(query).forEach (app) -> - if RocketChat.settings.get 'Push_debug' - console.log('Push: send to token', app.token) - - if app.token.apn? - service = 'apn' - token = app.token.apn - else if app.token.gcm? - service = 'gcm' - token = app.token.gcm - - sendPush service, token, options - - Push.enabled = true - -sendPush = (service, token, options, tries=0) -> - data = - data: - token: token - options: options - - HTTP.post RocketChat.settings.get('Push_gateway') + "/push/#{service}/send", data, (error, response) -> - if response?.statusCode is 406 - console.log('removing push token', token) - Push.appCollection.remove({ - $or: [ - { 'token.apn': token } - { 'token.gcm': token } - ] - }) - return - - if not error? - return - - SystemLogger.error 'Error sending push to gateway ('+tries+' try) ->', error - if tries <= 6 - milli = Math.pow(10, tries+2) - - SystemLogger.log 'Trying sending push to gateway again in', milli, 'milliseconds' - - # Try again in 0.1s, 1s, 10s, 1m40s, 16m40s, 2h46m40s and 27h46m40s - Meteor.setTimeout -> - sendPush service, token, options, tries+1 - , milli - -Meteor.startup -> - configurePush() - - ## Prepared to reconfigure the push plugin - # - # keys = [ - # 'Push_enable' - # 'Push_enable_gateway' - # 'Push_gcm_api_key' - # 'Push_gcm_project_number' - # 'Push_apn_passphrase' - # 'Push_apn_key' - # 'Push_apn_cert' - # 'Push_production' - # 'Push_apn_dev_passphrase' - # 'Push_apn_dev_key' - # 'Push_apn_dev_cert' - # 'Push_gateway' - # ] - - # configurePushDebounce = _.debounce Meteor.bindEnvironment(configurePush), 1000 - - # RocketChat.settings.onload keys, -> - # configurePushDebounce() - diff --git a/server/lib/cordova.js b/server/lib/cordova.js new file mode 100644 index 00000000000..64d1eb301b1 --- /dev/null +++ b/server/lib/cordova.js @@ -0,0 +1,215 @@ +/* global Push, SystemLogger */ + +Meteor.methods({ + log() { + return console.log(...arguments); + }, + + push_test() { + const user = Meteor.user(); + + if (!user) { + throw new Meteor.Error('error-not-allowed', 'Not allowed', { + method: 'push_test' + }); + } + + if (!RocketChat.authz.hasRole(user._id, 'admin')) { + throw new Meteor.Error('error-not-allowed', 'Not allowed', { + method: 'push_test' + }); + } + + if (Push.enabled !== true) { + throw new Meteor.Error('error-push-disabled', 'Push is disabled', { + method: 'push_test' + }); + } + + const query = { + $and: [{ + userId: user._id + }, { + $or: [{ + 'token.apn': { + $exists: true + } + }, { + 'token.gcm': { + $exists: true + } + }] + }] + }; + + const tokens = Push.appCollection.find(query).count(); + + if (tokens === 0) { + throw new Meteor.Error('error-no-tokens-for-this-user', 'There are no tokens for this user', { + method: 'push_test' + }); + } + + Push.send({ + from: 'push', + title: `@${user.username}`, + text: TAPi18n.__('This_is_a_push_test_messsage'), + apn: { + text: `@${user.username}:\n${TAPi18n.__('This_is_a_push_test_messsage')}` + }, + sound: 'default', + query: { + userId: user._id + } + }); + + return { + message: 'Your_push_was_sent_to_s_devices', + params: [tokens] + }; + } +}); + +function sendPush(service, token, options, tries = 0) { + const data = { + data: { + token: token, + options: options + } + }; + + return HTTP.post(RocketChat.settings.get('Push_gateway') + `/push/${service}/send`, data, function(error, response) { + if (response && response.statusCode === 406) { + console.log('removing push token', token); + Push.appCollection.remove({ + $or: [{ + 'token.apn': token + }, { + 'token.gcm': token + }] + }); + return; + } + + if (!error) { + return; + } + + SystemLogger.error(`Error sending push to gateway (${tries} try) ->`, error); + + if (tries <= 6) { + const milli = Math.pow(10, tries + 2); + + SystemLogger.log('Trying sending push to gateway again in', milli, 'milliseconds'); + + return Meteor.setTimeout(function() { + return sendPush(service, token, options, tries + 1); + }, milli); + } + }); +} + +function configurePush() { + if (RocketChat.settings.get('Push_debug')) { + Push.debug = true; + console.log('Push: configuring...'); + } + + if (RocketChat.settings.get('Push_enable') === true) { + Push.allow({ + send: function(userId/*, notification*/) { + return RocketChat.authz.hasRole(userId, 'admin'); + } + }); + + let apn, gcm; + + if (RocketChat.settings.get('Push_enable_gateway') === false) { + gcm = { + apiKey: RocketChat.settings.get('Push_gcm_api_key'), + projectNumber: RocketChat.settings.get('Push_gcm_project_number') + }; + + apn = { + passphrase: RocketChat.settings.get('Push_apn_passphrase'), + keyData: RocketChat.settings.get('Push_apn_key'), + certData: RocketChat.settings.get('Push_apn_cert') + }; + + if (RocketChat.settings.get('Push_production') !== true) { + apn = { + passphrase: RocketChat.settings.get('Push_apn_dev_passphrase'), + keyData: RocketChat.settings.get('Push_apn_dev_key'), + certData: RocketChat.settings.get('Push_apn_dev_cert'), + gateway: 'gateway.sandbox.push.apple.com' + }; + } + + if (!apn.keyData || apn.keyData.trim() === '' || !apn.certData || apn.certData.trim() === '') { + apn = undefined; + } + + if (!gcm.apiKey || gcm.apiKey.trim() === '' || !gcm.projectNumber || gcm.projectNumber.trim() === '') { + gcm = undefined; + } + } + + Push.Configure({ + apn: apn, + gcm: gcm, + production: RocketChat.settings.get('Push_production'), + sendInterval: 1000, + sendBatchSize: 10 + }); + + if (RocketChat.settings.get('Push_enable_gateway') === true) { + Push.serverSend = function(options = {badge: 0}) { + if (options.from !== String(options.from)) { + throw new Error('Push.send: option "from" not a string'); + } + if (options.title !== String(options.title)) { + throw new Error('Push.send: option "title" not a string'); + } + if (options.text !== String(options.text)) { + throw new Error('Push.send: option "text" not a string'); + } + if (RocketChat.settings.get('Push_debug')) { + console.log(`Push: send message "${options.title}" via query`, options.query); + } + + const query = { + $and: [options.query, { + $or: [{ + 'token.apn': { + $exists: true + } + }, { + 'token.gcm': { + $exists: true + } + }] + }] + }; + + return Push.appCollection.find(query).forEach((app) => { + if (RocketChat.settings.get('Push_debug')) { + console.log('Push: send to token', app.token); + } + + if (app.token.apn) { + return sendPush('apn', app.token.apn, options); + } + + if (app.token.gcm) { + return sendPush('gcm', app.token.gcm, options); + } + }); + }; + } + return Push.enabled = true; + } +} + +Meteor.startup(function() { + return configurePush(); +}); diff --git a/server/lib/cordova/facebook-login.coffee b/server/lib/cordova/facebook-login.coffee deleted file mode 100644 index b2ba6a26fd5..00000000000 --- a/server/lib/cordova/facebook-login.coffee +++ /dev/null @@ -1,29 +0,0 @@ -Accounts.registerLoginHandler (loginRequest) -> - if not loginRequest.cordova - return undefined - - loginRequest = loginRequest.authResponse - identity = getIdentity(loginRequest.accessToken) - - serviceData = - accessToken: loginRequest.accessToken - expiresAt: (+new Date) + (1000 * loginRequest.expiresIn) - - whitelisted = ['id', 'email', 'name', 'first_name', 'last_name', 'link', 'username', 'gender', 'locale', 'age_range'] - - fields = _.pick(identity, whitelisted) - _.extend(serviceData, fields) - - options = {profile: {}} - profileFields = _.pick(identity, whitelisted) - _.extend(options.profile, profileFields) - - return Accounts.updateOrCreateUserFromExternalService("facebook", serviceData, options) - - -getIdentity = (accessToken) -> - try - return HTTP.get("https://graph.facebook.com/me", {params: {access_token: accessToken}}).data - - catch err - throw _.extend new Error("Failed to fetch identity from Facebook. " + err.message), {response: err.response} \ No newline at end of file diff --git a/server/lib/cordova/facebook-login.js b/server/lib/cordova/facebook-login.js new file mode 100644 index 00000000000..78acf1e2c7c --- /dev/null +++ b/server/lib/cordova/facebook-login.js @@ -0,0 +1,38 @@ +function getIdentity(accessToken) { + try { + return HTTP.get('https://graph.facebook.com/me', { + params: { + access_token: accessToken + } + }).data; + } catch (error) { + throw _.extend(new Error(`Failed to fetch identity from Facebook. ${error.message}`), { + response: error.response + }); + } +} + +Accounts.registerLoginHandler(function(loginRequest) { + if (!loginRequest.cordova) { + return; + } + + loginRequest = loginRequest.authResponse; + + const identity = getIdentity(loginRequest.accessToken); + const serviceData = { + accessToken: loginRequest.accessToken, + expiresAt: Date.now() + 1000 * loginRequest.expiresIn + }; + + const whitelisted = ['id', 'email', 'name', 'first_name', 'last_name', 'link', 'username', 'gender', 'locale', 'age_range']; + const fields = _.pick(identity, whitelisted); + const options = { + profile: {} + }; + + _.extend(serviceData, fields); + _.extend(options.profile, fields); + + return Accounts.updateOrCreateUserFromExternalService('facebook', serviceData, options); +});