返回 DeepSeek-Reasonix
me.ts
根目录 / workers / accounts / src / routes / me.ts
1 import { Hono } from "hono";
2 import type { AppEnv } from "../env";
3 import { toAccountUser } from "../types";
4 import { repos } from "../db";
5 import type { ProfilePatch } from "../db/users";
6 import { requireAuth, currentUser } from "../http/auth";
7 import { ApiError } from "../http/errors";
8 import { hashPassword, verifyPassword } from "../auth/crypto";
9 import { setSessionCookie, clearSessionCookie } from "../auth/cookies";
10 import { isValidHandle } from "../lib/handle";
11 import { parseBody, ProfileSchema, PasswordChangeSchema } from "../lib/validation";
12
13 const me = new Hono<AppEnv>();
14
15 // Everything under /me requires a session.
16 me.use("*", requireAuth);
17
18 me.get("/", (c) => c.json({ user: currentUser(c) }));
19
20 me.patch("/", async (c) => {
21 const user = currentUser(c);
22 const patch = await parseBody(c, ProfileSchema);
23 const { users } = repos(c.env);
24
25 const update: ProfilePatch = {};
26 if (patch.handle !== undefined) {
27 const handle = patch.handle.toLowerCase();
28 if (!isValidHandle(handle)) {
29 throw new ApiError(422, "invalid_handle", "Handles are 3–30 chars: letters, numbers, and underscores.");
30 }
31 if (handle !== user.handle) {
32 update.handle = handle;
33 }
34 }
35 if (patch.displayName !== undefined) update.displayName = patch.displayName;
36 if (patch.bio !== undefined) update.bio = patch.bio;
37 if (patch.avatarUrl !== undefined) update.avatarUrl = patch.avatarUrl;
38
39 const row = await users.updateProfile(user.id, update);
40 if (!row) throw new ApiError(409, "handle_taken", "That handle is already taken.");
41 return c.json({ user: toAccountUser(row) });
42 });
43
44 me.post("/password", async (c) => {
45 const user = currentUser(c);
46 const { currentPassword, newPassword } = await parseBody(c, PasswordChangeSchema);
47 const { users, sessions } = repos(c.env);
48
49 const row = await users.byId(user.id);
50 if (!row || !(await verifyPassword(currentPassword, row.password_hash))) {
51 throw new ApiError(400, "invalid_password", "Your current password is incorrect.");
52 }
53 await users.updatePassword(user.id, await hashPassword(newPassword));
54
55 // Drop every session, then mint a fresh one so this device stays signed in.
56 await sessions.deleteAllForUser(user.id);
57 setSessionCookie(c, await sessions.create(user.id, { userAgent: c.req.header("user-agent") ?? "" }));
58 return c.json({ ok: true });
59 });
60
61 me.delete("/", async (c) => {
62 const user = currentUser(c);
63 const { users, sessions } = repos(c.env);
64 await users.softDelete(user.id);
65 await sessions.deleteAllForUser(user.id);
66 clearSessionCookie(c);
67 return c.json({ ok: true });
68 });
69
70 export default me;
71
71 lines TYPESCRIPT