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

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.
@@ -166,7 +166,8 @@ async function vwLogin(context, email, password) {
166
166
  // Generate PKCE values
167
167
  const codeVerifier = generateCodeVerifier();
168
168
  const codeChallenge = await generateCodeChallenge(codeVerifier);
169
- // Step 1: Get authorization page and extract form data
169
+ const stateParam = generateNonce();
170
+ // Step 1: Get authorization page - this now returns Auth0 login page
170
171
  const authorizeUrl = 'https://identity.vwgroup.io/oidc/v1/authorize';
171
172
  const authorizeParams = new URLSearchParams({
172
173
  client_id: VW_CLIENT_ID,
@@ -174,7 +175,7 @@ async function vwLogin(context, email, password) {
174
175
  response_type: VW_RESPONSE_TYPE,
175
176
  redirect_uri: VW_REDIRECT_URI,
176
177
  nonce: generateNonce(),
177
- state: generateNonce(),
178
+ state: stateParam,
178
179
  code_challenge: codeChallenge,
179
180
  code_challenge_method: 'plain',
180
181
  });
@@ -188,16 +189,25 @@ async function vwLogin(context, email, password) {
188
189
  encoding: 'text',
189
190
  returnFullResponse: true,
190
191
  ignoreHttpStatusErrors: true,
192
+ skipAutoFollowRedirects: true,
191
193
  });
192
- // Extract CSRF token and relay state from the response
193
- // Handle both string response and object with body property
194
+ // Extract response body and check for redirect
194
195
  let htmlContent;
196
+ let currentUrl = '';
197
+ let stateToken = '';
195
198
  if (typeof authorizeResponse === 'string') {
196
199
  htmlContent = authorizeResponse;
197
200
  }
198
201
  else if (authorizeResponse && typeof authorizeResponse === 'object') {
199
202
  const respObj = authorizeResponse;
200
- // Try different possible response structures
203
+ const respHeaders = respObj.headers;
204
+ // Check for redirect in headers (case-insensitive)
205
+ if (respHeaders) {
206
+ const locationHeader = respHeaders.location || respHeaders.Location;
207
+ if (locationHeader) {
208
+ currentUrl = locationHeader;
209
+ }
210
+ }
201
211
  if (respObj.body && typeof respObj.body === 'string') {
202
212
  htmlContent = respObj.body;
203
213
  }
@@ -205,94 +215,220 @@ async function vwLogin(context, email, password) {
205
215
  htmlContent = respObj.data;
206
216
  }
207
217
  else {
208
- // Serialize the object to see its structure in the error
209
218
  htmlContent = JSON.stringify(respObj);
210
219
  }
211
220
  }
212
221
  else {
213
222
  htmlContent = String(authorizeResponse);
214
223
  }
215
- const csrfMatch = htmlContent.match(/name="_csrf"\s+value="([^"]+)"/);
216
- const relayStateMatch = htmlContent.match(/name="relayState"\s+value="([^"]+)"/);
217
- const hmacMatch = htmlContent.match(/name="hmac"\s+value="([^"]+)"/);
218
- if (!csrfMatch) {
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}`);
224
+ // Follow redirects manually to capture the state parameter
225
+ let maxInitialRedirects = 5;
226
+ while (currentUrl && maxInitialRedirects > 0) {
227
+ // Extract state from redirect URL
228
+ const stateMatch = currentUrl.match(/state=([^&]+)/);
229
+ if (stateMatch) {
230
+ stateToken = decodeURIComponent(stateMatch[1]);
231
+ }
232
+ // If we reached the /u/login page, we have what we need
233
+ if (currentUrl.includes('/u/login') && stateToken) {
234
+ break;
235
+ }
236
+ // Follow the redirect
237
+ const followUrl = currentUrl.startsWith('http') ? currentUrl : `https://identity.vwgroup.io${currentUrl}`;
238
+ const followResponse = await context.helpers.httpRequest({
239
+ method: 'GET',
240
+ url: followUrl,
241
+ headers: {
242
+ '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',
243
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
244
+ },
245
+ encoding: 'text',
246
+ returnFullResponse: true,
247
+ ignoreHttpStatusErrors: true,
248
+ skipAutoFollowRedirects: true,
249
+ });
250
+ if (typeof followResponse === 'string') {
251
+ htmlContent = followResponse;
252
+ currentUrl = '';
253
+ }
254
+ else if (followResponse && typeof followResponse === 'object') {
255
+ const respObj = followResponse;
256
+ const respHeaders = respObj.headers;
257
+ currentUrl = '';
258
+ if (respHeaders) {
259
+ const locationHeader = respHeaders.location || respHeaders.Location;
260
+ if (locationHeader) {
261
+ currentUrl = locationHeader;
262
+ }
263
+ }
264
+ if (respObj.body && typeof respObj.body === 'string') {
265
+ htmlContent = respObj.body;
266
+ }
267
+ else if (respObj.data && typeof respObj.data === 'string') {
268
+ htmlContent = respObj.data;
269
+ }
270
+ }
271
+ maxInitialRedirects--;
222
272
  }
