Implement cookie storage on disk
parent
52131a143b
commit
b26c5de21b
|
@ -3,9 +3,11 @@
|
|||
// Core modules
|
||||
import * as fs from "fs";
|
||||
import { promisify } from "util";
|
||||
import path from "path";
|
||||
|
||||
// Public modules from npm
|
||||
import md5 from "md5";
|
||||
import tough, { CookieJar } from "tough-cookie";
|
||||
|
||||
// Promisifed functions
|
||||
const areadfile = promisify(fs.readFile);
|
||||
|
@ -19,12 +21,15 @@ export default class Session {
|
|||
/**
|
||||
* Max number of days the session is valid.
|
||||
*/
|
||||
private readonly SESSION_TIME: number = 1;
|
||||
private readonly SESSION_TIME: number = 3;
|
||||
private readonly COOKIEJAR_FILENAME: string = "f95cookiejar.json";
|
||||
private _path: string;
|
||||
private _isMapped: boolean;
|
||||
private _created: Date;
|
||||
private _hash: string;
|
||||
private _token: string;
|
||||
private _cookieJar: CookieJar;
|
||||
private _cookieJarPath: string;
|
||||
|
||||
//#endregion Fields
|
||||
|
||||
|
@ -50,18 +55,27 @@ export default class Session {
|
|||
* Token used to login to F95Zone.
|
||||
*/
|
||||
public get token() { return this._token; }
|
||||
/**
|
||||
* Cookie holder.
|
||||
*/
|
||||
public get cookieJar() { return this._cookieJar; }
|
||||
|
||||
//#endregion Getters
|
||||
|
||||
/**
|
||||
* Initializes the session by setting the path for saving information to disk.
|
||||
*/
|
||||
constructor(path: string) {
|
||||
this._path = path;
|
||||
constructor(p: string) {
|
||||
this._path = p;
|
||||
this._isMapped = fs.existsSync(this.path);
|
||||
this._created = new Date(Date.now());
|
||||
this._hash = null;
|
||||
this._token = null;
|
||||
this._cookieJar = new tough.CookieJar();
|
||||
|
||||
// Define the path for the cookiejar
|
||||
const basedir = path.dirname(p);
|
||||
this._cookieJarPath = path.join(basedir, this.COOKIEJAR_FILENAME);
|
||||
}
|
||||
|
||||
//#region Private Methods
|
||||
|
@ -122,6 +136,10 @@ export default class Session {
|
|||
|
||||
// Write data
|
||||
await awritefile(this.path, data);
|
||||
|
||||
// Write cookiejar
|
||||
const serializedJar = await this._cookieJar.serialize();
|
||||
await awritefile(this._cookieJarPath, JSON.stringify(serializedJar));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -134,9 +152,13 @@ export default class Session {
|
|||
const json = JSON.parse(data);
|
||||
|
||||
// Assign values
|
||||
this._created = json._created;
|
||||
this._created = new Date(json._created);
|
||||
this._hash = json._hash;
|
||||
this._token = json._token;
|
||||
|
||||
// Load cookiejar
|
||||
const serializedJar = await areadfile(this._cookieJarPath, { encoding: 'utf-8', flag: 'r' });
|
||||
this._cookieJar = await CookieJar.deserialize(JSON.parse(serializedJar));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,7 +167,11 @@ export default class Session {
|
|||
*/
|
||||
async delete(): Promise<void> {
|
||||
if (this.isMapped) {
|
||||
// Delete the session data
|
||||
await aunlinkfile(this.path);
|
||||
|
||||
// Delete the cookiejar
|
||||
await aunlinkfile(this._cookieJarPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,18 +180,22 @@ export default class Session {
|
|||
*/
|
||||
isValid(username: string, password: string): boolean {
|
||||
// Get the number of days from the file creation
|
||||
const diff = this.dateDiffInDays(new Date(Date.now()), this._created);
|
||||
const diff = this.dateDiffInDays(new Date(Date.now()), this.created);
|
||||
|
||||
// The session is valid if the number of days is minor than SESSION_TIME
|
||||
let valid = diff < this.SESSION_TIME;
|
||||
const dateValid = diff < this.SESSION_TIME;
|
||||
|
||||
if(valid) {
|
||||
// Check the _hash
|
||||
const value = `${username}%%%${password}`;
|
||||
valid = md5(value) === this._hash;
|
||||
}
|
||||
// Check the hash
|
||||
const value = `${username}%%%${password}`;
|
||||
const hashValid = md5(value) === this._hash;
|
||||
|
||||
return valid;
|
||||
// Search for expired cookies
|
||||
const jarValid = this._cookieJar
|
||||
.getCookiesSync("https://f95zone.to")
|
||||
.filter(el => el.TTL() === 0)
|
||||
.length === 0;
|
||||
|
||||
return dateValid && hashValid && jarValid;
|
||||
}
|
||||
|
||||
//#endregion Public Methods
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
import axios, { AxiosResponse } from "axios";
|
||||
import cheerio from "cheerio";
|
||||
import axiosCookieJarSupport from "axios-cookiejar-support";
|
||||
import tough from "tough-cookie";
|
||||
|
||||
// Modules from file
|
||||
import shared from "./shared.js";
|
||||
|
@ -21,13 +20,25 @@ const userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) " +
|
|||
// @ts-ignore
|
||||
axiosCookieJarSupport.default(axios);
|
||||
|
||||
/**
|
||||
* Common configuration used to send request via Axios.
|
||||
*/
|
||||
const commonConfig = {
|
||||
/**
|
||||
* Headers to add to the request.
|
||||
*/
|
||||
headers: {
|
||||
"User-Agent": userAgent,
|
||||
"Connection": "keep-alive"
|
||||
},
|
||||
/**
|
||||
* Specify if send credentials along the request.
|
||||
*/
|
||||
withCredentials: true,
|
||||
jar: new tough.CookieJar() // Used to store the token in the PC
|
||||
/**
|
||||
* Jar of cookies to send along the request.
|
||||
*/
|
||||
jar: shared.session.cookieJar,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -38,6 +49,7 @@ export async function fetchHTML(url: string): Promise<Result<GenericAxiosError |
|
|||
const response = await fetchGETResponse(url);
|
||||
|
||||
if (response.isSuccess()) {
|
||||
// Check if the response is a HTML source code
|
||||
const isHTML = response.value.headers["content-type"].includes("text/html");
|
||||
|
||||
const unexpectedResponseError = new UnexpectedResponseContentType({
|
||||
|
@ -93,7 +105,7 @@ export async function authenticate(credentials: credentials, force: boolean = fa
|
|||
|
||||
// Return the result of the authentication
|
||||
const result = errorMessage.trim() === "";
|
||||
const message = errorMessage.trim() === "" ? "Authentication successful" : errorMessage;
|
||||
const message = result ? "Authentication successful" : errorMessage;
|
||||
return new LoginResult(result, message);
|
||||
}
|
||||
else throw response.value;
|
||||
|
@ -127,6 +139,7 @@ export async function fetchGETResponse(url: string): Promise<Result<GenericAxios
|
|||
|
||||
try {
|
||||
// Fetch and return the response
|
||||
commonConfig.jar = shared.session.cookieJar;
|
||||
const response = await axios.get(secureURL, commonConfig);
|
||||
return success(response);
|
||||
} catch (e) {
|
||||
|
@ -142,10 +155,10 @@ export async function fetchGETResponse(url: string): Promise<Result<GenericAxios
|
|||
|
||||
/**
|
||||
* Enforces the scheme of the URL is https and returns the new URL.
|
||||
* @returns {String} Secure URL or `null` if the argument is not a string
|
||||
*/
|
||||
export function enforceHttpsUrl(url: string): string {
|
||||
return isStringAValidURL(url) ? url.replace(/^(https?:)?\/\//, "https://") : null;
|
||||
if (isStringAValidURL(url)) return url.replace(/^(https?:)?\/\//, "https://");
|
||||
else throw new Error(`${url} is not a valid URL`);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -159,7 +172,6 @@ export function isF95URL(url: string): boolean {
|
|||
* Checks if the string passed by parameter has a
|
||||
* properly formatted and valid path to a URL (HTTP/HTTPS).
|
||||
* @param {String} url String to check for correctness
|
||||
* @returns {Boolean} true if the string is a valid URL, false otherwise
|
||||
*/
|
||||
export function isStringAValidURL(url: string): boolean {
|
||||
// Many thanks to Daveo at StackOverflow (https://preview.tinyurl.com/y2f2e2pc)
|
||||
|
@ -217,6 +229,7 @@ export async function fetchPOSTResponse(url: string, params: { [s: string]: stri
|
|||
for (const [key, value] of Object.entries(params)) urlParams.append(key, value);
|
||||
|
||||
// Shallow copy of the common configuration object
|
||||
commonConfig.jar = shared.session.cookieJar;
|
||||
const config = Object.assign({}, commonConfig);
|
||||
|
||||
// Remove the cookies if forced
|
||||
|
|
Loading…
Reference in New Issue