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 = '
|
|
146
|
-
const VW_SCOPE = 'openid profile
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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