homebridge-adt-pulse
Version:
Homebridge security system platform for ADT Pulse
922 lines (921 loc) • 59.9 kB
JavaScript
import axios from 'axios';
import { wrapper } from 'axios-cookiejar-support';
import { JSDOM } from 'jsdom';
import _ from 'lodash';
import { isErrorLike, serializeError } from 'serialize-error';
import { CookieJar } from 'tough-cookie';
import { detectGlobalDebugParser, detectGlobalPortalVersion } from './detect.js';
import { generateFakeDynatracePCHeaderValue, generateFakeLoginFingerprint } from './fake.js';
import { objectKeyClientType, objectKeyLocale, objectKeyLogin, objectKeyPreAuthToken, objectKeySat, requestPathAccessSignIn, requestPathAccessSignInEXxPartnerAdt, requestPathMfaMfaSignInWorkflowChallenge, requestPathNgaServRunRraProxyHrefRestAdtUiClientMultiFactorAuthAddTrustedDeviceSatXx, requestPathNgaServRunRraProxyHrefRestAdtUiClientMultiFactorAuthRequestOtpForRegisteredPropertySatXx, requestPathNgaServRunRraProxyHrefRestAdtUiClientMultiFactorAuthValidateOtpSatXx, requestPathNgaServRunRraProxyHrefRestIcontrolUiClientMultiFactorAuthSatXx, requestPathNgaServRunRraProxyOnlyClientMultiFactorAuthExcludeSatXxHrefRestAdtUiUpdatesSatXx, requestPathSummarySummary, requestPathSystemSystem, textOneTimePasscode, } from './regex.js';
import { multiFactorAuth, otpResponse } from './schema.js';
import { debugLog, fetchErrorMessage, findNullKeys, generateHash, parseMultiFactorMethods, parseMultiFactorTrustedDevices, parseSensorsTable, stackTracer, } from './utility.js';
export class ADTPulseAuth {
#connection;
#credentials;
#internal;
#session;
constructor(config, internalConfig) {
this.#connection = {
subdomain: config.subdomain,
};
this.#credentials = {
fingerprint: generateFakeLoginFingerprint(),
password: config.password,
username: config.username,
};
this.#internal = {
baseUrl: internalConfig.baseUrl ?? `https://${this.#connection.subdomain}.adtpulse.com`,
debug: internalConfig.debug ?? false,
logger: internalConfig.logger ?? null,
reportedHashes: [],
};
this.#session = {
httpClient: wrapper(axios.create({
jar: new CookieJar(),
validateStatus: () => true,
})),
mfa: {
clientType: null,
locale: null,
login: null,
preAuthToken: null,
satCode: null,
token: null,
trustedDevices: [],
verificationMethods: [],
},
portalVersion: null,
status: 'logged-out',
};
}
async getVerificationMethods() {
let errorObject;
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'info', `Attempting to retrieve verification methods from "${this.#internal.baseUrl}"`);
}
try {
const sessions = {};
if (this.#session.status === 'complete'
|| this.#session.status === 'not-required') {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'info', 'Already retrieved verification methods');
}
return {
action: 'GET_VERIFICATION_METHODS',
success: true,
info: {
methods: this.#session.mfa.verificationMethods,
status: this.#session.status,
},
};
}
sessions.axiosIndex = await this.#session.httpClient.get(`${this.#internal.baseUrl}/`, this.getRequestConfig());
if (sessions.axiosIndex.status >= 400) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'error', `The remote server responded with a HTTP ${sessions.axiosIndex.status} status code`);
}
return {
action: 'GET_VERIFICATION_METHODS',
success: false,
info: {
message: `The remote server responded with a HTTP ${sessions.axiosIndex.status} status code`,
},
};
}
if (typeof sessions.axiosIndex?.request === 'undefined') {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'error', 'The HTTP client responded without the "request" object');
}
return {
action: 'GET_VERIFICATION_METHODS',
success: false,
info: {
message: 'The HTTP client responded without the "request" object',
},
};
}
const axiosIndexRequestPath = sessions.axiosIndex.request.path;
const axiosIndexRequestPathValid = requestPathAccessSignIn.test(axiosIndexRequestPath);
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'info', `Request path ➜ ${axiosIndexRequestPath}`);
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'info', `Request path valid ➜ ${axiosIndexRequestPathValid}`);
}
if (!axiosIndexRequestPathValid) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'error', `"${axiosIndexRequestPath} is not the sign-in page`);
}
this.handleLoginFailure(axiosIndexRequestPath, sessions.axiosIndex);
return {
action: 'GET_VERIFICATION_METHODS',
success: false,
info: {
message: `"${axiosIndexRequestPath} is not the sign-in page`,
},
};
}
const loginForm = new URLSearchParams();
loginForm.append('usernameForm', this.#credentials.username);
loginForm.append('passwordForm', this.#credentials.password);
loginForm.append('sun', 'yes');
loginForm.append('networkid', '');
loginForm.append('fingerprint', this.#credentials.fingerprint);
this.#session.portalVersion = axiosIndexRequestPath.replace(requestPathAccessSignIn, '$2');
await this.newInformationDispatcher('portal-version', { version: this.#session.portalVersion });
sessions.axiosSignIn = await this.#session.httpClient.post(`${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/access/signin.jsp?e=ns&partner=adt`, loginForm, this.getRequestConfig({
headers: {
'Cache-Control': 'max-age=0',
'Content-Type': 'application/x-www-form-urlencoded',
Origin: this.#internal.baseUrl,
Referer: `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/access/signin.jsp?e=ns&partner=adt`,
'Sec-Fetch-Site': 'same-origin',
},
}));
if (sessions.axiosSignIn.status >= 400) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'error', `The remote server responded with a HTTP ${sessions.axiosSignIn.status} status code`);
}
return {
action: 'GET_VERIFICATION_METHODS',
success: false,
info: {
message: `The remote server responded with a HTTP ${sessions.axiosSignIn.status} status code`,
},
};
}
if (typeof sessions.axiosSignIn?.request === 'undefined') {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'error', 'The HTTP client responded without the "request" object');
}
return {
action: 'GET_VERIFICATION_METHODS',
success: false,
info: {
message: 'The HTTP client responded without the "request" object',
},
};
}
const axiosSignInRequestPath = sessions.axiosSignIn.request.path;
const axiosSignInRequestPathValid = requestPathMfaMfaSignInWorkflowChallenge.test(axiosSignInRequestPath) || requestPathSummarySummary.test(axiosSignInRequestPath);
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'info', `Request path ➜ ${axiosSignInRequestPath}`);
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'info', `Request path valid ➜ ${axiosSignInRequestPathValid}`);
}
if (requestPathSummarySummary.test(axiosSignInRequestPath)) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'info', `Verification methods not required from "${this.#internal.baseUrl}"`);
}
this.#session.status = 'not-required';
return {
action: 'GET_VERIFICATION_METHODS',
success: true,
info: {
methods: this.#session.mfa.verificationMethods,
status: this.#session.status,
},
};
}
if (requestPathAccessSignIn.test(axiosSignInRequestPath) || requestPathAccessSignInEXxPartnerAdt.test(axiosSignInRequestPath)) {
const errorMessage = fetchErrorMessage(sessions.axiosSignIn);
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'error', errorMessage ?? 'Unknown error');
}
this.#session.status = 'logged-out';
return {
action: 'GET_VERIFICATION_METHODS',
success: false,
info: {
message: errorMessage ?? 'Unknown error',
},
};
}
if (!axiosSignInRequestPathValid) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'error', `"${axiosSignInRequestPath}" is not the workflow challenge page`);
}
this.handleLoginFailure(axiosSignInRequestPath, sessions.axiosSignIn);
return {
action: 'GET_VERIFICATION_METHODS',
success: false,
info: {
message: `"${axiosSignInRequestPath}" is not the workflow challenge page`,
},
};
}
if (typeof sessions.axiosSignIn.data !== 'string') {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'error', 'The response body of the workflow challenge page is not of type "string"');
}
return {
action: 'GET_VERIFICATION_METHODS',
success: false,
info: {
message: 'The response body of the workflow challenge page is not of type "string"',
},
};
}
const matchClientType = sessions.axiosSignIn.data.match(objectKeyClientType);
const matchLocale = sessions.axiosSignIn.data.match(objectKeyLocale);
const matchLogin = sessions.axiosSignIn.data.match(objectKeyLogin);
const matchPreAuthToken = sessions.axiosSignIn.data.match(objectKeyPreAuthToken);
const matchSatCode = sessions.axiosSignIn.data.match(objectKeySat);
this.#session.mfa.clientType = (matchClientType !== null && matchClientType.length >= 2) ? matchClientType[1] : null;
this.#session.mfa.locale = (matchLocale !== null && matchLocale.length >= 2) ? matchLocale[1] : null;
this.#session.mfa.login = (matchLogin !== null && matchLogin.length >= 2) ? matchLogin[1] : null;
this.#session.mfa.preAuthToken = (matchPreAuthToken !== null && matchPreAuthToken.length >= 2) ? matchPreAuthToken[1] : null;
this.#session.mfa.satCode = (matchSatCode !== null && matchSatCode.length >= 2) ? matchSatCode[1] : null;
if (this.#session.mfa.clientType === null
|| this.#session.mfa.locale === null
|| this.#session.mfa.login === null
|| this.#session.mfa.preAuthToken === null
|| this.#session.mfa.satCode === null) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'error', 'Failed to retrieve required MFA details from the workflow challenge page');
}
return {
action: 'GET_VERIFICATION_METHODS',
success: false,
info: {
message: 'Failed to retrieve required MFA details from the workflow challenge page',
},
};
}
sessions.axiosMethods = await this.#session.httpClient.get(`${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/nga/serv/RunRRAProxy?href=rest/icontrol/ui/client/multiFactorAuth&sat=${this.#session.mfa.satCode}`, this.getRequestConfig({
headers: {
Accept: 'application/json',
'Content-Type': undefined,
Referer: `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/mfa/mfaSignIn.jsp?workflow=challenge`,
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-User': undefined,
'Upgrade-Insecure-Requests': undefined,
'X-clientType': this.#session.mfa.clientType,
'X-format': 'json',
'X-locale': this.#session.mfa.locale,
'X-login': this.#session.mfa.login,
'X-preAuthToken': this.#session.mfa.preAuthToken,
'X-version': '7.0',
'x-dtpc': generateFakeDynatracePCHeaderValue('multi-factor'),
},
}));
if (sessions.axiosMethods.status >= 400) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'error', `The remote server responded with a HTTP ${sessions.axiosMethods.status} status code`);
}
return {
action: 'GET_VERIFICATION_METHODS',
success: false,
info: {
message: `The remote server responded with a HTTP ${sessions.axiosMethods.status} status code`,
},
};
}
if (typeof sessions.axiosMethods?.request === 'undefined') {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'error', 'The HTTP client responded without the "request" object');
}
return {
action: 'GET_VERIFICATION_METHODS',
success: false,
info: {
message: 'The HTTP client responded without the "request" object',
},
};
}
const axiosMethodsRequestPath = sessions.axiosMethods.request.path;
const axiosMethodsRequestPathValid = requestPathNgaServRunRraProxyHrefRestIcontrolUiClientMultiFactorAuthSatXx.test(axiosMethodsRequestPath);
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'info', `Request path ➜ ${axiosMethodsRequestPath}`);
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'info', `Request path valid ➜ ${axiosMethodsRequestPathValid}`);
}
if (!axiosMethodsRequestPathValid) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'error', `"${axiosMethodsRequestPath}" is not the MFA auth page`);
}
this.handleLoginFailure(axiosMethodsRequestPath, sessions.axiosMethods);
return {
action: 'GET_VERIFICATION_METHODS',
success: false,
info: {
message: `"${axiosMethodsRequestPath}" is not the MFA auth page`,
},
};
}
const parsedMethods = multiFactorAuth.safeParse(sessions.axiosMethods.data);
if (!parsedMethods.success) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'error', 'The response body of the MFA auth page is invalid');
}
return {
action: 'GET_VERIFICATION_METHODS',
success: false,
info: {
message: 'The response body of the MFA auth page is invalid',
},
};
}
const parsedMultiFactorMethods = parseMultiFactorMethods(parsedMethods.data);
if (parsedMultiFactorMethods.length === 0) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'error', 'Failed to retrieve verification methods');
}
return {
action: 'GET_VERIFICATION_METHODS',
success: false,
info: {
message: 'Failed to retrieve verification methods',
},
};
}
this.#session.mfa.verificationMethods = parsedMultiFactorMethods;
this.#session.status = 'complete';
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'success', `Successfully retrieved verification methods from "${this.#internal.baseUrl}"`);
}
return {
action: 'GET_VERIFICATION_METHODS',
success: true,
info: {
methods: parsedMultiFactorMethods,
status: this.#session.status,
},
};
}
catch (error) {
errorObject = (isErrorLike(error)) ? serializeError(error) : serializeError(new Error('Unknown error'));
}
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getVerificationMethods()', 'error', 'Method encountered an error during execution');
stackTracer('serialize-error', errorObject);
}
return {
action: 'GET_VERIFICATION_METHODS',
success: false,
info: {
error: errorObject,
},
};
}
async requestCode(methodId) {
let errorObject;
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.requestCode()', 'info', `Attempting to request a code using the "${methodId}" method at "${this.#internal.baseUrl}"`);
}
if (this.#session.mfa.verificationMethods.find((device) => device.id === methodId) === undefined) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.requestCode()', 'error', `The "${methodId}" verification method does not exist. Did you run the "getVerificationMethods()" yet?`);
}
return {
action: 'REQUEST_CODE',
success: false,
info: {
message: `The "${methodId}" verification method does not exist. Did you run the "getVerificationMethods()" yet?`,
},
};
}
try {
const sessions = {};
const requestCodeForm = new URLSearchParams();
requestCodeForm.append('id', methodId);
sessions.axiosRequestCode = await this.#session.httpClient.post(`${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/nga/serv/RunRRAProxy?href=rest/adt/ui/client/multiFactorAuth/requestOtpForRegisteredProperty&sat=${this.#session.mfa.satCode}`, requestCodeForm, this.getRequestConfig({
headers: {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
Origin: this.#internal.baseUrl,
Referer: `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/mfa/mfaSignIn.jsp?workflow=challenge`,
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-User': undefined,
'Upgrade-Insecure-Requests': undefined,
'X-clientType': this.#session.mfa.clientType,
'X-format': 'json',
'X-locale': this.#session.mfa.locale,
'X-login': this.#session.mfa.login,
'X-preAuthToken': this.#session.mfa.preAuthToken,
'X-version': '7.0',
'x-dtpc': generateFakeDynatracePCHeaderValue('multi-factor'),
},
}));
if (typeof sessions.axiosRequestCode?.request === 'undefined') {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.requestCode()', 'error', 'The HTTP client responded without the "request" object');
}
return {
action: 'REQUEST_CODE',
success: false,
info: {
message: 'The HTTP client responded without the "request" object',
},
};
}
const axiosRequestCodeRequestPath = sessions.axiosRequestCode.request.path;
const axiosRequestCodeRequestPathValid = requestPathNgaServRunRraProxyHrefRestAdtUiClientMultiFactorAuthRequestOtpForRegisteredPropertySatXx.test(axiosRequestCodeRequestPath);
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.requestCode()', 'info', `Request path ➜ ${axiosRequestCodeRequestPath}`);
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.requestCode()', 'info', `Request path valid ➜ ${axiosRequestCodeRequestPathValid}`);
}
if (!axiosRequestCodeRequestPathValid) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.requestCode()', 'error', `"${axiosRequestCodeRequestPath}" is not the request code page`);
}
this.handleLoginFailure(axiosRequestCodeRequestPath, sessions.axiosRequestCode);
return {
action: 'REQUEST_CODE',
success: false,
info: {
message: `"${axiosRequestCodeRequestPath}" is not the request code page`,
},
};
}
const parsedRequestCode = otpResponse.safeParse(sessions.axiosRequestCode.data);
if (!parsedRequestCode.success) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.requestCode()', 'error', 'The response body of the request code page is invalid');
}
return {
action: 'REQUEST_CODE',
success: false,
info: {
message: 'The response body of the request code page is invalid',
},
};
}
if (!parsedRequestCode.data.detail.includes('OK')) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.requestCode()', 'error', `Unable to get a verification code ➜ "${parsedRequestCode.data.detail}"`);
}
return {
action: 'REQUEST_CODE',
success: false,
info: {
message: `Unable to get a verification code ➜ "${parsedRequestCode.data.detail}"`,
},
};
}
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.requestCode()', 'success', `Successfully requested a code for "${methodId}" at "${this.#internal.baseUrl}"`);
}
return {
action: 'REQUEST_CODE',
success: true,
info: null,
};
}
catch (error) {
errorObject = (isErrorLike(error)) ? serializeError(error) : serializeError(new Error('Unknown error'));
}
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.requestCode()', 'error', 'Method encountered an error during execution');
stackTracer('serialize-error', errorObject);
}
return {
action: 'REQUEST_CODE',
success: false,
info: {
error: errorObject,
},
};
}
async validateCode(otpCode) {
let errorObject;
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.validateCode()', 'info', `Attempting to validate verification code at "${this.#internal.baseUrl}"`);
}
if (!textOneTimePasscode.test(otpCode)) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.validateCode()', 'error', `"${otpCode}" is an invalid verification code format`);
}
return {
action: 'VALIDATE_CODE',
success: false,
info: {
message: `"${otpCode}" is an invalid verification code format`,
},
};
}
try {
const sessions = {};
const validateCodeForm = new URLSearchParams();
validateCodeForm.append('otp', otpCode);
sessions.axiosValidateCode = await this.#session.httpClient.post(`${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/nga/serv/RunRRAProxy?href=rest/adt/ui/client/multiFactorAuth/validateOtp&sat=${this.#session.mfa.satCode}`, validateCodeForm, this.getRequestConfig({
headers: {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
Origin: this.#internal.baseUrl,
Referer: `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/mfa/mfaSignIn.jsp?workflow=challenge`,
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-User': undefined,
'Upgrade-Insecure-Requests': undefined,
'X-clientType': this.#session.mfa.clientType,
'X-format': 'json',
'X-locale': this.#session.mfa.locale,
'X-login': this.#session.mfa.login,
'X-preAuthToken': this.#session.mfa.preAuthToken,
'X-version': '7.0',
'x-dtpc': generateFakeDynatracePCHeaderValue('multi-factor'),
},
}));
if (typeof sessions.axiosValidateCode?.request === 'undefined') {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.validateCode()', 'error', 'The HTTP client responded without the "request" object');
}
return {
action: 'VALIDATE_CODE',
success: false,
info: {
message: 'The HTTP client responded without the "request" object',
},
};
}
const axiosValidateCodeRequestPath = sessions.axiosValidateCode.request.path;
const axiosValidateCodeRequestPathValid = requestPathNgaServRunRraProxyHrefRestAdtUiClientMultiFactorAuthValidateOtpSatXx.test(axiosValidateCodeRequestPath);
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.validateCode()', 'info', `Request path ➜ ${axiosValidateCodeRequestPath}`);
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.validateCode()', 'info', `Request path valid ➜ ${axiosValidateCodeRequestPathValid}`);
}
if (!axiosValidateCodeRequestPathValid) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.validateCode()', 'error', `"${axiosValidateCodeRequestPath}" is not the validate code page`);
}
this.handleLoginFailure(axiosValidateCodeRequestPath, sessions.axiosValidateCode);
return {
action: 'VALIDATE_CODE',
success: false,
info: {
message: `"${axiosValidateCodeRequestPath}" is not the validate code page`,
},
};
}
if (typeof sessions.axiosValidateCode.data === 'string'
&& sessions.axiosValidateCode.data === '') {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.validateCode()', 'error', 'Already passed verification');
}
return {
action: 'VALIDATE_CODE',
success: true,
info: null,
};
}
const parsedValidateCode = otpResponse.safeParse(sessions.axiosValidateCode.data);
if (!parsedValidateCode.success) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.validateCode()', 'error', 'The response body of the validate code page is invalid');
}
return {
action: 'VALIDATE_CODE',
success: false,
info: {
message: 'The response body of the validate code page is invalid',
},
};
}
if (!parsedValidateCode.data.detail.startsWith('u=')) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.validateCode()', 'error', 'The verification code submitted is either invalidated or expired');
}
return {
action: 'VALIDATE_CODE',
success: false,
info: {
message: 'The verification code submitted is either invalidated or expired',
},
};
}
this.#session.mfa.token = parsedValidateCode.data.detail;
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.validateCode()', 'success', `Successfully validated verification code at "${this.#internal.baseUrl}"`);
}
return {
action: 'VALIDATE_CODE',
success: true,
info: null,
};
}
catch (error) {
errorObject = (isErrorLike(error)) ? serializeError(error) : serializeError(new Error('Unknown error'));
}
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.validateCode()', 'error', 'Method encountered an error during execution');
stackTracer('serialize-error', errorObject);
}
return {
action: 'VALIDATE_CODE',
success: false,
info: {
error: errorObject,
},
};
}
async getTrustedDevices() {
let errorObject;
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getTrustedDevices()', 'info', `Attempting to retrieve trusted devices at "${this.#internal.baseUrl}"`);
}
try {
const sessions = {};
sessions.axiosDevicePoll = await this.#session.httpClient.get(`${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/nga/serv/RunRRAProxy?only=client.multiFactorAuth&exclude=&sat=${this.#session.mfa.satCode}&href=rest/adt/ui/updates&sat=${this.#session.mfa.satCode}&`, this.getRequestConfig({
headers: {
Accept: 'application/json',
Referer: `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/mfa/mfaSignIn.jsp?workflow=challenge`,
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-User': undefined,
'Upgrade-Insecure-Requests': undefined,
'X-clientType': this.#session.mfa.clientType,
'X-format': 'json',
'X-locale': this.#session.mfa.locale,
'X-login': this.#session.mfa.login,
'X-token': this.#session.mfa.token,
'X-version': '7.0',
'x-dtpc': generateFakeDynatracePCHeaderValue('multi-factor'),
},
}));
if (typeof sessions.axiosDevicePoll?.request === 'undefined') {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getTrustedDevices()', 'error', 'The HTTP client responded without the "request" object');
}
return {
action: 'GET_TRUSTED_DEVICES',
success: false,
info: {
message: 'The HTTP client responded without the "request" object',
},
};
}
const axiosDevicePollRequestPath = sessions.axiosDevicePoll.request.path;
const axiosDevicePollRequestPathValid = requestPathNgaServRunRraProxyOnlyClientMultiFactorAuthExcludeSatXxHrefRestAdtUiUpdatesSatXx.test(axiosDevicePollRequestPath);
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getTrustedDevices()', 'info', `Request path ➜ ${axiosDevicePollRequestPath}`);
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getTrustedDevices()', 'info', `Request path valid ➜ ${axiosDevicePollRequestPathValid}`);
}
if (!axiosDevicePollRequestPathValid) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getTrustedDevices()', 'error', `"${axiosDevicePollRequestPath}" is not the device polling page`);
}
this.handleLoginFailure(axiosDevicePollRequestPath, sessions.axiosDevicePoll);
return {
action: 'GET_TRUSTED_DEVICES',
success: false,
info: {
message: `"${axiosDevicePollRequestPath}" is not the device polling page`,
},
};
}
const parsedDevicePoll = multiFactorAuth.safeParse(_.get(sessions.axiosDevicePoll.data, ['update', 0, 'data', 'client', 'multiFactorAuth']));
if (!parsedDevicePoll.success) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getTrustedDevices()', 'error', 'The response body of the device polling page is invalid');
}
return {
action: 'GET_TRUSTED_DEVICES',
success: false,
info: {
message: 'The response body of the device polling page is invalid',
},
};
}
const parsedTrustedDevices = parseMultiFactorTrustedDevices(parsedDevicePoll.data);
this.#session.mfa.trustedDevices = parsedTrustedDevices;
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getTrustedDevices()', 'success', `Successfully retrieved trusted devices at "${this.#internal.baseUrl}"`);
}
return {
action: 'GET_TRUSTED_DEVICES',
success: true,
info: {
trustedDevices: parsedTrustedDevices,
},
};
}
catch (error) {
errorObject = (isErrorLike(error)) ? serializeError(error) : serializeError(new Error('Unknown error'));
}
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.getTrustedDevices()', 'error', 'Method encountered an error during execution');
stackTracer('serialize-error', errorObject);
}
return {
action: 'GET_TRUSTED_DEVICES',
success: false,
info: {
error: errorObject,
},
};
}
async addTrustedDevice(deviceName) {
let errorObject;
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.addTrustedDevice()', 'info', `Attempting to add trusted device at "${this.#internal.baseUrl}"`);
}
if (this.#session.mfa.trustedDevices.find((trustedDevice) => trustedDevice.name === encodeURIComponent(deviceName)) !== undefined) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.addTrustedDevice()', 'error', 'The name for the trusted device already exists');
}
return {
action: 'ADD_TRUSTED_DEVICE',
success: false,
info: {
message: 'The name for the trusted device already exists',
},
};
}
if (deviceName.length < 1 || deviceName.length > 100) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.addTrustedDevice()', 'error', 'The name for the trusted device must be between 1 to 100 characters');
}
return {
action: 'ADD_TRUSTED_DEVICE',
success: false,
info: {
message: 'The name for the trusted device must be between 1 to 100 characters',
},
};
}
try {
const sessions = {};
const trustedDeviceForm = new URLSearchParams();
trustedDeviceForm.append('name', encodeURIComponent(deviceName));
sessions.axiosAddDevice = await this.#session.httpClient.post(`${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/nga/serv/RunRRAProxy?href=rest/adt/ui/client/multiFactorAuth/addTrustedDevice&sat=${this.#session.mfa.satCode}`, trustedDeviceForm, this.getRequestConfig({
headers: {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
Origin: this.#internal.baseUrl,
Referer: `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/mfa/mfaSignIn.jsp?workflow=challenge`,
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-User': undefined,
'Upgrade-Insecure-Requests': undefined,
'X-clientType': this.#session.mfa.clientType,
'X-format': 'json',
'X-locale': this.#session.mfa.locale,
'X-login': this.#session.mfa.login,
'X-token': this.#session.mfa.token,
'X-version': '7.0',
'x-dtpc': generateFakeDynatracePCHeaderValue('multi-factor'),
},
}));
if (typeof sessions.axiosAddDevice?.request === 'undefined') {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.addTrustedDevice()', 'error', 'The HTTP client responded without the "request" object');
}
return {
action: 'ADD_TRUSTED_DEVICE',
success: false,
info: {
message: 'The HTTP client responded without the "request" object',
},
};
}
const axiosAddDeviceRequestPath = sessions.axiosAddDevice.request.path;
const axiosAddDeviceRequestPathValid = requestPathNgaServRunRraProxyHrefRestAdtUiClientMultiFactorAuthAddTrustedDeviceSatXx.test(axiosAddDeviceRequestPath);
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.addTrustedDevice()', 'info', `Request path ➜ ${axiosAddDeviceRequestPath}`);
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.addTrustedDevice()', 'info', `Request path valid ➜ ${axiosAddDeviceRequestPathValid}`);
}
if (!axiosAddDeviceRequestPathValid) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.addTrustedDevice()', 'error', `"${axiosAddDeviceRequestPath}" is not the add trusted device page`);
}
this.handleLoginFailure(axiosAddDeviceRequestPath, sessions.axiosAddDevice);
return {
action: 'ADD_TRUSTED_DEVICE',
success: false,
info: {
message: `"${axiosAddDeviceRequestPath}" is not the add trusted device page`,
},
};
}
const parsedValidateCode = otpResponse.safeParse(sessions.axiosAddDevice.data);
if (!parsedValidateCode.success) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.addTrustedDevice()', 'error', 'The response body of the add trusted device page is invalid');
}
return {
action: 'ADD_TRUSTED_DEVICE',
success: false,
info: {
message: 'The response body of the add trusted device page is invalid',
},
};
}
if (!parsedValidateCode.data.detail.includes('OK')) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.addTrustedDevice()', 'error', `The trusted device could not be added ➜ "${parsedValidateCode.data.detail}"`);
}
return {
action: 'ADD_TRUSTED_DEVICE',
success: false,
info: {
message: `The trusted device could not be added ➜ "${parsedValidateCode.data.detail}"`,
},
};
}
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.addTrustedDevice()', 'success', `Successfully added trusted device at "${this.#internal.baseUrl}"`);
}
return {
action: 'ADD_TRUSTED_DEVICE',
success: true,
info: null,
};
}
catch (error) {
errorObject = (isErrorLike(error)) ? serializeError(error) : serializeError(new Error('Unknown error'));
}
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.addTrustedDevice()', 'error', 'Method encountered an error during execution');
stackTracer('serialize-error', errorObject);
}
return {
action: 'ADD_TRUSTED_DEVICE',
success: false,
info: {
error: errorObject,
},
};
}
async completeSignIn() {
let errorObject;
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.completeSignIn()', 'info', `Attempting to complete sign in at "${this.#internal.baseUrl}"`);
}
try {
const sessions = {};
sessions.axiosPostSignIn = await this.#session.httpClient.get(`${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/access/PostSigninProcessServ`, this.getRequestConfig({
headers: {
Referer: `${this.#internal.baseUrl}/myhome/${this.#session.portalVersion}/mfa/mfaSignIn.jsp?workflow=challenge`,
'Sec-Fetch-Site': 'same-origin',
},
}));
if (sessions.axiosPostSignIn.status >= 400) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.completeSignIn()', 'error', `The remote server responded with a HTTP ${sessions.axiosPostSignIn.status} status code`);
}
return {
action: 'COMPLETE_SIGN_IN',
success: false,
info: {
message: `The remote server responded with a HTTP ${sessions.axiosPostSignIn.status} status code`,
},
};
}
if (typeof sessions.axiosPostSignIn?.request === 'undefined') {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.completeSignIn()', 'error', 'The HTTP client responded without the "request" object');
}
return {
action: 'COMPLETE_SIGN_IN',
success: false,
info: {
message: 'The HTTP client responded without the "request" object',
},
};
}
const axiosPostSignInRequestPath = sessions.axiosPostSignIn.request.path;
const axiosPostSignInRequestPathValid = requestPathSummarySummary.test(axiosPostSignInRequestPath);
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.completeSignIn()', 'info', `Request path ➜ ${axiosPostSignInRequestPath}`);
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.completeSignIn()', 'info', `Request path valid ➜ ${axiosPostSignInRequestPathValid}`);
}
if (!axiosPostSignInRequestPathValid) {
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.completeSignIn()', 'error', `"${axiosPostSignInRequestPath}" is not the summary page`);
}
this.handleLoginFailure(axiosPostSignInRequestPath, sessions.axiosPostSignIn);
return {
action: 'COMPLETE_SIGN_IN',
success: false,
info: {
message: `"${axiosPostSignInRequestPath}" is not the summary page`,
},
};
}
this.#session.mfa = {
clientType: null,
locale: null,
login: null,
preAuthToken: null,
satCode: null,
token: null,
trustedDevices: [],
verificationMethods: [],
};
if (this.#internal.debug) {
debugLog(this.#internal.logger, 'auth.ts / ADTPulseAuth.completeSignIn()', 'success', `Successfully completed sign in at "${this.#internal.baseUrl}"`);
}
return {
action: 'COMPLETE_SIGN_IN',
success: true,
info: null,
};
}
catch (error) {
errorObject = (isErrorLike(error)) ? serializeError(error) : serializeError(new Error('Unknown error'));
}
if (this.#internal.debug) {