Switch to a system where you can use either a verifyCode or a captcha token for any endpoint with one or the other
This commit is contained in:
23
src/index.ts
23
src/index.ts
@@ -585,10 +585,11 @@ app.post(
|
||||
tags: ['Accounts']
|
||||
},
|
||||
body: t.Object({
|
||||
token: t.Optional(t.String()),
|
||||
verifyCode: t.Optional(t.String()),
|
||||
username: t.String(),
|
||||
password: t.String(),
|
||||
email: t.String(),
|
||||
verifyCode: t.String()
|
||||
email: t.String()
|
||||
}),
|
||||
headers: t.Object({
|
||||
'x-forwarded-for': t.Optional(
|
||||
@@ -654,8 +655,9 @@ app.post('/account/forgot-username', accountForgotUsernamePostHandler, {
|
||||
tags: ['Accounts']
|
||||
},
|
||||
body: t.Object({
|
||||
email: t.String(),
|
||||
verifyCode: t.String()
|
||||
token: t.Optional(t.String()),
|
||||
verifyCode: t.Optional(t.String()),
|
||||
email: t.String()
|
||||
}),
|
||||
headers: t.Object({
|
||||
'x-forwarded-for': t.Optional(
|
||||
@@ -671,8 +673,9 @@ app.post('/account/forgot-password', accountForgotPasswordPostHandler, {
|
||||
tags: ['Accounts']
|
||||
},
|
||||
body: t.Object({
|
||||
email: t.String(),
|
||||
verifyCode: t.String()
|
||||
token: t.Optional(t.String()),
|
||||
verifyCode: t.Optional(t.String()),
|
||||
email: t.String()
|
||||
}),
|
||||
headers: t.Object({
|
||||
'x-forwarded-for': t.Optional(
|
||||
@@ -687,7 +690,8 @@ app.post('/account/reset-password', accountResetPasswordPostHandler, {
|
||||
hide: true
|
||||
},
|
||||
body: t.Object({
|
||||
token: t.String(),
|
||||
token: t.Optional(t.String()),
|
||||
verifyCode: t.Optional(t.String()),
|
||||
code: t.String(),
|
||||
password: t.String()
|
||||
}),
|
||||
@@ -1087,8 +1091,8 @@ app.post(
|
||||
tags: ['Berry Dash', 'Icon Marketplace']
|
||||
},
|
||||
body: t.Object({
|
||||
verifyCode: t.Optional(t.String()),
|
||||
token: t.Optional(t.String()),
|
||||
verifyCode: t.Optional(t.String()),
|
||||
price: t.String(),
|
||||
name: t.String(),
|
||||
fileContent: t.String()
|
||||
@@ -1233,7 +1237,8 @@ app.post('/berrydash/splash-text', berryDashSplashTextPostHandler, {
|
||||
hide: true
|
||||
},
|
||||
body: t.Object({
|
||||
token: t.String(),
|
||||
token: t.Optional(t.String()),
|
||||
verifyCode: t.Optional(t.String()),
|
||||
content: t.String()
|
||||
}),
|
||||
headers: t.Object({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import mysql from 'mysql2'
|
||||
import { drizzle } from 'drizzle-orm/mysql2'
|
||||
import { drizzle, MySql2Database } from 'drizzle-orm/mysql2'
|
||||
import {
|
||||
allowedDatabaseVersions,
|
||||
allowedVersions,
|
||||
@@ -11,6 +11,8 @@ import axios from 'axios'
|
||||
import FormData from 'form-data'
|
||||
import nodemailer from 'nodemailer'
|
||||
import { createHash } from 'crypto'
|
||||
import { and, eq, sql } from 'drizzle-orm'
|
||||
import { verifyCodes } from './tables'
|
||||
|
||||
export function jsonResponse (data: any, status = 200) {
|
||||
return new Response(JSON.stringify(data, null, 2), {
|
||||
@@ -123,7 +125,57 @@ export const validateTurnstile = async (token: string, remoteip: string) => {
|
||||
}
|
||||
)
|
||||
|
||||
return response.data
|
||||
return response.data.success
|
||||
}
|
||||
|
||||
export const validateVerifyCode = async (
|
||||
db0: MySql2Database,
|
||||
ip: string,
|
||||
verifyCode: string
|
||||
): Promise<boolean> => {
|
||||
const time = Math.floor(Date.now() / 1000)
|
||||
const codeExists = await db0
|
||||
.select({ id: verifyCodes.id })
|
||||
.from(verifyCodes)
|
||||
.where(
|
||||
and(
|
||||
eq(verifyCodes.ip, ip),
|
||||
eq(verifyCodes.usedTimestamp, 0),
|
||||
eq(verifyCodes.code, verifyCode),
|
||||
sql`${verifyCodes.timestamp} >= UNIX_TIMESTAMP() - 600`
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
.execute()
|
||||
if (codeExists[0]) {
|
||||
await db0
|
||||
.update(verifyCodes)
|
||||
.set({ usedTimestamp: time })
|
||||
.where(
|
||||
and(
|
||||
eq(verifyCodes.id, codeExists[0].id),
|
||||
eq(verifyCodes.ip, ip),
|
||||
eq(verifyCodes.usedTimestamp, 0),
|
||||
eq(verifyCodes.code, verifyCode)
|
||||
)
|
||||
)
|
||||
.execute()
|
||||
return true
|
||||
} else return false
|
||||
}
|
||||
|
||||
export const verifyTurstileOrVerifyCode = (
|
||||
token: string | null,
|
||||
verifyCode: string | null,
|
||||
ip: string,
|
||||
db0: MySql2Database
|
||||
) => {
|
||||
if (token != null) {
|
||||
return validateTurnstile(token, ip)
|
||||
} else if (verifyCode != null) {
|
||||
return validateVerifyCode(db0, ip, verifyCode)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export const sendEmail = async (to: string, title: string, body: string) => {
|
||||
|
||||
@@ -3,16 +3,18 @@ import {
|
||||
getClientIp,
|
||||
getDatabaseConnection,
|
||||
jsonResponse,
|
||||
sendEmail
|
||||
sendEmail,
|
||||
verifyTurstileOrVerifyCode
|
||||
} from '../../../lib/util'
|
||||
import { resetCodes, users, verifyCodes } from '../../../lib/tables'
|
||||
import { resetCodes, users } from '../../../lib/tables'
|
||||
import { and, desc, eq, sql } from 'drizzle-orm'
|
||||
import isEmail from 'validator/lib/isEmail'
|
||||
import { randomBytes } from 'crypto'
|
||||
|
||||
type Body = {
|
||||
token: string | null
|
||||
verifyCode: string | null
|
||||
email: string
|
||||
verifyCode: string
|
||||
}
|
||||
|
||||
export async function handler (context: Context) {
|
||||
@@ -58,38 +60,14 @@ export async function handler (context: Context) {
|
||||
)
|
||||
}
|
||||
const time = Math.floor(Date.now() / 1000)
|
||||
const codeExists = await db0
|
||||
.select({ id: verifyCodes.id })
|
||||
.from(verifyCodes)
|
||||
.where(
|
||||
and(
|
||||
eq(verifyCodes.ip, ip),
|
||||
eq(verifyCodes.usedTimestamp, 0),
|
||||
eq(verifyCodes.code, body.verifyCode),
|
||||
sql`${verifyCodes.timestamp} >= UNIX_TIMESTAMP() - 600`
|
||||
)
|
||||
)
|
||||
.orderBy(desc(verifyCodes.id))
|
||||
.limit(1)
|
||||
.execute()
|
||||
if (codeExists[0]) {
|
||||
await db0
|
||||
.update(verifyCodes)
|
||||
.set({ usedTimestamp: time })
|
||||
.where(
|
||||
and(
|
||||
eq(verifyCodes.id, codeExists[0].id),
|
||||
eq(verifyCodes.ip, ip),
|
||||
eq(verifyCodes.usedTimestamp, 0),
|
||||
eq(verifyCodes.code, body.verifyCode)
|
||||
)
|
||||
)
|
||||
.execute()
|
||||
} else
|
||||
if (!(await verifyTurstileOrVerifyCode(body.token, body.verifyCode, ip, db0)))
|
||||
return jsonResponse(
|
||||
{
|
||||
success: false,
|
||||
message: 'Invalid verify code (codes can only be used once)'
|
||||
message:
|
||||
body.token != null
|
||||
? 'Invalid captcha token'
|
||||
: 'Invalid verify code (codes can only be used once)'
|
||||
},
|
||||
400
|
||||
)
|
||||
|
||||
@@ -3,15 +3,17 @@ import {
|
||||
getClientIp,
|
||||
getDatabaseConnection,
|
||||
jsonResponse,
|
||||
sendEmail
|
||||
sendEmail,
|
||||
verifyTurstileOrVerifyCode
|
||||
} from '../../../lib/util'
|
||||
import { users, verifyCodes } from '../../../lib/tables'
|
||||
import { and, desc, eq, sql } from 'drizzle-orm'
|
||||
import isEmail from 'validator/lib/isEmail'
|
||||
import { users } from '../../../lib/tables'
|
||||
import { eq } from 'drizzle-orm'
|
||||
|
||||
type Body = {
|
||||
token: string | null
|
||||
verifyCode: string | null
|
||||
email: string
|
||||
verifyCode: string
|
||||
}
|
||||
|
||||
export async function handler (context: Context) {
|
||||
@@ -57,38 +59,14 @@ export async function handler (context: Context) {
|
||||
)
|
||||
}
|
||||
const time = Math.floor(Date.now() / 1000)
|
||||
const codeExists = await db0
|
||||
.select({ id: verifyCodes.id })
|
||||
.from(verifyCodes)
|
||||
.where(
|
||||
and(
|
||||
eq(verifyCodes.ip, ip),
|
||||
eq(verifyCodes.usedTimestamp, 0),
|
||||
eq(verifyCodes.code, body.verifyCode),
|
||||
sql`${verifyCodes.timestamp} >= UNIX_TIMESTAMP() - 600`
|
||||
)
|
||||
)
|
||||
.orderBy(desc(verifyCodes.id))
|
||||
.limit(1)
|
||||
.execute()
|
||||
if (codeExists[0]) {
|
||||
await db0
|
||||
.update(verifyCodes)
|
||||
.set({ usedTimestamp: time })
|
||||
.where(
|
||||
and(
|
||||
eq(verifyCodes.id, codeExists[0].id),
|
||||
eq(verifyCodes.ip, ip),
|
||||
eq(verifyCodes.usedTimestamp, 0),
|
||||
eq(verifyCodes.code, body.verifyCode)
|
||||
)
|
||||
)
|
||||
.execute()
|
||||
} else
|
||||
if (!(await verifyTurstileOrVerifyCode(body.token, body.verifyCode, ip, db0)))
|
||||
return jsonResponse(
|
||||
{
|
||||
success: false,
|
||||
message: 'Invalid verify code (codes can only be used once)'
|
||||
message:
|
||||
body.token != null
|
||||
? 'Invalid captcha token'
|
||||
: 'Invalid verify code (codes can only be used once)'
|
||||
},
|
||||
400
|
||||
)
|
||||
|
||||
@@ -2,19 +2,20 @@ import { Context } from 'elysia'
|
||||
import {
|
||||
getClientIp,
|
||||
getDatabaseConnection,
|
||||
jsonResponse
|
||||
jsonResponse,
|
||||
verifyTurstileOrVerifyCode
|
||||
} from '../../../lib/util'
|
||||
import isEmail from 'validator/lib/isEmail'
|
||||
import { berryDashUserData, users, verifyCodes } from '../../../lib/tables'
|
||||
import { and, desc, eq, sql } from 'drizzle-orm'
|
||||
import { berryDashUserData, users } from '../../../lib/tables'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import { randomBytes } from 'crypto'
|
||||
|
||||
type Body = {
|
||||
token: string | null
|
||||
verifyCode: string | null
|
||||
username: string
|
||||
password: string
|
||||
email: string
|
||||
verifyCode: string
|
||||
}
|
||||
|
||||
export async function handler (context: Context) {
|
||||
@@ -65,37 +66,14 @@ export async function handler (context: Context) {
|
||||
)
|
||||
}
|
||||
const time = Math.floor(Date.now() / 1000)
|
||||
const codeExists = await db0
|
||||
.select({ id: verifyCodes.id })
|
||||
.from(verifyCodes)
|
||||
.where(
|
||||
and(
|
||||
eq(verifyCodes.ip, ip),
|
||||
eq(verifyCodes.usedTimestamp, 0),
|
||||
eq(verifyCodes.code, body.verifyCode),
|
||||
sql`${verifyCodes.timestamp} >= UNIX_TIMESTAMP() - 600`
|
||||
)
|
||||
)
|
||||
.orderBy(desc(verifyCodes.id))
|
||||
.limit(1)
|
||||
.execute()
|
||||
if (codeExists[0]) {
|
||||
await db0
|
||||
.update(verifyCodes)
|
||||
.set({ usedTimestamp: time })
|
||||
.where(
|
||||
and(
|
||||
eq(verifyCodes.id, codeExists[0].id),
|
||||
eq(verifyCodes.ip, ip),
|
||||
eq(verifyCodes.usedTimestamp, 0),
|
||||
eq(verifyCodes.code, body.verifyCode)
|
||||
)
|
||||
)
|
||||
} else
|
||||
if (!(await verifyTurstileOrVerifyCode(body.token, body.verifyCode, ip, db0)))
|
||||
return jsonResponse(
|
||||
{
|
||||
success: false,
|
||||
message: 'Invalid verify code (codes can only be used once)'
|
||||
message:
|
||||
body.token != null
|
||||
? 'Invalid captcha token'
|
||||
: 'Invalid verify code (codes can only be used once)'
|
||||
},
|
||||
400
|
||||
)
|
||||
|
||||
@@ -3,14 +3,15 @@ import {
|
||||
getClientIp,
|
||||
getDatabaseConnection,
|
||||
jsonResponse,
|
||||
validateTurnstile
|
||||
verifyTurstileOrVerifyCode
|
||||
} 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
|
||||
token: string | null
|
||||
verifyCode: string | null
|
||||
code: string
|
||||
password: string
|
||||
}
|
||||
@@ -58,17 +59,17 @@ export async function handler (context: Context) {
|
||||
)
|
||||
}
|
||||
|
||||
const result = await validateTurnstile(body.token, ip)
|
||||
if (!result.success) {
|
||||
connection0.end()
|
||||
if (!(await verifyTurstileOrVerifyCode(body.token, body.verifyCode, ip, db0)))
|
||||
return jsonResponse(
|
||||
{
|
||||
success: false,
|
||||
message: 'Unable to verify captcha key'
|
||||
message:
|
||||
body.token != null
|
||||
? 'Invalid captcha token'
|
||||
: 'Invalid verify code (codes can only be used once)'
|
||||
},
|
||||
400
|
||||
)
|
||||
}
|
||||
|
||||
const time = Math.floor(Date.now() / 1000)
|
||||
const codeExists = await db0
|
||||
|
||||
@@ -4,18 +4,17 @@ import {
|
||||
getDatabaseConnection,
|
||||
hash,
|
||||
jsonResponse,
|
||||
validateTurnstile
|
||||
verifyTurstileOrVerifyCode
|
||||
} from '../../../../lib/util'
|
||||
import { checkAuthorization } from '../../../../lib/auth'
|
||||
import { berryDashMarketplaceIcons, verifyCodes } from '../../../../lib/tables'
|
||||
import { and, desc, eq, sql } from 'drizzle-orm'
|
||||
import { berryDashMarketplaceIcons } from '../../../../lib/tables'
|
||||
import { Buffer } from 'buffer'
|
||||
import sizeOf from 'image-size'
|
||||
import { Connection } from 'mysql2/typings/mysql/lib/Connection'
|
||||
|
||||
type Body = {
|
||||
verifyCode: string
|
||||
token: string
|
||||
token: string | null
|
||||
verifyCode: string | null
|
||||
price: string
|
||||
name: string
|
||||
fileContent: string
|
||||
@@ -136,55 +135,17 @@ export async function handler (context: Context) {
|
||||
)
|
||||
|
||||
const time = Math.floor(Date.now() / 1000)
|
||||
if (body.verifyCode) {
|
||||
const codeExists = await db0
|
||||
.select({ id: verifyCodes.id })
|
||||
.from(verifyCodes)
|
||||
.where(
|
||||
and(
|
||||
eq(verifyCodes.ip, ip),
|
||||
eq(verifyCodes.usedTimestamp, 0),
|
||||
eq(verifyCodes.code, body.verifyCode),
|
||||
sql`${verifyCodes.timestamp} >= UNIX_TIMESTAMP() - 600`
|
||||
)
|
||||
)
|
||||
.orderBy(desc(verifyCodes.id))
|
||||
.limit(1)
|
||||
.execute()
|
||||
if (codeExists[0]) {
|
||||
await db0
|
||||
.update(verifyCodes)
|
||||
.set({ usedTimestamp: time })
|
||||
.where(
|
||||
and(
|
||||
eq(verifyCodes.id, codeExists[0].id),
|
||||
eq(verifyCodes.ip, ip),
|
||||
eq(verifyCodes.usedTimestamp, 0),
|
||||
eq(verifyCodes.code, body.verifyCode)
|
||||
)
|
||||
)
|
||||
.execute()
|
||||
} else
|
||||
if (!(await verifyTurstileOrVerifyCode(body.token, body.verifyCode, ip, db0)))
|
||||
return jsonResponse(
|
||||
{
|
||||
success: false,
|
||||
message: 'Invalid verify code (codes can only be used once)'
|
||||
message:
|
||||
body.token != null
|
||||
? 'Invalid captcha token'
|
||||
: 'Invalid verify code (codes can only be used once)'
|
||||
},
|
||||
400
|
||||
)
|
||||
} else {
|
||||
const result = await validateTurnstile(body.token, ip)
|
||||
if (!result.success) {
|
||||
connection0.end()
|
||||
return jsonResponse(
|
||||
{
|
||||
success: false,
|
||||
message: 'Unable to verify captcha key'
|
||||
},
|
||||
400
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const hashResult = hash(atob(body.fileContent), 'sha512')
|
||||
const id = crypto.randomUUID()
|
||||
|
||||
@@ -3,14 +3,15 @@ import {
|
||||
getClientIp,
|
||||
getDatabaseConnection,
|
||||
jsonResponse,
|
||||
validateTurnstile
|
||||
verifyTurstileOrVerifyCode
|
||||
} from '../../../lib/util'
|
||||
import { checkAuthorization } from '../../../lib/auth'
|
||||
import { berryDashSplashTexts } from '../../../lib/tables'
|
||||
import { eq } from 'drizzle-orm'
|
||||
|
||||
type Body = {
|
||||
token: string
|
||||
token: string | null
|
||||
verifyCode: string | null
|
||||
content: string
|
||||
}
|
||||
|
||||
@@ -107,18 +108,17 @@ export async function handler (context: Context) {
|
||||
)
|
||||
}
|
||||
|
||||
const result = await validateTurnstile(body.token, ip)
|
||||
if (!result.success) {
|
||||
connection0.end()
|
||||
connection1.end()
|
||||
if (!(await verifyTurstileOrVerifyCode(body.token, body.verifyCode, ip, db0)))
|
||||
return jsonResponse(
|
||||
{
|
||||
success: false,
|
||||
message: 'Unable to verify captcha key'
|
||||
message:
|
||||
body.token != null
|
||||
? 'Invalid captcha token'
|
||||
: 'Invalid verify code (codes can only be used once)'
|
||||
},
|
||||
400
|
||||
)
|
||||
}
|
||||
|
||||
const time = Math.floor(Date.now() / 1000)
|
||||
await db1
|
||||
|
||||
Reference in New Issue
Block a user