From 32b3ad753c2b9167b0921fb5e45482b1d0d26ae0 Mon Sep 17 00:00:00 2001 From: MillenniumEarl Date: Fri, 5 Mar 2021 10:56:27 +0100 Subject: [PATCH] Implements 2fa authentication --- src/example.ts | 22 +++++++++++++++++++++- src/index.ts | 38 ++++++++++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/example.ts b/src/example.ts index 6977e96..42565ed 100644 --- a/src/example.ts +++ b/src/example.ts @@ -15,6 +15,7 @@ F95_PASSWORD = YOUR_PASSWORD "use strict"; // Public modules from npm +import inquirer from "inquirer"; import dotenv from "dotenv"; // Modules from file @@ -33,6 +34,24 @@ dotenv.config(); main(); +/** + * Ask the user to enter the OTP code + * necessary to authenticate on the server. + */ +async function insert2faCode(): Promise { + const questions = [ + { + type: "input", + name: "code", + message: "Insert 2FA code:" + } + ]; + + // Prompt the user to insert the code + const answers = await inquirer.prompt(questions); + return answers.code as number; +} + async function main() { // Local variables const gameList = ["City of broken dreamers", "Seeds of chaos", "MIST"]; @@ -41,7 +60,8 @@ async function main() { console.log("Authenticating..."); const result = await login( process.env.F95_USERNAME, - process.env.F95_PASSWORD + process.env.F95_PASSWORD, + insert2faCode ); console.log(`Authentication result: ${result.message}\n`); diff --git a/src/index.ts b/src/index.ts index 745aa11..8ce9c44 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,12 @@ // Modules from file import shared from "./scripts/shared.js"; import search from "./scripts/search.js"; -import { authenticate, urlExists, isF95URL } from "./scripts/network-helper.js"; +import { + authenticate, + urlExists, + isF95URL, + send2faCode +} from "./scripts/network-helper.js"; import fetchLatestHandiworkURLs from "./scripts/fetch-data/fetch-latest.js"; import fetchPlatformData from "./scripts/fetch-data/fetch-platform-data.js"; import getHandiworkInformation from "./scripts/scrape-data/handiwork-parse.js"; @@ -30,6 +35,7 @@ const USER_NOT_LOGGED = "User not authenticated, unable to continue"; //#endregion //#region Re-export classes + export { default as Animation } from "./scripts/classes/handiwork/animation.js"; export { default as Asset } from "./scripts/classes/handiwork/asset.js"; export { default as Comic } from "./scripts/classes/handiwork/comic.js"; @@ -44,9 +50,11 @@ export { default as UserProfile } from "./scripts/classes/mapping/user-profile.j export { default as HandiworkSearchQuery } from "./scripts/classes/query/handiwork-search-query.js"; export { default as LatestSearchQuery } from "./scripts/classes/query/latest-search-query.js"; export { default as ThreadSearchQuery } from "./scripts/classes/query/thread-search-query.js"; + //#endregion Re-export classes //#region Export properties + /** * Set the logger level for module debugging. */ @@ -60,6 +68,7 @@ shared.logger.level = "warn"; // By default log only the warn messages export function isLogged(): boolean { return shared.isLogged; } + //#endregion Export properties //#region Export methods @@ -68,10 +77,15 @@ export function isLogged(): boolean { * Log in to the F95Zone platform. * * This **must** be the first operation performed before accessing any other script functions. + * + * @param cb2fa + * Callback used if two-factor authentication is required for the profile. + * It must return he OTP code to use for the login. */ export async function login( username: string, - password: string + password: string, + cb2fa?: () => Promise ): Promise { // Try to load a previous session await shared.session.load(); @@ -93,17 +107,25 @@ export async function login( await creds.fetchToken(); shared.logger.trace(`Authentication for ${username}`); - const result = await authenticate(creds); + let result = await authenticate(creds); shared.setIsLogged(result.success); - if (result.success) { - // Load platform data - await fetchPlatformData(); + // 2FA Authentication is required, fetch OTP + if (result.message === "Two-factor authentication is needed to continue") { + const code = await cb2fa(); + const response2fa = await send2faCode(code, creds.token); + if (response2fa.isSuccess()) result = response2fa.value; + else throw response2fa.value; + } + if (result.success) { // Recreate the session, overwriting the old one shared.session.create(username, password, creds.token); await shared.session.save(); + // Load platform data + await fetchPlatformData(); + shared.logger.info("User logged in through the platform"); } else shared.logger.warn(`Error during authentication: ${result.message}`); @@ -147,7 +169,7 @@ export async function checkIfHandiworkHasUpdate( */ export async function searchHandiwork( query: HandiworkSearchQuery, - limit = 10 + limit: number = 10 ): Promise { // Check if the user is logged if (!shared.isLogged) throw new UserNotLogged(USER_NOT_LOGGED); @@ -203,7 +225,7 @@ export async function getUserData(): Promise { */ export async function getLatestUpdates( query: LatestSearchQuery, - limit = 10 + limit: number = 10 ): Promise { // Check limit value if (limit <= 0) throw new Error("limit must be greater than 0");