Skip to content

Commit

Permalink
add 2FA required exception
Browse files Browse the repository at this point in the history
  • Loading branch information
hfxbse committed Mar 25, 2024
1 parent f52b83b commit 7917c7f
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 20 deletions.
18 changes: 12 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import * as prompt from '@inquirer/prompts';
import {encryptPassword, fetchVerification, login} from "./instagram";
import {ExitPromptError} from "@inquirer/prompts";


async function authenticate() {
const verification = await fetchVerification()

while (true) {
try {
const user = await prompt.input({message: "Instagram username, phone number, or email: "})
const password = await prompt.password({message: "Password: "})
const user = await prompt.input({message: "Instagram username, phone number, or email: "})
const password = await prompt.password({message: "Password: "})

const encryptedPassword = await encryptPassword({password, key: verification.key})
const encryptedPassword = await encryptPassword({password, key: verification.key})

try {
return await login({user, password: encryptedPassword, verification})
} catch (e) {
console.error((e as Error).message)
}
}
}

const sessionId = await authenticate()
console.dir({sessionId})
try {
console.dir({sessionId: await authenticate()})
} catch (e) {
if (!(e instanceof ExitPromptError)) {
console.error(e)
}
}
17 changes: 16 additions & 1 deletion src/instagram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import sealBox from "tweetnacl-sealedbox-js";
const crypto = globalThis.crypto
const encoder = new TextEncoder()

export class TwoFactorRequired extends Error {
constructor() {
super("Two factor authentication is enabled for this account.");
}
}

export interface InstagramEncryptionKey {
public: string,
id: number,
Expand Down Expand Up @@ -117,7 +123,16 @@ export async function login({user, password, verification}: {

if (!response.ok) {
if (response.headers.get("Content-Type").startsWith("application/json;")) {
throw new Error((await response.json()).message ?? "Login attempted failed.")
const data = await response.json() as {
message?: string,
two_factor_required?: boolean
}

if (data.two_factor_required) {
throw new TwoFactorRequired()
}

throw new Error(data.message ?? "Login attempted failed.")
} else {
throw new Error(await response.text())
}
Expand Down
36 changes: 23 additions & 13 deletions test/instagram.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
encryptPassword,
fetchVerification,
InstagramEncryptionKey,
login,
login, TwoFactorRequired,
VerificationData
} from "../src/instagram";

Expand Down Expand Up @@ -198,7 +198,7 @@ describe("Login request handler", () => {
expect(result).toStrictEqual(sessionId)
})

describe("Throws exception on failed login", () => {
describe("Throws on failed login", () => {
test.each([undefined, "Received error description"])("Message: %s", async (message) => {
const headers = new Headers()
headers.set("Content-Type", "application/json; charset=utf-8")
Expand Down Expand Up @@ -229,18 +229,14 @@ describe("Login request handler", () => {
} as Response))

try {
await login({
user: "user",
password: encryptedPassword,
verification
})
await login({user: "user", password: encryptedPassword, verification})
} catch (e) {
expect.assertions(1)
expect(e.message).toStrictEqual(expect.any(String))
}
})

test("Throws exception on failed request", async () => {
test("Throws on failed request", async () => {
const message = "Error message"

const headers = new Headers()
Expand All @@ -253,14 +249,28 @@ describe("Login request handler", () => {
} as Response))

try {
await login({
user: "user",
password: encryptedPassword,
verification
})
await login({user: "user", password: encryptedPassword, verification})
} catch (e) {
expect.assertions(1)
expect(e.message).toStrictEqual(message)
}
})

test("Throws if 2FA is required", async () => {
const headers = new Headers()
headers.set("Content-Type", "application/json; charset=utf-8")

jest.spyOn(global, "fetch").mockImplementation(() => Promise.resolve({
ok: false,
json: () => Promise.resolve({two_factor_required: true}),
headers
} as Response))

try {
await login({user: "user", password: encryptedPassword, verification})
} catch (e) {
expect.assertions(1)
expect(e).toBeInstanceOf(TwoFactorRequired)
}
})
})

0 comments on commit 7917c7f

Please sign in to comment.