n8n-nodes-proxmox
Version:
n8n community node for Proxmox Virtual Environment (VE) API integration with VM, container, storage, and cluster management capabilities
172 lines (171 loc) • 7.06 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.apiRequest = apiRequest;
exports.apiRequestAllItems = apiRequestAllItems;
const n8n_workflow_1 = require("n8n-workflow");
const ticketCache = {};
/**
* Check if a ticket is expired
*/
function isTicketExpired(expiresAt) {
return Date.now() > expiresAt - 60000; // Consider expired 60 seconds before actual expiration
}
/**
* Get authentication ticket for username/password auth
*/
async function getAuthTicket(credentials) {
var _a, _b;
const cacheKey = `${credentials.host}_${credentials.port}_${credentials.username}`;
// Check cache first
if (ticketCache[cacheKey] && !isTicketExpired(ticketCache[cacheKey].expiresAt)) {
return {
ticket: ticketCache[cacheKey].ticket,
csrfToken: ticketCache[cacheKey].csrfToken,
};
}
// Get new ticket
const protocol = credentials.skipSslVerify ? 'http' : 'https';
const baseUrl = `${protocol}://${credentials.host}:${credentials.port}`;
try {
const response = await this.helpers.request({
method: 'POST',
uri: `${baseUrl}/api2/json/access/ticket`,
body: {
username: credentials.username,
password: credentials.password,
},
json: true,
rejectUnauthorized: !credentials.skipSslVerify,
timeout: credentials.timeout || 30000,
});
if (!((_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.ticket) || !((_b = response === null || response === void 0 ? void 0 : response.data) === null || _b === void 0 ? void 0 : _b.CSRFPreventionToken)) {
throw new Error('Invalid authentication response from ProxMox');
}
// Cache ticket (ProxMox tickets typically expire after 2 hours)
const expiresAt = Date.now() + 2 * 60 * 60 * 1000; // 2 hours
ticketCache[cacheKey] = {
ticket: response.data.ticket,
csrfToken: response.data.CSRFPreventionToken,
expiresAt,
};
return {
ticket: response.data.ticket,
csrfToken: response.data.CSRFPreventionToken,
};
}
catch (error) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `ProxMox authentication failed: ${error.message}`);
}
}
/**
* Make an API request to ProxMox VE
*/
async function apiRequest(method, endpoint, body = {}, qs = {}) {
var _a, _b, _c;
const credentials = await this.getCredentials('proxMoxApi');
if (!credentials) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No credentials provided');
}
const protocol = credentials.skipSslVerify ? 'http' : 'https';
const baseUrl = `${protocol}://${credentials.host}:${credentials.port}`;
const fullUrl = `${baseUrl}/api2/json${endpoint}`;
let headers = {
'Content-Type': 'application/json',
};
// Handle authentication
if (credentials.authMethod === 'apiToken') {
// API Token authentication - ProxMox expects 'PVEAPIToken=TOKEN' format
const tokenValue = credentials.tokenId;
// Check if token already has PVEAPIToken prefix
if (tokenValue.startsWith('PVEAPIToken=')) {
headers.Authorization = tokenValue;
}
else {
headers.Authorization = `PVEAPIToken=${tokenValue}`;
}
}
else {
// Username/Password with ticket authentication
const auth = await getAuthTicket.call(this, credentials);
headers.Cookie = `PVEAuthCookie=${auth.ticket}`;
// Add CSRF token for write operations
if (method !== 'GET') {
headers.CSRFPreventionToken = auth.csrfToken;
}
}
const requestOptions = {
method,
url: fullUrl,
headers,
body: Object.keys(body).length > 0 ? body : undefined,
qs: Object.keys(qs).length > 0 ? qs : undefined,
json: true,
rejectUnauthorized: !credentials.skipSslVerify,
timeout: credentials.timeout || 30000,
resolveWithFullResponse: true,
};
try {
const response = await this.helpers.httpRequest(requestOptions);
// ProxMox API returns data in response.data
if (((_a = response.body) === null || _a === void 0 ? void 0 : _a.data) !== undefined) {
return response.body.data;
}
return response.body || response;
}
catch (error) {
if (error.response) {
let message = 'ProxMox API error';
const errorBody = error.error || ((_b = error.response) === null || _b === void 0 ? void 0 : _b.body);
if (errorBody) {
if (typeof errorBody === 'object') {
if (errorBody.errors) {
// ProxMox validation errors
const errors = Array.isArray(errorBody.errors)
? errorBody.errors.join(', ')
: JSON.stringify(errorBody.errors);
message = `Validation error: ${errors}`;
}
else if (errorBody.message) {
message = errorBody.message;
}
else if (errorBody.reason) {
message = errorBody.reason;
}
}
else if (typeof errorBody === 'string') {
message = errorBody;
}
}
// Handle specific HTTP status codes
if (error.statusCode === 401) {
// Clear cached ticket if authentication fails
const cacheKey = `${credentials.host}_${credentials.port}_${credentials.username}`;
delete ticketCache[cacheKey];
message = 'Authentication failed. Please check your credentials.';
}
else if (error.statusCode === 403) {
// Permission denied - include the status text for more detail
const statusText = ((_c = error.response) === null || _c === void 0 ? void 0 : _c.statusText) || 'Permission denied';
message = `Permission denied: ${statusText}`;
}
throw new n8n_workflow_1.NodeApiError(this.getNode(), error, {
message: `${message} (Status: ${error.statusCode})`,
});
}
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `ProxMox API request failed: ${error.message}`);
}
}
/**
* Make paginated requests to ProxMox API
*/
async function apiRequestAllItems(method, endpoint, body = {}, qs = {}) {
// ProxMox API doesn't support standard pagination, just make a single request
const response = await apiRequest.call(this, method, endpoint, body, qs);
if (Array.isArray(response)) {
return response;
}
else {
// Single item response
return [response];
}
}