Add endpoints for login and register
This commit is contained in:
14
bun.lock
14
bun.lock
@@ -7,12 +7,16 @@
|
||||
"dependencies": {
|
||||
"@elysiajs/cors": "1.4.1",
|
||||
"@elysiajs/swagger": "1.3.1",
|
||||
"bcryptjs": "3.0.3",
|
||||
"crypto": "1.0.1",
|
||||
"dotenv": "17.2.3",
|
||||
"drizzle-orm": "0.45.1",
|
||||
"elysia": "1.4.22",
|
||||
"mysql2": "3.16.1",
|
||||
"validator": "13.15.26",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/validator": "13.15.10",
|
||||
"bun-types": "1.3.6",
|
||||
},
|
||||
},
|
||||
@@ -36,16 +40,22 @@
|
||||
|
||||
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
|
||||
|
||||
"@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="],
|
||||
"@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
|
||||
|
||||
"@types/validator": ["@types/validator@13.15.10", "", {}, "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA=="],
|
||||
|
||||
"@unhead/schema": ["@unhead/schema@1.11.20", "", { "dependencies": { "hookable": "^5.5.3", "zhead": "^2.2.4" } }, "sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA=="],
|
||||
|
||||
"aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="],
|
||||
|
||||
"bcryptjs": ["bcryptjs@3.0.3", "", { "bin": { "bcrypt": "bin/bcrypt" } }, "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
|
||||
|
||||
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
|
||||
|
||||
"crypto": ["crypto@1.0.1", "", {}, "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],
|
||||
@@ -106,6 +116,8 @@
|
||||
|
||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
|
||||
"validator": ["validator@13.15.26", "", {}, "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA=="],
|
||||
|
||||
"zhead": ["zhead@2.2.4", "", {}, "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag=="],
|
||||
|
||||
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
@@ -8,12 +8,16 @@
|
||||
"dependencies": {
|
||||
"@elysiajs/cors": "1.4.1",
|
||||
"@elysiajs/swagger": "1.3.1",
|
||||
"bcryptjs": "3.0.3",
|
||||
"crypto": "1.0.1",
|
||||
"dotenv": "17.2.3",
|
||||
"drizzle-orm": "0.45.1",
|
||||
"elysia": "1.4.22",
|
||||
"mysql2": "3.16.1"
|
||||
"mysql2": "3.16.1",
|
||||
"validator": "13.15.26"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/validator": "13.15.10",
|
||||
"bun-types": "1.3.6"
|
||||
},
|
||||
"module": "src/index.js"
|
||||
|
||||
32
src/index.ts
32
src/index.ts
@@ -23,6 +23,8 @@ import { handler as berrydashProfilePostsPutHandler } from './routes/berrydash/p
|
||||
|
||||
import { handler as berryDashIconMarketplacePostHandler } from './routes/berrydash/icon-marketplace/post'
|
||||
|
||||
import { handler as berryDashAccountLoginPostHandler } from './routes/berrydash/account/login/post'
|
||||
import { handler as berryDashAccountRegisterPostHandler } from './routes/berrydash/account/register/post'
|
||||
import { handler as berryDashAccountSaveGetHandler } from './routes/berrydash/account/save/get'
|
||||
import { handler as berryDashAccountSavePostHandler } from './routes/berrydash/account/save/post'
|
||||
|
||||
@@ -348,6 +350,36 @@ app.post(
|
||||
})
|
||||
}
|
||||
)
|
||||
app.post(
|
||||
'/berrydash/account/login',
|
||||
context => berryDashAccountLoginPostHandler(context),
|
||||
{
|
||||
detail: {
|
||||
description:
|
||||
'The endpoint for logging into an account. This is also the endpoint for refreshing login.',
|
||||
tags: ['Berry Dash', 'Accounts']
|
||||
},
|
||||
body: t.Object({
|
||||
username: t.String(),
|
||||
password: t.String()
|
||||
})
|
||||
}
|
||||
)
|
||||
app.post(
|
||||
'/berrydash/account/register',
|
||||
context => berryDashAccountRegisterPostHandler(context),
|
||||
{
|
||||
detail: {
|
||||
description: 'The endpoint for registering an account.',
|
||||
tags: ['Berry Dash', 'Accounts']
|
||||
},
|
||||
body: t.Object({
|
||||
username: t.String(),
|
||||
password: t.String(),
|
||||
email: t.String()
|
||||
})
|
||||
}
|
||||
)
|
||||
app.all('*', () =>
|
||||
jsonResponse(
|
||||
{
|
||||
|
||||
101
src/routes/berrydash/account/login/post.ts
Normal file
101
src/routes/berrydash/account/login/post.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { Context } from 'elysia'
|
||||
import { getDatabaseConnection, jsonResponse } from '../../../../lib/util'
|
||||
import { berryDashUserData, users } from '../../../../lib/tables'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import bcrypt from 'bcryptjs'
|
||||
|
||||
type Body = {
|
||||
username: string
|
||||
password: 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', data: null },
|
||||
500
|
||||
)
|
||||
const { connection: connection0, db: db0 } = dbInfo0
|
||||
const { connection: connection1, db: db1 } = dbInfo1
|
||||
|
||||
const body = context.body as Body
|
||||
if (!body.username || !body.password) {
|
||||
connection0.end()
|
||||
connection1.end()
|
||||
return jsonResponse(
|
||||
{
|
||||
success: false,
|
||||
message: 'Username and password must be in POST data',
|
||||
data: null
|
||||
},
|
||||
400
|
||||
)
|
||||
}
|
||||
|
||||
const user = await db0
|
||||
.select({
|
||||
id: users.id,
|
||||
username: users.username,
|
||||
password: users.password
|
||||
})
|
||||
.from(users)
|
||||
.where(eq(users.username, body.username))
|
||||
.limit(1)
|
||||
.execute()
|
||||
if (!user[0]) {
|
||||
connection0.end()
|
||||
connection1.end()
|
||||
return jsonResponse(
|
||||
{
|
||||
success: false,
|
||||
message: 'Invalid username or password',
|
||||
data: null
|
||||
},
|
||||
401
|
||||
)
|
||||
}
|
||||
if (!(await bcrypt.compare(body.password, user[0].password))) {
|
||||
connection0.end()
|
||||
connection1.end()
|
||||
return jsonResponse(
|
||||
{
|
||||
success: false,
|
||||
message: 'Invalid username or password',
|
||||
data: null
|
||||
},
|
||||
401
|
||||
)
|
||||
}
|
||||
|
||||
const user2 = await db1
|
||||
.select({ token: berryDashUserData.token })
|
||||
.from(berryDashUserData)
|
||||
.where(eq(berryDashUserData.id, user[0].id))
|
||||
.limit(1)
|
||||
.execute()
|
||||
if (!user2[0]) {
|
||||
connection0.end()
|
||||
connection1.end()
|
||||
return jsonResponse(
|
||||
{
|
||||
success: false,
|
||||
message: 'Invalid username or password',
|
||||
data: null
|
||||
},
|
||||
401
|
||||
)
|
||||
}
|
||||
|
||||
return jsonResponse({
|
||||
success: true,
|
||||
message: null,
|
||||
data: {
|
||||
session: user2[0].token,
|
||||
username: user[0].username,
|
||||
id: user[0].id
|
||||
}
|
||||
})
|
||||
}
|
||||
150
src/routes/berrydash/account/register/post.ts
Normal file
150
src/routes/berrydash/account/register/post.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { Context } from 'elysia'
|
||||
import {
|
||||
getClientIp,
|
||||
getDatabaseConnection,
|
||||
jsonResponse
|
||||
} from '../../../../lib/util'
|
||||
import isEmail from 'validator/lib/isEmail'
|
||||
import { berryDashUserData, users } from '../../../../lib/tables'
|
||||
import { eq, or } from 'drizzle-orm'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import { randomBytes } from 'crypto'
|
||||
|
||||
type Body = {
|
||||
username: string
|
||||
password: string
|
||||
email: 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', data: null },
|
||||
500
|
||||
)
|
||||
const { connection: connection0, db: db0 } = dbInfo0
|
||||
const { connection: connection1, db: db1 } = dbInfo1
|
||||
|
||||
const body = context.body as Body
|
||||
if (!body.username || !body.password || !body.email) {
|
||||
connection0.end()
|
||||
connection1.end()
|
||||
return jsonResponse(
|
||||
{
|
||||
success: false,
|
||||
message: 'Username, password and email must be in POST data',
|
||||
data: null
|
||||
},
|
||||
400
|
||||
)
|
||||
}
|
||||
|
||||
if (!/^[a-zA-Z0-9]{3,16}$/.test(body.username)) {
|
||||
connection0.end()
|
||||
connection1.end()
|
||||
return jsonResponse(
|
||||
{
|
||||
success: false,
|
||||
message: 'Username must be 3-16 characters, letters and numbers only',
|
||||
data: null
|
||||
},
|
||||
400
|
||||
)
|
||||
}
|
||||
|
||||
if (!isEmail(body.email)) {
|
||||
connection0.end()
|
||||
connection1.end()
|
||||
return jsonResponse(
|
||||
{
|
||||
success: false,
|
||||
message: 'Email is invalid',
|
||||
data: null
|
||||
},
|
||||
400
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
!/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d!@#$%^&*()_\-+=]{8,}$/.test(body.password)
|
||||
) {
|
||||
connection0.end()
|
||||
connection1.end()
|
||||
return jsonResponse(
|
||||
{
|
||||
success: false,
|
||||
message:
|
||||
'Password must be at least 8 characters with at least one letter and one number',
|
||||
data: null
|
||||
},
|
||||
400
|
||||
)
|
||||
}
|
||||
|
||||
const existingCheck = await db0
|
||||
.select({ id: users.id })
|
||||
.from(users)
|
||||
.where(or(eq(users.username, body.email), eq(users.email, body.email)))
|
||||
.limit(1)
|
||||
.execute()
|
||||
if (existingCheck[0]) {
|
||||
connection0.end()
|
||||
connection1.end()
|
||||
return jsonResponse(
|
||||
{
|
||||
success: false,
|
||||
message: 'Username or email is already taken',
|
||||
data: null
|
||||
},
|
||||
409
|
||||
)
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(body.password, 10)
|
||||
const token = randomBytes(256).toString('hex')
|
||||
const ip = getClientIp(context)
|
||||
const time = Math.floor(Date.now() / 1000)
|
||||
if (!ip) {
|
||||
connection0.end()
|
||||
connection1.end()
|
||||
return jsonResponse(
|
||||
{
|
||||
success: false,
|
||||
message: 'Failed to get required info',
|
||||
data: null
|
||||
},
|
||||
400
|
||||
)
|
||||
}
|
||||
|
||||
const result = await db0
|
||||
.insert(users)
|
||||
.values({
|
||||
username: body.username,
|
||||
password: hashedPassword,
|
||||
email: body.email,
|
||||
registerTime: time,
|
||||
latestIp: ip
|
||||
})
|
||||
.execute()
|
||||
|
||||
await db1
|
||||
.insert(berryDashUserData)
|
||||
.values({
|
||||
id: result[0].insertId,
|
||||
token
|
||||
})
|
||||
.execute()
|
||||
|
||||
return jsonResponse(
|
||||
{
|
||||
success: true,
|
||||
message: null,
|
||||
data: null
|
||||
},
|
||||
200
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user