Add reset-password endpoint

This commit is contained in:
2026-01-23 21:00:50 -07:00
parent c1216abe07
commit 3dd5d34095
2 changed files with 139 additions and 0 deletions

View File

@@ -16,6 +16,7 @@ import { handler as launcherLoaderUpdateDataHandler } from './routes/launcher/lo
import { handler as accountForgotUsernamePostHandler } from './routes/account/forgot-username/post'
import { handler as accountForgotPasswordPostHandler } from './routes/account/forgot-password/post'
import { handler as accountResetPasswordPostHandler } from './routes/account/reset-password/post'
import { handler as berryDashLatestVersionGetHandler } from './routes/berrydash/latest-version/get'
@@ -165,6 +166,17 @@ app.post('/account/forgot-password', accountForgotPasswordPostHandler, {
verifyCode: t.String()
})
})
app.post('/account/reset-password', accountResetPasswordPostHandler, {
detail: {
description: 'The endpoint for resetting the password for an account.',
tags: ['Accounts']
},
body: t.Object({
token: t.String(),
code: t.String(),
password: t.String()
})
})
app.get('/berrydash/latest-version', berryDashLatestVersionGetHandler, {
detail: {
description: 'The endpoint for getting the latest berry dash version.',

View File

@@ -0,0 +1,127 @@
import { Context } from 'elysia'
import {
getClientIp,
getDatabaseConnection,
jsonResponse,
validateTurnstile
} from '../../../lib/util'
import { resetCodes, users } from '../../../lib/tables'
import { and, desc, eq, sql } from 'drizzle-orm'
import bcrypt from 'bcryptjs'
type Body = {
token: string
code: string
password: string
}
export async function handler (context: Context) {
const dbInfo0 = getDatabaseConnection(0)
if (!dbInfo0)
return jsonResponse(
{ success: false, message: 'Failed to connect to database' },
500
)
const { connection: connection0, db: db0 } = dbInfo0
const body = context.body as Body
if (!body.token || !body.code || !body.password) {
connection0.end()
return jsonResponse(
{
success: false,
message: 'Token, code and password must be in POST data'
},
400
)
}
if (body.code.length != 64) {
connection0.end()
return jsonResponse(
{
success: false,
message: 'Invalid verify code (codes can only be used once)'
},
400
)
}
const ip = getClientIp(context)
if (!ip) {
connection0.end()
return jsonResponse(
{
success: false,
message: 'Failed to get required info'
},
400
)
}
const result = await validateTurnstile(body.token, ip)
if (!result.success) {
connection0.end()
return jsonResponse(
{
success: false,
message: 'Unable to verify captcha key'
},
400
)
}
const time = Math.floor(Date.now() / 1000)
const codeExists = await db0
.select({ id: resetCodes.id, userId: resetCodes.userId })
.from(resetCodes)
.where(
and(
eq(resetCodes.ip, ip),
eq(resetCodes.usedTimestamp, 0),
eq(resetCodes.code, body.code),
sql`${resetCodes.timestamp} >= UNIX_TIMESTAMP() - 600`,
eq(resetCodes.type, 0)
)
)
.orderBy(desc(resetCodes.id))
.limit(1)
.execute()
if (codeExists[0]) {
await db0
.update(resetCodes)
.set({ usedTimestamp: time })
.where(
and(
eq(resetCodes.id, codeExists[0].id),
eq(resetCodes.ip, ip),
eq(resetCodes.usedTimestamp, 0),
eq(resetCodes.code, body.code),
eq(resetCodes.type, 0)
)
)
.execute()
const hashedPassword = await bcrypt.hash(body.password, 10)
await db0
.update(users)
.set({ password: hashedPassword })
.where(eq(users.id, codeExists[0].userId))
.execute()
connection0.end()
return jsonResponse(
{
success: true,
message: null
},
200
)
} else {
connection0.end()
return jsonResponse(
{
success: false,
message: 'Invalid reset code (codes can only be used once)'
},
400
)
}
}