diff --git a/src/api/util/utility/Base64.ts b/src/api/util/utility/Base64.ts
new file mode 100644
index 00000000..46cff77a
--- /dev/null
+++ b/src/api/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/src/api/util/utility/RandomInviteID.ts b/src/api/util/utility/RandomInviteID.ts
new file mode 100644
index 00000000..7ea344e0
--- /dev/null
+++ b/src/api/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/src/api/util/utility/String.ts b/src/api/util/utility/String.ts
new file mode 100644
index 00000000..982b7e11
--- /dev/null
+++ b/src/api/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/src/api/util/utility/ipAddress.ts b/src/api/util/utility/ipAddress.ts
new file mode 100644
index 00000000..8d986b26
--- /dev/null
+++ b/src/api/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() 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] || 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
new file mode 100644
index 00000000..8eca63b8
--- /dev/null
+++ b/src/api/util/utility/passwordStrength.ts
@@ -0,0 +1,59 @@
+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;
+}
|