const axios = require('axios');
const axiosCookieJarSupport = require('axios-cookiejar-support').default;
const tough = require('tough-cookie');
const uuid = require('uuid').v4;
const crypto = require('crypto');
const querystring = require('querystring');
const url = require('url');
// please don't modify the password
const LOGIN_EMAIL = 'Alice84@1secmail.com';
const LOGIN_PASSWORD = 'LqkBqKLG6A_HC5U';
// AZURE AD D2C OAuth Flow
// https://docs.microsoft.com/en-us/azure/active-directory-b2c/authorization-code-flow
const AUTH_URL = 'https://auth.7eleven.com.au';
const AZURE_TENANT = 'e3aa5c67-b75d-4de2-b160-5d75da2ec18b'; // id registered by 7-Eleven in Azure AD B2C
const AZURE_CLIENT_ID = '6a7ee7ae-e7ae-473b-9066-ec34fc5b541a';
const AZURE_CUSTOM_SIGNIN_POLICY = 'b2c_1a_v1_sign_in';
const REDIRECT_URL = 'au.com.7eleven.app://oauth/redirect';
const AUTHORIZE_SCOPE = 'openid https://auth.7eleven.com.au/api/user_impersonation offline_access';
const API_HOST = 'app.api.7eleven.com.au';
const BASE_API_URL = `https://${API_HOST}`;
const AZURE_CLIENT_SECRET = '7613CF77-2BD5-4AD1-B7A0-52799BB23DF3';
const APP_VERSION = '2.0.1.11904';
const USER_AGENT = 'My 7-Eleven/11904 CFNetwork/978.0.7 Darwin/18.7.0';
const DEVICE_ID = uuid();
axiosCookieJarSupport(axios);
const cookieJar = new tough.CookieJar();
function hmacSHA256(text) {
const key = Buffer.from(AZURE_CLIENT_SECRET, 'utf8');
const hmac = crypto.createHmac('sha256', key);
hmac.update(text);
return Buffer.from(hmac.digest()).toString('base64');
}
async function elevenRequest(method, urlPath, body, utcTime, accessToken) {
const headers = {
Host: API_HOST,
'x-device-os-version': '13.4',
Accept: 'application/json',
'x-device-os': 'iOS',
'x-tz-offset': '36000',
'x-device-build-version': APP_VERSION,
'Accept-Language': 'en-au',
'Accept-Encoding': 'br, gzip, deflate',
Date: utcTime,
signature: hmacSHA256(body),
'x-device-uuid': DEVICE_ID,
'x-device-identifier': "iPad7,6",
'User-Agent': USER_AGENT,
'Content-Type': 'application/json',
Connection: 'keep-alive',
Authorization: `Bearer ${accessToken}`,
};
return axios({
method,
headers,
data: body,
url: `${BASE_API_URL}${urlPath}`,
});
}
// step 1: access authorize page (static HTML)
const { data: authorizePageHtml } = await axios.get(`${AUTH_URL}/${AZURE_TENANT}/${AZURE_CUSTOM_SIGNIN_POLICY}/oauth2/v2.0/authorize`, {
jar: cookieJar,
withCredentials: true,
params: {
redirect_uri: REDIRECT_URL,
client_id: AZURE_CLIENT_ID,
response_type: 'code',
prompt: 'login',
state: 'pNb3esTxtjy12-bcfOFGW', // random string
scope: AUTHORIZE_SCOPE,
ui_theme: 'light'
}
});
// step 2: extract info from the HTML response
const statePropertiesRegex = /"StateProperties=(\w+)"/;
const stateProperties = statePropertiesRegex.exec(authorizePageHtml)[1];
const csrfTokenRegex = /"csrf":"(.+?)"/;
const csrfToken = csrfTokenRegex.exec(authorizePageHtml)[1];
// step 3: login
await axios.post(`${AUTH_URL}/${AZURE_TENANT}/${AZURE_CUSTOM_SIGNIN_POLICY}/SelfAsserted?tx=StateProperties=${stateProperties}&p=${AZURE_CUSTOM_SIGNIN_POLICY}`, querystring.stringify({
signInName: LOGIN_EMAIL,
password: LOGIN_PASSWORD,
request_type: 'RESPONSE',
}),
{
headers: {
'X-CSRF-TOKEN': csrfToken,
},
jar: cookieJar,
withCredentials: true,
});
// step 4: get authorization code
try {
await axios.get(`${AUTH_URL}/${AZURE_TENANT}/${AZURE_CUSTOM_SIGNIN_POLICY}/api/SelfAsserted/confirmed`, {
params: {
csrf_token: csrfToken,
tx: `StateProperties=${stateProperties}`,
p: AZURE_CUSTOM_SIGNIN_POLICY,
},
jar: cookieJar,
withCredentials: true,
});
} catch (e) {
// 302 redirection response to 7-Eleven's own app URL like ("au.com.7eleven.app://oauth/redirect")
// what we need is just the code on URL
const codeRegexp = /code=(.+)/;
const authorizationCode = codeRegexp.exec(e.config.url)[1];
// step 5: exchange access token with authorization code
const { data } = await axios.post(`${AUTH_URL}/${AZURE_TENANT}/${AZURE_CUSTOM_SIGNIN_POLICY}/oauth2/v2.0/token`,
querystring.stringify({
code: authorizationCode,
client_id: AZURE_CLIENT_ID,
grant_type: 'authorization_code',
redirect_uri: REDIRECT_URL,
})
);
const accessToken = data.access_token;
console.log(`Access Token:`);
console.log(accessToken);
// step 6: use access token to call API (nearby stores)
const LATITUDE = "-38.14";
const LONGITUDE = "145.125";
const FUEL_TYPE = 'E10';
const utc = new Date().toUTCString();
const reqBody = `iOS,${APP_VERSION},GET,/api/stores/nearbyfuel,?fuel=${FUEL_TYPE}&lat=${LATITUDE}&lon=${LONGITUDE},${utc},${DEVICE_ID}`;
const response = await elevenRequest('GET', `/api/stores/nearbyfuel?fuel=${FUEL_TYPE}&lat=${LATITUDE}&lon=${LONGITUDE}`, reqBody, utc, accessToken);
console.log('Nearby Stores response:');
console.log(response.data);
}