Make ConfigValue a directory, move defaults to those classes instead of a separate object

This commit is contained in:
TheArcaneBrony
2022-08-13 21:57:51 +02:00
parent 99c40c2082
commit 34ca1a8a6c
52 changed files with 364 additions and 409 deletions
+1 -1
View File
@@ -7,12 +7,12 @@ import {
emitEvent,
Guild,
Member,
Region,
VoiceServerUpdateEvent,
VoiceState,
VoiceStateUpdateEvent,
} from "@fosscord/util";
import { OrmUtils } from "@fosscord/util";
import { Region } from "@fosscord/util/src/config";
// TODO: check if a voice server is setup
// Notice: Bot users respect the voice channel's user limit, if set. When the voice channel is full, you will not receive the Voice State Update or Voice Server Update events in response to your own Voice State Update. Having MANAGE_CHANNELS permission bypasses this limit and allows you to join regardless of the channel being full or not.
+22
View File
@@ -0,0 +1,22 @@
import { ApiConfiguration, ClientConfiguration, DefaultsConfiguration, EndpointConfiguration, GeneralConfiguration, GifConfiguration, GuildConfiguration, KafkaConfiguration, LimitsConfiguration, LoginConfiguration, MetricsConfiguration, RabbitMQConfiguration, RegionConfiguration, RegisterConfiguration, SecurityConfiguration, SentryConfiguration, TemplateConfiguration } from "../config";
export class ConfigValue {
gateway: EndpointConfiguration = new EndpointConfiguration();
cdn: EndpointConfiguration = new EndpointConfiguration();
api: ApiConfiguration = new ApiConfiguration();
general: GeneralConfiguration = new GeneralConfiguration();
limits: LimitsConfiguration = new LimitsConfiguration();
security: SecurityConfiguration = new SecurityConfiguration();
login: LoginConfiguration = new LoginConfiguration();
register: RegisterConfiguration = new RegisterConfiguration();
regions: RegionConfiguration = new RegionConfiguration();
guild: GuildConfiguration = new GuildConfiguration();
gif: GifConfiguration = new GifConfiguration();
rabbitmq: RabbitMQConfiguration = new RabbitMQConfiguration();
kafka: KafkaConfiguration = new KafkaConfiguration();
templates: TemplateConfiguration = new TemplateConfiguration();
client: ClientConfiguration = new ClientConfiguration();
metrics: MetricsConfiguration = new MetricsConfiguration();
sentry: SentryConfiguration = new SentryConfiguration();
defaults: DefaultsConfiguration = new DefaultsConfiguration();
}
+2
View File
@@ -0,0 +1,2 @@
export * from "./Config";
export * from "./types/index";
@@ -0,0 +1,5 @@
export class ApiConfiguration {
defaultVersion: string = "9";
activeVersions: string[] = ["6", "7", "8", "9"];
useFosscordEnhancements: boolean = true;
}
@@ -0,0 +1,8 @@
import { ClientReleaseConfiguration } from ".";
export class ClientConfiguration {
//classes
releases: ClientReleaseConfiguration = new ClientReleaseConfiguration();
//base types
useTestClient: boolean = true;
}
@@ -0,0 +1,5 @@
import { GuildDefaults } from ".";
export class DefaultsConfiguration {
guild: GuildDefaults = new GuildDefaults();
}
@@ -0,0 +1,5 @@
export class EndpointConfiguration {
endpointClient: string | null = null;
endpointPrivate: string | null = null;
endpointPublic: string | null = null;
}
@@ -0,0 +1,12 @@
import { Snowflake } from "../../util";
export class GeneralConfiguration {
instanceName: string = "Fosscord Instance";
instanceDescription: string | null = "This is a Fosscord instance made in the pre-release days";
frontPage: string | null = null;
tosPage: string | null = null;
correspondenceEmail: string | null = "noreply@localhost.local";
correspondenceUserID: string | null = null;
image: string | null = null;
instanceId: string = Snowflake.generate();
}
@@ -0,0 +1,5 @@
export class GifConfiguration {
enabled: boolean = true;
provider: "tenor" = "tenor"; // more coming soon
apiKey?: string = "LIVDSRZULELA";
}
@@ -0,0 +1,6 @@
import { DiscoveryConfiguration, AutoJoinConfiguration } from ".";
export class GuildConfiguration {
discovery: DiscoveryConfiguration = new DiscoveryConfiguration();
autoJoin: AutoJoinConfiguration = new AutoJoinConfiguration();
}
@@ -0,0 +1,5 @@
import { KafkaBroker } from ".";
export class KafkaConfiguration {
brokers: KafkaBroker[] | null = null;
}
@@ -0,0 +1,9 @@
import { ChannelLimits, GuildLimits, MessageLimits, RateLimits, UserLimits } from ".";
export class LimitsConfiguration {
user: UserLimits = new UserLimits();
guild: GuildLimits = new GuildLimits();
message: MessageLimits = new MessageLimits();
channel: ChannelLimits = new ChannelLimits();
rate: RateLimits = new RateLimits();
}
@@ -0,0 +1,3 @@
export class LoginConfiguration {
requireCaptcha: boolean = false;
}
@@ -0,0 +1,3 @@
export class MetricsConfiguration {
timeout: number = 30000;
}
@@ -0,0 +1,3 @@
export class RabbitMQConfiguration {
host: string | null = null;
}
@@ -0,0 +1,16 @@
import { Region } from ".";
export class RegionConfiguration {
default: string = "fosscord";
useDefaultAsOptimal: boolean = true;
available: Region[] = [
{
id: "fosscord",
name: "Fosscord",
endpoint: "127.0.0.1:3004",
vip: false,
custom: false,
deprecated: false,
},
];
}
@@ -0,0 +1,18 @@
import { DateOfBirthConfiguration, EmailConfiguration, PasswordConfiguration } from ".";
export class RegisterConfiguration {
//classes
email: EmailConfiguration = new EmailConfiguration();
dateOfBirth: DateOfBirthConfiguration = new DateOfBirthConfiguration();
password: PasswordConfiguration = new PasswordConfiguration();
//base types
disabled: boolean = false;
requireCaptcha: boolean = true;
requireInvite: boolean = false;
guestsRequireInvite: boolean = true;
allowNewRegistration: boolean = true;
allowMultipleAccounts: boolean = true;
blockProxies: boolean = true;
incrementingDiscriminators: boolean = false; // random otherwise
defaultRights: string = "0";
}
@@ -0,0 +1,17 @@
import crypto from "crypto";
import { CaptchaConfiguration, TwoFactorConfiguration } from ".";
export class SecurityConfiguration {
//classes
captcha: CaptchaConfiguration = new CaptchaConfiguration();
twoFactor: TwoFactorConfiguration = new TwoFactorConfiguration();
//base types
autoUpdate: boolean | number = true;
requestSignature: string = crypto.randomBytes(32).toString("base64");
jwtSecret: string = crypto.randomBytes(256).toString("base64");
// header to get the real user ip address
// X-Forwarded-For for nginx/reverse proxies
// CF-Connecting-IP for cloudflare
forwadedFor: string | null = null;
ipdataApiKey: string | null = "eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9";
}
@@ -0,0 +1,8 @@
import { hostname } from "os";
export class SentryConfiguration {
enabled: boolean = false;
endpoint: string = "https://05e8e3d005f34b7d97e920ae5870a5e5@sentry.thearcanebrony.net/6";
traceSampleRate: number = 1.0;
environment: string = hostname();
}
@@ -0,0 +1,6 @@
export class TemplateConfiguration {
enabled: boolean = true;
allowTemplateCreation: boolean = true;
allowDiscordTemplates: boolean = true;
allowRaws: boolean = true;
}
+18
View File
@@ -0,0 +1,18 @@
export * from "./ApiConfiguration";
export * from "./ClientConfiguration";
export * from "./DefaultsConfiguration";
export * from "./EndpointConfiguration";
export * from "./GeneralConfiguration";
export * from "./GifConfiguration";
export * from "./GuildConfiguration";
export * from "./KafkaConfiguration";
export * from "./LimitConfigurations";
export * from "./LoginConfiguration";
export * from "./MetricsConfiguration";
export * from "./RabbitMQConfiguration";
export * from "./RegionConfiguration";
export * from "./RegisterConfiguration";
export * from "./SecurityConfiguration";
export * from "./SentryConfiguration";
export * from "./TemplateConfiguration";
export * from "./subconfigurations/index";
@@ -0,0 +1,4 @@
export class ClientReleaseConfiguration {
useLocalRelease: boolean = true; //TODO
upstreamVersion: string = "0.0.264";
}
@@ -0,0 +1 @@
export * from "./ClientReleaseConfiguration";
@@ -0,0 +1,8 @@
export class GuildDefaults {
maxPresences: number = 250000;
maxVideoChannelUsers: number = 200;
afkTimeout: number = 300;
defaultMessageNotifications: number = 1;
explicitContentFilter: number = 0;
test: number = 123;
}
@@ -0,0 +1 @@
export * from "./GuildDefaults";
@@ -0,0 +1,5 @@
export class AutoJoinConfiguration {
enabled: boolean = true;
guilds: string[] = [];
canLeave: boolean = true;
}
@@ -0,0 +1,6 @@
export class DiscoveryConfiguration {
showAllGuilds: boolean = false;
useRecommendation: boolean = false; // TODO: Recommendation, privacy concern?
offset: number = 0;
limit: number = 24;
}
@@ -0,0 +1,2 @@
export * from "./AutoJoin";
export * from "./Discovery";
@@ -0,0 +1,8 @@
export * from "./client/index";
export * from "./defaults/index";
export * from "./guild/index";
export * from "./kafka/index";
export * from "./limits/index";
export * from "./region/index";
export * from "./register/index";
export * from "./security/index";
@@ -0,0 +1,4 @@
export interface KafkaBroker {
ip: string;
port: number;
}
@@ -0,0 +1 @@
export * from "./KafkaBroker";
@@ -0,0 +1,5 @@
export class ChannelLimits {
maxPins: number = 500;
maxTopic: number = 1024;
maxWebhooks: number = 100;
}
@@ -0,0 +1,8 @@
export class GuildLimits {
maxRoles: number = 1000;
maxEmojis: number = 2000;
maxMembers: number = 25000000;
maxChannels: number = 65535;
maxChannelsInCategory: number = 65535;
hideOfflineMember: number = 3;
}
@@ -0,0 +1,8 @@
export class MessageLimits {
maxCharacters: number = 1048576;
maxTTSCharacters: number = 160;
maxReactions: number = 2048;
maxAttachmentSize: number = 1024 * 1024 * 1024;
maxBulkDelete: number = 1000;
maxEmbedDownloadSize: number = 1024 * 1024 * 5;
}
@@ -0,0 +1,18 @@
import { RouteRateLimit, RateLimitOptions } from ".";
export class RateLimits {
disabled: boolean = true;
ip: Omit<RateLimitOptions, "bot_count"> = {
count: 500,
window: 5
};
global: RateLimitOptions = {
count: 250,
window: 5
};
error: RateLimitOptions = {
count: 10,
window: 5
};
routes: RouteRateLimit;
}
@@ -0,0 +1,5 @@
export class UserLimits {
maxGuilds: number = 1048576;
maxUsername: number = 127;
maxFriends: number = 5000;
}
@@ -0,0 +1,6 @@
export * from "./ChannelLimits";
export * from "./GuildLimits";
export * from "./MessageLimits";
export * from "./RateLimits";
export * from "./UserLimits";
export * from "./ratelimits/index";
@@ -0,0 +1,12 @@
import { RateLimitOptions } from "./RateLimitOptions";
export class AuthRateLimit {
login: RateLimitOptions = {
count: 5,
window: 60
};
register: RateLimitOptions = {
count: 2,
window: 60 * 60 * 12
};
}
@@ -0,0 +1,6 @@
export interface RateLimitOptions {
bot?: number;
count: number;
window: number;
onyIp?: boolean;
}
@@ -0,0 +1,19 @@
import { AuthRateLimit } from ".";
import { RateLimitOptions } from "./RateLimitOptions";
export class RouteRateLimit {
guild: RateLimitOptions = {
count: 5,
window: 5
};
webhook: RateLimitOptions = {
count: 10,
window: 5
};
channel: RateLimitOptions = {
count: 10,
window: 5
};
auth: AuthRateLimit;
// TODO: rate limit configuration for all routes
}
@@ -0,0 +1,3 @@
export * from "./Auth";
export * from "./RateLimitOptions";
export * from "./Route";
@@ -0,0 +1,12 @@
export interface Region {
id: string;
name: string;
endpoint: string;
location?: {
latitude: number;
longitude: number;
};
vip: boolean;
custom: boolean;
deprecated: boolean;
}
@@ -0,0 +1 @@
export * from "./Region";
@@ -0,0 +1,4 @@
export class DateOfBirthConfiguration {
required: boolean = true;
minimum: number = 13; // in years
}
@@ -0,0 +1,7 @@
export class EmailConfiguration {
required: boolean = false;
allowlist: boolean = false;
blocklist: boolean = true;
domains: string[] = [];// TODO: efficiently save domain blocklist in database
// domains: fs.readFileSync(__dirname + "/blockedEmailDomains.txt", { encoding: "utf8" }).split("\n"),
}
@@ -0,0 +1,7 @@
export class PasswordConfiguration {
required: boolean = false;
minLength: number = 8;
minNumbers: number = 2;
minUpperCase: number =2;
minSymbols: number = 0;
}
@@ -0,0 +1,3 @@
export * from "./DateOfBirth";
export * from "./Email";
export * from "./Password";
@@ -0,0 +1,6 @@
export class CaptchaConfiguration {
enabled: boolean = false;
service: "recaptcha" | "hcaptcha" | null = null; // TODO: hcaptcha, custom
sitekey: string | null = null;
secret: string | null = null;
}
@@ -0,0 +1,3 @@
export class TwoFactorConfiguration {
generateBackupCodes: boolean = true;
}
@@ -0,0 +1,2 @@
export * from "./Captcha";
export * from "./TwoFactor";
+1 -405
View File
@@ -1,9 +1,5 @@
import { Column, Entity } from "typeorm";
import { BaseClassWithoutId, PrimaryIdColumn } from "./BaseClass";
import crypto from "crypto";
import { Snowflake } from "../util/Snowflake";
import { SessionsReplace } from "..";
import { hostname } from "os";
@Entity("config")
export class ConfigEntity extends BaseClassWithoutId {
@@ -12,404 +8,4 @@ export class ConfigEntity extends BaseClassWithoutId {
@Column({ type: "simple-json", nullable: true })
value: number | boolean | null | string | undefined;
}
export interface RateLimitOptions {
bot?: number;
count: number;
window: number;
onyIp?: boolean;
}
export interface Region {
id: string;
name: string;
endpoint: string;
location?: {
latitude: number;
longitude: number;
};
vip: boolean;
custom: boolean;
deprecated: boolean;
}
export interface KafkaBroker {
ip: string;
port: number;
}
export interface ConfigValue {
gateway: {
endpointClient: string | null;
endpointPrivate: string | null;
endpointPublic: string | null;
};
cdn: {
endpointClient: string | null;
endpointPublic: string | null;
endpointPrivate: string | null;
};
api: {
defaultVersion: string;
activeVersions: string[];
useFosscordEnhancements: boolean;
};
general: {
instanceName: string;
instanceDescription: string | null;
frontPage: string | null;
tosPage: string | null;
correspondenceEmail: string | null;
correspondenceUserID: string | null;
image: string | null;
instanceId: string;
};
limits: {
user: {
maxGuilds: number;
maxUsername: number;
maxFriends: number;
};
guild: {
maxRoles: number;
maxEmojis: number;
maxMembers: number;
maxChannels: number;
maxChannelsInCategory: number;
hideOfflineMember: number;
};
message: {
maxCharacters: number;
maxTTSCharacters: number;
maxReactions: number;
maxAttachmentSize: number;
maxBulkDelete: number;
maxEmbedDownloadSize: number;
};
channel: {
maxPins: number;
maxTopic: number;
maxWebhooks: number;
};
rate: {
disabled: boolean;
ip: Omit<RateLimitOptions, "bot_count">;
global: RateLimitOptions;
error: RateLimitOptions;
routes: {
guild: RateLimitOptions;
webhook: RateLimitOptions;
channel: RateLimitOptions;
auth: {
login: RateLimitOptions;
register: RateLimitOptions;
};
// TODO: rate limit configuration for all routes
};
};
};
security: {
autoUpdate: boolean | number;
requestSignature: string;
jwtSecret: string;
forwadedFor: string | null; // header to get the real user ip address
captcha: {
enabled: boolean;
service: "recaptcha" | "hcaptcha" | null; // TODO: hcaptcha, custom
sitekey: string | null;
secret: string | null;
};
ipdataApiKey: string | null;
twoFactor: {
generateBackupCodes: boolean;
};
};
login: {
requireCaptcha: boolean;
};
register: {
defaultRights: string;
email: {
required: boolean;
allowlist: boolean;
blocklist: boolean;
domains: string[];
};
dateOfBirth: {
required: boolean;
minimum: number; // in years
};
disabled: boolean;
requireCaptcha: boolean;
requireInvite: boolean;
guestsRequireInvite: boolean;
allowNewRegistration: boolean;
allowMultipleAccounts: boolean;
blockProxies: boolean;
password: {
required: boolean;
minLength: number;
minNumbers: number;
minUpperCase: number;
minSymbols: number;
};
incrementingDiscriminators: boolean; // random otherwise
};
regions: {
default: string;
useDefaultAsOptimal: boolean;
available: Region[];
};
guild: {
discovery: {
showAllGuilds: boolean;
useRecommendation: boolean; // TODO: Recommendation, privacy concern?
offset: number;
limit: number;
};
autoJoin: {
enabled: boolean;
guilds: string[];
canLeave: boolean;
};
};
gif: {
enabled: boolean;
provider: "tenor"; // more coming soon
apiKey?: string;
};
rabbitmq: {
host: string | null;
};
kafka: {
brokers: KafkaBroker[] | null;
};
templates: {
enabled: Boolean;
allowTemplateCreation: Boolean;
allowDiscordTemplates: Boolean;
allowRaws: Boolean;
},
client: {
useTestClient: Boolean;
releases: {
useLocalRelease: Boolean; //TODO
upstreamVersion: string;
}
},
metrics: {
timeout: number;
},
sentry: {
enabled: boolean;
endpoint: string;
traceSampleRate: number;
environment: string;
}
}
export const DefaultConfigOptions: ConfigValue = {
gateway: {
endpointClient: null,
endpointPrivate: null,
endpointPublic: null,
},
cdn: {
endpointClient: null,
endpointPrivate: null,
endpointPublic: null,
},
api: {
defaultVersion: "9",
activeVersions: ["6", "7", "8", "9"],
useFosscordEnhancements: true,
},
general: {
instanceName: "Fosscord Instance",
instanceDescription: "This is a Fosscord instance made in pre-release days",
frontPage: null,
tosPage: null,
correspondenceEmail: "noreply@localhost.local",
correspondenceUserID: null,
image: null,
instanceId: Snowflake.generate(),
},
limits: {
user: {
maxGuilds: 1048576,
maxUsername: 127,
maxFriends: 5000,
},
guild: {
maxRoles: 1000,
maxEmojis: 2000,
maxMembers: 25000000,
maxChannels: 65535,
maxChannelsInCategory: 65535,
hideOfflineMember: 3,
},
message: {
maxCharacters: 1048576,
maxTTSCharacters: 160,
maxReactions: 2048,
maxAttachmentSize: 1024 * 1024 * 1024,
maxEmbedDownloadSize: 1024 * 1024 * 5,
maxBulkDelete: 1000,
},
channel: {
maxPins: 500,
maxTopic: 1024,
maxWebhooks: 100,
},
rate: {
disabled: true,
ip: {
count: 500,
window: 5,
},
global: {
count: 250,
window: 5,
},
error: {
count: 10,
window: 5,
},
routes: {
guild: {
count: 5,
window: 5,
},
webhook: {
count: 10,
window: 5,
},
channel: {
count: 10,
window: 5,
},
auth: {
login: {
count: 5,
window: 60,
},
register: {
count: 2,
window: 60 * 60 * 12,
},
},
},
},
},
security: {
autoUpdate: true,
requestSignature: crypto.randomBytes(32).toString("base64"),
jwtSecret: crypto.randomBytes(256).toString("base64"),
forwadedFor: null,
// forwadedFor: "X-Forwarded-For" // nginx/reverse proxy
// forwadedFor: "CF-Connecting-IP" // cloudflare:
captcha: {
enabled: false,
service: null,
sitekey: null,
secret: null,
},
ipdataApiKey: "eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9",
twoFactor: {
generateBackupCodes: true,
},
},
login: {
requireCaptcha: false,
},
register: {
email: {
required: false,
allowlist: false,
blocklist: true,
domains: [], // TODO: efficiently save domain blocklist in database
// domains: fs.readFileSync(__dirname + "/blockedEmailDomains.txt", { encoding: "utf8" }).split("\n"),
},
dateOfBirth: {
required: true,
minimum: 13,
},
disabled: false,
requireInvite: false,
guestsRequireInvite: true,
requireCaptcha: true,
allowNewRegistration: true,
allowMultipleAccounts: true,
blockProxies: true,
password: {
required: false,
minLength: 8,
minNumbers: 2,
minUpperCase: 2,
minSymbols: 0,
},
incrementingDiscriminators: false,
defaultRights: "0"
},
regions: {
default: "fosscord",
useDefaultAsOptimal: true,
available: [
{
id: "fosscord",
name: "Fosscord",
endpoint: "127.0.0.1:3004",
vip: false,
custom: false,
deprecated: false,
},
],
},
guild: {
discovery: {
showAllGuilds: false,
useRecommendation: false,
offset: 0,
limit: 24,
},
autoJoin: {
enabled: true,
canLeave: true,
guilds: [],
},
},
gif: {
enabled: true,
provider: "tenor",
apiKey: "LIVDSRZULELA",
},
rabbitmq: {
host: null,
},
kafka: {
brokers: null,
},
templates: {
enabled: true,
allowTemplateCreation: true,
allowDiscordTemplates: true,
allowRaws: false
},
client: {
useTestClient: true,
releases: {
useLocalRelease: true,
upstreamVersion: "0.0.264"
}
},
metrics: {
timeout: 30000
},
sentry: {
enabled: false,
endpoint: "https://05e8e3d005f34b7d97e920ae5870a5e5@sentry.thearcanebrony.net/6",
traceSampleRate: 1.0,
environment: hostname()
}
};
}
+11 -3
View File
@@ -1,6 +1,7 @@
import { ConfigValue, ConfigEntity, DefaultConfigOptions } from "../entities/Config";
import path from "path";
import { ConfigEntity } from "../entities/Config";
import fs from "fs";
import { ConfigValue } from "../config";
import { OrmUtils } from ".";
// TODO: yaml instead of json
const overridePath = process.env.CONFIG_PATH ?? "";
@@ -14,9 +15,11 @@ let pairs: ConfigEntity[];
export const Config = {
init: async function init() {
if (config) return config;
console.log('[Config] Loading configuration...')
pairs = await ConfigEntity.find();
config = pairsToConfig(pairs);
config = (config || {}).merge(DefaultConfigOptions);
//config = (config || {}).merge(new ConfigValue());
config = OrmUtils.mergeDeep(new ConfigValue(), config)
if(process.env.CONFIG_PATH)
try {
@@ -30,6 +33,11 @@ export const Config = {
return this.set(config);
},
get: function get() {
if(!config) {
if(/--debug|--inspect/.test(process.execArgv.join(' ')))
console.log("Oops.. trying to get config without config existing... Returning defaults... (Is the database still initialising?)");
return new ConfigValue();
}
return config;
},
set: function set(val: Partial<ConfigValue>) {