From ec12002cc74ccdb547213aa8ad0173bf16b0df4d Mon Sep 17 00:00:00 2001 From: Tim Klingeleers Date: Tue, 3 Oct 2017 08:47:30 +0200 Subject: [PATCH 1/3] FIFA 18 support only one login flow is necessary since both the app and the website use the same code and authentication mechanism. Probably should give an option to use email as 2 factor mechanism as well. --- src/index.js | 13 +-- src/lib/mobile-login.js | 175 +++++++++++++++------------------------- 2 files changed, 65 insertions(+), 123 deletions(-) diff --git a/src/index.js b/src/index.js index 56c4a4a..829b51a 100644 --- a/src/index.js +++ b/src/index.js @@ -3,7 +3,6 @@ import Promise from 'bluebird' import assert from 'assert' import utils from './lib/utils' -import Login from './lib/login' import MobileLogin from './lib/mobile-login' import _ from 'underscore' import Methods from './lib/methods' @@ -55,13 +54,7 @@ let Fut = class Fut extends Methods { this.isReady = false // instance will be ready after we called _init func Object.assign(this.options, defaultOptions, options) - if (this.options.loginType === 'web') { - this.loginLib = Promise.promisifyAll(new Login({proxy: options.proxy})) - } else if (this.options.loginType === 'mobile') { - this.loginLib = new MobileLogin({...options, tfCodeHandler: options.tfAuthHandler}) - } else { - throw new Error(`Unknown loginType ${this.options.loginType}`) - } + this.loginLib = new MobileLogin({...options, tfCodeHandler: options.tfAuthHandler}) } async loadVariable (key) { @@ -86,15 +79,13 @@ let Fut = class Fut extends Methods { async login () { await this._init() - const loginMethod = this.options.loginType === 'web' ? 'loginAsync' : 'login' + const loginMethod = 'login' const loginResponse = await this.loginLib[loginMethod](this.options.email, this.options.password, this.options.secret, this.options.platform, this.options.tfAuthHandler, this.options.captchaHandler) - await this.saveVariable('cookie', this.loginLib.getCookieJarJSON()) this.rawApi = loginResponse.apiRequest const loginDefaults = _.omit(this.loginLib.getLoginDefaults(), 'jar') await this.saveVariable('loginDefaults', loginDefaults) - if (this.options.loginType === 'web') this.rawApi = Promise.promisify(this.rawApi, this) this.isReady = true const user = await this.getUser() if (user.userInfo.feature.trade === 0) { diff --git a/src/lib/mobile-login.js b/src/lib/mobile-login.js index 29e421d..54d0b4b 100644 --- a/src/lib/mobile-login.js +++ b/src/lib/mobile-login.js @@ -8,8 +8,6 @@ import eaHasher from './eaHasher' import {CookieJar} from 'tough-cookie' import lodash from 'lodash' -const crypto = Promise.promisifyAll(require('crypto')) - export default class MobileLogin { jar = request.jar() loginDefaults = {} @@ -18,10 +16,7 @@ export default class MobileLogin { * [constructor description] * @param {[type]} options.email [description] * @param {[type]} options.password [description] - * @param {[type]} options.secret [description] * @param {[type]} options.platform [description] - * @param {[type]} options.captchaHandler [description] - * @param {[type]} options.tfCodeHandler [description] * @param {[String]} options.proxy [description] * @return {[type]} [description] */ @@ -83,9 +78,7 @@ export default class MobileLogin { } async getLogin () { - // We will use machine key later at getNucleusCode - this.machineKey = await generateMachineKey() - const url = 'https://accounts.ea.com/connect/auth?client_id=FIFA-16-MOBILE-COMPANION&response_type=code&display=web2/login&scope=basic.identity+offline+signin&locale=en_US&prompt=login&machineProfileKey=' + this.machineKey + const url = 'https://accounts.ea.com/connect/auth?prompt=login&accessToken=&client_id=FIFA-18-WEBCLIENT&response_type=token&display=web2/login&locale=en_US&redirect_uri=https://www.easports.com/fifa/ultimate-team/web-app/auth.html&scope=basic.identity+offline+signin' const response = await this.defaultRequest.getAsync(url) const title = getTitle(response) @@ -99,7 +92,7 @@ export default class MobileLogin { const form = { email: this.options.email, password: this.options.password, - country: 'HU', + country: 'US', phoneNumber: '', passwordForPhone: '', _rememberMe: 'on', @@ -116,49 +109,62 @@ export default class MobileLogin { if (!response.body.includes('redirectUri')) { throw new Error(`Unknow response at 'postLogin' title was: ${title}`) } - return this.postLoginRedirect(response) + return await this.postLoginRedirect(response) } async postLoginRedirect (prevResponse) { - const urlRegex = new RegExp("var redirectUri = '(.*)'") - const qsRegex = /redirectUri = redirectUri \+ "(.*)";/ + const urlRegex = new RegExp(/(https?:\/\/(www\/.)?[-a-zA-Z0-9@:%._\/+~#=]{2,256}\/.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\/+.~#?&//=]*))/gm) let nextUrl try { - nextUrl = urlRegex.exec(prevResponse.body)[1] - nextUrl += qsRegex.exec(prevResponse.body)[1] + nextUrl = urlRegex.exec(prevResponse.body)[0] } catch (e) { throw new Error(`RegExp failed at 'postLogin' body was: ${prevResponse.body}`) } const response = await this.defaultRequest.getAsync(nextUrl) - const title = getTitle(response) - if (title === 'Login Verification') return this.handleTwoFactorCode(response.request.href) - else if (response.request.href.includes('code=')) { - try { - let code = urlModule.parse(response.request.href, true).query.code - return this.wtfLogin(code) - } catch (e) { - throw new Error(`Couldn't parse code from url at postLoginRedirect: ${e.message}`) + if (response.body.indexOf('Your credentials are incorrect') !== -1) { + throw new Error('Unable to login. Wrong email or password ?') + } + + return await this.sendEnd(response) + } + + async sendEnd (prevResponse) { + let url = prevResponse.request.href + '&_eventId=end' + + const response = await this.defaultRequest.getAsync(url) + if (response.body.indexOf('Login Verification') !== -1) { + if (response.body.indexOf('In order to verify your identity') !== -1) { + const form = { + codeType: 'APP', + _eventId: 'submit' + } + const response2 = await this.defaultRequest.postAsync(response.request.href, {form}) + + if (response2.body.indexOf('Login Verification') !== -1) { + await this.handleTwoFactorCode(response2.request.href) + } + + await this.wtfLogin() + + return response2.request.href } } - throw Error(`Unknow response at 'postLoginRedirect' title was: ${title}`) + + await this.wtfLogin() } async handleTwoFactorCode (url) { const tfCode = await this.options.tfCodeHandler() const form = { - twofactorCode: tfCode, + oneTimeCode: tfCode, trustThisDevice: 'on', + _trustThisDevice: 'on', _eventId: 'submit' } const response = await this.defaultRequest.postAsync(url, {form}) - const title = getTitle(response) - - if (title === 'Set Up an App Authenticator') return this.cancelLoginVerificationUpdate(response.request.href) - if (title === 'Login Verification') throw new Error('Wrong two factor code.') - - throw Error(`Unknow response at 'handleTwoFactorCode' title was: ${title}`) + if (response.body.indexOf('Login Verification') !== -1) throw new Error('Wrong two factor code.') } async cancelLoginVerificationUpdate (url) { @@ -180,27 +186,22 @@ export default class MobileLogin { return true } }) - return this.wtfLogin(code) + return await this.wtfLogin(code) } - async wtfLogin (code) { - const postUrl = `https://accounts.ea.com/connect/token?grant_type=authorization_code&code=${code}&client_id=FIFA-16-MOBILE-COMPANION&client_secret=KrEoFK9ssvXKRWnTMgAu1OAMn7Y37ueUh1Vy7dIk2earFDUDABCvZuNIidYxxNbhwbj3y8pq6pSf8zBW` - let response = await this.defaultRequest.postAsync(postUrl, {json: true, headers: {'content-type': 'application/x-www-form-urlencoded'}}) - let token = response.body.access_token + async wtfLogin () { + var url = 'https://accounts.ea.com/connect/auth?response_type=token&redirect_uri=nucleus%3Arest&prompt=none&client_id=ORIGIN_JS_SDK' + let response = await this.defaultRequest.getAsync(url) + let token = JSON.parse(response.body).access_token assert(token, 'Failed to get access token at `wtfLogin`') - // this stuff seems useless but let's just do it - const url1 = `https://signin.ea.com/p/mobile/fifa/companion/code?code=${code}` - await this.defaultRequest.getAsync(url1) - const nucleusUserId = await this.getPid(token) - const sidCode = await this.getSidCode(token) + // const sidCode = await this.getSidCode(token) const sidCode2 = await this.getSidCode(token) // We will get the api url after shards await this.getShards() - await this.getUserAccounts(nucleusUserId) - const powSessionId = await this.getPowSid(sidCode) - const nucleusPersonaId = await this.getNucleusPersonaId(nucleusUserId, powSessionId) + let userAccounts = await this.getUserAccounts(nucleusUserId) + const nucleusPersonaId = await this.getNucleusPersonaId(userAccounts.body.userAccountInfo.personas) const sid = await this.getSid(sidCode2, nucleusPersonaId) const requestConfigObj1 = { @@ -214,7 +215,7 @@ export default class MobileLogin { lodash.merge(this.loginDefaults, requestConfigObj1) this.api = Promise.promisifyAll(this.defaultRequest.defaults(requestConfigObj1)) - const phisingToken = await this.validate() + const phisingToken = await this.validate(sid) const requestConfigObj2 = { headers: { @@ -263,29 +264,23 @@ export default class MobileLogin { async getUserAccounts (nucleusUserId) { const timestamp = new Date().getTime() - const url = `${this.apiUrl}/ut/game/fifa16/user/accountinfo?sku=FUT16IOS&_=${timestamp}` + const url = `${this.apiUrl}/ut/game/fifa16/user/accountinfo?sku=FUT18WEB&_=${timestamp}` return await this.defaultRequest.getAsync(url, {json: true, headers: { 'Easw-Session-Data-Nucleus-Id': nucleusUserId, 'X-UT-SID': '' }}) } - async getNucleusPersonaId (nucleusUserId, powSessionId) { - const timestamp = new Date().getTime() - const url = `https://pas.mob.v5.easfc.ea.com:8095/pow/user/self/tiergp/NucleusId/tiertp/${nucleusUserId}?offset=0&count=50&_=${timestamp}` - const {body} = await this.defaultRequest.getAsync(url, {json: true, headers: { - 'Easw-Session-Data-Nucleus-Id': nucleusUserId, - 'X-POW-SID': powSessionId - }}) - - return _.findWhere(body.userData.data, {sku: this.options.gameSku}).nucPersId + async getNucleusPersonaId (personas) { + return personas[0].personaId + // return _.findWhere(personas, {sku: this.options.gameSku}).nucPersId } async getSid (code, nucleusPersonaId) { const requestBody = { - isReadOnly: true, - sku: 'FUT16IOS', - clientVersion: 20, + isReadOnly: false, + sku: 'FUT18WEB', + clientVersion: 1, locale: 'en-US', method: 'authcode', priorityLevel: 4, @@ -312,39 +307,14 @@ export default class MobileLogin { return response.body.sid } - async getPowSid (code) { - const requestBody = { - isReadOnly: true, - sku: 'FUT16IOS', - clientVersion: 20, - locale: 'en-US', - method: 'authcode', - priorityLevel: 4, - identification: { - authCode: code, - redirectUrl: 'nucleus:rest' + async validate (sid) { + const uri = `/ut/game/fifa18/phishing/validate?answer=${this.options.secret}` + const {body} = await this.api.postAsync(uri, { + body: this.options.secret, + headers: { + 'X-UT-SID': sid } - } - // 1474229595686 - const timestamp = new Date().getTime() - const url = `https://pas.mob.v5.easfc.ea.com:8095/pow/auth?timestamp=${timestamp}` - const response = await this.defaultRequest.postAsync(url, { - body: requestBody, - json: true - // headers: { - // 'X-UT-SID': '', - // 'X-POW-SID': '', - // Accept: 'text/plain, */*; q=0.01', - // Origin: 'file://' - // } }) - return response.body.sid - // return powSid - } - - async validate () { - const uri = `/ut/game/fifa16/phishing/validate?answer=${this.options.secret}` - const {body} = await this.api.postAsync(uri, {body: this.options.secret}) return body.token } } @@ -355,37 +325,18 @@ function getTitle (response) { return title } -// example EEA58055-E4E8-42E6-B89D-DFFBBD37AF57 -async function generateMachineKey () { - let parts = await Promise.all([ - randomHex(8), - randomHex(4), - randomHex(4), - randomHex(4), - randomHex(12) - ]) - return `${parts[0]}-${parts[1]}-${parts[2]}-${parts[3]}-${parts[4]}` -} - -async function randomHex (length, uppercase = true) { - let randomHexStr = await crypto.randomBytesAsync(48) - randomHexStr = randomHexStr.toString('hex').substring(0, length) - if (uppercase) randomHexStr = randomHexStr.toUpperCase() - return randomHexStr -} - function getGameSku (platform) { switch (platform) { case 'pc': - return 'FFA16PCC' + return 'FFA18PCC' case 'ps3': - return 'FFA16PS3' + return 'FFA18PS3' case 'ps4': - return 'FFA16PS4' + return 'FFA18PS4' case 'x360': - return 'FFA16XBX' + return 'FFA18XBX' case 'xone': - return 'FFA16XBO' + return 'FFA18XBO' } return null From d494dca49a8f89e7a6465049f8d97d73dfec0067 Mon Sep 17 00:00:00 2001 From: Tim Klingeleers Date: Tue, 3 Oct 2017 08:50:09 +0200 Subject: [PATCH 2/3] remove obsolete login file no longer needed in favor of mobile-login.js --- src/lib/login.js | 490 ----------------------------------------------- 1 file changed, 490 deletions(-) delete mode 100644 src/lib/login.js diff --git a/src/lib/login.js b/src/lib/login.js deleted file mode 100644 index 5a6fe8e..0000000 --- a/src/lib/login.js +++ /dev/null @@ -1,490 +0,0 @@ -'use strict' - -module.exports = function (options) { - var __ = require('underscore') - var request = require('request') - var hasher = require('./eaHasher') - var urls = require('./urls')() - var CookieJar = require('tough-cookie').CookieJar - var utils = require('./utils') - const lodash = require('lodash') - - var defaultRequest = null - - var loginDetails = {} - - var loginResponse = { - nucleusId: null, - shardInfos: null, - shard: null, - persona: null, - sessionData: null, - apiRequest: null - } - - var jar = request.jar() - - var loginDefaults = {} - - var Login = function () {} - - Login.prototype.login = function (email, password, secret, platform, tfCodeCb, captchaCb, loginCb, version = 17) { - Login.version = version - - if (!email || !__.isString(email) || email.trim().length <= 0) return loginCb(new Error('Email is empty.')) - - if (!password || !__.isString(password) || password.trim().length <= 0) return loginCb(new Error('Password is empty.')) - - if (!secret || !__.isString(secret) || email.trim().length <= 0) return loginCb(new Error('Secret is empty.')) - - if (!platform || !__.isString(platform) || platform.trim().length <= 0) return loginCb(new Error('Platform is empty.')) - if (!getPlatform(platform)) return loginCb(new Error('Platform is invalid.')) - - if (!__.isFunction(tfCodeCb)) return loginCb(new Error('tfCodeCb is not a function.')) - - if (!__.isFunction(captchaCb)) return loginCb(new Error('captchaCb is not a function.')) - - if (!__.isFunction(loginCb)) return loginCb(new Error('loginCb is not a function.')) - - loginDetails = { - 'email': email, - 'password': password, - 'secret': hasher(secret), - 'tfCodeCb': tfCodeCb, - 'loginCb': loginCb, - 'gameSku': getGameSku(platform, version), - 'platform': getPlatform(platform), - captchaCb: captchaCb - } - - let requestConfigObj = { - jar: jar, - followAllRedirects: true, - gzip: true, - headers: { - 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36', - 'Accept': 'text/html, application/xhtml+xml, */*', - 'Accept-Encoding': 'gzip, deflate', - 'Accept-Language': 'en-US,en;q=0.8', - 'Connection': 'keep-alive', - 'DNT': '1', - 'Cache-Control': 'no-cache' - } - } - - if (options.proxy) { - requestConfigObj.proxy = options.proxy - } - - defaultRequest = request.defaults(requestConfigObj) - lodash.merge(loginDefaults, requestConfigObj) - - getMain() - } - - Login.prototype.getCookieJarJSON = function () { - return jar._jar.serializeSync() - } - - Login.prototype.setCookieJarJSON = function (json) { - jar._jar = CookieJar.deserializeSync(json) - } - - Login.prototype.getLoginDefaults = function () { - return loginDefaults - } - - Login.prototype.getCaptcha = function (cb) { - const url = urls.login.getCaptcha + new Date().getTime() - defaultRequest.get(url, function (error, response, body) { - if (error) return cb(error) - if (response.statusCode !== 200) return cb(new Error(`Captcha error ${response.statusCode} ${body}`)) - cb(body) - }) - } - - function getGameSku (platform, version) { - switch (platform) { - case 'pc': - return `FFA${version}PCC` - case 'ps3': - return `FFA${version}PS3` - case 'ps4': - return `FFA${version}PS4` - case 'x360': - return `FFA${version}XBX` - case 'xone': - return `FFA${version}XBO` - } - - return null - } - - function getPlatform (platform) { - switch (platform) { - case 'pc': - return 'pc' - case 'ps3': - case 'ps4': - return 'ps3' - case 'x360': - case 'xone': - return '360' - } - return null - } - - function getMain () { - defaultRequest.get(urls.login.main, { maxRedirects: 20 }, function (error, response, body) { - if (error) return loginDetails.loginCb(error) - - if (body.indexOf('FIFA Football | Football Club | EA SPORTS') > 0) return getNucleus() - - if (body.indexOf('FIFA Football | FUT Web App | EA SPORTS') > 0) return getNucleus() - - if (body.indexOf('Log In') > 0) return loginForm(response.request.href) - - loginDetails.loginCb(new Error('Unknown response. Unable to login.')) - }) - } - - function loginForm (url) { - defaultRequest.post(url, { - form: { - 'email': loginDetails.email, - 'password': loginDetails.password, - 'country': 'US', // is it important? - 'phoneNumber': '', // TODO: add phone code verification - 'passwordForPhone': '', - 'gCaptchaResponse': '', - 'isPhoneNumberLogin': 'false', // TODO: add phone login - 'isIncompletePhone': '', - '_rememberMe': 'on', - 'rememberMe': 'on', - '_eventId': 'submit' - } - }, function (error, response, body) { - if (error) return loginDetails.loginCb(error) - - if (body.indexOf('var redirectUri') > 0) return handleRedirect(body) - - if (body.indexOf('FIFA Football | FUT Web App | EA SPORTS') > 0) return getNucleus() - - if (body.indexOf('Log In') > 0) return loginDetails.loginCb(new Error('Unable to log in.')) - - if (body.indexOf('Set Up an App Authenticator') > 0) return cancelLoginVerificationUpdate(response.request.href) - - if (body.indexOf('Account Update') > 0) return acceptAccountUpdate(response.request.href) - - if (body.indexOf('Login Verification') > 0) { - loginDetails.tfCodeCb().then((tfCode) => sendTwoFactorCode(response.request.href, tfCode)) - return - } - loginDetails.loginCb(new Error(JSON.stringify(response, null, 2))) - }) - } - - function handleRedirect (body) { - var url = body.match(/var redirectUri = '(https?:\/\/.+\/p\/web[0-9]+\/login\?execution=.+?)'/) - defaultRequest.get(url[1] + '&_eventId=end', function (error, response, body) { - if (error) return loginDetails.loginCb(error) - - if (body.indexOf('FIFA Football | FUT Web App | EA SPORTS') > 0) return getNucleus() - - if (body.indexOf('Log In') > 0) return loginDetails.loginCb(new Error('Unable to log in.')) - - if (body.indexOf('Set Up an App Authenticator') > 0) return cancelLoginVerificationUpdate(response.request.href) - - if (body.indexOf('Account Update') > 0) return acceptAccountUpdate(response.request.href) - - if (body.indexOf('Login Verification') > 0) { - loginDetails.tfCodeCb().then((tfCode) => sendTwoFactorCode(response.request.href, tfCode)) - return - } - loginDetails.loginCb(new Error(JSON.stringify(response, null, 2))) - }) - } - - function sendTwoFactorCode (url, tfCode) { - defaultRequest.post(url, { - form: { - 'twofactorCode': tfCode, - 'twoFactorCode': tfCode, - '_eventId': 'submit', - '_trustThisDevice': 'on', - 'trustThisDevice': 'on' - } - }, function (error, response, body) { - if (error) return loginDetails.loginCb(error) - - if (body.indexOf('FIFA Football | FUT Web App | EA SPORTS') > 0) return getNucleus() - - if (body.indexOf('Set Up an App Authenticator') > 0) return cancelLoginVerificationUpdate(response.request.href) - - if (body.indexOf('Login Verification') > 0) return loginDetails.loginCb(new Error('Wrong two factor code.')) - - loginDetails.loginCb(new Error('Unknown response. Unable to login.')) - }) - } - - function acceptAccountUpdate (url) { - defaultRequest.post(url, { - form: { - '_eventId': 'submit' - } - }, function (error, response, body) { - if (error) return loginDetails.loginCb(error) - - if (body.indexOf('Login Verification') > 0) return chooseEmailSending(response.request.href) - - loginDetails.loginCb(new Error('Unknown response. Unable to login. At acceptAccountUpdate')) - }) - } - - function chooseEmailSending (url) { - defaultRequest.post(url, { - form: { - tfa_type: '', - twofactorType: 'EMAIL', - country: 0, - phoneNumber: '', - _eventId: 'submit', - appleDevice: 'IPHONE' - } - }, function (error, response, body) { - if (error) return loginDetails.loginCb(error) - - if (body.indexOf('Login Verification') > 0) { - loginDetails.tfCodeCb().then((tfCode) => sendTwoFactorCode(response.request.href, tfCode)) - return - } - - loginDetails.loginCb(new Error('Unknown response. Unable to login. At chooseEmailSending')) - }) - } - - function cancelLoginVerificationUpdate (url) { - defaultRequest.post(url, { - form: { - '_eventId': 'cancel', - 'appDevice': 'IPHONE' - } - }, function (error, response, body) { - if (error) return loginDetails.loginCb(error) - - if (body.indexOf('FIFA Football | FUT Web App | EA SPORTS') > 0) return getNucleus() - - loginDetails.loginCb(new Error('Unknown response. Unable to login.')) - }) - } - - function getNucleus () { - defaultRequest.get(urls.login.nucleus, function (error, response, body) { - if (error) return loginDetails.loginCb(error) - - var match = body.match(/EASW_ID\W*=\W*'(\d*)'/) - if (match === null || match[1] === null) return loginDetails.loginCb(new Error("Unable to get the 'EASW_ID'. Unable to login.")) - - loginResponse.nucleusId = match[1] - - getShards() - }) - } - - function getShards () { - let requestConfigObj = { - json: true, - headers: { - 'Easw-Session-Data-Nucleus-Id': loginResponse.nucleusId, - 'X-UT-Embed-Error': true, - 'X-UT-Route': 'https://utas.external.fut.ea.com', - 'X-Requested-With': 'XMLHttpRequest', - 'Referer': urls.referer - } - } - - defaultRequest = defaultRequest.defaults(requestConfigObj) - lodash.merge(loginDefaults, requestConfigObj) - - defaultRequest.get(urls.login.shards, function (error, responsse, body) { - if (error) return loginDetails.loginCb(error) - - if (!body || !body.shardInfo) return loginDetails.loginCb(new Error('Unable to get shards. Unable to login.')) - - loginResponse.shardInfos = body.shardInfo - - getAccount() - }) - } - - function getAccount () { - loginResponse.shard = __.find(loginResponse.shardInfos, function (si) { - return si.skus.indexOf(loginDetails.gameSku) >= 0 - }) - - if (!loginResponse.shard) return loginDetails.loginCb(new Error('Unable to find shardInfo.')) - - let requestConfigObj = { - headers: { - 'X-UT-Route': 'https://' + loginResponse.shard.clientFacingIpPort - } - } - - defaultRequest = defaultRequest.defaults(requestConfigObj) - lodash.merge(loginDefaults, requestConfigObj) - - defaultRequest.get(urls.login.accounts, function (error, response, body) { - if (error) return loginDetails.loginCb(error) - - if (!body.userAccountInfo) return loginDetails.loginCb(new Error('Unable to get account infos.')) - - loginResponse.persona = __.find(body.userAccountInfo.personas, - function (persona) { - return __.some(persona.userClubList, function (userClub) { - return userClub.platform === loginDetails.platform - }) - }) - - if (!loginResponse.persona) return loginDetails.loginCb(new Error('Unable to get account info persona.')) - - getSession() - }) - } - - function getSession () { - var data = { - 'isReadOnly': false, - 'sku': `FUT${Login.version}WEB`, - 'clientVersion': 1, - 'nucleusPersonaId': loginResponse.persona.personaId, - 'nucleusPersonaDisplayName': loginResponse.persona.personaName, - 'gameSku': loginDetails.gameSku, - 'nucleusPersonaPlatform': getPlatform(loginDetails.platform), - 'locale': 'en-GB', - 'method': 'authcode', - 'priorityLevel': 4, - 'identification': {'authCode': ''} - } - - defaultRequest.post(urls.login.session, { body: data, qs: { 'sku_a': `F${Login.version}` } }, function (error, response, body) { - if (error) return loginDetails.loginCb(error) - if (response.statusCode !== 200) return loginDetails.loginCb(new Error(`Unknown response. Unable to login. ${response.statusCode} ${JSON.stringify(body)}`)) - - loginResponse.sessionData = body - - if (loginResponse.sessionData.sid) return phishing() - - loginDetails.loginCb(new Error(`Unknown response. Unable to login. ${response.statusCode} ${JSON.stringify(body)}`)) - }) - } - - function phishing () { - let requestConfigObj = { - headers: { - 'X-UT-SID': loginResponse.sessionData.sid - } - } - defaultRequest = defaultRequest.defaults(requestConfigObj) - lodash.merge(loginDefaults, requestConfigObj) - - defaultRequest.get(urls.login.question, function (error, response, body) { - if (error) return loginDetails.loginCb(error) - - if (utils.isApiMessage(body) && body.token) { - loginResponse.token = body.token - let requestConfigObj = { - baseUrl: 'https://' + loginResponse.sessionData.ipPort.split(':')[0], - headers: { - 'X-UT-PHISHING-TOKEN': loginResponse.token, - 'X-HTTP-Method-Override': 'GET', - 'X-UT-Route': 'https://' + loginResponse.sessionData.ipPort.split(':')[0], - 'x-flash-version': '20,0,0,272' - } - } - loginResponse.apiRequest = defaultRequest.defaults(requestConfigObj) - lodash.merge(loginDefaults, requestConfigObj) - - return loginDetails.loginCb(null, loginResponse) - } - - if (body.question) return validate() - else if (body.code === '459') { - // validate captcha - // then do the phishing() again - getCaptcha((err, rawCaptcha) => { - if (err) loginDetails.loginCb(err) - loginDetails.captchaCb(rawCaptcha, (captcha) => { - validateCaptcha(captcha, (err) => { - if (err) loginDetails.loginCb(err) - return phishing() - }) - }) - }) - } else { - let error = new Error(`Unknown response. Unable to login. response: ${JSON.stringify(body)}`) - error.futLoginError = body - loginDetails.loginCb(error) - } - }) - } - - function getCaptcha (cb) { - const url = urls.login.captchaImg + new Date().getTime() - defaultRequest.get({ - url, - encoding: null - }, function (error, response, body) { - if (error) return cb(error) - if (response.statusCode !== 200) return cb(new Error(`Captcha error ${response.statusCode} ${body}`)) - cb(null, body) - }) - } - - function validateCaptcha (captcha, cb) { - const url = urls.login.validateCaptcha - defaultRequest.post(url, { - json: true, - body: { - 'token': 'AAAA', - 'answer': captcha - } - }, (error, response, body) => { - if (error) return cb(error) - if (response.statusCode !== 200) return cb(new Error(`Captcha is not ok ${response.statusCode} ${body}`)) - cb() - }) - } - - function validate () { - defaultRequest.post(urls.login.validate, { - form: { answer: loginDetails.secret } - }, function (error, response, body) { - if (error) return loginDetails.loginCb(error) - - if (!body) return loginDetails.loginCb(new Error('Unknown response. Unable to login.')) - - if (body.string !== 'OK') return loginDetails.loginCb(new Error('Wrong secret. Unable to login.')) - - if (body.string === 'OK') loginResponse.token = body.token - - if (!loginResponse.token) return loginDetails.loginCb(new Error('Unknown response. Unable to login.')) - - let requestConfigObj = { - baseUrl: 'https://' + loginResponse.sessionData.ipPort.split(':')[0], - headers: { - 'X-UT-PHISHING-TOKEN': loginResponse.token, - 'X-HTTP-Method-Override': 'GET', - 'X-UT-Route': 'https://' + loginResponse.sessionData.ipPort.split(':')[0], - 'x-flash-version': '20,0,0,272', - 'Accept': 'application/json' - } - } - loginResponse.apiRequest = defaultRequest.defaults(requestConfigObj) - lodash.merge(loginDefaults, requestConfigObj) - - loginDetails.loginCb(null, loginResponse) - }) - } - - return new Login() -} From 3b765879c1f7f74095edf9d625424f9531e2dd59 Mon Sep 17 00:00:00 2001 From: Tim Klingeleers Date: Tue, 3 Oct 2017 19:00:33 +0200 Subject: [PATCH 3/3] update urls to 18 --- src/lib/urls.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/urls.js b/src/lib/urls.js index b878ab2..04c0123 100644 --- a/src/lib/urls.js +++ b/src/lib/urls.js @@ -1,7 +1,7 @@ // @flow 'use strict' -const urls = (version:number = 17) => { +const urls = (version:number = 18) => { return { referer: `https://www.easports.com/iframe/fut${version}/?baseShowoffUrl=https%3A%2F%2Fwww.easports.com%2Fde%2Ffifa%2Fultimate-team%2Fweb-app%2Fshow-off&guest_app_uri=http%3A%2F%2Fwww.easports.com%2Fde%2Ffifa%2Fultimate-team%2Fweb-app&locale=en_US`, login: { @@ -9,7 +9,7 @@ const urls = (version:number = 17) => { nucleus: `https://www.easports.com/iframe/fut${version}/?locale=en_US&baseShowoffUrl=https%3A%2F%2Fwww.easports.com%2Fde%2Ffifa%2Fultimate-team%2Fweb-app%2Fshow-off&guest_app_uri=http%3A%2F%2Fwww.easports.com%2Fde%2Ffifa%2Fultimate-team%2Fweb-app`, personas: 'https://www.easports.com/fifa/api/personas', shards: `https://www.easports.com/iframe/fut${version}/p/ut/shards/v2`, - accounts: `https://www.easports.com/iframe/fut${version}/p/ut/game/fifa${version}/user/accountinfo?sku=FUT${version}WEB&returningUserGameYear=2015&_=`, + accounts: `https://www.easports.com/iframe/fut${version}/p/ut/game/fifa${version}/user/accountinfo?sku=FUT${version}WEB&returningUserGameYear=2017&_=`, session: `https://www.easports.com/iframe/fut${version}/p/ut/auth`, question: `https://www.easports.com/iframe/fut${version}/p/ut/game/fifa${version}/phishing/question?redirect=false&_=`, validate: `https://www.easports.com/iframe/fut${version}/p/ut/game/fifa${version}/phishing/validate?_=`,