bluelinky2
Version:
An unofficial nodejs API wrapper for Hyundai bluelink
143 lines (138 loc) • 5.93 kB
text/typescript
import got from 'got';
import { CookieJar } from 'tough-cookie';
import { EULanguages, EuropeanBrandEnvironment } from '../../constants/europe';
import { AuthStrategy, Code, initSession } from './authStrategy';
import Url, { URLSearchParams } from 'url';
import logger from '../../logger';
const stdHeaders = {
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_1 like Mac OS X) AppleWebKit/604.3.5 (KHTML, like Gecko) Version/11.0 Mobile/15B92 Safari/604.1'
};
const manageGot302 = <T extends Buffer | string | Record<string, unknown>>(got: Promise<got.Response<T>>): Promise<got.Response<T>> => {
return got.catch((error) => {
if (error.name === 'HTTPError' && error.statusCode === 302) {
return error.response;
}
return Promise.reject(error);
});
};
export class EuropeanBrandAuthStrategy implements AuthStrategy {
constructor(private readonly environment: EuropeanBrandEnvironment, private readonly language: EULanguages) { }
public get name(): string {
return 'EuropeanBrandAuthStrategy';
}
public async login(user: { username: string; password: string; }, options?: { cookieJar?: CookieJar }): Promise<{ code: Code, cookies: CookieJar }> {
const cookieJar = await initSession(this.environment, this.language, options?.cookieJar);
const { body: { userId, serviceId } } = await got(this.environment.endpoints.integration, {
cookieJar,
json: true,
headers: stdHeaders
});
const brandAuthUrl = this.environment.brandAuthUrl({ language: this.language, userId, serviceId });
const parsedBrandUrl = Url.parse(brandAuthUrl, true);
const { body: authForm } = await got(
brandAuthUrl, {
cookieJar,
headers: stdHeaders
});
const actionUrl = /action="([a-z0-9:/\-.?_=&;]*)"/gi.exec(authForm);
const preparedUrl = actionUrl?.[1].replace(/&/g, '&');
if (!preparedUrl) {
throw new Error('@EuropeanBrandAuthStrategy.login: cannot found the auth url from the form.');
}
const formData = new URLSearchParams();
formData.append('username', user.username);
formData.append('password', user.password);
formData.append('credentialId', '');
formData.append('rememberMe', 'on');
const { headers: { location: redirectTo }, body: afterAuthForm } = await manageGot302(got.post(preparedUrl, {
cookieJar,
body: formData.toString(),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
...stdHeaders
},
}));
if(!redirectTo) {
const errorMessage = /<span class="kc-feedback-text">(.+)<\/span>/gm.exec(afterAuthForm);
if (errorMessage) {
throw new Error(`@EuropeanBrandAuthStrategy.login: Authentication failed with message : ${errorMessage[1]}`);
}
throw new Error('@EuropeanBrandAuthStrategy.login: Authentication failed, cannot retrieve error message');
}
const authResult = await got(redirectTo, {
cookieJar,
headers: stdHeaders
});
let url = authResult.url;
let htmlPage = authResult.body;
if(!url) {
throw new Error(`@EuropeanBrandAuthStrategy.login: after login redirection got stuck : ${htmlPage}`);
}
if(url.includes('login-actions/required-action')) {
const loginActionUrl = /action="([a-z0-9:/\-.?_=&;]*)"/gi.exec(htmlPage);
const loginActionCode = /name="code" value="(.*)"/gi.exec(htmlPage);
if (!loginActionUrl) {
throw new Error('@EuropeanBrandAuthStrategy.login: Cannot find login-actions url.');
}
if (!loginActionCode) {
throw new Error('@EuropeanBrandAuthStrategy.login: Cannot find login-actions code.');
}
const actionUrl = (loginActionUrl[1].startsWith('/')) ? `${parsedBrandUrl.protocol}//${parsedBrandUrl.host}${loginActionUrl[1]}` : loginActionUrl[1];
const loginActionForm = new URLSearchParams();
loginActionForm.append('code', loginActionCode[1]);
loginActionForm.append('accept', '');
const { headers: { location: loginActionRedirect }, body: AfterLoginActionAuthForm } = await manageGot302(got.post(actionUrl, {
cookieJar,
body: loginActionForm.toString(),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
...stdHeaders
},
}));
if(!loginActionRedirect) {
const errorMessage = /<span class="kc-feedback-text">(.+)<\/span>/gm.exec(AfterLoginActionAuthForm);
if (errorMessage) {
throw new Error(`@EuropeanBrandAuthStrategy.login: Authentication action failed with message : ${errorMessage[1]}`);
}
throw new Error('@EuropeanBrandAuthStrategy.login: Authentication action failed, cannot retrieve error message');
}
const authResult = await got(loginActionRedirect, {
cookieJar,
headers: stdHeaders
});
url = authResult.url;
htmlPage = authResult.body;
}
const { intUserId: appUser } = Url.parse(url, true).query;
if (!appUser) {
logger.warn('@EuropeanBrandAuthStrategy.login: Cannot find the argument userId, please use the following url and connect from a browser.');
logger.warn(this.environment.endpoints.session);
throw new Error(`@EuropeanBrandAuthStrategy.login: Cannot find the argument userId in ${url}.
Please use the following url and connect from a browser, it may probably ask you to "Change your password" or "Accept the new conditions"
Once done, try again.
${this.environment.endpoints.session}`);
}
const { body, statusCode } = await got.post(this.environment.endpoints.silentSignIn, {
cookieJar,
body: {
intUserId: appUser
},
json: true,
headers: {
...stdHeaders,
'ccsp-service-id': this.environment.clientId,
}
});
if(!body.redirectUrl) {
throw new Error(`@EuropeanBrandAuthStrategy.login: silent sign In didn't work, could not retrieve auth code. status: ${statusCode}, body: ${JSON.stringify(body)}`);
}
const { code } = Url.parse(body.redirectUrl, true).query;
if (!code) {
throw new Error(`@EuropeanBrandAuthStrategy.login: Cannot find the argument code in ${body.redirectUrl}.`);
}
return {
code: code as Code,
cookies: cookieJar,
};
}
}