diff options
Diffstat (limited to 'api/src/util/utility')
-rw-r--r-- | api/src/util/utility/Base64.ts | 47 | ||||
-rw-r--r-- | api/src/util/utility/RandomInviteID.ts | 32 | ||||
-rw-r--r-- | api/src/util/utility/String.ts | 18 | ||||
-rw-r--r-- | api/src/util/utility/ipAddress.ts | 95 | ||||
-rw-r--r-- | api/src/util/utility/passwordStrength.ts | 49 |
5 files changed, 241 insertions, 0 deletions
diff --git a/api/src/util/utility/Base64.ts b/api/src/util/utility/Base64.ts new file mode 100644 index 00000000..46cff77a --- /dev/null +++ b/api/src/util/utility/Base64.ts @@ -0,0 +1,47 @@ +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/api/src/util/utility/RandomInviteID.ts b/api/src/util/utility/RandomInviteID.ts new file mode 100644 index 00000000..7ea344e0 --- /dev/null +++ b/api/src/util/utility/RandomInviteID.ts @@ -0,0 +1,32 @@ +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/api/src/util/utility/String.ts b/api/src/util/utility/String.ts new file mode 100644 index 00000000..982b7e11 --- /dev/null +++ b/api/src/util/utility/String.ts @@ -0,0 +1,18 @@ +import { Request } from "express"; +import { ntob } from "./Base64"; +import { FieldErrors } from "@fosscord/util"; + +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/api/src/util/utility/ipAddress.ts b/api/src/util/utility/ipAddress.ts new file mode 100644 index 00000000..13cc9603 --- /dev/null +++ b/api/src/util/utility/ipAddress.ts @@ -0,0 +1,95 @@ +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(); +} + +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] || 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/api/src/util/utility/passwordStrength.ts b/api/src/util/utility/passwordStrength.ts new file mode 100644 index 00000000..047df008 --- /dev/null +++ b/api/src/util/utility/passwordStrength.ts @@ -0,0 +1,49 @@ +import { Config } from "@fosscord/util"; +import "missing-native-js-functions"; + +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 + * + * Returns: 0 > pw > 1 + */ +export function checkPassword(password: string): number { + const { minLength, minNumbers, minUpperCase, minSymbols } = Config.get().register.password; + var strength = 0; + + // checks for total password len + if (password.length >= minLength - 1) { + strength += 0.25; + } + + // checks for amount of Numbers + if (password.count(reNUMBER) >= minNumbers - 1) { + strength += 0.25; + } + + // checks for amount of Uppercase Letters + if (password.count(reUPPERCASELETTER) >= minUpperCase - 1) { + strength += 0.25; + } + + // checks for amount of symbols + if (password.replace(reSYMBOLS, "").length >= minSymbols - 1) { + strength += 0.25; + } + + // 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; + } + + return strength; +} |