UNPKG

n8n-nodes-refresh-token-auth

Version:
331 lines 13.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DebugRefreshToken = void 0; const CREDENTIALS_NAME = 'refreshTokenAuth'; const SENSITIVE_HEADERS = [ 'authorization', 'x-api-key', 'api-key', 'client-secret', 'client_secret', ]; const SENSITIVE_BODY_KEYS = ['access_token', 'refresh_token', 'client_secret', 'id_token']; const isObject = (v) => !!v && typeof v === 'object' && !Array.isArray(v); function safeParseBody(body) { if (Buffer.isBuffer(body)) body = body.toString('utf8'); if (typeof body === 'string') { try { return JSON.parse(body); } catch { return body; } } return body !== null && body !== void 0 ? body : null; } function redactHeaders(h, extraSensitiveKeys = []) { const out = {}; const allSensitiveKeys = [ ...SENSITIVE_HEADERS, ...extraSensitiveKeys.filter((k) => !k.includes('.')), ]; for (const [k, v] of Object.entries(h || {})) { if (allSensitiveKeys.some((sk) => sk.toLowerCase() === k.toLowerCase())) { out[k] = '***REDACTED***'; } else if (typeof v === 'string' && v.trim().startsWith('eyJ')) { out[k] = '***REDACTED***'; } else { out[k] = v; } } return out; } function redactAtPath(obj, path) { if (!obj || typeof obj !== 'object') return; const parts = path.split('.'); let current = obj; for (let i = 0; i < parts.length - 1; i++) { const part = parts[i]; const actualKey = Object.keys(current).find((k) => k.toLowerCase() === part.toLowerCase()); if (!actualKey || !current[actualKey] || typeof current[actualKey] !== 'object') return; current = current[actualKey]; } const lastPart = parts[parts.length - 1]; const actualKey = Object.keys(current).find((k) => k.toLowerCase() === lastPart.toLowerCase()); if (actualKey && current[actualKey] !== undefined) { current[actualKey] = '***REDACTED***'; } } function redactBodyDeep(v, extraSensitiveKeys = []) { if (!v || typeof v !== 'object') return v; const copy = JSON.parse(JSON.stringify(v)); const simpleSensitiveKeys = [...SENSITIVE_BODY_KEYS]; const pathSensitiveKeys = []; for (const key of extraSensitiveKeys) { if (key.includes('.')) { pathSensitiveKeys.push(key); } else { simpleSensitiveKeys.push(key); } } const walk = (o) => { if (!o || typeof o !== 'object') return; for (const k of Object.keys(o)) { const value = o[k]; if (simpleSensitiveKeys.some((sk) => sk.toLowerCase() === k.toLowerCase())) { o[k] = '***REDACTED***'; } else if (typeof value === 'string' && value.trim().startsWith('eyJ')) { o[k] = '***REDACTED***'; } else { walk(value); } } }; walk(copy); for (const path of pathSensitiveKeys) { redactAtPath(copy, path); } return copy; } function looksLikePreAuth(url, body, tokenUrlFromCreds) { const u = (url || '').toLowerCase(); if (tokenUrlFromCreds && u === tokenUrlFromCreds.toLowerCase()) return true; const asStr = typeof body === 'string' ? body : Buffer.isBuffer(body) ? body.toString('utf8') : ''; if (asStr.includes('grant_type=refresh_token')) return true; if (isObject(body) && String(body.grant_type) === 'refresh_token') return true; if (u.includes('/oauth') && u.includes('/token')) return true; return false; } function truncate(v, maxLen) { if (maxLen === 0) return v; const s = typeof v === 'string' ? v : JSON.stringify(v, null, 2); if (s.length > maxLen) { return s.slice(0, maxLen) + `… [truncated ${s.length - maxLen} chars]`; } return v; } function formatEvent(e, redact, truncLen, extraSensitiveKeys = []) { return { ...e, headers: redact ? redactHeaders(e.headers, extraSensitiveKeys) : e.headers, body: truncate(redact ? redactBodyDeep(e.body, extraSensitiveKeys) : e.body, truncLen), }; } class DebugRefreshToken { constructor() { this.description = { displayName: 'RefreshToken (Debug)', name: 'refreshToken', subtitle: '={{$parameter["method"] + ": " + $parameter["url"]}}', icon: 'file:refresh-token-auth.svg', group: ['transform'], version: 1, description: 'Runs an authenticated request with RefreshToken credentials and captures BOTH the token refresh and the main call (headers + bodies).', defaults: { name: 'RefreshToken (Debug)' }, inputs: ['main'], outputs: ['main'], credentials: [{ name: CREDENTIALS_NAME, required: true }], properties: [ { displayName: 'URL', name: 'url', type: 'string', default: '', required: true, placeholder: 'https://api.example.com/v1/resource', }, { displayName: 'Method', name: 'method', type: 'options', default: 'GET', options: ['DELETE', 'GET', 'PATCH', 'POST', 'PUT'].map((m) => ({ name: m, value: m })), }, { displayName: 'Query (JSON)', name: 'qsJson', type: 'json', default: '{}', description: 'Merged into query string of the main request', }, { displayName: 'Headers (JSON)', name: 'headersJson', type: 'json', default: '{}', description: 'Extra headers for the main request', }, { displayName: 'Body (JSON)', name: 'bodyJson', type: 'json', default: '{}', description: 'Used for POST/PUT/PATCH/DELETE when sending JSON', displayOptions: { show: { method: ['POST', 'PUT', 'PATCH', 'DELETE'] } }, }, { displayName: 'Send Pre-Authentication', name: 'sendPreauth', type: 'boolean', default: true, description: 'Whether to send pre-auth requests. If off, pre-auth requests are captured but NOT sent (mocked response).', }, { displayName: 'Send Main Request', name: 'sendMain', type: 'boolean', default: true, description: 'Whether to send main request. If off, main request is captured but NOT sent (mocked response).', }, { displayName: 'Redact Secrets in Output', name: 'redact', type: 'boolean', default: false, description: 'Whether to mask Authorization, tokens, and client_secret', }, { displayName: 'Truncate Bodies To (Chars)', name: 'truncate', type: 'number', typeOptions: { minValue: 0 }, default: 10000, description: '0 = no truncation', }, ], }; } async execute() { var _a, _b, _c; this.logger.debug('🔥🔥🔥 RefreshToken.execute CALLED! 🔥🔥🔥'); const items = this.getInputData(); const out = []; let tokenUrlFromCreds; let accessTokenFieldName; let refreshTokenFieldName; try { const creds = (await this.getCredentials(CREDENTIALS_NAME)); tokenUrlFromCreds = ((_c = (_b = (_a = creds === null || creds === void 0 ? void 0 : creds.tokenUrl) !== null && _a !== void 0 ? _a : creds === null || creds === void 0 ? void 0 : creds.accessTokenUrl) !== null && _b !== void 0 ? _b : creds === null || creds === void 0 ? void 0 : creds.refreshUrl) !== null && _c !== void 0 ? _c : creds === null || creds === void 0 ? void 0 : creds.authUrl); accessTokenFieldName = creds === null || creds === void 0 ? void 0 : creds.accessTokenFieldName; refreshTokenFieldName = creds === null || creds === void 0 ? void 0 : creds.refreshTokenFieldName; } catch { } const extraSensitiveKeys = []; if (accessTokenFieldName) extraSensitiveKeys.push(accessTokenFieldName); if (refreshTokenFieldName) extraSensitiveKeys.push(refreshTokenFieldName); for (let i = 0; i < items.length; i++) { const url = this.getNodeParameter('url', i); const method = this.getNodeParameter('method', i); const qs = this.getNodeParameter('qsJson', i, {}); const headersExtra = this.getNodeParameter('headersJson', i, {}); const sendPreauth = this.getNodeParameter('sendPreauth', i, true); const sendMain = this.getNodeParameter('sendMain', i, true); const redact = this.getNodeParameter('redact', i, false); const truncLen = this.getNodeParameter('truncate', i, 12000); const body = method === 'GET' ? undefined : this.getNodeParameter('bodyJson', i, {}); const events = []; const originalHttpRequest = this.helpers.httpRequest.bind(this.helpers); const wrappedHttpRequest = async (opts) => { var _a, _b, _c, _d, _e, _f; const reqUrl = String(opts.url || opts.baseURL || ''); const reqBody = (_b = (_a = opts.form) !== null && _a !== void 0 ? _a : opts.body) !== null && _b !== void 0 ? _b : opts.json; const isPre = looksLikePreAuth(reqUrl, reqBody, tokenUrlFromCreds); const shouldSend = isPre ? sendPreauth : sendMain; events.push({ stage: 'request', ts: new Date().toISOString(), source: 'helper', url: reqUrl, method: String(opts.method || 'GET').toUpperCase(), headers: { ...(opts.headers || {}) }, body: safeParseBody(reqBody), isPreAuth: isPre, }); if (!shouldSend) { const mock = { body: { mocked: true, sent: false, reason: 'SKIPPED_BY_NODE' }, headers: {}, statusCode: 0, statusMessage: 'MOCKED-NOT-SENT', }; events.push({ stage: 'response', ts: new Date().toISOString(), source: 'helper', url: reqUrl, statusCode: 0, statusMessage: 'MOCKED-NOT-SENT', headers: {}, body: mock.body, isPreAuth: isPre, }); return opts.returnFullResponse ? mock : mock.body; } const result = await originalHttpRequest(opts); const respBody = (_c = result.body) !== null && _c !== void 0 ? _c : result; events.push({ stage: 'response', ts: new Date().toISOString(), source: 'helper', url: reqUrl, statusCode: (_d = result.statusCode) !== null && _d !== void 0 ? _d : 200, statusMessage: (_e = result.statusMessage) !== null && _e !== void 0 ? _e : 'OK', headers: (_f = result.headers) !== null && _f !== void 0 ? _f : {}, body: safeParseBody(respBody), isPreAuth: isPre, }); return result; }; this.helpers.httpRequest = wrappedHttpRequest; const reqOpts = { method: method, url, qs, headers: { ...headersExtra }, json: true, body, returnFullResponse: true, }; try { await this.helpers.httpRequestWithAuthentication.call(this, CREDENTIALS_NAME, reqOpts); } catch (err) { out.push({ json: { error: String((err === null || err === void 0 ? void 0 : err.message) || err), events: events.map((e) => formatEvent(e, redact, truncLen, extraSensitiveKeys)), note: 'Main request errored (often expected if sending disabled).', }, }); continue; } finally { this.helpers.httpRequest = originalHttpRequest; } out.push({ json: { events: events.map((e) => formatEvent(e, redact, truncLen, extraSensitiveKeys)) }, }); } return [out]; } } exports.DebugRefreshToken = DebugRefreshToken; //# sourceMappingURL=DebugRefreshToken.node.js.map