summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorPuyodead1 <puyodead@protonmail.com>2023-09-08 21:33:04 -0400
committerPuyodead1 <puyodead@protonmail.com>2023-12-23 16:38:15 -0500
commit4d027a424e442f10371d205e68aa3bf63d0eba0e (patch)
treecfce10a0f47f497ce89f4f18ec31cc685cb2941a /src
parentupdate user modify for unique usernames (diff)
downloadserver-4d027a424e442f10371d205e68aa3bf63d0eba0e.tar.xz
Implement Pomelo Registration
Diffstat (limited to 'src')
-rw-r--r--src/api/routes/auth/register.ts11
-rw-r--r--src/api/routes/unique-username/username-attempt-unauthed.ts33
-rw-r--r--src/api/routes/unique-username/username-suggestions-unauthed.ts37
-rw-r--r--src/api/routes/users/@me/index.ts6
-rw-r--r--src/util/entities/User.ts40
-rw-r--r--src/util/schemas/UsernameAttemptUnauthedSchema.ts3
-rw-r--r--src/util/schemas/index.ts1
-rw-r--r--src/util/schemas/responses/UsernameAttemptResponse.ts3
8 files changed, 128 insertions, 6 deletions
diff --git a/src/api/routes/auth/register.ts b/src/api/routes/auth/register.ts

