Refactor to mono-repo + upgrade packages

This commit is contained in:
Madeline
2022-09-25 18:24:21 +10:00
parent 59d94b4894
commit f44f5d7ac2
583 changed files with 900 additions and 3898 deletions
+47
View File
@@ -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;
};
+32
View File
@@ -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("");
}
+18
View File
@@ -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));
}
+46
View File
@@ -0,0 +1,46 @@
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;
}
+95
View File
@@ -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; // TODO: types
}
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
}
+60
View File
@@ -0,0 +1,60 @@
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
* - 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;
var 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;
}