From d7dcc473a9f2fec24c8e044d3a2a2405feba0763 Mon Sep 17 00:00:00 2001 From: Miao_Spring <539943419@qq.com> Date: Tue, 14 Jan 2025 02:10:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E7=95=8C=E9=9D=A2=EF=BC=8Capi=E8=BF=98?= =?UTF-8?q?=E9=9C=80=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/icons/cat.vue | 8 ++ hooks/pinia/useUserStore.ts | 11 +- hooks/useFetch.ts | 27 ---- hooks/useMiaoFetch.ts | 106 +++++++++++++++ pages/posts/index.vue | 4 - pages/user.vue | 2 +- pages/user/index.vue | 90 ++----------- pages/user/login.vue | 215 +++++++++++++++++++++++++++++- pages/user/register.vue | 172 ++++++++++++++++++++++++ pages/user/test.vue | 99 ++++++++++++++ server/api/user/login.ts | 2 +- server/api/user/needRegister.ts | 16 +++ server/api/user/refreshToken.ts | 8 +- server/api/user/register.ts | 19 ++- server/api/user/updatePassword.ts | 4 +- server/middleware/auth.ts | 18 ++- server/server.config.ts | 5 +- server/utils/createToken.ts | 4 +- 18 files changed, 681 insertions(+), 129 deletions(-) create mode 100644 components/icons/cat.vue delete mode 100644 hooks/useFetch.ts create mode 100644 hooks/useMiaoFetch.ts create mode 100644 pages/user/register.vue create mode 100644 pages/user/test.vue create mode 100644 server/api/user/needRegister.ts diff --git a/components/icons/cat.vue b/components/icons/cat.vue new file mode 100644 index 0000000..045388b --- /dev/null +++ b/components/icons/cat.vue @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/hooks/pinia/useUserStore.ts b/hooks/pinia/useUserStore.ts index 55f146a..199bc2e 100644 --- a/hooks/pinia/useUserStore.ts +++ b/hooks/pinia/useUserStore.ts @@ -35,6 +35,14 @@ export const useUserStore = defineStore("user", () => { _token.value = value; localStorage.setItem("miao-token", value); }; + + const logout = () => { + setLoginStatus(false); + _token.value = undefined; + _tokenExpireTime.value = undefined; + localStorage.removeItem("miao-token"); + localStorage.removeItem("miao-token-expire-time"); + }; return { isLogin, setLoginStatus, @@ -43,7 +51,8 @@ export const useUserStore = defineStore("user", () => { tokenExpireTime, setTokenExpireTime, _tokenExpireTime, - _token + _token, + logout }; }); diff --git a/hooks/useFetch.ts b/hooks/useFetch.ts deleted file mode 100644 index b80be50..0000000 --- a/hooks/useFetch.ts +++ /dev/null @@ -1,27 +0,0 @@ - -export const uploadPost = (body: { - title: string, - summary: string, - type: string, - tags: string[], - url: string, - date: number, - content: string -}) => $fetch('/api/posts/uploadPost', { - method: 'POST', - body -}) - -export const updatePost = (body: { - id: number, - title: string, - summary: string, - type: string, - tags: string[], - url: string, - date: number, - content: string -}) => $fetch('/api/posts/uploadPost', { - method: 'POST', - body -}) \ No newline at end of file diff --git a/hooks/useMiaoFetch.ts b/hooks/useMiaoFetch.ts new file mode 100644 index 0000000..7c6b792 --- /dev/null +++ b/hooks/useMiaoFetch.ts @@ -0,0 +1,106 @@ +import needRegister from "~/server/api/user/needRegister"; +import useUserStore from "./pinia/useUserStore" + + +export default function useFetch() { + const userStore = useUserStore(); + return { + post: { + uploadPost: (body: { + title: string, + summary: string, + type: string, + tags: string[], + url: string, + date: number, + content: string + }) => $fetch('/api/posts/uploadPost', { + method: 'POST', + body, + headers: { + 'Authorization': `Bearer ${userStore.token}` + } + }), + updatePost: (body: { + id: number, + title: string, + summary: string, + type: string, + tags: string[], + url: string, + date: number, + content: string + }) => $fetch('/api/posts/editPost', { + method: 'POST', + body, + headers: { + 'Authorization': `Bearer ${userStore.token}` + } + }), + getPost: (id: number) => $fetch(`/api/posts/getPost?id=${id}`, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${userStore.token}` + } + }), + getPostList: (body: { + page?: number, + tag?: string + }) => $fetch('/api/posts/getPostList', { + method: 'POST', + body, + headers: { + 'Authorization': `Bearer ${userStore.token}` + } + }), + }, + user: { + login: (body: { + username: string, + password: string + }) => $fetch('/api/user/login', { + method: 'POST', + body + }), + register: (body: { + username: string, + password: string + }) => $fetch('/api/user/register', { + method: 'POST', + body + }), + needRegister: () => $fetch('/api/user/needRegister'), + refreshToken: () => $fetch('/api/user/refreshToken', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${userStore.token}` + } + }), + // 暂未实现 + updatePassword: (body: { + oldPassword: string, + newPassword: string + }) => $fetch('/api/user/updatePassword', { + method: 'POST', + body, + headers: { + 'Authorization': `Bearer ${userStore.token}` + } + }) + }, + } +} + + +export const testAuth = () => { + const userStore = useUserStore(); + (async () => { + const res = await $fetch('/api/test/testDir', { + method: 'GET', + headers: { + 'Authorization': `Bearer ${userStore.token}` + } + }) + console.log(res) + })() +} \ No newline at end of file diff --git a/pages/posts/index.vue b/pages/posts/index.vue index ce63430..bf026f6 100644 --- a/pages/posts/index.vue +++ b/pages/posts/index.vue @@ -53,10 +53,6 @@ onMounted(async () => { // await sleep(1000) postsData.value = await getData() isInit.value = true; - (async () => { - const res = await $fetch('/api/test/testDir') - console.log(res) - })() // console.log(postsData.value) }) diff --git a/pages/user.vue b/pages/user.vue index 6a79b62..d832102 100644 --- a/pages/user.vue +++ b/pages/user.vue @@ -15,7 +15,7 @@ justify-content: center; align-items: center; margin: 0 auto; - max-width: 800px; + // max-width: 800px; padding: 0; } \ No newline at end of file diff --git a/pages/user/index.vue b/pages/user/index.vue index 8c833a1..d15d41a 100644 --- a/pages/user/index.vue +++ b/pages/user/index.vue @@ -1,90 +1,28 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/pages/user/login.vue b/pages/user/login.vue index ca4befc..bb8b27c 100644 --- a/pages/user/login.vue +++ b/pages/user/login.vue @@ -1,13 +1,224 @@ + + + \ No newline at end of file diff --git a/pages/user/register.vue b/pages/user/register.vue new file mode 100644 index 0000000..77c2225 --- /dev/null +++ b/pages/user/register.vue @@ -0,0 +1,172 @@ + + + + + \ No newline at end of file diff --git a/pages/user/test.vue b/pages/user/test.vue new file mode 100644 index 0000000..0dbbd28 --- /dev/null +++ b/pages/user/test.vue @@ -0,0 +1,99 @@ + + + + + \ No newline at end of file diff --git a/server/api/user/login.ts b/server/api/user/login.ts index 3c26926..7dea17a 100644 --- a/server/api/user/login.ts +++ b/server/api/user/login.ts @@ -19,7 +19,7 @@ export default defineEventHandler(async (event) => { userDB.prepare('UPDATE user SET lastLoginTime = ? WHERE id = ?').run(Date.now(), user.id); // 生成 JWT - const token = createToken(user.id); + const token = createToken({ userId: user.id, password: user.password, username: user.username }); userDB.close(); diff --git a/server/api/user/needRegister.ts b/server/api/user/needRegister.ts new file mode 100644 index 0000000..9e5d4e4 --- /dev/null +++ b/server/api/user/needRegister.ts @@ -0,0 +1,16 @@ +// import bcrypt from 'bcryptjs'; +import BetterSqlite3 from 'better-sqlite3'; + +export default defineEventHandler(async (event) => { + // const body = await readBody(event); + const userDB = BetterSqlite3('./userData/db/user.db'); + + // 临时使用,若表中已有用户,则拒绝创建新的用户 + const existingUser = userDB.prepare('SELECT * FROM user').get(); + userDB.close(); + if (existingUser) { + return true + } else { + return false + } +}); \ No newline at end of file diff --git a/server/api/user/refreshToken.ts b/server/api/user/refreshToken.ts index 0f3f183..34b61c0 100644 --- a/server/api/user/refreshToken.ts +++ b/server/api/user/refreshToken.ts @@ -9,9 +9,13 @@ export default defineEventHandler(async (event) => { try { // 验证旧的 Token - const decoded = jwt.verify(token, jwtSecret) as { userId: number }; + const decoded = jwt.verify(token, jwtSecret) as any; // 生成新的 Token - const newToken = jwt.sign({ userId: decoded.userId }, jwtSecret, { expiresIn: '168h' }); + const newToken = jwt.sign({ + username: decoded.username, + password: decoded.password, + userId: decoded.userId, + }, jwtSecret, { expiresIn: '168h' }); return { token: newToken, expiresIn: Date.now() + 168 * 60 * 60 * 1000 }; } catch (err) { diff --git a/server/api/user/register.ts b/server/api/user/register.ts index 8cee05e..15b15b7 100644 --- a/server/api/user/register.ts +++ b/server/api/user/register.ts @@ -4,16 +4,23 @@ import BetterSqlite3 from 'better-sqlite3'; export default defineEventHandler(async (event) => { const body = await readBody(event); const { username, password } = body; - - // 检查用户名是否已存在 const userDB = BetterSqlite3('./userData/db/user.db'); - const existingUser = userDB.prepare('SELECT * FROM user WHERE username = ?').get(username); + + // 临时使用,若表中已有用户,则拒绝创建新的用户 + const existingUser = userDB.prepare('SELECT * FROM user').get(); if (existingUser) { userDB.close(); - throw createError({ statusCode: 400, statusMessage: 'Username already exists' }); + throw createError({ statusCode: 400, statusMessage: 'User already exists' }); } - // 哈希密码 + // // 检查用户名是否已存在 + // const existingUser = userDB.prepare('SELECT * FROM user WHERE username = ?').get(username); + // if (existingUser) { + // userDB.close(); + // throw createError({ statusCode: 400, statusMessage: 'Username already exists' }); + // } + + // 哈希密码,暂未实现,未来实现 // const salt = getKey().substring(0, 10); // const hashedPassword = bcrypt.hashSync(password, salt); const hashedPassword = password @@ -26,7 +33,7 @@ export default defineEventHandler(async (event) => { // 生成 JWT const userId = result.lastInsertRowid as number; - const token = createToken(userId); + const token = createToken({ userId, password: hashedPassword, username }); userDB.close(); diff --git a/server/api/user/updatePassword.ts b/server/api/user/updatePassword.ts index 58adc2f..b0d151b 100644 --- a/server/api/user/updatePassword.ts +++ b/server/api/user/updatePassword.ts @@ -2,6 +2,8 @@ import bcrypt from 'bcryptjs'; import BetterSqlite3 from 'better-sqlite3'; +// 暂未实现 + export default defineEventHandler(async (event) => { const body = await readBody(event); const { oldPassword, newPassword } = body; @@ -23,7 +25,7 @@ export default defineEventHandler(async (event) => { userDB.prepare('UPDATE user SET password = ? WHERE id = ?').run(hashedPassword, userId); // 生成新的 JWT - const token = createToken(userId); + const token = createToken({ userId, password: user.password, username: user.username }); userDB.close(); diff --git a/server/middleware/auth.ts b/server/middleware/auth.ts index b4d31e1..810cc93 100644 --- a/server/middleware/auth.ts +++ b/server/middleware/auth.ts @@ -5,21 +5,29 @@ import getKey from "../utils/getKey" const pathNeedAuth = serverConfig.auth.api -export default defineEventHandler((event) => { - const key = getKey() +export default defineEventHandler(async (event) => { const path = getRequestURL(event).pathname + // console.log(path) if (pathNeedAuth.has(path)) { - const authHeader = event.node.req.headers.authorization + const key = getKey() + // @ts-ignore + const authHeader = event.headers.get('authorization'); + // console.log(authHeader) if (!authHeader || !authHeader.startsWith('Bearer ')) { throw createError({ statusCode: 401, statusMessage: 'Unauthorized' }) } const token = authHeader.split('Bearer ')[1].trim() try { - const decoded = jwt.verify(token, key) + const decoded = jwt.verify(token, key) as { userId: number, password: string, username: string, exp: number } + if (decoded.exp > Date.now()) { + // console.log('token expired') + throw createError({ statusCode: 401, statusMessage: 'Token invalid or expired', data: { err: 'token expired', token } }) + } + // console.log(decoded) // 将用户ID挂载到事件对象,供后续使用 // event.context.userId = decoded.userId } catch (err) { - throw createError({ statusCode: 401, statusMessage: 'Token invalid or expired' }) + throw createError({ statusCode: 401, statusMessage: 'Token invalid or expired', data: { err, token } }) } } }) \ No newline at end of file diff --git a/server/server.config.ts b/server/server.config.ts index 78204db..8f98d03 100644 --- a/server/server.config.ts +++ b/server/server.config.ts @@ -2,7 +2,10 @@ export default { auth: { // 需要进行鉴权的api api: new Set([ - '/api/test/testDir' + '/api/test/testDir', + '/api/posts/editPost', + '/api/posts/uploadPost', + '/api/user/updatePassword' ]) } } \ No newline at end of file diff --git a/server/utils/createToken.ts b/server/utils/createToken.ts index 6367e5f..7754d6e 100644 --- a/server/utils/createToken.ts +++ b/server/utils/createToken.ts @@ -2,7 +2,7 @@ import jwt from "jsonwebtoken" import getKey from "./getKey" -export default function createToken(userId: number): string { +export default function createToken(payload: { userId: number, password: string, username: string }): string { const key = getKey() - return jwt.sign({ userId }, key, { expiresIn: '168h' }) + return jwt.sign(payload, key, { expiresIn: '168h' }) } \ No newline at end of file