From c8ba2a8a517e9a1fc08b1425703ecdf7c4d2dac8 Mon Sep 17 00:00:00 2001 From: MikiVL Date: Tue, 5 May 2026 05:55:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B3=A8=E5=86=8C/=E7=99=BB=E5=BD=95/?= =?UTF-8?q?=E9=82=80=E8=AF=B7=E7=A0=81=E6=BF=80=E6=B4=BB=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routes/auth.ts | 58 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 server/routes/auth.ts diff --git a/server/routes/auth.ts b/server/routes/auth.ts new file mode 100644 index 0000000..e40e7a1 --- /dev/null +++ b/server/routes/auth.ts @@ -0,0 +1,58 @@ +import { Hono } from 'hono' +import bcrypt from 'bcryptjs' +import jwt from 'jsonwebtoken' +import { eq } from 'drizzle-orm' +import { db, users, inviteCodes } from '../db' +import { requireAuth } from '../middleware/auth' + +export const authRouter = new Hono() + +function nanoid() { + return Math.random().toString(36).slice(2, 11) + Date.now().toString(36) +} + +authRouter.post('/register', async (c) => { + const { username, password } = await c.req.json<{ username: string; password: string }>() + if (!username || !password) return c.json({ error: '用户名和密码不能为空' }, 400) + if (username.length < 2 || username.length > 20) return c.json({ error: '用户名长度 2-20 位' }, 400) + if (password.length < 6) return c.json({ error: '密码至少 6 位' }, 400) + + const existing = db.select().from(users).where(eq(users.username, username)).all() + if (existing.length > 0) return c.json({ error: '用户名已存在' }, 409) + + const passwordHash = await bcrypt.hash(password, 10) + const id = nanoid() + db.insert(users).values({ id, username, passwordHash, cloudEnabled: false, createdAt: Date.now() }).run() + + const token = jwt.sign({ userId: id, username }, process.env.JWT_SECRET!, { expiresIn: '30d' }) + return c.json({ token, user: { id, username, cloudEnabled: false } }) +}) + +authRouter.post('/login', async (c) => { + const { username, password } = await c.req.json<{ username: string; password: string }>() + if (!username || !password) return c.json({ error: '用户名和密码不能为空' }, 400) + + const [user] = db.select().from(users).where(eq(users.username, username)).all() + if (!user) return c.json({ error: '用户名或密码错误' }, 401) + + const ok = await bcrypt.compare(password, user.passwordHash) + if (!ok) return c.json({ error: '用户名或密码错误' }, 401) + + const token = jwt.sign({ userId: user.id, username: user.username }, process.env.JWT_SECRET!, { expiresIn: '30d' }) + return c.json({ token, user: { id: user.id, username: user.username, cloudEnabled: user.cloudEnabled } }) +}) + +authRouter.post('/activate', requireAuth, async (c) => { + const userId = c.get('userId') + const { code } = await c.req.json<{ code: string }>() + if (!code) return c.json({ error: '邀请码不能为空' }, 400) + + const [invite] = db.select().from(inviteCodes).where(eq(inviteCodes.code, code.trim().toUpperCase())).all() + if (!invite) return c.json({ error: '邀请码不存在' }, 404) + if (invite.usedByUserId) return c.json({ error: '邀请码已被使用' }, 409) + + db.update(inviteCodes).set({ usedByUserId: userId, usedAt: Date.now() }).where(eq(inviteCodes.code, code.trim().toUpperCase())).run() + db.update(users).set({ cloudEnabled: true }).where(eq(users.id, userId)).run() + + return c.json({ success: true }) +})