223
- const csrf = csrfMatch[1];
224
- const relayState = relayStateMatch ? relayStateMatch[1] : '';
225
- const hmac = hmacMatch ? hmacMatch[1] : '';
226
- // Step 2: Submit email
227
- const identifierResponse = await context.helpers.httpRequest({
228
- method: 'POST',
229
- url: 'https://identity.vwgroup.io/signin-service/v1/signin/login/identifier',
230
- headers: {
231
- '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',
232
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
233
- 'Content-Type': 'application/x-www-form-urlencoded',
234
- },
235
- body: new URLSearchParams({
236
- _csrf: csrf,
237
- relayState: relayState,
238
- hmac: hmac,
239
- email: email,
240
- }).toString(),
241
- encoding: 'text',
242
- returnFullResponse: true,
243
- ignoreHttpStatusErrors: true,
244
- });
245
- // Extract new CSRF for password submission
246
- let identifierHtml;
247
- if (typeof identifierResponse === 'string') {
248
- identifierHtml = identifierResponse;
273
+ // Try to extract state token from the final HTML if not found in URL
274
+ if (!stateToken) {
275
+ const stateHtmlMatch = htmlContent.match(/name="state"\s+value="([^"]+)"/);
276
+ if (stateHtmlMatch) {
277
+ stateToken = stateHtmlMatch[1];
278
+ }
249
279
  }
250
- else if (identifierResponse && typeof identifierResponse === 'object') {
251
- const respObj = identifierResponse;
252
- identifierHtml = respObj.body || JSON.stringify(respObj);
280
+ // Try to find state in form action URL
281
+ if (!stateToken) {
282
+ const formActionMatch = htmlContent.match(/action="[^"]*\?state=([^"&]+)/);
283
+ if (formActionMatch) {
284
+ stateToken = decodeURIComponent(formActionMatch[1]);
285
+ }
253
286
  }
254
- else {
255
- identifierHtml = String(identifierResponse);
287
+ // Try to extract state from any URL in the page
288
+ if (!stateToken) {
289
+ const anyStateMatch = htmlContent.match(/state=([a-zA-Z0-9_.-]+)/);
290
+ if (anyStateMatch) {
291
+ stateToken = anyStateMatch[1];
292
+ }
256
293
  }
257
- const csrf2Match = identifierHtml.match(/name="_csrf"\s+value="([^"]+)"/);
258
- const relayState2Match = identifierHtml.match(/name="relayState"\s+value="([^"]+)"/);
259
- const hmac2Match = identifierHtml.match(/name="hmac"\s+value="([^"]+)"/);
260
- const csrf2 = csrf2Match ? csrf2Match[1] : csrf;
261
- const relayState2 = relayState2Match ? relayState2Match[1] : relayState;
262
- const hmac2 = hmac2Match ? hmac2Match[1] : hmac;
263
- // Step 3: Submit password
264
- const authResponse = await context.helpers.httpRequest({
265
- method: 'POST',
266
- url: 'https://identity.vwgroup.io/signin-service/v1/signin/login/authenticate',
267
- headers: {
268
- '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',
269
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
270
- 'Content-Type': 'application/x-www-form-urlencoded',
271
- },
272
- body: new URLSearchParams({
273
- _csrf: csrf2,
274
- relayState: relayState2,
275
- hmac: hmac2,
276
- email: email,
277
- password: password,
278
- }).toString(),
279
- encoding: 'text',
280
- returnFullResponse: true,
281
- ignoreHttpStatusErrors: true,
282
- });
283
- // Get the redirect URL which contains the authorization code
294
+ // Try legacy CSRF-based flow first
295
+ const csrfMatch = htmlContent.match(/name="_csrf"\s+value="([^"]+)"/);
296
+ const relayStateMatch = htmlContent.match(/name="relayState"\s+value="([^"]+)"/);
297
+ const hmacMatch = htmlContent.match(/name="hmac"\s+value="([^"]+)"/);
284
298
  let redirectUrl = '';
