diff --git a/src/index.ts b/src/index.ts index c153d20..d9443f9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,6 +32,8 @@ import { handler as berryDashIconMarketplacePostHandler } from './routes/berryda import { handler as berryDashAccountLoginPostHandler } from './routes/berrydash/account/login/post' import { handler as berryDashAccountRegisterPostHandler } from './routes/berrydash/account/register/post' +import { handler as berryDashAccountChangeUsernamePostHandler } from './routes/berrydash/account/change-username/post' +import { handler as berryDashAccountChangePasswordPostHandler } from './routes/berrydash/account/change-password/post' import { handler as berryDashAccountSaveGetHandler } from './routes/berrydash/account/save/get' import { handler as berryDashAccountSavePostHandler } from './routes/berrydash/account/save/post' @@ -392,34 +394,6 @@ app.post( }) } ) -app.get( - '/berrydash/account/save', - context => berryDashAccountSaveGetHandler(context), - { - detail: { - description: - "The endpoint for getting the account's save file. The contents will fully replace the current save file entirely on the client.", - tags: ['Berry Dash', 'Accounts'] - }, - headers: t.Object({ - authorization: t.String() - }) - } -) -app.post( - '/berrydash/account/save', - context => berryDashAccountSavePostHandler(context), - { - detail: { - description: - "The endpoint for overwriting the account's save file on the server.", - tags: ['Berry Dash', 'Accounts'] - }, - headers: t.Object({ - authorization: t.String() - }) - } -) app.post( '/berrydash/account/login', context => berryDashAccountLoginPostHandler(context), @@ -451,6 +425,66 @@ app.post( }) } ) +app.post( + '/berrydash/account/change-username', + context => berryDashAccountChangeUsernamePostHandler(context), + { + detail: { + description: "The endpoint for changing the account's user name.", + tags: ['Berry Dash', 'Accounts'] + }, + body: t.Object({ + newUsername: t.String() + }), + headers: t.Object({ + authorization: t.String() + }) + } +) +app.post( + '/berrydash/account/change-password', + context => berryDashAccountChangePasswordPostHandler(context), + { + detail: { + description: "The endpoint for changing the account's password.", + tags: ['Berry Dash', 'Accounts'] + }, + body: t.Object({ + newPassword: t.String() + }), + headers: t.Object({ + authorization: t.String() + }) + } +) +app.get( + '/berrydash/account/save', + context => berryDashAccountSaveGetHandler(context), + { + detail: { + description: + "The endpoint for getting the account's save file. The contents will fully replace the current save file entirely on the client.", + tags: ['Berry Dash', 'Accounts'] + }, + headers: t.Object({ + authorization: t.String() + }) + } +) +app.post( + '/berrydash/account/save', + context => berryDashAccountSavePostHandler(context), + { + detail: { + description: + "The endpoint for overwriting the account's save file on the server.", + tags: ['Berry Dash', 'Accounts'] + }, + headers: t.Object({ + authorization: t.String() + }) + } +) app.all('*', () => jsonResponse( { diff --git a/src/routes/berrydash/account/change-password/post.ts b/src/routes/berrydash/account/change-password/post.ts new file mode 100644 index 0000000..9adbce4 --- /dev/null +++ b/src/routes/berrydash/account/change-password/post.ts @@ -0,0 +1,80 @@ +import { Context } from 'elysia' +import { + getClientIp, + getDatabaseConnection, + jsonResponse +} from '../../../../lib/util' +import { checkAuthorization } from '../../../../lib/bd/auth' +import { users } from '../../../../lib/tables' +import { eq } from 'drizzle-orm' +import bcrypt from 'bcryptjs' + +type Body = { + newPassword: string +} + +export async function handler (context: Context) { + const dbInfo0 = getDatabaseConnection(0) + const dbInfo1 = getDatabaseConnection(1) + + if (!dbInfo0 || !dbInfo1) + return jsonResponse( + { success: false, message: 'Failed to connect to database' }, + 500 + ) + const { connection: connection0, db: db0 } = dbInfo0 + const { connection: connection1, db: db1 } = dbInfo1 + + const ip = getClientIp(context) + const authorizationToken = context.headers.authorization + const authResult = await checkAuthorization( + authorizationToken as string, + db1, + db0, + ip + ) + if (!authResult.valid) { + connection0.end() + connection1.end() + return jsonResponse({ success: false, message: 'Unauthorized' }, 401) + } + const userId = authResult.id + + const body = context.body as Body + if (!body.newPassword) { + connection0.end() + connection1.end() + return jsonResponse( + { success: false, message: 'No new password provided' }, + 400 + ) + } + + if ( + !/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d!@#$%^&*()_\-+=]{8,}$/.test( + body.newPassword + ) + ) { + connection0.end() + connection1.end() + return jsonResponse( + { + success: false, + message: + 'New password must be at least 8 characters with at least one letter and one number', + data: null + }, + 400 + ) + } + + const hashedPassword = await bcrypt.hash(body.newPassword, 10) + + await db0 + .update(users) + .set({ password: hashedPassword }) + .where(eq(users.id, userId)) + .execute() + + return jsonResponse({ success: true, message: null }) +} diff --git a/src/routes/berrydash/account/change-username/post.ts b/src/routes/berrydash/account/change-username/post.ts new file mode 100644 index 0000000..88c141e --- /dev/null +++ b/src/routes/berrydash/account/change-username/post.ts @@ -0,0 +1,73 @@ +import { Context } from 'elysia' +import { + getClientIp, + getDatabaseConnection, + jsonResponse +} from '../../../../lib/util' +import { checkAuthorization } from '../../../../lib/bd/auth' +import { users } from '../../../../lib/tables' +import { eq } from 'drizzle-orm' + +type Body = { + newUsername: string +} + +export async function handler (context: Context) { + const dbInfo0 = getDatabaseConnection(0) + const dbInfo1 = getDatabaseConnection(1) + + if (!dbInfo0 || !dbInfo1) + return jsonResponse( + { success: false, message: 'Failed to connect to database' }, + 500 + ) + const { connection: connection0, db: db0 } = dbInfo0 + const { connection: connection1, db: db1 } = dbInfo1 + + const ip = getClientIp(context) + const authorizationToken = context.headers.authorization + const authResult = await checkAuthorization( + authorizationToken as string, + db1, + db0, + ip + ) + if (!authResult.valid) { + connection0.end() + connection1.end() + return jsonResponse({ success: false, message: 'Unauthorized' }, 401) + } + const userId = authResult.id + + const body = context.body as Body + if (!body.newUsername) { + connection0.end() + connection1.end() + return jsonResponse( + { success: false, message: 'No new username provided' }, + 400 + ) + } + + if (!/^[a-zA-Z0-9]{3,16}$/.test(body.newUsername)) { + connection0.end() + connection1.end() + return jsonResponse( + { + success: false, + message: + 'New username must be 3-16 characters, letters and numbers only', + data: null + }, + 400 + ) + } + + await db0 + .update(users) + .set({ username: body.newUsername }) + .where(eq(users.id, userId)) + .execute() + + return jsonResponse({ success: true, message: null }) +}