diff --git a/components/twofactor.js b/components/twofactor.js index e18478b..7213261 100644 --- a/components/twofactor.js +++ b/components/twofactor.js @@ -1,3 +1,4 @@ +const StdLib = require('@doctormckay/stdlib'); const SteamTotp = require('steam-totp'); const SteamCommunity = require('../index.js'); @@ -6,152 +7,158 @@ const Helpers = require('./helpers.js'); const ETwoFactorTokenType = { None: 0, // No token-based two-factor authentication ValveMobileApp: 1, // Tokens generated using Valve's special charset (5 digits, alphanumeric) - ThirdParty: 2 // Tokens generated using literally everyone else's standard charset (6 digits, numeric). This is disabled. + ThirdParty: 2 // Tokens generated using literally everyone else's standard charset (6 digits, numeric). This is disabled on the backend. }; +/** + * @param {function} [callback] + * @return {Promise} + */ SteamCommunity.prototype.enableTwoFactor = function(callback) { - this._verifyMobileAccessToken(); + return StdLib.Promises.callbackPromise(null, callback, false, async (resolve, reject) => { + this._verifyMobileAccessToken(); - if (!this.mobileAccessToken) { - callback(new Error('No mobile access token available. Provide one by calling setMobileAppAccessToken()')); - return; - } - - this.httpRequestPost({ - uri: "https://api.steampowered.com/ITwoFactorService/AddAuthenticator/v1/?access_token=" + this.mobileAccessToken, - // TODO: Send this as protobuf to more closely mimic official app behavior - form: { - steamid: this.steamID.getSteamID64(), - authenticator_time: Math.floor(Date.now() / 1000), - authenticator_type: ETwoFactorTokenType.ValveMobileApp, - device_identifier: SteamTotp.getDeviceID(this.steamID), - sms_phone_id: '1' - }, - json: true - }, (err, response, body) => { - if (err) { - callback(err); - return; + if (!this.mobileAccessToken) { + return reject(new Error('No mobile access token available. Provide one by calling setMobileAppAccessToken()')); } - if (!body.response) { - callback(new Error('Malformed response')); - return; - } - - if (body.response.status != 1) { - var error = new Error('Error ' + body.response.status); - error.eresult = body.response.status; - callback(error); - return; - } - - callback(null, body.response); - }, 'steamcommunity'); -}; - -SteamCommunity.prototype.finalizeTwoFactor = function(secret, activationCode, callback) { - this._verifyMobileAccessToken(); - - if (!this.mobileAccessToken) { - callback(new Error('No mobile access token available. Provide one by calling setMobileAppAccessToken()')); - return; - } - - let attemptsLeft = 30; - let diff = 0; - - let finalize = () => { - let code = SteamTotp.generateAuthCode(secret, diff); - - this.httpRequestPost({ - uri: 'https://api.steampowered.com/ITwoFactorService/FinalizeAddAuthenticator/v1/?access_token=' + this.mobileAccessToken, + let {jsonBody} = await this.httpRequest({ + method: 'POST', + url: `https://api.steampowered.com/ITwoFactorService/AddAuthenticator/v1/?access_token=${this.mobileAccessToken}`, + // TODO: Send this as protobuf to more closely mimic official app behavior form: { steamid: this.steamID.getSteamID64(), - authenticator_code: code, authenticator_time: Math.floor(Date.now() / 1000), - activation_code: activationCode + authenticator_type: ETwoFactorTokenType.ValveMobileApp, + device_identifier: SteamTotp.getDeviceID(this.steamID), + sms_phone_id: '1' }, - json: true - }, (err, response, body) => { - if (err) { - callback(err); - return; + source: 'steamcommunity' + }); + + + if (!jsonBody.response) { + return reject(new Error('Malformed response')); + } + + if (jsonBody.response.status != 1) { + let error = new Error(`Error ${jsonBody.response.status}`); + error.eresult = jsonBody.response.status; + return reject(error); + } + + resolve(jsonBody.response); + }); +}; + +/** + * @param {string} secret + * @param {string} activationCode + * @param {function} [callback] + * @return Promise + */ +SteamCommunity.prototype.finalizeTwoFactor = function(secret, activationCode, callback) { + return StdLib.Promises.callbackPromise(null, callback, false, async (resolve, reject) => { + this._verifyMobileAccessToken(); + + if (!this.mobileAccessToken) { + return reject(new Error('No mobile access token available. Provide one by calling setMobileAppAccessToken()')); + } + + let attemptsLeft = 30; + let diff = 0; + + await new Promise((resolve, reject) => { + SteamTotp.getTimeOffset(function(err, offset, latency) { + if (err) { + return reject(err); + } + + diff = offset; + resolve(); + }); + }); + + let finalize = async () => { + let code = SteamTotp.generateAuthCode(secret, diff); + + let {jsonBody} = this.httpRequest({ + method: 'POST', + url: `https://api.steampowered.com/ITwoFactorService/FinalizeAddAuthenticator/v1/?access_token=${this.mobileAccessToken}`, + form: { + steamid: this.steamID.getSteamID64(), + authenticator_code: code, + authenticator_time: Math.floor(Date.now() / 1000), + activation_code: activationCode + }, + source: 'steamcommunity' + }); + + if (!jsonBody.response) { + return reject(new Error('Malformed response')); } - if (!body.response) { - callback(new Error('Malformed response')); - return; + jsonBody = jsonBody.response; + + if (jsonBody.server_time) { + diff = jsonBody.server_time - Math.floor(Date.now() / 1000); } - body = body.response; - - if (body.server_time) { - diff = body.server_time - Math.floor(Date.now() / 1000); - } - - if (body.status == SteamCommunity.EResult.TwoFactorActivationCodeMismatch) { - callback(new Error('Invalid activation code')); - } else if (body.want_more) { + if (jsonBody.status == SteamCommunity.EResult.TwoFactorActivationCodeMismatch) { + return reject(new Error('Invalid activation code')); + } else if (jsonBody.want_more) { if (--attemptsLeft <= 0) { // We made more than 30 attempts, something must be wrong - return callback(Helpers.eresultError(SteamCommunity.EResult.Fail)); + return reject(Helpers.eresultError(SteamCommunity.EResult.Fail)); } diff += 30; finalize(); - } else if(!body.success) { - callback(new Error('Error ' + body.status)); + } else if (!jsonBody.success) { + return reject(new Error(`Error ${jsonBody.status}`)); } else { - callback(null); + resolve(); } - }, 'steamcommunity'); - } + }; - SteamTotp.getTimeOffset(function(err, offset, latency) { - if (err) { - callback(err); - return; - } - - diff = offset; finalize(); }); }; +/** + * @param {string} revocationCode + * @param {function} [callback] + * @return Promise + */ SteamCommunity.prototype.disableTwoFactor = function(revocationCode, callback) { - this._verifyMobileAccessToken(); + return StdLib.Promises.callbackPromise(null, callback, false, async (resolve, reject) => { + this._verifyMobileAccessToken(); - if (!this.mobileAccessToken) { - callback(new Error('No mobile access token available. Provide one by calling setMobileAppAccessToken()')); - return; - } - - this.httpRequestPost({ - uri: 'https://api.steampowered.com/ITwoFactorService/RemoveAuthenticator/v1/?access_token=' + this.mobileAccessToken, - form: { - steamid: this.steamID.getSteamID64(), - revocation_code: revocationCode, - steamguard_scheme: 1 - }, - json: true - }, function(err, response, body) { - if (err) { - callback(err); + if (!this.mobileAccessToken) { + callback(new Error('No mobile access token available. Provide one by calling setMobileAppAccessToken()')); return; } - if (!body.response) { - callback(new Error('Malformed response')); - return; + let {jsonBody} = await this.httpRequest({ + method: 'POST', + url: `https://api.steampowered.com/ITwoFactorService/RemoveAuthenticator/v1/?access_token=${this.mobileAccessToken}`, + form: { + steamid: this.steamID.getSteamID64(), + revocation_code: revocationCode, + steamguard_scheme: 1 + }, + source: 'steamcommunity' + }); + + if (!jsonBody.response) { + return reject(new Error('Malformed response')); } - if (!body.response.success) { - callback(new Error('Request failed')); - return; + if (!jsonBody.response.success) { + return reject(new Error('Request failed')); } // success = true means it worked - callback(null); - }, 'steamcommunity'); + resolve(); + }); };