285
299
  let authHtml = '';
286
- if (typeof authResponse === 'string') {
287
- authHtml = authResponse;
300
+ if (csrfMatch) {
301
+ // Legacy flow with CSRF token
302
+ const csrf = csrfMatch[1];
303
+ const relayState = relayStateMatch ? relayStateMatch[1] : '';
304
+ const hmac = hmacMatch ? hmacMatch[1] : '';
305
+ // Step 2: Submit email
306
+ const identifierResponse = await context.helpers.httpRequest({
307
+ method: 'POST',
308
+ url: 'https://identity.vwgroup.io/signin-service/v1/signin/login/identifier',
309
+ headers: {
310
+ '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',
311
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
312
+ 'Content-Type': 'application/x-www-form-urlencoded',
313
+ },
314
+ body: new URLSearchParams({
315
+ _csrf: csrf,
316
+ relayState: relayState,
317
+ hmac: hmac,
318
+ email: email,
319
+ }).toString(),
320
+ encoding: 'text',
321
+ returnFullResponse: true,
322
+ ignoreHttpStatusErrors: true,
323
+ });
324
+ let identifierHtml;
325
+ if (typeof identifierResponse === 'string') {
326
+ identifierHtml = identifierResponse;
327
+ }
328
+ else if (identifierResponse && typeof identifierResponse === 'object') {
329
+ const respObj = identifierResponse;
330
+ identifierHtml = respObj.body || JSON.stringify(respObj);
331
+ }
332
+ else {
333
+ identifierHtml = String(identifierResponse);
334
+ }
335
+ const csrf2Match = identifierHtml.match(/name="_csrf"\s+value="([^"]+)"/);
336
+ const relayState2Match = identifierHtml.match(/name="relayState"\s+value="([^"]+)"/);
337
+ const hmac2Match = identifierHtml.match(/name="hmac"\s+value="([^"]+)"/);
338
+ const csrf2 = csrf2Match ? csrf2Match[1] : csrf;
339
+ const relayState2 = relayState2Match ? relayState2Match[1] : relayState;
340
+ const hmac2 = hmac2Match ? hmac2Match[1] : hmac;
341
+ // Step 3: Submit password
342
+ const authResponse = await context.helpers.httpRequest({
343
+ method: 'POST',
344
+ url: 'https://identity.vwgroup.io/signin-service/v1/signin/login/authenticate',
345
+ headers: {
346
+ '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',
347
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
348
+ 'Content-Type': 'application/x-www-form-urlencoded',
349
+ },
350
+ body: new URLSearchParams({
351
+ _csrf: csrf2,
352
+ relayState: relayState2,
353
+ hmac: hmac2,
354
+ email: email,
355
+ password: password,
356
+ }).toString(),
357
+ encoding: 'text',
358
+ returnFullResponse: true,
359
+ ignoreHttpStatusErrors: true,
360
+ });
361
+ if (typeof authResponse === 'string') {
362
+ authHtml = authResponse;
363
+ }
364
+ else if (authResponse && typeof authResponse === 'object') {
365
+ const respObj = authResponse;
366
+ const respHeaders = respObj.headers;
367
+ if (respHeaders && respHeaders.location) {
368
+ redirectUrl = respHeaders.location;
369
+ }
370
+ authHtml = respObj.body || '';
371
+ }
288
372
  }
