n8n-nodes-jygse-vw-weconnect 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -141,21 +141,42 @@ class VwWeConnect {
141
141
  }
142
142
  }
143
143
  exports.VwWeConnect = VwWeConnect;
144
- // VW OAuth2 Configuration
145
- const VW_CLIENT_ID = '9496332b-ea03-4091-a224-8c746b885068@apps_vw-dilab_com';
146
- const VW_SCOPE = 'openid profile mbb';
144
+ // VW OAuth2 Configuration (Updated January 2026)
145
+ const VW_CLIENT_ID = 'a24fba63-34b3-4d43-b181-942111e6bda8@apps_vw-dilab_com';
146
+ const VW_SCOPE = 'openid profile badge cars dealers birthdate vin';
147
147
  const VW_REDIRECT_URI = 'weconnect://authenticated';
148
+ const VW_RESPONSE_TYPE = 'code id_token token';
149
+ // Generate PKCE code verifier and challenge
150
+ function generateCodeVerifier() {
151
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
152
+ let result = '';
153
+ for (let i = 0; i < 64; i++) {
154
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
155
+ }
156
+ return result;
157
+ }
158
+ async function generateCodeChallenge(verifier) {
159
+ // Simple base64url encoding of SHA256 hash
160
+ // Since we can't use crypto directly, we'll use a simplified approach
161
+ // For now, use plain verifier (S256 would be better but requires crypto)
162
+ return verifier;
163
+ }
148
164
  async function vwLogin(context, email, password) {
149
165
  try {
166
+ // Generate PKCE values
167
+ const codeVerifier = generateCodeVerifier();
168
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
150
169
  // Step 1: Get authorization page and extract form data
151
170
  const authorizeUrl = 'https://identity.vwgroup.io/oidc/v1/authorize';
152
171
  const authorizeParams = new URLSearchParams({
153
172
  client_id: VW_CLIENT_ID,
154
173
  scope: VW_SCOPE,
155
- response_type: 'code',
174
+ response_type: VW_RESPONSE_TYPE,
156
175
  redirect_uri: VW_REDIRECT_URI,
157
176
  nonce: generateNonce(),
158
177
  state: generateNonce(),
178
+ code_challenge: codeChallenge,
179
+ code_challenge_method: 'plain',
159
180
  });
160
181
  const authorizeResponse = await context.helpers.httpRequest({
161
182
  method: 'GET',
@@ -176,7 +197,17 @@ async function vwLogin(context, email, password) {
176
197
  }
177
198
  else if (authorizeResponse && typeof authorizeResponse === 'object') {
178
199
  const respObj = authorizeResponse;
179
- htmlContent = respObj.body || JSON.stringify(respObj);
200
+ // Try different possible response structures
201
+ if (respObj.body && typeof respObj.body === 'string') {
202
+ htmlContent = respObj.body;
203
+ }
204
+ else if (respObj.data && typeof respObj.data === 'string') {
205
+ htmlContent = respObj.data;
206
+ }
207
+ else {
208
+ // Serialize the object to see its structure in the error
209
+ htmlContent = JSON.stringify(respObj);
210
+ }
180
211
  }
181
212
  else {
182
213
  htmlContent = String(authorizeResponse);
@@ -185,7 +216,9 @@ async function vwLogin(context, email, password) {
185
216
  const relayStateMatch = htmlContent.match(/name="relayState"\s+value="([^"]+)"/);
186
217
  const hmacMatch = htmlContent.match(/name="hmac"\s+value="([^"]+)"/);
187
218
  if (!csrfMatch) {
188
- throw new Error(`Could not extract CSRF token from login page. Response type: ${typeof authorizeResponse}`);
219
+ // Show first 500 chars of response for debugging
220
+ const preview = htmlContent.substring(0, 500);
221
+ throw new Error(`Could not extract CSRF token. Response preview: ${preview}`);
189
222
  }
190
223
  const csrf = csrfMatch[1];
191
224
  const relayState = relayStateMatch ? relayStateMatch[1] : '';
@@ -268,17 +301,35 @@ async function vwLogin(context, email, password) {
268
301
  redirectUrl = redirectMatch[1];
269
302
  }
270
303
  }
271
- // Follow redirects to get the auth code
304
+ // Follow redirects to get the auth code, id_token, and access_token
272
305
  let authCode = '';
306
+ let idToken = '';
307
+ let accessToken = '';
273
308
  let maxRedirects = 10;
274
309
  while (maxRedirects > 0 && redirectUrl && !authCode) {
310
+ // Extract tokens from URL fragment or query params
311
+ // The response_type 'code id_token token' returns all three
275
312
  if (redirectUrl.includes('code=')) {
276
- const codeMatch = redirectUrl.match(/code=([^&]+)/);
313
+ const codeMatch = redirectUrl.match(/code=([^&#]+)/);
277
314
  if (codeMatch) {
278
315
  authCode = codeMatch[1];
279
- break;
280
316
  }
281
317
  }
318
+ if (redirectUrl.includes('id_token=')) {
319
+ const idTokenMatch = redirectUrl.match(/id_token=([^&#]+)/);
320
+ if (idTokenMatch) {
321
+ idToken = idTokenMatch[1];
322
+ }
323
+ }
324
+ if (redirectUrl.includes('access_token=')) {
325
+ const accessTokenMatch = redirectUrl.match(/access_token=([^&#]+)/);
326
+ if (accessTokenMatch) {
327
+ accessToken = accessTokenMatch[1];
328
+ }
329
+ }
330
+ if (authCode) {
331
+ break;
332
+ }
282
333
  const followResponse = await context.helpers.httpRequest({
283
334
  method: 'GET',
284
335
  url: redirectUrl.startsWith('http') ? redirectUrl : `https://identity.vwgroup.io${redirectUrl}`,
@@ -308,21 +359,48 @@ async function vwLogin(context, email, password) {
308
359
  throw new Error('Could not obtain authorization code. Please check your credentials.');
309
360
  }
310
361
  // Step 4: Exchange auth code for tokens
311
- const tokenResponse = await context.helpers.httpRequest({
312
- method: 'POST',
313
- url: 'https://tokenrefreshservice.apps.emea.vwapps.io/exchangeAuthCode',
314
- headers: {
315
- 'User-Agent': 'Mozilla/5.0 (Linux; Android 12; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36',
316
- 'Accept': 'application/json',
317
- 'Content-Type': 'application/json',
318
- 'X-Client-Id': VW_CLIENT_ID,
319
- },
320
- body: {
321
- auth_code: authCode,
322
- id_token: '',
323
- brand: 'vw',
324
- },
325
- });
362
+ // Try the new CARIAD login endpoint first
363
+ let tokenResponse;
364
+ try {
365
+ const cariadResponse = await context.helpers.httpRequest({
366
+ method: 'POST',
367
+ url: 'https://emea.bff.cariad.digital/user-login/login/v1',
368
+ headers: {
369
+ 'User-Agent': 'Mozilla/5.0 (Linux; Android 12; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36',
370
+ 'Accept': 'application/json',
371
+ 'Content-Type': 'application/json',
372
+ },
373
+ body: {
374
+ state: authorizeParams.get('state'),
375
+ id_token: idToken,
376
+ redirect_uri: VW_REDIRECT_URI,
377
+ region: 'emea',
378
+ access_token: accessToken,
379
+ authorizationCode: authCode,
380
+ code_verifier: codeVerifier,
381
+ },
382
+ });
383
+ tokenResponse = cariadResponse;
384
+ }
385
+ catch {
386
+ // Fallback to old token exchange endpoint
387
+ tokenResponse = await context.helpers.httpRequest({
388
+ method: 'POST',
389
+ url: 'https://tokenrefreshservice.apps.emea.vwapps.io/exchangeAuthCode',
390
+ headers: {
391
+ 'User-Agent': 'Mozilla/5.0 (Linux; Android 12; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36',
392
+ 'Accept': 'application/json',
393
+ 'Content-Type': 'application/json',
394
+ 'X-Client-Id': VW_CLIENT_ID,
395
+ },
396
+ body: {
397
+ auth_code: authCode,
398
+ id_token: idToken || '',
399
+ brand: 'vw',
400
+ code_verifier: codeVerifier,
401
+ },
402
+ });
403
+ }
326
404
  return {
327
405
  accessToken: tokenResponse.access_token,
328
406
  refreshToken: tokenResponse.refresh_token,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-jygse-vw-weconnect",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "n8n community node for VW We Connect - Control your Volkswagen T6.1 and other VW vehicles",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",