index de1cbd3d..33fc6062 100644 --- a/src/api/routes/auth/register.ts +++ b/src/api/routes/auth/register.ts
@@ -50,9 +50,18 @@ router.post( }), async (req: Request, res: Response) => { const body = req.body as RegisterSchema; - const { register, security, limits } = Config.get(); + const { register, security, limits, general } = Config.get(); const ip = getIpAdress(req); + if (!general.uniqueUsernames && body.unique_username_registration) { + throw FieldErrors({ + unique_username_registration: { + code: "UNIQUE_USERNAMES_DISABLED", + message: req.t("auth:register.UNIQUE_USERNAMES_DISABLED"), + }, + }); + } + // Reg tokens // They're a one time use token that bypasses registration limits ( rates, disabled reg, etc ) let regTokenUsed = false; diff --git a/src/api/routes/unique-username/username-attempt-unauthed.ts b/src/api/routes/unique-username/username-attempt-unauthed.ts new file mode 100644
index 00000000..a1f63a69 --- /dev/null +++ b/src/api/routes/unique-username/username-attempt-unauthed.ts
@@ -0,0 +1,33 @@ +import { route } from "@spacebar/api"; +import { Config, User, UsernameAttemptUnauthedSchema } from "@spacebar/util"; +import { Request, Response, Router } from "express"; +import { HTTPError } from "lambert-server"; +const router = Router(); + +router.post( + "/", + route({ + requestBody: "UsernameAttemptUnauthedSchema", + responses: { + 200: { body: "UsernameAttemptResponse" }, + 400: { body: "APIErrorResponse" }, + }, + description: "Check if a username is available", + }), + async (req: Request, res: Response) => { + const body = req.body as UsernameAttemptUnauthedSchema; + const { uniqueUsernames } = Config.get().general; + if (!uniqueUsernames) { + throw new HTTPError( + "Unique Usernames feature is not enabled on this instance.", + 400, + ); + } + + res.json({ + taken: !User.isUsernameAvailable(body.username), + }); + }, +); + +export default router; diff --git a/src/api/routes/unique-username/username-suggestions-unauthed.ts b/src/api/routes/unique-username/username-suggestions-unauthed.ts new file mode 100644
index 00000000..9b112b55 --- /dev/null +++ b/src/api/routes/unique-username/username-suggestions-unauthed.ts
@@ -0,0 +1,37 @@ +import { route } from "@spacebar/api"; +import { Request, Response, Router } from "express"; +import { HTTPError } from "lambert-server"; +import { Config } from "../../../util"; +const router = Router(); + +router.get( + "/", + route({ + query: { + global_name: { + type: "string", + required: false, + }, + }, + responses: { + 400: { body: "APIErrorResponse" }, + }, + }), + async (req: Request, res: Response) => { + const globalName = req.query.globalName as string | undefined; + const { uniqueUsernames } = Config.get().general; + if (!uniqueUsernames) { + throw new HTTPError( + "Unique Usernames feature is not enabled on this instance.", + 400, + ); + } + + // return a random suggestion + if (!globalName) return res.json({ username: "" }); + // return a suggestion based on the globalName + return res.json({ username: globalName }); + }, +); + +export default router; diff --git a/src/api/routes/users/@me/index.ts b/src/api/routes/users/@me/index.ts
index f4578126..55d2ce12 100644 --- a/src/api/routes/users/@me/index.ts +++ b/src/api/routes/users/@me/index.ts
@@ -172,10 +172,7 @@ router.patch( } // check if username is already taken (pomelo only) - const userCount = await User.count({ - where: { username: body.username }, - }); - if (userCount > 0) { + if (!User.isUsernameAvailable(body.username)) throw FieldErrors({ username: { code: "USERNAME_ALREADY_TAKEN", @@ -184,7 +181,6 @@ router.patch( ), }, }); - } } // handle username changes (old username system) diff --git a/src/util/entities/User.ts b/src/util/entities/User.ts
index d1fbb5c2..5ec9862e 100644 --- a/src/util/entities/User.ts +++ b/src/util/entities/User.ts
@@ -344,6 +344,7 @@ export class User extends BaseClass { password, id, req, + global_name, }: { username: string; password?: string; @@ -351,8 +352,10 @@ export class User extends BaseClass { date_of_birth?: Date; // "2000-04-03" id?: string; req?: Request; + global_name?: string; }) { const { uniqueUsernames } = Config.get().general; + const { minUsername, maxUsername } = Config.get().limits.user; // trim special uf8 control characters -> Backspace, Newline, ... username = trimSpecial(username); @@ -374,6 +377,34 @@ export class User extends BaseClass { } } + if (uniqueUsernames) { + // check if there is already an account with this username + if (!User.isUsernameAvailable(username)) + throw FieldErrors({ + username: { + code: "USERNAME_ALREADY_TAKEN", + message: + req?.t("common:field.USERNAME_ALREADY_TAKEN") || "", + }, + }); + + // validate username length + if ( + username.length < minUsername || + username.length > maxUsername + ) { + throw FieldErrors({ + username: { + code: "BASE_TYPE_BAD_LENGTH", + message: + req?.t("common:field.BASE_TYPE_BAD_LENGTH", { + length: `${minUsername} and ${maxUsername}`, + }) || "", + }, + }); + } + } + // TODO: save date_of_birth // appearently discord doesn't save the date of birth and just calculate if nsfw is allowed // if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false @@ -386,6 +417,7 @@ export class User extends BaseClass { const user = User.create({ username: uniqueUsernames ? username.toLowerCase() : username, + global_name: uniqueUsernames ? global_name : undefined, discriminator, id: id || Snowflake.generate(), email: email, @@ -429,6 +461,14 @@ export class User extends BaseClass { return user; } + + static async isUsernameAvailable(username: string) { + const user = await User.findOne({ + where: { username }, + select: ["id"], + }); + return !user; + } } export const CUSTOM_USER_FLAG_OFFSET = BigInt(1) << BigInt(32); diff --git a/src/util/schemas/UsernameAttemptUnauthedSchema.ts b/src/util/schemas/UsernameAttemptUnauthedSchema.ts new file mode 100644
index 00000000..0ac83dd0 --- /dev/null +++ b/src/util/schemas/UsernameAttemptUnauthedSchema.ts
@@ -0,0 +1,3 @@ +export interface UsernameAttemptUnauthedSchema { + username: string; +} diff --git a/src/util/schemas/index.ts b/src/util/schemas/index.ts
index 44a504cd..bb449e45 100644 --- a/src/util/schemas/index.ts +++ b/src/util/schemas/index.ts
@@ -72,6 +72,7 @@ export * from "./UserModifySchema"; export * from "./UserNoteUpdateSchema"; export * from "./UserProfileModifySchema"; export * from "./UserSettingsSchema"; +export * from "./UsernameAttemptUnauthedSchema"; export * from "./Validator"; export * from "./VanityUrlSchema"; export * from "./VoiceIdentifySchema"; diff --git a/src/util/schemas/responses/UsernameAttemptResponse.ts b/src/util/schemas/responses/UsernameAttemptResponse.ts new file mode 100644
index 00000000..864a3bb0 --- /dev/null +++ b/src/util/schemas/responses/UsernameAttemptResponse.ts
@@ -0,0 +1,3 @@ +export interface UsernameAttemptResponse { + taken: boolean; +}