289
- else if (authResponse && typeof authResponse === 'object') {
290
- const respObj = authResponse;
291
- const respHeaders = respObj.headers;
292
- if (respHeaders && respHeaders.location) {
293
- redirectUrl = respHeaders.location;
373
+ else if (stateToken || htmlContent.includes('auth0.com') || htmlContent.includes('/u/login')) {
374
+ // New Auth0-based flow
375
+ // Extract state from the login page URL if not already found
376
+ if (!stateToken) {
377
+ // Try to get state from the response URL or form
378
+ const loginFormMatch = htmlContent.match(/action="([^"]*\/u\/login[^"]*)"/);
379
+ if (loginFormMatch) {
380
+ const formUrl = loginFormMatch[1];
381
+ const formStateMatch = formUrl.match(/state=([^&"]+)/);
382
+ if (formStateMatch) {
383
+ stateToken = decodeURIComponent(formStateMatch[1]);
384
+ }
385
+ }
386
+ }
387
+ if (!stateToken) {
388
+ // Last resort: extract from any state parameter in the HTML
389
+ const anyStateMatch = htmlContent.match(/state=([a-zA-Z0-9_-]+)/);
390
+ if (anyStateMatch) {
391
+ stateToken = anyStateMatch[1];
392
+ }
393
+ }
394
+ if (!stateToken) {
395
+ throw new Error(`Could not extract state token from Auth0 login page. Response preview: ${htmlContent.substring(0, 500)}`);
396
+ }
397
+ // Submit credentials to Auth0 /u/login endpoint
398
+ const loginResponse = await context.helpers.httpRequest({
399
+ method: 'POST',
400
+ url: `https://identity.vwgroup.io/u/login?state=${encodeURIComponent(stateToken)}`,
401
+ headers: {
402
+ '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',
403
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
404
+ 'Content-Type': 'application/x-www-form-urlencoded',
405
+ 'Origin': 'https://identity.vwgroup.io',
406
+ 'Referer': `https://identity.vwgroup.io/u/login?state=${encodeURIComponent(stateToken)}`,
407
+ },
408
+ body: new URLSearchParams({
409
+ state: stateToken,
410
+ username: email,
411
+ password: password,
412
+ action: 'default',
413
+ }).toString(),
414
+ encoding: 'text',
415
+ returnFullResponse: true,
416
+ ignoreHttpStatusErrors: true,
417
+ });
418
+ if (typeof loginResponse === 'string') {
419
+ authHtml = loginResponse;
420
+ }
421
+ else if (loginResponse && typeof loginResponse === 'object') {
422
+ const respObj = loginResponse;
423
+ const respHeaders = respObj.headers;
424
+ if (respHeaders && respHeaders.location) {
425
+ redirectUrl = respHeaders.location;
426
+ }
427
+ authHtml = respObj.body || '';
294
428
  }
295
- authHtml = respObj.body || '';
429
+ }
430
+ else {
431
+ throw new Error(`Unknown login page format. Response preview: ${htmlContent.substring(0, 500)}`);
296
432
  }
297
433
  if (!redirectUrl && authHtml) {
298
434
  // Check if we got a redirect in the response HTML
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-jygse-vw-weconnect",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
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",