UNPKG

bluelinky2

Version:

An unofficial nodejs API wrapper for Hyundai bluelink

143 lines (138 loc) 5.93 kB
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(/&amp;/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, }; } }