diff --git a/assets/locales/en/auth.json b/assets/locales/en/auth.json
index a78d4d60..b6264a43 100644
--- a/assets/locales/en/auth.json
+++ b/assets/locales/en/auth.json
@@ -13,6 +13,7 @@
"EMAIL_ALREADY_REGISTERED": "Email is already registered",
"DATE_OF_BIRTH_UNDERAGE": "You need to be {{years}} years or older",
"CONSENT_REQUIRED": "You must agree to the Terms of Service and Privacy Policy.",
- "USERNAME_TOO_MANY_USERS": "Too many users have this username, please try another"
+ "USERNAME_TOO_MANY_USERS": "Too many users have this username, please try another",
+ "GUESTS_DISABLED": "Guest users are disabled"
}
}
diff --git a/assets/schemas.json b/assets/schemas.json
index e3200800..05650a4e 100644
--- a/assets/schemas.json
+++ b/assets/schemas.json
@@ -1105,6 +1105,9 @@
},
"code": {
"type": "string"
+ },
+ "email": {
+ "type": "string"
}
},
"additionalProperties": false,
diff --git a/src/api/middlewares/TestClient.ts b/src/api/middlewares/TestClient.ts
index 2784c8ab..2c195994 100644
--- a/src/api/middlewares/TestClient.ts
+++ b/src/api/middlewares/TestClient.ts
@@ -45,6 +45,9 @@ export default function TestClient(app: Application) {
res.set(name, value);
});
} else {
+ if(req.params.file.endsWith(".map")) {
+ return res.status(404).send("Not found");
+ }
console.log(`[TestClient] Downloading file not yet cached! Asset file: ${req.params.file}`);
response = await fetch(`https://discord.com/assets/${req.params.file}`, {
agent,
diff --git a/src/api/routes/auth/register.ts b/src/api/routes/auth/register.ts
index d3b5a59c..5cc28f7a 100644
--- a/src/api/routes/auth/register.ts
+++ b/src/api/routes/auth/register.ts
@@ -1,6 +1,6 @@
+import { getIpAdress, IPAnalysis, isProxy, route, verifyCaptcha } from "@fosscord/api";
+import { adjustEmail, Config, FieldErrors, generateToken, HTTPError, Invite, RegisterSchema, User } from "@fosscord/util";
import { Request, Response, Router } from "express";
-import { Config, generateToken, Invite, FieldErrors, User, adjustEmail, RegisterSchema } from "@fosscord/util";
-import { route, getIpAdress, IPAnalysis, isProxy, verifyCaptcha } from "@fosscord/api";
let bcrypt: any;
try {
@@ -9,7 +9,6 @@ try {
bcrypt = require("bcryptjs");
console.log("Warning: using bcryptjs because bcrypt is not installed! Performance will be affected.");
}
-import { HTTPError } from "@fosscord/util";
const router: Router = Router();
@@ -44,6 +43,12 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re
});
}
+ if (!register.allowGuests) {
+ throw FieldErrors({
+ email: { code: "GUESTS_DISABLED", message: req.t("auth:register.GUESTS_DISABLED") }
+ });
+ }
+
if (register.requireCaptcha && security.captcha.enabled) {
const { sitekey, service } = security.captcha;
if (!body.captcha_key) {
@@ -60,7 +65,7 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re
captcha_key: verify["error-codes"],
captcha_sitekey: sitekey,
captcha_service: service
- })
+ });
}
}
@@ -111,7 +116,8 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re
});
}
- if (register.dateOfBirth.required && !body.date_of_birth) {
+ // If no password is provided, this is a guest account
+ if (register.dateOfBirth.required && !body.date_of_birth && body.password) {
throw FieldErrors({
date_of_birth: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") }
});
diff --git a/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts b/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts
index fdd775b7..f8c78bc5 100644
--- a/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts
+++ b/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts
@@ -9,7 +9,7 @@ const skus = new Map([
[
{
id: "511651856145973248",
- name: "Individual Premium Tier 2 Monthly (Legacy)",
+ name: "Individual Premium Tier 3 Monthly (Legacy)",
interval: 1,
interval_count: 1,
tax_inclusive: true,
@@ -20,7 +20,7 @@ const skus = new Map([
},
{
id: "511651860671627264",
- name: "Individiual Premium Tier 2 Yearly (Legacy)",
+ name: "Individiual Premium Tier 3 Yearly (Legacy)",
interval: 2,
interval_count: 1,
tax_inclusive: true,
@@ -36,7 +36,7 @@ const skus = new Map([
[
{
id: "511651871736201216",
- name: "Individual Premium Tier 1 Monthly",
+ name: "Individual Premium Tier 2 Monthly",
interval: 1,
interval_count: 1,
tax_inclusive: true,
@@ -47,7 +47,7 @@ const skus = new Map([
},
{
id: "511651876987469824",
- name: "Individual Premum Tier 1 Yearly",
+ name: "Individual Premum Tier 2 Yearly",
interval: 2,
interval_count: 1,
tax_inclusive: true,
@@ -58,7 +58,7 @@ const skus = new Map([
},
{
id: "978380684370378761",
- name: "Individual Premum Tier 0",
+ name: "Individual Premum Tier 1",
interval: 2,
interval_count: 1,
tax_inclusive: true,
@@ -74,7 +74,7 @@ const skus = new Map([
[
{
id: "642251038925127690",
- name: "Individual Premium Tier 2 Quarterly",
+ name: "Individual Premium Tier 3 Quarterly",
interval: 1,
interval_count: 3,
tax_inclusive: true,
@@ -85,7 +85,7 @@ const skus = new Map([
},
{
id: "511651880837840896",
- name: "Individual Premium Tier 2 Monthly",
+ name: "Individual Premium Tier 3 Monthly",
interval: 1,
interval_count: 1,
tax_inclusive: true,
@@ -96,7 +96,7 @@ const skus = new Map([
},
{
id: "511651885459963904",
- name: "Individual Premium Tier 2 Yearly",
+ name: "Individual Premium Tier 3 Yearly",
interval: 2,
interval_count: 1,
tax_inclusive: true,
@@ -134,6 +134,166 @@ const skus = new Map([
price: 0,
price_tier: null
}
+ ],
+ ],
+ [
+ "978380684370378762",
+ [
+ [
+ {
+ "id": "978380692553465866",
+ "name": "Premium Tier 0 Monthly",
+ "interval": 1,
+ "interval_count": 1,
+ "tax_inclusive": true,
+ "sku_id": "978380684370378762",
+ "currency": "usd",
+ "price": 299,
+ "price_tier": null,
+ "prices": {
+ "0": {
+ "country_prices": {
+ "country_code": "US",
+ "prices": [
+ {
+ "currency": "usd",
+ "amount": 0,
+ "exponent": 2
+ }
+ ]
+ },
+ "payment_source_prices": {
+ "775487223059316758": [
+ {
+ "currency": "usd",
+ "amount": 0,
+ "exponent": 2
+ }
+ ],
+ "736345864146255982": [
+ {
+ "currency": "usd",
+ "amount": 0,
+ "exponent": 2
+ }
+ ],
+ "683074999590060249": [
+ {
+ "currency": "usd",
+ "amount": 0,
+ "exponent": 2
+ }
+ ]
+ }
+ },
+ "3": {
+ "country_prices": {
+ "country_code": "US",
+ "prices": [
+ {
+ "currency": "usd",
+ "amount": 0,
+ "exponent": 2
+ }
+ ]
+ },
+ "payment_source_prices": {
+ "775487223059316758": [
+ {
+ "currency": "usd",
+ "amount": 0,
+ "exponent": 2
+ }
+ ],
+ "736345864146255982": [
+ {
+ "currency": "usd",
+ "amount": 0,
+ "exponent": 2
+ }
+ ],
+ "683074999590060249": [
+ {
+ "currency": "usd",
+ "amount": 0,
+ "exponent": 2
+ }
+ ]
+ }
+ },
+ "4": {
+ "country_prices": {
+ "country_code": "US",
+ "prices": [
+ {
+ "currency": "usd",
+ "amount": 0,
+ "exponent": 2
+ }
+ ]
+ },
+ "payment_source_prices": {
+ "775487223059316758": [
+ {
+ "currency": "usd",
+ "amount": 0,
+ "exponent": 2
+ }
+ ],
+ "736345864146255982": [
+ {
+ "currency": "usd",
+ "amount": 0,
+ "exponent": 2
+ }
+ ],
+ "683074999590060249": [
+ {
+ "currency": "usd",
+ "amount": 0,
+ "exponent": 2
+ }
+ ]
+ }
+ },
+ "1": {
+ "country_prices": {
+ "country_code": "US",
+ "prices": [
+ {
+ "currency": "usd",
+ "amount": 0,
+ "exponent": 2
+ }
+ ]
+ },
+ "payment_source_prices": {
+ "775487223059316758": [
+ {
+ "currency": "usd",
+ "amount": 0,
+ "exponent": 2
+ }
+ ],
+ "736345864146255982": [
+ {
+ "currency": "usd",
+ "amount": 0,
+ "exponent": 2
+ }
+ ],
+ "683074999590060249": [
+ {
+ "currency": "usd",
+ "amount": 0,
+ "exponent": 2
+ }
+ ]
+ }
+ }
+ }
+ }
+ ]
]
]
]);
diff --git a/src/api/routes/users/#id/profile.ts b/src/api/routes/users/#id/profile.ts
index 27717c79..766c9880 100644
--- a/src/api/routes/users/#id/profile.ts
+++ b/src/api/routes/users/#id/profile.ts
@@ -13,45 +13,81 @@ export interface UserProfileResponse {
router.get("/", route({ test: { response: { body: "UserProfileResponse" } } }), async (req: Request, res: Response) => {
if (req.params.id === "@me") req.params.id = req.user_id;
+
+ const { guild_id, with_mutual_guilds } = req.query;
+
const user = await User.getPublicUser(req.params.id, { relations: ["connected_accounts"] });
- let mutual_guilds: object[] = [];
- let premium_guild_since;
- const requested_member = await Member.find({ where: { id: req.params.id } });
- const self_member = await Member.find({ where: { id: req.user_id } });
+ var mutual_guilds: object[] = [];
+ var premium_guild_since;
+
+ if (with_mutual_guilds == "true") {
+ const requested_member = await Member.find({ where: { id: req.params.id } });
+ const self_member = await Member.find({ where: { id: req.user_id } });
- for (const rmem of requested_member) {
- if (rmem.premium_since) {
- if (premium_guild_since) {
- if (premium_guild_since > rmem.premium_since) {
+ for (const rmem of requested_member) {
+ if (rmem.premium_since) {
+ if (premium_guild_since) {
+ if (premium_guild_since > rmem.premium_since) {
+ premium_guild_since = rmem.premium_since;
+ }
+ } else {
premium_guild_since = rmem.premium_since;
}
- } else {
- premium_guild_since = rmem.premium_since;
}
- }
- for (const smem of self_member) {
- if (smem.guild_id === rmem.guild_id) {
- mutual_guilds.push({ id: rmem.guild_id, nick: rmem.nick });
+ for (const smem of self_member) {
+ if (smem.guild_id === rmem.guild_id) {
+ mutual_guilds.push({ id: rmem.guild_id, nick: rmem.nick });
+ }
}
}
}
+
+ const guild_member =
+ guild_id && typeof guild_id == "string"
+ ? await Member.findOneOrFail({ where: { id: req.params.id, guild_id: guild_id }, relations: ["roles"] })
+ : undefined;
+
+ // TODO: make proper DTO's in util?
+
+ const userDto = {
+ username: user.username,
+ discriminator: user.discriminator,
+ id: user.id,
+ public_flags: user.public_flags,
+ avatar: user.avatar,
+ accent_color: user.accent_color,
+ banner: user.banner,
+ bio: req.user_bot ? null : user.bio,
+ bot: user.bot
+ };
+
+ const guildMemberDto = guild_member
+ ? {
+ avatar: user.avatar, // TODO
+ banner: user.banner, // TODO
+ bio: req.user_bot ? null : user.bio, // TODO
+ communication_disabled_until: null, // TODO
+ deaf: guild_member.deaf,
+ flags: user.flags,
+ is_pending: guild_member.pending,
+ pending: guild_member.pending, // why is this here twice, discord?
+ joined_at: guild_member.joined_at,
+ mute: guild_member.mute,
+ nick: guild_member.nick,
+ premium_since: guild_member.premium_since,
+ roles: guild_member.roles.map((x) => x.id).filter((id) => id != guild_id),
+ user: userDto
+ }
+ : undefined;
+
res.json({
connected_accounts: user.connected_accounts,
premium_guild_since: premium_guild_since, // TODO
premium_since: user.premium_since, // TODO
mutual_guilds: mutual_guilds, // TODO {id: "", nick: null} when ?with_mutual_guilds=true
- user: {
- username: user.username,
- discriminator: user.discriminator,
- id: user.id,
- public_flags: user.public_flags,
- avatar: user.avatar,
- accent_color: user.accent_color,
- banner: user.banner,
- bio: req.user_bot ? null : user.bio,
- bot: user.bot
- }
+ user: userDto,
+ guild_member: guildMemberDto
});
});
diff --git a/src/api/routes/users/@me/index.ts b/src/api/routes/users/@me/index.ts
index fcb0a9df..563300dc 100644
--- a/src/api/routes/users/@me/index.ts
+++ b/src/api/routes/users/@me/index.ts
@@ -1,5 +1,7 @@
import { route } from "@fosscord/api";
import {
+ adjustEmail,
+ Config,
emitEvent,
FieldErrors,
generateToken,
@@ -45,6 +47,13 @@ router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res:
}
}
+ if (body.email) {
+ body.email = adjustEmail(body.email);
+ if (!body.email && Config.get().register.email.required)
+ throw FieldErrors({ email: { message: req.t("auth:register.EMAIL_INVALID"), code: "EMAIL_INVALID" } });
+ if (!body.password) throw FieldErrors({ password: { message: req.t("auth:register.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } });
+ }
+
if (body.new_password) {
if (!body.password && !user.email) {
throw FieldErrors({
diff --git a/src/util/config/types/RegisterConfiguration.ts b/src/util/config/types/RegisterConfiguration.ts
index 939605a6..68946272 100644
--- a/src/util/config/types/RegisterConfiguration.ts
+++ b/src/util/config/types/RegisterConfiguration.ts
@@ -9,6 +9,7 @@ export class RegisterConfiguration {
disabled: boolean = false;
requireCaptcha: boolean = true;
requireInvite: boolean = false;
+ allowGuests: boolean = true;
guestsRequireInvite: boolean = true;
allowNewRegistration: boolean = true;
allowMultipleAccounts: boolean = true;
diff --git a/src/util/config/types/SecurityConfiguration.ts b/src/util/config/types/SecurityConfiguration.ts
index 98c04c99..a2cebbd3 100644
--- a/src/util/config/types/SecurityConfiguration.ts
+++ b/src/util/config/types/SecurityConfiguration.ts
@@ -14,4 +14,6 @@ export class SecurityConfiguration {
// CF-Connecting-IP for cloudflare
forwadedFor: string | null = null;
ipdataApiKey: string | null = "eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9";
+ mfaBackupCodeCount: number = 10;
+ mfaBackupCodeBytes: number = 4;
}
diff --git a/src/util/entities/Channel.ts b/src/util/entities/Channel.ts
index 23fc6544..b17fdba0 100644
--- a/src/util/entities/Channel.ts
+++ b/src/util/entities/Channel.ts
@@ -181,10 +181,16 @@ export class Channel extends BaseClass {
for (let character of InvisibleCharacters)
if (channel.name.includes(character)) throw new HTTPError("Channel name cannot include invalid characters", 403);
- if (channel.name.match(/\-\-+/g)) throw new HTTPError("Channel name cannot include multiple adjacent dashes.", 403);
+ // Categories and voice skip these checks on discord.com
+ const skipChecksTypes = [ChannelType.GUILD_CATEGORY, ChannelType.GUILD_VOICE];
+ if ((channel.type && !skipChecksTypes.includes(channel.type)) || guild.features.includes("IRC_LIKE_CHANNEL_NAMES")) {
+ if (channel.name.includes(" ")) throw new HTTPError("Channel name cannot include invalid characters", 403);
- if (channel.name.charAt(0) === "-" || channel.name.charAt(channel.name.length - 1) === "-")
- throw new HTTPError("Channel name cannot start/end with dash.", 403);
+ if (channel.name.match(/\-\-+/g)) throw new HTTPError("Channel name cannot include multiple adjacent dashes.", 403);
+
+ if (channel.name.charAt(0) === "-" || channel.name.charAt(channel.name.length - 1) === "-")
+ throw new HTTPError("Channel name cannot start/end with dash.", 403);
+ } else channel.name = channel.name.trim(); //category names are trimmed client side on discord.com
}
if (!guild.features.includes("ALLOW_UNNAMED_CHANNELS")) {
diff --git a/src/util/schemas/UserModifySchema.ts b/src/util/schemas/UserModifySchema.ts
index d69f83f4..622497d9 100644
--- a/src/util/schemas/UserModifySchema.ts
+++ b/src/util/schemas/UserModifySchema.ts
@@ -15,4 +15,5 @@ export interface UserModifySchema {
password?: string;
new_password?: string;
code?: string;
+ email?: string;
}
diff --git a/src/util/util/InvisibleCharacters.ts b/src/util/util/InvisibleCharacters.ts
index 4c809e48..9bffec58 100644
--- a/src/util/util/InvisibleCharacters.ts
+++ b/src/util/util/InvisibleCharacters.ts
@@ -1,9 +1,10 @@
// List from https://invisible-characters.com/
export const InvisibleCharacters = [
"\u{9}", //Tab
- "\u{20}", //Space
+ "\u{c}", //Form feed
+ //'\u{20}', //Space //categories can have spaces in them
"\u{ad}", //Soft hyphen
- "\u{34f}", //Combining grapheme joiner
+ // '\u{34f}', //Combining grapheme joiner
"\u{61c}", //Arabic letter mark
"\u{115f}", //Hangul choseong filler
"\u{1160}", //Hangul jungseong filler
@@ -23,12 +24,12 @@ export const InvisibleCharacters = [
"\u{200a}", //Hair space
"\u{200b}", //Zero width space
"\u{200c}", //Zero width non-joiner
- "\u{200d}", //Zero width joiner
+ // '\u{200d}', //Zero width joiner
"\u{200e}", //Left-to-right mark
"\u{200f}", //Right-to-left mark
"\u{202f}", //Narrow no-break space
"\u{205f}", //Medium mathematical space
- "\u{2060}", //Word joiner
+ // '\u{2060}', //Word joiner -- WJ is required in some languages that don't use spaces to split words
"\u{2061}", //Function application
"\u{2062}", //Invisible times
"\u{2063}", //Invisible separator
diff --git a/src/util/util/MFA.ts b/src/util/util/MFA.ts
index a2afcad6..b9af6d23 100644
--- a/src/util/util/MFA.ts
+++ b/src/util/util/MFA.ts
@@ -1,12 +1,13 @@
import crypto from "crypto";
+import { Config } from ".";
import { BackupCode } from "../entities/BackupCodes";
export function generateMfaBackupCodes(user_id: string) {
let backup_codes: BackupCode[] = [];
- for (let i = 0; i < 10; i++) {
+ for (let i = 0; i < Config.get().security.mfaBackupCodeCount; i++) {
const code = BackupCode.create({
user: { id: user_id },
- code: crypto.randomBytes(4).toString("hex"), // 8 characters
+ code: crypto.randomBytes(Config.get().security.mfaBackupCodeBytes).toString("hex"), // 8 characters
consumed: false,
expired: false
});
|