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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
255
|
-
|
|
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
|
-
|
|
258
|
-
const
|
|
259
|
-
const
|
|
260
|
-
const
|
|
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 (
|
|
287
|
-
|
|
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 (
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (
|
|
293
|
-
|
|
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
|
-
|
|
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