1
0
mirror of https://github.com/spacebarchat/server.git synced 2024-11-26 04:03:03 +01:00

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

This commit is contained in:
TheArcaneBrony 2022-08-11 23:11:48 +02:00 committed by Madeline
parent 94b07e5efb
commit bc669ea9e1
52 changed files with 356 additions and 424 deletions

View File

@ -6,13 +6,12 @@ import {
emitEvent, emitEvent,
Guild, Guild,
Member, Member,
Region,
VoiceServerUpdateEvent, VoiceServerUpdateEvent,
VoiceState, VoiceState,
VoiceStateUpdateEvent, VoiceStateUpdateEvent,
VoiceStateUpdateSchema, VoiceStateUpdateSchema,
} from "@fosscord/util"; } from "@fosscord/util";
import { Region } from "@fosscord/util/src/config";
// TODO: check if a voice server is setup // TODO: check if a voice server is setup
// Notice: Bot users respect the voice channel's user limit, if set. // Notice: Bot users respect the voice channel's user limit, if set.

22
src/util/config/Config.ts Normal file
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
src/util/config/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from "./Config";
export * from "./types/index";

View File

@ -0,0 +1,5 @@
export class ApiConfiguration {
defaultVersion: string = "9";
activeVersions: string[] = ["6", "7", "8", "9"];
useFosscordEnhancements: boolean = true;
}

View File

@ -0,0 +1,8 @@
import { ClientReleaseConfiguration } from ".";
export class ClientConfiguration {
//classes
releases: ClientReleaseConfiguration = new ClientReleaseConfiguration();
//base types
useTestClient: boolean = true;
}

View File

@ -0,0 +1,5 @@
import { GuildDefaults } from ".";
export class DefaultsConfiguration {
guild: GuildDefaults = new GuildDefaults();
}

View File

@ -0,0 +1,5 @@
export class EndpointConfiguration {
endpointClient: string | null = null;
endpointPrivate: string | null = null;
endpointPublic: string | null = null;
}

View File

@ -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();
}

View File

@ -0,0 +1,5 @@
export class GifConfiguration {
enabled: boolean = true;
provider: "tenor" = "tenor"; // more coming soon
apiKey?: string = "LIVDSRZULELA";
}

View File

@ -0,0 +1,6 @@
import { DiscoveryConfiguration, AutoJoinConfiguration } from ".";
export class GuildConfiguration {
discovery: DiscoveryConfiguration = new DiscoveryConfiguration();
autoJoin: AutoJoinConfiguration = new AutoJoinConfiguration();
}

View File

@ -0,0 +1,5 @@
import { KafkaBroker } from ".";
export class KafkaConfiguration {
brokers: KafkaBroker[] | null = null;
}

View File

@ -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();
}

View File

@ -0,0 +1,3 @@
export class LoginConfiguration {
requireCaptcha: boolean = false;
}

View File

@ -0,0 +1,3 @@
export class MetricsConfiguration {
timeout: number = 30000;
}

View File

@ -0,0 +1,3 @@
export class RabbitMQConfiguration {
host: string | null = null;
}

View File

@ -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,
},
];
}

View File

@ -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";
}

View File

@ -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";
}

View File

@ -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();
}

View File

@ -0,0 +1,6 @@
export class TemplateConfiguration {
enabled: boolean = true;
allowTemplateCreation: boolean = true;
allowDiscordTemplates: boolean = true;
allowRaws: boolean = true;
}

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";

View File

@ -0,0 +1,4 @@
export class ClientReleaseConfiguration {
useLocalRelease: boolean = true; //TODO
upstreamVersion: string = "0.0.264";
}

View File

@ -0,0 +1 @@
export * from "./ClientReleaseConfiguration";

View File

@ -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;
}

View File

@ -0,0 +1 @@
export * from "./GuildDefaults";

View File

@ -0,0 +1,5 @@
export class AutoJoinConfiguration {
enabled: boolean = true;
guilds: string[] = [];
canLeave: boolean = true;
}

View File

@ -0,0 +1,6 @@
export class DiscoveryConfiguration {
showAllGuilds: boolean = false;
useRecommendation: boolean = false; // TODO: Recommendation, privacy concern?
offset: number = 0;
limit: number = 24;
}

View File

@ -0,0 +1,2 @@
export * from "./AutoJoin";
export * from "./Discovery";

View File

@ -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";

View File

@ -0,0 +1,4 @@
export interface KafkaBroker {
ip: string;
port: number;
}

View File

@ -0,0 +1 @@
export * from "./KafkaBroker";

View File

