Skip to content

Commit cfb838b

Browse files
committed
new EU-logon, ccs2 functions
1 parent 3919d25 commit cfb838b

6 files changed

Lines changed: 192 additions & 170 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bluelinky",
3-
"version": "9.1.0",
3+
"version": "9.2.0",
44
"description": "An unofficial nodejs API wrapper for Hyundai bluelink",
55
"main": "dist/index.cjs",
66
"module": "dist/index.esm.js",

src/constants/europe.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export const DEFAULT_LANGUAGE: EULanguages = 'en';
3939
export interface EuropeanBrandEnvironment {
4040
brand: Brand;
4141
host: string;
42+
idpUrl: string;
4243
baseUrl: string;
4344
clientId: string;
4445
appId: string;
@@ -79,12 +80,14 @@ const getHyundaiEnvironment = ({
7980
stampsFile,
8081
}: EnvironmentConfig): EuropeanBrandEnvironment => {
8182
const host = 'prd.eu-ccapi.hyundai.com:8080';
83+
const idpUrl = 'https://idpconnect-eu.hyundai.com';
8284
const baseUrl = `https://${host}`;
8385
const clientId = '6d477c38-3ca4-4cf3-9557-2a1929a94654';
8486
const appId = '1eba27d2-9a5b-4eba-8ec7-97eb6c62fb51';
8587
return {
8688
brand: 'hyundai',
8789
host,
90+
idpUrl,
8891
baseUrl,
8992
clientId,
9093
appId,
@@ -112,12 +115,15 @@ const getKiaEnvironment = ({
112115
stampsFile,
113116
}: EnvironmentConfig): EuropeanBrandEnvironment => {
114117
const host = 'prd.eu-ccapi.kia.com:8080';
118+
const idpUrl = 'https://idpconnect-eu.kia.com';
115119
const baseUrl = `https://${host}`;
116120
const clientId = 'fdc85c00-0a2f-4c64-bcb4-2cfb1500730a';
117-
const appId = 'a2b8469b-30a3-4361-8e13-6fceea8fbe74';
121+
// const appId = 'a2b8469b-30a3-4361-8e13-6fceea8fbe74';
122+
const appId = '1518dd6b-2759-4995-9ae5-c9ad4a9ddad1';
118123
return {
119124
brand: 'kia',
120125
host,
126+
idpUrl,
121127
baseUrl,
122128
clientId,
123129
appId,
@@ -141,7 +147,7 @@ const getKiaEnvironment = ({
141147

142148
export const getBrandEnvironment = ({
143149
brand,
144-
stampMode = StampMode.DISTANT,
150+
stampMode = StampMode.LOCAL,
145151
stampsFile,
146152
}: BrandEnvironmentConfig): EuropeanBrandEnvironment => {
147153
switch (brand) {
Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import got from 'got';
22
import { CookieJar } from 'tough-cookie';
3-
import { DEFAULT_LANGUAGE, EULanguages, EuropeanBrandEnvironment } from '../../constants/europe';
3+
import { EuropeanBrandEnvironment } from '../../constants/europe';
44

55
export type Code = string;
66

@@ -14,15 +14,9 @@ export interface AuthStrategy {
1414

1515
export async function initSession(
1616
environment: EuropeanBrandEnvironment,
17-
language: EULanguages = DEFAULT_LANGUAGE,
1817
cookies?: CookieJar
1918
): Promise<CookieJar> {
2019
const cookieJar = cookies ?? new CookieJar();
2120
await got(environment.endpoints.session, { cookieJar });
22-
await got(environment.endpoints.language, {
23-
method: 'POST',
24-
body: `{"lang":"${language}"}`,
25-
cookieJar,
26-
});
2721
return cookieJar;
2822
}

src/controllers/authStrategies/european.brandAuth.strategy.ts

Lines changed: 39 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import got from 'got';
22
import { CookieJar } from 'tough-cookie';
33
import { EULanguages, EuropeanBrandEnvironment } from '../../constants/europe';
44
import { AuthStrategy, Code, initSession } from './authStrategy';
5-
import Url, { URLSearchParams } from 'url';
5+
//import Url,
6+
import { URLSearchParams } from 'url';
67

78
const stdHeaders = {
89
'User-Agent':
@@ -31,104 +32,60 @@ export class EuropeanBrandAuthStrategy implements AuthStrategy {
3132
}
3233

3334
public async login(user: { username: string; password: string; }, options?: { cookieJar?: CookieJar }): Promise<{ code: Code, cookies: CookieJar }> {
34-
const cookieJar = await initSession(this.environment, this.language, options?.cookieJar);
35+
const cookieJar = await initSession(this.environment, options?.cookieJar);
3536
const { body: { userId, serviceId } } = await got(this.environment.endpoints.integration, {
3637
cookieJar,
3738
json: true,
3839
headers: stdHeaders
3940
});
4041
const brandAuthUrl = this.environment.brandAuthUrl({ language: this.language, userId, serviceId });
41-
const parsedBrandUrl = Url.parse(brandAuthUrl, true);
42-
const { body: authForm } = await got(
42+
//const parsedBrandUrl = Url.parse(brandAuthUrl, true);
43+
//const { body: authForm } =
44+
await got(
4345
brandAuthUrl, {
4446
cookieJar,
4547
headers: stdHeaders
4648
});
47-
const actionUrl = /action="([a-z0-9:/\-.?_=&;]*)"/gi.exec(authForm);
48-
const preparedUrl = actionUrl?.[1].replace(/&amp;/g, '&');
49-
if (!preparedUrl) {
50-
throw new Error('@EuropeanBrandAuthStrategy.login: cannot found the auth url from the form.');
51-
}
52-
const formData = new URLSearchParams();
53-
formData.append('username', user.username);
54-
formData.append('password', user.password);
55-
formData.append('credentialId', '');
56-
formData.append('rememberMe', 'on');
57-
const { headers: { location: redirectTo }, body: afterAuthForm } = await manageGot302(got.post(preparedUrl, {
58-
cookieJar,
59-
body: formData.toString(),
60-
headers: {
61-
'Content-Type': 'application/x-www-form-urlencoded',
62-
...stdHeaders
63-
},
64-
followRedirect: false,
65-
}));
49+
50+
const authUrl = `${this.environment.idpUrl}/auth/api/v2/user/oauth2/authorize`;
51+
const params = {
52+
response_type: 'code',
53+
client_id: serviceId,
54+
redirect_uri: this.environment.endpoints.redirectUri, // `${this.environment.baseUrl}/api/v1/user/oauth2/redirect`,
55+
state: 'ccsp',
56+
lang: 'en'
57+
};
58+
await got(authUrl, { params });
59+
60+
const loginUrl = `${this.environment.idpUrl}/auth/account/signin`;
61+
const loginData = new URLSearchParams({
62+
username: user.username,
63+
password: user.password,
64+
encryptedPassword: 'false',
65+
remember_me: 'false',
66+
redirect_uri: this.environment.endpoints.redirectUri, // `${this.environment.baseUrl}/api/v1/user/oauth2/redirect`,
67+
state: 'ccsp',
68+
client_id: this.environment.clientId
69+
});
70+
const { headers: { location: redirectTo }, body: afterAuthForm } = await manageGot302(got.post(loginUrl, {
71+
body: loginData.toString(),
72+
headers: {
73+
'Content-Type': 'application/x-www-form-urlencoded'
74+
},
75+
followRedirect: false, // equivalent to maxRedirects: 0
76+
throwHttpErrors: false // needed so it doesn't throw on 302
77+
}));
78+
6679
if(!redirectTo) {
6780
const errorMessage = /<span class="kc-feedback-text">(.+)<\/span>/gm.exec(afterAuthForm);
6881
if (errorMessage) {
6982
throw new Error(`@EuropeanBrandAuthStrategy.login: Authentication failed with message : ${errorMessage[1]}`);
7083
}
7184
throw new Error('@EuropeanBrandAuthStrategy.login: Authentication failed, cannot retrieve error message');
7285
}
73-
const authResult = await got(redirectTo, {
74-
cookieJar,
75-
headers: stdHeaders
76-
});
77-
let url = authResult.url;
78-
let htmlPage = authResult.body;
79-
if(!url) {
80-
throw new Error(`@EuropeanBrandAuthStrategy.login: after login redirection got stuck : ${htmlPage}`);
81-
}
82-
if(url.includes('login-actions/required-action')) {
83-
const loginActionUrl = /action="([a-z0-9:/\-.?_=&;]*)"/gi.exec(htmlPage);
84-
const loginActionCode = /name="code" value="(.*)"/gi.exec(htmlPage);
85-
if (!loginActionUrl) {
86-
throw new Error('@EuropeanBrandAuthStrategy.login: Cannot find login-actions url.');
87-
}
88-
if (!loginActionCode) {
89-
throw new Error('@EuropeanBrandAuthStrategy.login: Cannot find login-actions code.');
90-
}
91-
const actionUrl = (loginActionUrl[1].startsWith('/')) ? `${parsedBrandUrl.protocol}//${parsedBrandUrl.host}${loginActionUrl[1]}` : loginActionUrl[1];
92-
const loginActionForm = new URLSearchParams();
93-
loginActionForm.append('code', loginActionCode[1]);
94-
loginActionForm.append('accept', '');
95-
const { headers: { location: loginActionRedirect }, body: AfterLoginActionAuthForm } = await manageGot302(got.post(actionUrl, {
96-
cookieJar,
97-
body: loginActionForm.toString(),
98-
headers: {
99-
'Content-Type': 'application/x-www-form-urlencoded',
100-
...stdHeaders
101-
},
102-
}));
103-
if(!loginActionRedirect) {
104-
const errorMessage = /<span class="kc-feedback-text">(.+)<\/span>/gm.exec(AfterLoginActionAuthForm);
105-
if (errorMessage) {
106-
throw new Error(`@EuropeanBrandAuthStrategy.login: Authentication action failed with message : ${errorMessage[1]}`);
107-
}
108-
throw new Error('@EuropeanBrandAuthStrategy.login: Authentication action failed, cannot retrieve error message');
109-
}
110-
const authResult = await got(loginActionRedirect, {
111-
cookieJar,
112-
headers: stdHeaders
113-
});
114-
url = authResult.url;
115-
htmlPage = authResult.body;
116-
}
117-
const { body, statusCode } = await got.post(this.environment.endpoints.silentSignIn, {
118-
cookieJar,
119-
body: {
120-
intUserId: ''
121-
},
122-
json: true,
123-
headers: {
124-
...stdHeaders,
125-
'ccsp-service-id': this.environment.clientId,
126-
}
127-
});
128-
if(!body.redirectUrl) {
129-
throw new Error(`@EuropeanBrandAuthStrategy.login: silent sign In didn't work, could not retrieve auth code. status: ${statusCode}, body: ${JSON.stringify(body)}`);
130-
}
131-
const { code } = Url.parse(body.redirectUrl, true).query;
86+
const parsedUrl = new URL(redirectTo);
87+
const code = parsedUrl.searchParams.get('code');
88+
13289
if (!code) {
13390
throw new Error(`@EuropeanBrandAuthStrategy.login: Cannot find the argument code in ${body.redirectUrl}.`);
13491
}

src/controllers/european.controller.ts

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { URLSearchParams } from 'url';
1616

1717
import { CookieJar } from 'tough-cookie';
1818
import { VehicleRegisterOptions } from '../interfaces/common.interfaces';
19-
import { asyncMap, manageBluelinkyError, Stringifiable, uuidV4 } from '../tools/common.tools';
19+
import { asyncMap, manageBluelinkyError, Stringifiable } from '../tools/common.tools';
2020
import { AuthStrategy, Code } from './authStrategies/authStrategy';
2121
import { EuropeanBrandAuthStrategy } from './authStrategies/european.brandAuth.strategy';
2222
import { EuropeanLegacyAuthStrategy } from './authStrategies/european.legacyAuth.strategy';
@@ -53,7 +53,8 @@ export class EuropeanController extends SessionController<EuropeBlueLinkyConfig>
5353
)} are.`
5454
);
5555
}
56-
this.session.deviceId = uuidV4();
56+
// this.session.deviceId = uuidV4();
57+
this.session.deviceId = 'c629f223-3c53-4128-8a83-36bea6b74207';
5758
this._environment = getBrandEnvironment(userConfig);
5859
this.authStrategies = {
5960
main: new EuropeanBrandAuthStrategy(this._environment, this.userConfig.language),
@@ -70,7 +71,8 @@ export class EuropeanController extends SessionController<EuropeBlueLinkyConfig>
7071
accessToken: undefined,
7172
refreshToken: undefined,
7273
controlToken: undefined,
73-
deviceId: uuidV4(),
74+
// deviceId: uuidV4(),
75+
deviceId: 'c629f223-3c53-4128-8a83-36bea6b74207',
7476
tokenExpiresAt: 0,
7577
controlTokenExpiresAt: 0,
7678
};
@@ -155,6 +157,41 @@ export class EuropeanController extends SessionController<EuropeBlueLinkyConfig>
155157
}
156158
}
157159

160+
public async register(): Promise<string> {
161+
/*
162+
const genRanHex = size =>
163+
[...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');
164+
const notificationReponse = await got(
165+
`${this.environment.baseUrl}/api/v1/spa/notifications/register`,
166+
{
167+
method: 'POST',
168+
headers: {
169+
'ccsp-service-id': this.environment.clientId,
170+
'Content-Type': 'application/json;charset=UTF-8',
171+
'Host': this.environment.host,
172+
'Connection': 'Keep-Alive',
173+
'Accept-Encoding': 'gzip',
174+
'User-Agent': 'okhttp/3.10.0',
175+
'ccsp-application-id': this.environment.appId,
176+
'Stamp': await this.environment.stamp(),
177+
},
178+
body: {
179+
pushRegId: genRanHex(64),
180+
pushType: 'APNS',
181+
uuid: this.session.deviceId,
182+
},
183+
json: true,
184+
}
185+
);
186+
187+
if (notificationReponse) {
188+
this.session.deviceId = notificationReponse.body.resMsg.deviceId;
189+
}
190+
logger.debug('@EuropeController.login: Device registered');
191+
*/
192+
return 'Registered again';
193+
}
194+
158195
public async login(): Promise<string> {
159196
try {
160197
if (!this.userConfig.password || !this.userConfig.username) {
@@ -202,7 +239,7 @@ export class EuropeanController extends SessionController<EuropeBlueLinkyConfig>
202239
},
203240
body: {
204241
pushRegId: genRanHex(64),
205-
pushType: 'APNS',
242+
pushType: 'GCM',
206243
uuid: this.session.deviceId,
207244
},
208245
json: true,
@@ -213,24 +250,25 @@ export class EuropeanController extends SessionController<EuropeBlueLinkyConfig>
213250
this.session.deviceId = notificationReponse.body.resMsg.deviceId;
214251
}
215252
logger.debug('@EuropeController.login: Device registered');
216-
217253
const formData = new URLSearchParams();
218254
formData.append('grant_type', 'authorization_code');
219-
formData.append('redirect_uri', this.environment.endpoints.redirectUri);
255+
formData.append('redirect_uri', this.environment.endpoints.redirectUri ); //`${this.environment.baseUrl}/api/v1/user/oauth2/redirect`);
220256
formData.append('code', authResult.code);
257+
formData.append('client_id', this.environment.clientId);
258+
formData.append('client_secret','secret');
221259

222-
const response = await got(this.environment.endpoints.token, {
260+
const response = await got(`${this.environment.idpUrl}/auth/api/v2/user/oauth2/token`, {
223261
method: 'POST',
224262
headers: {
225-
'Authorization': this.environment.basicToken,
263+
//'Authorization': this.environment.basicToken,
226264
'Content-Type': 'application/x-www-form-urlencoded',
227-
'Host': this.environment.host,
228-
'Connection': 'Keep-Alive',
265+
//'Host': this.environment.host,
266+
//'Connection': 'Keep-Alive',
229267
'Accept-Encoding': 'gzip',
230268
'User-Agent': 'okhttp/3.10.0',
231-
'grant_type': 'authorization_code',
232-
'ccsp-application-id': this.environment.appId,
233-
'Stamp': await this.environment.stamp(),
269+
//'grant_type': 'authorization_code',
270+
//'ccsp-application-id': this.environment.appId,
271+
'Stamp': false, // await this.environment.stamp(),
234272
},
235273
body: formData.toString(),
236274
cookieJar: authResult.cookies,
@@ -321,14 +359,18 @@ export class EuropeanController extends SessionController<EuropeBlueLinkyConfig>
321359
}
322360
}
323361

324-
public async getVehicleHttpService(): Promise<GotInstance<GotJSONFn>> {
362+
public async getVehicleHttpService(vehicle?: EuropeanVehicle): Promise<GotInstance<GotJSONFn>> {
325363
await this.checkControlToken();
364+
const ccuccs2 = Number (vehicle ? vehicle.vehicleConfig.ccuCCS2ProtocolSupport : false);
365+
326366
return got.extend({
327367
baseUrl: this.environment.baseUrl,
328368
headers: {
329369
...this.defaultHeaders,
330370
'Authorization': this.session.controlToken,
371+
'AuthorizationCCSP': this.session.controlToken,
331372
'Stamp': await this.environment.stamp(),
373+
'Ccuccs2protocolsupport': ccuccs2,
332374
},
333375
json: true,
334376
});

0 commit comments

Comments
 (0)