Implements 2fa authentication

pull/81/head
MillenniumEarl 2021-03-05 10:56:27 +01:00
parent e8f47fcc21
commit 32b3ad753c
2 changed files with 51 additions and 9 deletions

View File

@ -15,6 +15,7 @@ F95_PASSWORD = YOUR_PASSWORD
"use strict"; "use strict";
// Public modules from npm // Public modules from npm
import inquirer from "inquirer";
import dotenv from "dotenv"; import dotenv from "dotenv";
// Modules from file // Modules from file
@ -33,6 +34,24 @@ dotenv.config();
main(); main();
/**
* Ask the user to enter the OTP code
* necessary to authenticate on the server.
*/
async function insert2faCode(): Promise<number> {
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() { async function main() {
// Local variables // Local variables
const gameList = ["City of broken dreamers", "Seeds of chaos", "MIST"]; const gameList = ["City of broken dreamers", "Seeds of chaos", "MIST"];
@ -41,7 +60,8 @@ async function main() {
console.log("Authenticating..."); console.log("Authenticating...");
const result = await login( const result = await login(
process.env.F95_USERNAME, process.env.F95_USERNAME,
process.env.F95_PASSWORD process.env.F95_PASSWORD,
insert2faCode
); );
console.log(`Authentication result: ${result.message}\n`); console.log(`Authentication result: ${result.message}\n`);

View File

@ -8,7 +8,12 @@
// Modules from file // Modules from file
import shared from "./scripts/shared.js"; import shared from "./scripts/shared.js";
import search from "./scripts/search.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 fetchLatestHandiworkURLs from "./scripts/fetch-data/fetch-latest.js";
import fetchPlatformData from "./scripts/fetch-data/fetch-platform-data.js"; import fetchPlatformData from "./scripts/fetch-data/fetch-platform-data.js";
import getHandiworkInformation from "./scripts/scrape-data/handiwork-parse.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 //#endregion
//#region Re-export classes //#region Re-export classes
export { default as Animation } from "./scripts/classes/handiwork/animation.js"; export { default as Animation } from "./scripts/classes/handiwork/animation.js";
export { default as Asset } from "./scripts/classes/handiwork/asset.js"; export { default as Asset } from "./scripts/classes/handiwork/asset.js";
export { default as Comic } from "./scripts/classes/handiwork/comic.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 HandiworkSearchQuery } from "./scripts/classes/query/handiwork-search-query.js";
export { default as LatestSearchQuery } from "./scripts/classes/query/latest-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"; export { default as ThreadSearchQuery } from "./scripts/classes/query/thread-search-query.js";
//#endregion Re-export classes //#endregion Re-export classes
//#region Export properties //#region Export properties
/** /**
* Set the logger level for module debugging. * 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 { export function isLogged(): boolean {
return shared.isLogged; return shared.isLogged;
} }
//#endregion Export properties //#endregion Export properties
//#region Export methods //#region Export methods
@ -68,10 +77,15 @@ export function isLogged(): boolean {
* Log in to the F95Zone platform. * Log in to the F95Zone platform.
* *
* This **must** be the first operation performed before accessing any other script functions. * 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( export async function login(
username: string, username: string,
password: string password: string,
cb2fa?: () => Promise<number>
): Promise<LoginResult> { ): Promise<LoginResult> {
// Try to load a previous session // Try to load a previous session
await shared.session.load(); await shared.session.load();
@ -93,17 +107,25 @@ export async function login(
await creds.fetchToken(); await creds.fetchToken();
shared.logger.trace(`Authentication for ${username}`); shared.logger.trace(`Authentication for ${username}`);
const result = await authenticate(creds); let result = await authenticate(creds);
shared.setIsLogged(result.success); shared.setIsLogged(result.success);
if (result.success) { // 2FA Authentication is required, fetch OTP
// Load platform data if (result.message === "Two-factor authentication is needed to continue") {
await fetchPlatformData(); 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 // Recreate the session, overwriting the old one
shared.session.create(username, password, creds.token); shared.session.create(username, password, creds.token);
await shared.session.save(); await shared.session.save();
// Load platform data
await fetchPlatformData();
shared.logger.info("User logged in through the platform"); shared.logger.info("User logged in through the platform");
} else shared.logger.warn(`Error during authentication: ${result.message}`); } else shared.logger.warn(`Error during authentication: ${result.message}`);
@ -147,7 +169,7 @@ export async function checkIfHandiworkHasUpdate(
*/ */
export async function searchHandiwork<T extends IBasic>( export async function searchHandiwork<T extends IBasic>(
query: HandiworkSearchQuery, query: HandiworkSearchQuery,
limit = 10 limit: number = 10
): Promise<T[]> { ): Promise<T[]> {
// Check if the user is logged // Check if the user is logged
if (!shared.isLogged) throw new UserNotLogged(USER_NOT_LOGGED); if (!shared.isLogged) throw new UserNotLogged(USER_NOT_LOGGED);
@ -203,7 +225,7 @@ export async function getUserData(): Promise<UserProfile> {
*/ */
export async function getLatestUpdates<T extends IBasic>( export async function getLatestUpdates<T extends IBasic>(
query: LatestSearchQuery, query: LatestSearchQuery,
limit = 10 limit: number = 10
): Promise<T[]> { ): Promise<T[]> {
// Check limit value // Check limit value
if (limit <= 0) throw new Error("limit must be greater than 0"); if (limit <= 0) throw new Error("limit must be greater than 0");