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/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/users/#id/profile.ts b/src/api/routes/users/#id/profile.ts
index 27717c79..fa1d52c3 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({ 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/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
|