diff options
author | TheArcaneBrony <myrainbowdash949@gmail.com> | 2022-09-17 23:35:31 +0200 |
---|---|---|
committer | TheArcaneBrony <myrainbowdash949@gmail.com> | 2022-09-17 23:35:31 +0200 |
commit | 258b96757f2d30f68ce873be04b5169de1e1eb9b (patch) | |
tree | 97cd6fe041c820d7d61a419b1b424af109e85aee /src/api/util | |
parent | Partially refactor code to use localization (diff) | |
download | server-258b96757f2d30f68ce873be04b5169de1e1eb9b.tar.xz |
Cryptographically secure invites, add generation of tokens
Diffstat (limited to 'src/api/util')
-rw-r--r-- | src/api/util/handlers/Voice.ts | 3 | ||||
-rw-r--r-- | src/api/util/index.ts | 8 | ||||
-rw-r--r-- | src/api/util/utility/Base64.ts | 47 | ||||
-rw-r--r-- | src/api/util/utility/RandomInviteID.ts | 31 | ||||
-rw-r--r-- | src/api/util/utility/String.ts | 18 | ||||
-rw-r--r-- | src/api/util/utility/captcha.ts | 47 | ||||
-rw-r--r-- | src/api/util/utility/ipAddress.ts | 99 | ||||
-rw-r--r-- | src/api/util/utility/passwordStrength.ts | 59 |
8 files changed, 2 insertions, 310 deletions
diff --git a/src/api/util/handlers/Voice.ts b/src/api/util/handlers/Voice.ts index 4d60eb91..98d28ff0 100644 --- a/src/api/util/handlers/Voice.ts +++ b/src/api/util/handlers/Voice.ts @@ -1,5 +1,4 @@ -import { Config } from "@fosscord/util"; -import { distanceBetweenLocations, IPAnalysis } from "../utility/ipAddress"; +import { Config, distanceBetweenLocations, IPAnalysis } from "@fosscord/util"; export async function getVoiceRegions(ipAddress: string, vip: boolean) { const regions = Config.get().regions; diff --git a/src/api/util/index.ts b/src/api/util/index.ts index d06860cd..7223d6f4 100644 --- a/src/api/util/index.ts +++ b/src/api/util/index.ts @@ -1,10 +1,4 @@ export * from "./entities/AssetCacheItem"; export * from "./handlers/Message"; export * from "./handlers/route"; -export * from "./handlers/Voice"; -export * from "./utility/Base64"; -export * from "./utility/captcha"; -export * from "./utility/ipAddress"; -export * from "./utility/passwordStrength"; -export * from "./utility/RandomInviteID"; -export * from "./utility/String"; +export * from "./handlers/Voice"; \ No newline at end of file diff --git a/src/api/util/utility/Base64.ts b/src/api/util/utility/Base64.ts deleted file mode 100644 index 46cff77a..00000000 --- a/src/api/util/utility/Base64.ts +++ /dev/null @@ -1,47 +0,0 @@ -const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+"; - -// binary to string lookup table -const b2s = alphabet.split(""); - -// string to binary lookup table -// 123 == 'z'.charCodeAt(0) + 1 -const s2b = new Array(123); -for (let i = 0; i < alphabet.length; i++) { - s2b[alphabet.charCodeAt(i)] = i; -} - -// number to base64 -export const ntob = (n: number): string => { - if (n < 0) return `-${ntob(-n)}`; - - let lo = n >>> 0; - let hi = (n / 4294967296) >>> 0; - - let right = ""; - while (hi > 0) { - right = b2s[0x3f & lo] + right; - lo >>>= 6; - lo |= (0x3f & hi) << 26; - hi >>>= 6; - } - - let left = ""; - do { - left = b2s[0x3f & lo] + left; - lo >>>= 6; - } while (lo > 0); - - return left + right; -}; - -// base64 to number -export const bton = (base64: string) => { - let number = 0; - const sign = base64.charAt(0) === "-" ? 1 : 0; - - for (let i = sign; i < base64.length; i++) { - number = number * 64 + s2b[base64.charCodeAt(i)]; - } - - return sign ? -number : number; -}; diff --git a/src/api/util/utility/RandomInviteID.ts b/src/api/util/utility/RandomInviteID.ts deleted file mode 100644 index feebfd3d..00000000 --- a/src/api/util/utility/RandomInviteID.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Snowflake } from "@fosscord/util"; - -export function random(length = 6) { - // Declare all characters - let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - - // Pick characers randomly - let str = ""; - for (let i = 0; i < length; i++) { - str += chars.charAt(Math.floor(Math.random() * chars.length)); - } - - return str; -} - -export function snowflakeBasedInvite() { - // Declare all characters - let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - let base = BigInt(chars.length); - let snowflake = Snowflake.generateWorkerProcess(); - - // snowflakes hold ~10.75 characters worth of entropy; - // safe to generate a 8-char invite out of them - let str = ""; - for (let i = 0; i < 10; i++) { - str.concat(chars.charAt(Number(snowflake % base))); - snowflake = snowflake / base; - } - - return str.substr(3, 8).split("").reverse().join(""); -} diff --git a/src/api/util/utility/String.ts b/src/api/util/utility/String.ts deleted file mode 100644 index a2e491e4..00000000 --- a/src/api/util/utility/String.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { FieldErrors } from "@fosscord/util"; -import { Request } from "express"; -import { ntob } from "./Base64"; - -export function checkLength(str: string, min: number, max: number, key: string, req: Request) { - if (str.length < min || str.length > max) { - throw FieldErrors({ - [key]: { - code: "BASE_TYPE_BAD_LENGTH", - message: req.t("common:field.BASE_TYPE_BAD_LENGTH", { length: `${min} - ${max}` }) - } - }); - } -} - -export function generateCode() { - return ntob(Date.now() + Math.randomIntBetween(0, 10000)); -} diff --git a/src/api/util/utility/captcha.ts b/src/api/util/utility/captcha.ts deleted file mode 100644 index 02983f3f..00000000 --- a/src/api/util/utility/captcha.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Config } from "@fosscord/util"; -import fetch from "node-fetch"; - -export interface hcaptchaResponse { - success: boolean; - challenge_ts: string; - hostname: string; - credit: boolean; - "error-codes": string[]; - score: number; // enterprise only - score_reason: string[]; // enterprise only -} - -export interface recaptchaResponse { - success: boolean; - score: number; // between 0 - 1 - action: string; - challenge_ts: string; - hostname: string; - "error-codes"?: string[]; -} - -const verifyEndpoints = { - hcaptcha: "https://hcaptcha.com/siteverify", - recaptcha: "https://www.google.com/recaptcha/api/siteverify" -}; - -export async function verifyCaptcha(response: string, ip?: string) { - const { security } = Config.get(); - const { service, secret, sitekey } = security.captcha; - - if (!service) throw new Error("Cannot verify captcha without service"); - - const res = await fetch(verifyEndpoints[service], { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded" - }, - body: - `response=${encodeURIComponent(response)}` + - `&secret=${encodeURIComponent(secret!)}` + - `&sitekey=${encodeURIComponent(sitekey!)}` + - (ip ? `&remoteip=${encodeURIComponent(ip!)}` : "") - }); - - return (await res.json()) as hcaptchaResponse | recaptchaResponse; -} diff --git a/src/api/util/utility/ipAddress.ts b/src/api/util/utility/ipAddress.ts deleted file mode 100644 index c96feb9e..00000000 --- a/src/api/util/utility/ipAddress.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { Config } from "@fosscord/util"; -import { Request } from "express"; -// use ipdata package instead of simple fetch because of integrated caching -import fetch from "node-fetch"; - -const exampleData = { - ip: "", - is_eu: true, - city: "", - region: "", - region_code: "", - country_name: "", - country_code: "", - continent_name: "", - continent_code: "", - latitude: 0, - longitude: 0, - postal: "", - calling_code: "", - flag: "", - emoji_flag: "", - emoji_unicode: "", - asn: { - asn: "", - name: "", - domain: "", - route: "", - type: "isp" - }, - languages: [ - { - name: "", - native: "" - } - ], - currency: { - name: "", - code: "", - symbol: "", - native: "", - plural: "" - }, - time_zone: { - name: "", - abbr: "", - offset: "", - is_dst: true, - current_time: "" - }, - threat: { - is_tor: false, - is_proxy: false, - is_anonymous: false, - is_known_attacker: false, - is_known_abuser: false, - is_threat: false, - is_bogon: false - }, - count: 0, - status: 200 -}; - -//TODO add function that support both ip and domain names -export async function IPAnalysis(ip: string): Promise<typeof exampleData> { - const { ipdataApiKey } = Config.get().security; - if (!ipdataApiKey) return { ...exampleData, ip }; - - return (await fetch(`https://api.ipdata.co/${ip}?api-key=${ipdataApiKey}`)).json() as any; -} - -export function isProxy(data: typeof exampleData) { - if (!data || !data.asn || !data.threat) return false; - if (data.asn.type !== "isp") return true; - if (Object.values(data.threat).some((x) => x)) return true; - - return false; -} - -export function getIpAdress(req: Request): string { - // @ts-ignore - return ( - req.headers[Config.get().security.forwadedFor as string] || - req.headers[Config.get().security.forwadedFor?.toLowerCase() as string] || - req.socket.remoteAddress - ); -} - -export function distanceBetweenLocations(loc1: any, loc2: any): number { - return distanceBetweenCoords(loc1.latitude, loc1.longitude, loc2.latitude, loc2.longitude); -} - -//Haversine function -function distanceBetweenCoords(lat1: number, lon1: number, lat2: number, lon2: number) { - const p = 0.017453292519943295; // Math.PI / 180 - const c = Math.cos; - const a = 0.5 - c((lat2 - lat1) * p) / 2 + (c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p))) / 2; - - return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km -} diff --git a/src/api/util/utility/passwordStrength.ts b/src/api/util/utility/passwordStrength.ts deleted file mode 100644 index ff83d3df..00000000 --- a/src/api/util/utility/passwordStrength.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Config } from "@fosscord/util"; - -const reNUMBER = /[0-9]/g; -const reUPPERCASELETTER = /[A-Z]/g; -const reSYMBOLS = /[A-Z,a-z,0-9]/g; - -const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored in db -/* - * https://en.wikipedia.org/wiki/Password_policy - * password must meet following criteria, to be perfect: - * - min <n> chars - * - min <n> numbers - * - min <n> symbols - * - min <n> uppercase chars - * - shannon entropy folded into [0, 1) interval - * - * Returns: 0 > pw > 1 - */ -export function checkPassword(password: string): number { - const { minLength, minNumbers, minUpperCase, minSymbols } = Config.get().register.password; - let strength = 0; - - // checks for total password len - if (password.length >= minLength - 1) { - strength += 0.05; - } - - // checks for amount of Numbers - if (password.count(reNUMBER) >= minNumbers - 1) { - strength += 0.05; - } - - // checks for amount of Uppercase Letters - if (password.count(reUPPERCASELETTER) >= minUpperCase - 1) { - strength += 0.05; - } - - // checks for amount of symbols - if (password.replace(reSYMBOLS, "").length >= minSymbols - 1) { - strength += 0.05; - } - - // checks if password only consists of numbers or only consists of chars - if (password.length == password.count(reNUMBER) || password.length === password.count(reUPPERCASELETTER)) { - strength = 0; - } - - let entropyMap: { [key: string]: number } = {}; - for (let i = 0; i < password.length; i++) { - if (entropyMap[password[i]]) entropyMap[password[i]]++; - else entropyMap[password[i]] = 1; - } - - let entropies = Object.values(entropyMap); - - entropies.map((x) => x / entropyMap.length); - strength += entropies.reduceRight((a: number, x: number) => a - x * Math.log2(x)) / Math.log2(password.length); - return strength; -} |