@inite/n8n-nodes-instagram-private-api
Version:
n8n node for Instagram Private API with 2FA support
347 lines (346 loc) • 16.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.InstagramAuth = void 0;
const instagram_private_api_1 = require("instagram-private-api");
class InstagramAuth {
constructor() {
this.description = {
displayName: 'Instagram Auth',
name: 'instagramAuth',
icon: 'file:instagram.svg',
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"]}}',
description: 'Handle Instagram authentication',
defaults: {
name: 'Instagram Auth',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'instagramPrivateApi',
required: true,
},
],
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Login',
value: 'login',
description: 'Login to Instagram',
action: 'Login to Instagram',
},
{
name: 'Submit 2FA Code',
value: 'submit2FA',
description: 'Submit two-factor authentication code',
action: 'Submit two factor authentication code',
},
{
name: 'Load Session',
value: 'loadSession',
description: 'Load existing session',
action: 'Load existing session',
},
],
default: 'login',
},
{
displayName: 'Session Data',
name: 'sessionData',
type: 'string',
default: '',
description: 'Previously saved session data',
displayOptions: {
show: {
operation: ['loadSession'],
},
},
},
{
displayName: '2FA Code',
name: 'twoFactorCode',
type: 'string',
default: '',
required: true,
description: 'Two-factor authentication code from SMS or TOTP (Google Authenticator)',
displayOptions: {
show: {
operation: ['submit2FA'],
},
},
},
{
displayName: 'Verification Method',
name: 'verificationMethod',
type: 'options',
options: [
{
name: 'SMS',
value: '1',
description: 'Use SMS verification',
},
{
name: 'TOTP (Google Authenticator)',
value: '0',
description: 'Use TOTP verification (Google Authenticator)',
},
],
default: '0',
description: 'Method to use for verification',
displayOptions: {
show: {
operation: ['submit2FA'],
},
},
},
{
displayName: 'Trust This Device',
name: 'trustThisDevice',
type: 'boolean',
default: true,
description: 'Whether to trust this device for future logins',
displayOptions: {
show: {
operation: ['submit2FA'],
},
},
},
{
displayName: 'Two Factor Identifier',
name: 'twoFactorIdentifier',
type: 'string',
default: '',
description: 'Two factor identifier from previous login step',
displayOptions: {
show: {
operation: ['submit2FA'],
},
},
},
{
displayName: 'Username',
name: 'username',
type: 'string',
default: '',
description: 'Username from previous login step',
displayOptions: {
show: {
operation: ['submit2FA'],
},
},
},
],
};
}
async execute() {
var _a, _b, _c, _d;
const items = this.getInputData();
const returnData = [];
const operation = this.getNodeParameter('operation', 0);
const credentials = await this.getCredentials('instagramPrivateApi');
const ig = new instagram_private_api_1.IgApiClient();
ig.state.generateDevice(credentials.username);
// Create a promise to capture the session data
let sessionPromise = null;
let sessionResolver = null;
// Set up state management with a promise to ensure we capture the session
ig.request.end$.subscribe(async () => {
try {
const serialized = await ig.state.serialize();
delete serialized.constants; // Use library's version info
// Store the session data in the return data
if (returnData.length > 0) {
returnData[0] = { ...returnData[0], sessionData: serialized };
}
else {
returnData.push({ sessionData: serialized });
}
// Resolve the promise if it exists
if (sessionResolver) {
sessionResolver(serialized);
sessionResolver = null;
}
}
catch (error) {
console.error('Failed to serialize session:', error);
}
});
try {
switch (operation) {
case 'login':
try {
// Try to load existing session from input
const existingSession = ((_b = (_a = items[0]) === null || _a === void 0 ? void 0 : _a.json) === null || _b === void 0 ? void 0 : _b.sessionData) || '';
if (existingSession) {
await ig.state.deserialize(existingSession);
returnData.push({
success: true,
authenticated: true,
userId: ig.state.cookieUserId,
sessionData: existingSession,
requiresTwoFactor: false,
message: 'Loaded existing session',
});
break;
}
// Normal login flow
await ig.simulate.preLoginFlow();
const loggedInUser = await ig.account.login(credentials.username, credentials.password);
// Post login flow in next tick
process.nextTick(async () => await ig.simulate.postLoginFlow());
// Wait for session data to be captured
sessionPromise = new Promise((resolve) => {
sessionResolver = resolve;
});
// Wait a short time for the session to be captured
const loginSessionData = await Promise.race([
sessionPromise,
new Promise((_, reject) => setTimeout(() => reject(new Error('Session capture timeout')), 5000))
]).catch(() => null);
// Store session data in credentials
if (loginSessionData) {
console.log('Session data captured. Please update your Instagram credentials with this session data.');
}
returnData.push({
success: true,
authenticated: true,
userId: ig.state.cookieUserId,
sessionData: loginSessionData,
requiresTwoFactor: false,
user: loggedInUser,
});
}
catch (error) {
// Check if this is a 2FA required error
const err = error;
if ((_d = (_c = err.response) === null || _c === void 0 ? void 0 : _c.body) === null || _d === void 0 ? void 0 : _d.two_factor_info) {
// Extract 2FA info
const { username, totp_two_factor_on, two_factor_identifier } = err.response.body.two_factor_info;
// Determine the default verification method based on what's enabled
const defaultVerificationMethod = totp_two_factor_on ? '0' : '1';
returnData.push({
success: false,
authenticated: false,
requiresTwoFactor: true,
username,
twoFactorIdentifier: two_factor_identifier,
totpEnabled: totp_two_factor_on,
defaultVerificationMethod,
message: `Two-factor authentication required. Please use the 'Submit 2FA Code' operation with the code received via ${defaultVerificationMethod === '1' ? 'SMS' : 'TOTP (Google Authenticator)'}`,
});
}
else {
throw error;
}
}
break;
case 'submit2FA':
const twoFactorCode = this.getNodeParameter('twoFactorCode', 0);
const verificationMethod = this.getNodeParameter('verificationMethod', 0);
const trustThisDevice = this.getNodeParameter('trustThisDevice', 0);
// Get data from previous step or parameters
let twoFactorIdentifier = this.getNodeParameter('twoFactorIdentifier', 0);
let username = this.getNodeParameter('username', 0);
// If not provided in parameters, try to get from input
if (!twoFactorIdentifier || !username) {
// Check if we have input data
if (items.length > 0 && items[0].json) {
twoFactorIdentifier = items[0].json.twoFactorIdentifier || '';
username = items[0].json.username || '';
}
// If still not available, use credentials username
if (!username) {
username = credentials.username;
}
}
// Validate required fields
if (!twoFactorCode) {
throw new Error('Two-factor code is required');
}
if (!twoFactorIdentifier) {
throw new Error('Two-factor identifier is required. Please connect this node to the output of a Login node that returned a 2FA requirement.');
}
if (!username) {
throw new Error('Username is required. Please connect this node to the output of a Login node that returned a 2FA requirement.');
}
// Verify 2FA code
const loggedInUser = await ig.account.twoFactorLogin({
username,
verificationCode: twoFactorCode,
twoFactorIdentifier,
verificationMethod,
trustThisDevice: trustThisDevice ? '1' : '0',
});
// Post login flow in next tick
process.nextTick(async () => await ig.simulate.postLoginFlow());
// Wait for session data to be captured
sessionPromise = new Promise((resolve) => {
sessionResolver = resolve;
});
// Wait a short time for the session to be captured
const twoFactorSessionData = await Promise.race([
sessionPromise,
new Promise((_, reject) => setTimeout(() => reject(new Error('Session capture timeout')), 5000))
]).catch(() => null);
// Store session data in credentials
if (twoFactorSessionData) {
console.log('Session data captured. Please update your Instagram credentials with this session data.');
}
returnData.push({
success: true,
authenticated: true,
userId: ig.state.cookieUserId,
sessionData: twoFactorSessionData,
requiresTwoFactor: false,
user: loggedInUser,
});
break;
case 'loadSession':
const savedSessionData = this.getNodeParameter('sessionData', 0);
if (!savedSessionData) {
throw new Error('Session data is required');
}
await ig.state.deserialize(savedSessionData);
// Store session data in credentials
console.log('Session data captured. Please update your Instagram credentials with this session data.');
returnData.push({
success: true,
authenticated: true,
userId: ig.state.cookieUserId,
sessionData: savedSessionData,
message: 'Session loaded successfully',
});
break;
default:
throw new Error(`The operation "${operation}" is not supported!`);
}
}
catch (error) {
if (this.continueOnFail()) {
returnData.push({
success: false,
error: error instanceof Error ? error.message : 'An unknown error occurred',
});
}
else {
throw error;
}
}
// Always return data, even if empty
if (returnData.length === 0) {
returnData.push({
success: false,
message: 'No data returned from operation',
});
}
return [this.helpers.returnJsonArray(returnData)];
}
}
exports.InstagramAuth = InstagramAuth;