@ -0,0 +1,5 @@
export class ChannelLimits {
maxPins: number = 500;
maxTopic: number = 1024;
maxWebhooks: number = 100;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -0,0 +1,5 @@
export class UserLimits {
maxGuilds: number = 1048576;
maxUsername: number = 127;
maxFriends: number = 5000;
}

View File

@ -0,0 +1,6 @@
export * from "./ChannelLimits";
export * from "./GuildLimits";
export * from "./MessageLimits";
export * from "./RateLimits";
export * from "./UserLimits";
export * from "./ratelimits/index";

View File

@ -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
};
}

View File

@ -0,0 +1,6 @@
export interface RateLimitOptions {
bot?: number;
count: number;
window: number;
onyIp?: boolean;
}

View File

@ -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
}

View File

@ -0,0 +1,3 @@
export * from "./Auth";
export * from "./RateLimitOptions";
export * from "./Route";

View File

@ -0,0 +1,12 @@
export interface Region {
id: string;
name: string;
endpoint: string;
location?: {
latitude: number;
longitude: number;
};
vip: boolean;
custom: boolean;
deprecated: boolean;
}

View File

@ -0,0 +1 @@
export * from "./Region";

View File

@ -0,0 +1,4 @@
export class DateOfBirthConfiguration {
required: boolean = true;
minimum: number = 13; // in years
}

View File

@ -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"),
}

View File

@ -0,0 +1,7 @@
export class PasswordConfiguration {
required: boolean = false;
minLength: number = 8;
minNumbers: number = 2;
minUpperCase: number =2;
minSymbols: number = 0;
}

View File

@ -0,0 +1,3 @@
export * from "./DateOfBirth";
export * from "./Email";
export * from "./Password";

View File

@ -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;
}

View File

@ -0,0 +1,3 @@
export class TwoFactorConfiguration {
generateBackupCodes: boolean = true;
}

View File

@ -0,0 +1,2 @@
export * from "./Captcha";
export * from "./TwoFactor";

View File

@ -1,8 +1,5 @@
import { Column, Entity } from "typeorm"; import { Column, Entity } from "typeorm";
import { BaseClassWithoutId, PrimaryIdColumn } from "./BaseClass"; import { BaseClassWithoutId, PrimaryIdColumn } from "./BaseClass";
import crypto from "crypto";
import { Snowflake } from "../util/Snowflake";
import { hostname } from "os";
@Entity("config") @Entity("config")
export class ConfigEntity extends BaseClassWithoutId { export class ConfigEntity extends BaseClassWithoutId {
@ -12,414 +9,3 @@ export class ConfigEntity extends BaseClassWithoutId {
@Column({ type: "simple-json", nullable: true }) @Column({ type: "simple-json", nullable: true })
value: number | boolean | null | string | undefined; 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;
resizeHeightMax: number | null;
resizeWidthMax: number | null;
imagorServerUrl: 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;
defaultRights: string;
};
login: {
requireCaptcha: boolean;
};
register: {
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;
};
defaultFeatures: string[];
};
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;
};
external: {
twitter: string | null;
};
}
export const DefaultConfigOptions: ConfigValue = {
gateway: {
endpointClient: null,
endpointPrivate: null,
endpointPublic: null,
},
cdn: {
endpointClient: null,
endpointPrivate: null,
endpointPublic: null,
resizeHeightMax: 1000,
resizeWidthMax: 1000,
imagorServerUrl: 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",
defaultRights: "30644591655940", // See `npm run generate:rights`
},
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,
},
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: [],
},
defaultFeatures: [],
},
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(),
},
external: {
twitter: null,
}
};

View File

@ -1,11 +1,6 @@
import "missing-native-js-functions"; import { ConfigEntity } from "../entities/Config";
import {
ConfigValue,
ConfigEntity,
DefaultConfigOptions,
} from "../entities/Config";
import path from "path";
import fs from "fs"; import fs from "fs";
import { ConfigValue } from "../config";
// TODO: yaml instead of json // TODO: yaml instead of json
const overridePath = process.env.CONFIG_PATH ?? ""; const overridePath = process.env.CONFIG_PATH ?? "";
@ -19,9 +14,10 @@ var pairs: ConfigEntity[];
export const Config = { export const Config = {
init: async function init() { init: async function init() {
if (config) return config; if (config) return config;
console.log('[Config] Loading configuration...')
pairs = await ConfigEntity.find(); pairs = await ConfigEntity.find();
config = pairsToConfig(pairs); config = pairsToConfig(pairs);
config = (config || {}).merge(DefaultConfigOptions); config = (config || {}).merge(new ConfigValue());
if (process.env.CONFIG_PATH) { if (process.env.CONFIG_PATH) {
console.log(`[Config] Using config path from environment rather than database.`); console.log(`[Config] Using config path from environment rather than database.`);