diff --git a/CHANGELOG.md b/CHANGELOG.md index 16684fa6d..386168ca1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # CHANGELOG +## 0.7.1 Dec 31, 2021 + +Contributed: + +- Too many URLs to mention + +Changes: + +- Add `detectPackage` to check for duplicate instances +- Adjust consistency tests +- Split metadata into months + + ## 0.6.1 Feb 28, 2021 Contributed: diff --git a/package.json b/package.json index 1d7b66df3..7f9cb858e 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "url": "https://github.com/polkadot-js/phishing.git" }, "sideEffects": false, - "version": "0.6.598", + "version": "0.7.0", "workspaces": [ "packages/*" ], diff --git a/packages/phishing/package.json b/packages/phishing/package.json index 4e0559941..3acc074dd 100644 --- a/packages/phishing/package.json +++ b/packages/phishing/package.json @@ -12,9 +12,12 @@ "type": "git", "url": "https://github.com/polkadot-js/phishing.git" }, - "sideEffects": false, + "sideEffects": [ + "./detectPackage.js", + "./detectPackage.cjs" + ], "type": "module", - "version": "0.6.598", + "version": "0.7.0", "main": "index.js", "dependencies": { "@babel/runtime": "^7.16.7", diff --git a/packages/phishing/src/bundle.ts b/packages/phishing/src/bundle.ts new file mode 100644 index 000000000..0fade9f7f --- /dev/null +++ b/packages/phishing/src/bundle.ts @@ -0,0 +1,137 @@ +// Copyright 2020-2021 @polkadot/phishing authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { AddressList, HostList } from './types'; + +import { u8aEq } from '@polkadot/util'; +import { decodeAddress } from '@polkadot/util-crypto'; + +import { fetchWithTimeout } from './fetch'; + +export { packageInfo } from './packageInfo'; + +// Equivalent to https://raw.githubusercontent.com/polkadot-js/phishing/master/{address,all}.json +const ADDRESS_JSON = 'https://polkadot.js.org/phishing/address.json'; +const ALL_JSON = 'https://polkadot.js.org/phishing/all.json'; +// 45 minutes cache refresh +const CACHE_TIMEOUT = 45 * 60 * 1000; + +let cacheAddrEnd = 0; +let cacheAddrList: AddressList | null = null; +let cacheAddrU8a: [string, Uint8Array[]][] | null = null; +let cacheHostEnd = 0; +let cacheHostList: HostList | null = null; + +// gets the host-only part for a host +function extractHost (path: string): string { + return path + .replace(/https:\/\/|http:\/\/|wss:\/\/|ws:\/\//, '') + .split('/')[0]; +} + +/** + * Retrieve a list of known phishing addresses + */ +export async function retrieveAddrList (allowCached = true): Promise { + const now = Date.now(); + + if (allowCached && cacheAddrList && (now < cacheAddrEnd)) { + return cacheAddrList; + } + + const response = await fetchWithTimeout(ADDRESS_JSON); + const list = (await response.json()) as AddressList; + + cacheAddrEnd = now + CACHE_TIMEOUT; + cacheAddrList = list; + + return list; +} + +async function retrieveAddrU8a (allowCached = true): Promise<[string, Uint8Array[]][]> { + const now = Date.now(); + + if (allowCached && cacheAddrU8a && (now < cacheAddrEnd)) { + return cacheAddrU8a; + } + + const all = await retrieveAddrList(allowCached); + + cacheAddrU8a = Object + .entries(all) + .map(([key, addresses]): [string, Uint8Array[]] => [key, addresses.map((a) => decodeAddress(a))]); + + return cacheAddrU8a; +} + +/** + * Retrieve allow/deny from our list provider + */ +export async function retrieveHostList (allowCached = true): Promise { + const now = Date.now(); + + if (allowCached && cacheHostList && (now < cacheHostEnd)) { + return cacheHostList; + } + + const response = await fetchWithTimeout(ALL_JSON); + const list = (await response.json()) as HostList; + + cacheHostEnd = now + CACHE_TIMEOUT; + cacheHostList = list; + + return list; +} + +/** + * Checks a host to see if it appears in the provided list + */ +export function checkHost (items: string[], host: string): boolean { + const hostParts = extractHost(host).split('.').reverse(); + + return items.some((item): boolean => { + const checkParts = item.split('.').reverse(); + + // first we need to ensure it has less or equal parts to our source + if (checkParts.length > hostParts.length) { + return false; + } + + // ensure each section matches + return checkParts.every((part, index) => hostParts[index] === part); + }); +} + +/** + * Determines if a host is in our deny list. Returns a string containing the phishing site if host is a + * problematic one. Returns null if the address is not associated with phishing. + */ +export async function checkAddress (address: string | Uint8Array, allowCached = true): Promise { + try { + const all = await retrieveAddrU8a(allowCached); + const u8a = decodeAddress(address); + const entry = all.find(([, all]) => all.some((a) => u8aEq(a, u8a))) || [null]; + + return entry[0]; + } catch (error) { + console.error('Exception while checking address, assuming non-phishing', (error as Error).message); + + return null; + } +} + +/** + * Determines if a host is in our deny list. Returns true if host is a problematic one. Returns + * false if the host provided is not in our list of less-than-honest sites. + */ +export async function checkIfDenied (host: string, allowCached = true): Promise { + try { + const { deny } = await retrieveHostList(allowCached); + + return checkHost(deny, host); + } catch (error) { + console.error(`Exception while checking ${host}, assuming non-phishing`, (error as Error).message); + + return false; + } +} diff --git a/packages/phishing/src/cjs/dirname.d.ts b/packages/phishing/src/cjs/dirname.d.ts new file mode 100644 index 000000000..90c734a48 --- /dev/null +++ b/packages/phishing/src/cjs/dirname.d.ts @@ -0,0 +1,6 @@ +// Copyright 2020-2021 @polkadot/phishing authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +declare const __dirname: string | undefined; + +export default __dirname; diff --git a/packages/phishing/src/cjs/dirname.js b/packages/phishing/src/cjs/dirname.js new file mode 100644 index 000000000..9596109ac --- /dev/null +++ b/packages/phishing/src/cjs/dirname.js @@ -0,0 +1,6 @@ +// Copyright 2020-2021 @polkadot/phishing authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +module.exports = typeof __dirname === 'string' + ? __dirname.replace('/cjs', '') + : undefined; diff --git a/packages/phishing/src/cjs/package.json b/packages/phishing/src/cjs/package.json new file mode 100644 index 000000000..5bbefffba --- /dev/null +++ b/packages/phishing/src/cjs/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/packages/phishing/src/detectPackage.ts b/packages/phishing/src/detectPackage.ts new file mode 100644 index 000000000..94b7df83e --- /dev/null +++ b/packages/phishing/src/detectPackage.ts @@ -0,0 +1,9 @@ +// Copyright 2020-2021 @polkadot/phishing authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { detectPackage } from '@polkadot/util'; + +import __dirname from './cjs/dirname'; +import { packageInfo } from './packageInfo'; + +detectPackage(packageInfo, typeof __dirname !== 'undefined' && __dirname, []); diff --git a/packages/phishing/src/index.ts b/packages/phishing/src/index.ts index b4ad7e61b..fdd7cbaec 100644 --- a/packages/phishing/src/index.ts +++ b/packages/phishing/src/index.ts @@ -1,135 +1,6 @@ // Copyright 2020-2021 @polkadot/phishing authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { AddressList, HostList } from './types'; +import './detectPackage'; -import { u8aEq } from '@polkadot/util'; -import { decodeAddress } from '@polkadot/util-crypto'; - -import { fetchWithTimeout } from './fetch'; - -// Equivalent to https://raw.githubusercontent.com/polkadot-js/phishing/master/{address,all}.json -const ADDRESS_JSON = 'https://polkadot.js.org/phishing/address.json'; -const ALL_JSON = 'https://polkadot.js.org/phishing/all.json'; -// 1 hour cache refresh -const CACHE_TIMEOUT = 45 * 60 * 1000; - -let cacheAddrEnd = 0; -let cacheAddrList: AddressList | null = null; -let cacheAddrU8a: [string, Uint8Array[]][] | null = null; -let cacheHostEnd = 0; -let cacheHostList: HostList | null = null; - -// gets the host-only part for a host -function extractHost (path: string): string { - return path - .replace(/https:\/\/|http:\/\/|wss:\/\/|ws:\/\//, '') - .split('/')[0]; -} - -/** - * Retrieve a list of known phishing addresses - */ -export async function retrieveAddrList (allowCached = true): Promise { - const now = Date.now(); - - if (allowCached && cacheAddrList && (now < cacheAddrEnd)) { - return cacheAddrList; - } - - const response = await fetchWithTimeout(ADDRESS_JSON); - const list = (await response.json()) as AddressList; - - cacheAddrEnd = now + CACHE_TIMEOUT; - cacheAddrList = list; - - return list; -} - -async function retrieveAddrU8a (allowCached = true): Promise<[string, Uint8Array[]][]> { - const now = Date.now(); - - if (allowCached && cacheAddrU8a && (now < cacheAddrEnd)) { - return cacheAddrU8a; - } - - const all = await retrieveAddrList(allowCached); - - cacheAddrU8a = Object - .entries(all) - .map(([key, addresses]): [string, Uint8Array[]] => [key, addresses.map((a) => decodeAddress(a))]); - - return cacheAddrU8a; -} - -/** - * Retrieve allow/deny from our list provider - */ -export async function retrieveHostList (allowCached = true): Promise { - const now = Date.now(); - - if (allowCached && cacheHostList && (now < cacheHostEnd)) { - return cacheHostList; - } - - const response = await fetchWithTimeout(ALL_JSON); - const list = (await response.json()) as HostList; - - cacheHostEnd = now + CACHE_TIMEOUT; - cacheHostList = list; - - return list; -} - -/** - * Checks a host to see if it appears in the provided list - */ -export function checkHost (items: string[], host: string): boolean { - const hostParts = extractHost(host).split('.').reverse(); - - return items.some((item): boolean => { - const checkParts = item.split('.').reverse(); - - // first we need to ensure it has less or equal parts to our source - if (checkParts.length > hostParts.length) { - return false; - } - - // ensure each section matches - return checkParts.every((part, index) => hostParts[index] === part); - }); -} - -/** - * Determines if a host is in our deny list. Returns a string containing the phishing site if host is a - * problematic one. Returns null if the address is not associated with phishing. - */ -export async function checkAddress (address: string | Uint8Array, allowCached = true): Promise { - try { - const all = await retrieveAddrU8a(allowCached); - const u8a = decodeAddress(address); - const entry = all.find(([, all]) => all.some((a) => u8aEq(a, u8a))) || [null]; - - return entry[0]; - } catch (error) { - console.error('Exception while checking address, assuming non-phishing', (error as Error).message); - - return null; - } -} - -/** - * Determines if a host is in our deny list. Returns true if host is a problematic one. Returns - * false if the host provided is not in our list of less-than-honest sites. - */ -export async function checkIfDenied (host: string, allowCached = true): Promise { - try { - const { deny } = await retrieveHostList(allowCached); - - return checkHost(deny, host); - } catch (error) { - console.error(`Exception while checking ${host}, assuming non-phishing`, (error as Error).message); - - return false; - } -} +export * from './bundle';