From c4e3d4cb9b049d2fd5dbcc1a7a8aa085e20c22cd Mon Sep 17 00:00:00 2001 From: Last2014 Date: Fri, 3 Oct 2025 19:50:42 +0900 Subject: [PATCH] =?UTF-8?q?=E8=AA=8D=E8=A8=BC=E9=96=A2=E9=80=A3=E3=81=AE?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E3=83=BBSQL=E3=81=AE=E6=A7=8B=E9=80=A0?= =?UTF-8?q?=E3=82=92=E5=A4=89=E6=9B=B4=E3=83=BB/api/timeline=E3=82=92?= =?UTF-8?q?=E4=BD=9C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yaml | 2 + locales/ja.yaml | 2 + package.json | 3 -- packages/web/package.json | 2 +- packages/web/pnpm-lock.yaml | 43 ++++------------- packages/web/src/lib/token.ts | 33 +++++++++++++ packages/web/src/routes/api/main.ts | 3 ++ packages/web/src/routes/api/signin.ts | 19 ++++++-- packages/web/src/routes/api/signup.ts | 15 ++++-- .../web/src/routes/api/timeline/community.ts | 5 ++ packages/web/src/routes/api/timeline/main.ts | 11 +++++ packages/web/src/routes/api/timeline/users.ts | 46 +++++++++++++++++++ packages/web/src/routes/pages/home.tsx | 5 +- packages/web/src/routes/pages/index.tsx | 7 ++- packages/web/src/routes/pages/mailverify.tsx | 7 ++- packages/web/src/routes/pages/signin.tsx | 7 ++- packages/web/src/routes/pages/signup.tsx | 7 ++- packages/web/types/api/scope.d.ts | 3 +- scripts/peas.sql | 4 +- 19 files changed, 159 insertions(+), 65 deletions(-) create mode 100644 packages/web/src/routes/api/timeline/community.ts create mode 100644 packages/web/src/routes/api/timeline/main.ts create mode 100644 packages/web/src/routes/api/timeline/users.ts diff --git a/locales/en.yaml b/locales/en.yaml index 74278c2..8762f8c 100644 --- a/locales/en.yaml +++ b/locales/en.yaml @@ -84,3 +84,5 @@ ERR_code_invalid: Incorrect code ERR_code_not_found: Code not found ERR_user_not_found: User not found ERR_password_invalid: Incorrect password +ERR_not_mailverify: Email not verified +ERR_auth_failed: Auth failed diff --git a/locales/ja.yaml b/locales/ja.yaml index d213883..96fa293 100644 --- a/locales/ja.yaml +++ b/locales/ja.yaml @@ -84,3 +84,5 @@ ERR_code_invalid: コードが正しくありません ERR_code_not_found: コードが見つかりません ERR_user_not_found: ユーザーが見つかりません ERR_password_invalid: パスワードが異なります +ERR_not_mailverify: メール認証されていません +ERR_auth_failed: 認証に失敗しました diff --git a/package.json b/package.json index e0e9023..8c8a4a1 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,6 @@ "version": "1.0.0", "private": true, "description": "Peas is a SNS designed for sharing profiles.", - "scripts": { - "build:locale": "tsc" - }, "keywords": [ "sns", "profile" diff --git a/packages/web/package.json b/packages/web/package.json index f504317..10fa7cd 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -17,6 +17,7 @@ "@types/bcrypt": "^6.0.0", "@types/nodemailer": "^7.0.2", "bcrypt": "^6.0.0", + "child_process": "^1.0.2", "daisyui": "^5.1.25", "fs": "0.0.1-security", "globals": "^16.4.0", @@ -26,7 +27,6 @@ "nodemailer": "^7.0.6", "path": "^0.12.7", "tailwindcss": "^4.1.13", - "ua-parser-js": "^2.0.5", "url": "^0.11.4", "uuid": "^13.0.0" }, diff --git a/packages/web/pnpm-lock.yaml b/packages/web/pnpm-lock.yaml index 3fb4959..759f96a 100644 --- a/packages/web/pnpm-lock.yaml +++ b/packages/web/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: bcrypt: specifier: ^6.0.0 version: 6.0.0 + child_process: + specifier: ^1.0.2 + version: 1.0.2 daisyui: specifier: ^5.1.25 version: 5.1.26 @@ -50,9 +53,6 @@ importers: tailwindcss: specifier: ^4.1.13 version: 4.1.14 - ua-parser-js: - specifier: ^2.0.5 - version: 2.0.5 url: specifier: ^0.11.4 version: 0.11.4 @@ -754,6 +754,9 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} + child_process@1.0.2: + resolution: {integrity: sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g==} + chownr@3.0.0: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} @@ -765,9 +768,6 @@ packages: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} - detect-europe-js@0.1.2: - resolution: {integrity: sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==} - detect-libc@1.0.3: resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} engines: {node: '>=0.10'} @@ -880,9 +880,6 @@ packages: is-property@1.0.2: resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} - is-standalone-pwa@0.1.1: - resolution: {integrity: sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==} - jiti@2.6.1: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true @@ -1097,20 +1094,9 @@ packages: engines: {node: '>=14.17'} hasBin: true - ua-is-frozen@0.1.2: - resolution: {integrity: sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==} - - ua-parser-js@2.0.5: - resolution: {integrity: sha512-sZErtx3rhpvZQanWW5umau4o/snfoLqRcQwQIZ54377WtRzIecnIKvjpkd5JwPcSUMglGnbIgcsQBGAbdi3S9Q==} - hasBin: true - undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici@7.16.0: - resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} - engines: {node: '>=20.18.1'} - url@0.11.4: resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==} engines: {node: '>= 0.4'} @@ -2064,14 +2050,14 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + child_process@1.0.2: {} + chownr@3.0.0: {} daisyui@5.1.26: {} denque@2.1.0: {} - detect-europe-js@0.1.2: {} - detect-libc@1.0.3: {} detect-libc@2.1.1: {} @@ -2195,8 +2181,6 @@ snapshots: is-property@1.0.2: {} - is-standalone-pwa@0.1.1: {} - jiti@2.6.1: {} lightningcss-darwin-arm64@1.30.1: @@ -2379,19 +2363,8 @@ snapshots: typescript@5.9.3: {} - ua-is-frozen@0.1.2: {} - - ua-parser-js@2.0.5: - dependencies: - detect-europe-js: 0.1.2 - is-standalone-pwa: 0.1.1 - ua-is-frozen: 0.1.2 - undici: 7.16.0 - undici-types@6.21.0: {} - undici@7.16.0: {} - url@0.11.4: dependencies: punycode: 1.4.1 diff --git a/packages/web/src/lib/token.ts b/packages/web/src/lib/token.ts index 3fb7062..008b7ab 100644 --- a/packages/web/src/lib/token.ts +++ b/packages/web/src/lib/token.ts @@ -24,7 +24,40 @@ export async function getScopeAPIToken(token: string) { [token] ); + if (tokenData === undefined) { + return false; + } + const scope: scope[] = tokenData[0].scope.split(","); return scope; } + +export async function Auth(token: string, scopes: scope[]) { + const scope = await getScopeAPIToken(token); + if (scope === false) + return false; + if (scope.indexOf("admin:client") !== -1) + return true; + if ( + scope.indexOf("user:client") !== -1 && + !scopes.some(s => s.includes("admin")) + ) + return true; + if (scopes.sort().toString() === scope.sort().toString()) + return true; + return false; +} + +export async function isSignin(token: string) { + return await getScopeAPIToken(token) !== false; +} + +export function getHeaderToken(header: string) { + const match = header.match(/^Bearer\s(.+)$/); + + if (match && match.length > 1) { + return match[1]; + } + return ""; +} diff --git a/packages/web/src/routes/api/main.ts b/packages/web/src/routes/api/main.ts index 88da13f..1ce2d0a 100644 --- a/packages/web/src/routes/api/main.ts +++ b/packages/web/src/routes/api/main.ts @@ -14,6 +14,9 @@ API.route("/signin", SignInAPI); import mailverifyAPI from "./mailverify.js"; API.route("/mailverify", mailverifyAPI); +import TimeLine from "./timeline/main.js"; +API.route("/timelime", TimeLine); + import NotFound from "./notfound.js"; API.route("*", NotFound); diff --git a/packages/web/src/routes/api/signin.ts b/packages/web/src/routes/api/signin.ts index 02f97e5..0ee97ca 100644 --- a/packages/web/src/routes/api/signin.ts +++ b/packages/web/src/routes/api/signin.ts @@ -5,10 +5,9 @@ import { getKeys as TurnstileKeys, isEnabled as Turnstile } from "../../lib/turn import { getConnInfo } from "@hono/node-server/conninfo"; import { compareSync as passwordHashCheck } from "bcrypt"; import { v4 as uuid } from "uuid"; -import MailVerifySend from "../../lib/mailverify.js"; import { createAPIToken } from "src/lib/token.js"; -import { UAParser } from "ua-parser-js"; import { setCookie } from "hono/cookie"; +import type scope from "../../../types/api/scope.js"; const SignInAPI = new Hono(); @@ -26,7 +25,7 @@ SignInAPI.post("/", async (c) => { if ( body.username.length < 3 || - body.username.length > 15 || + body.username.length > 20 || body.password.length < 8 || body.password.length > 15 ) { @@ -95,10 +94,22 @@ SignInAPI.post("/", async (c) => { }, 400); } + if (user[0].mailverify === 0) { + return c.json({ + success: false, + error: "not_mailverify", + }, 400); + } + if (passwordHashCheck(body.password, user[0].password)) { + const scope: scope[] = ["user:client"]; + if (user[0].isAdmin === 1) { + scope.push("admin:client"); + } + const token = await createAPIToken( body.username, - ["client"], + scope, "Peas Client" ); diff --git a/packages/web/src/routes/api/signup.ts b/packages/web/src/routes/api/signup.ts index d29878e..c2fef06 100644 --- a/packages/web/src/routes/api/signup.ts +++ b/packages/web/src/routes/api/signup.ts @@ -24,7 +24,7 @@ SignUpAPI.post("/", async (c) => { if ( body.username.length < 3 || - body.username.length > 15 || + body.username.length > 20 || body.password.length < 8 || body.password.length > 15 ) { @@ -107,9 +107,18 @@ SignUpAPI.post("/", async (c) => { const passwordHash = await generatePasswordHash(body.password, 10); + const [otherUser] = await pool.execute( + "SELECT * FROM users", + ); + + let isAdmin = 0; + if (otherUser.length === 0) { + isAdmin = 1; + } + const [result] = await pool.execute( - "INSERT INTO `users` (`id`, `password`, `email`, `mailverified`, `time`) VALUES (?, ?, ?, '0', current_timestamp(3))", - [body.username, passwordHash, body.email], + "INSERT INTO `users` (`id`, `name`, `password`, `email`, `mailverified`, `isAdmin`, `time`) VALUES (?, ?, ?, ?, '0', ?, current_timestamp(3))", + [body.username, body.username, passwordHash, body.email, String(isAdmin)], ); if ((result as RowDataPacket).affectedRows === 1) { diff --git a/packages/web/src/routes/api/timeline/community.ts b/packages/web/src/routes/api/timeline/community.ts new file mode 100644 index 0000000..913d7cb --- /dev/null +++ b/packages/web/src/routes/api/timeline/community.ts @@ -0,0 +1,5 @@ +import { Hono } from "hono"; + +const Community = new Hono(); + +export default Community; diff --git a/packages/web/src/routes/api/timeline/main.ts b/packages/web/src/routes/api/timeline/main.ts new file mode 100644 index 0000000..c43cabf --- /dev/null +++ b/packages/web/src/routes/api/timeline/main.ts @@ -0,0 +1,11 @@ +import { Hono } from "hono"; + +const TimeLine = new Hono(); + +import Users from "./users.js"; +TimeLine.route("/users", Users); + +import Community from "./community.js"; +TimeLine.route("/community", Community); + +export default TimeLine; diff --git a/packages/web/src/routes/api/timeline/users.ts b/packages/web/src/routes/api/timeline/users.ts new file mode 100644 index 0000000..c74ee46 --- /dev/null +++ b/packages/web/src/routes/api/timeline/users.ts @@ -0,0 +1,46 @@ +import { Hono } from "hono"; +import { Auth, getHeaderToken } from "../../../lib/token.js"; +import pool from "../../../lib/database.js"; +import type { RowDataPacket } from "mysql2"; + +const Users = new Hono(); + +Users.post("/", async (c) => { + const body = await c.req.json(); + if (!Auth(getHeaderToken(c.req.header("Authorization") ?? ""), ["read:user"])) { + return c.json({ + success: false, + error: "auth_failed", + }); + } + + let page = 1; + if ( + typeof body.page === "string" && + !isNaN(Number(body.page)) && + Number(body.page) >= 1 + ) { + page = Number(body.page); + } + + const [usersData] = await pool.execute( + "SELECT * FROM users" + ); + + let result = []; + for (let i = usersData.length - 1; i < 0; i--) { + result.push({ + username: usersData[i].id, + name: usersData[i].name, + isAdmin: usersData[i].isAdmin === 1 ? true: false, + createAt: usersData[i].time, + }); + } + + return c.json({ + success: true, + users: result, + }); +}); + +export default Users; diff --git a/packages/web/src/routes/pages/home.tsx b/packages/web/src/routes/pages/home.tsx index 4b133cd..6950820 100644 --- a/packages/web/src/routes/pages/home.tsx +++ b/packages/web/src/routes/pages/home.tsx @@ -4,11 +4,14 @@ import type InfoAPI from "../../../types/api/info"; import Config from "../../../config/peas.config.js"; import { getCookie } from "hono/cookie"; import { Locale } from "../../lib/locale.js"; +import { isSignin } from "../../lib/token.js"; const Home = new Hono(); Home.get("/", async (c) => { - if (getCookie(c, "token") === undefined) return c.redirect("/signin"); + if (await isSignin(getCookie(c, "token") ?? "") === false) { + return c.redirect("/signin"); + } const InfoReq = await fetch(`${Config.server.origin}/api/info`, { method: "POST", diff --git a/packages/web/src/routes/pages/index.tsx b/packages/web/src/routes/pages/index.tsx index 7338691..e396168 100644 --- a/packages/web/src/routes/pages/index.tsx +++ b/packages/web/src/routes/pages/index.tsx @@ -6,14 +6,13 @@ import Icon from "../../components/iconify.js"; import { Locale } from "../../lib/locale.js"; import toComponent from "../../lib/toComponent.js"; import { getCookie } from "hono/cookie"; -import { getScopeAPIToken } from "../../lib/token.js"; +import { isSignin } from "../../lib/token.js"; const Index = new Hono(); Index.get("/", async (c) => { - if (getCookie(c, "token") !== undefined) { - const scope = await getScopeAPIToken(getCookie(c, "token") ?? ""); - if (scope !== undefined) return c.redirect("/home"); + if (await isSignin(getCookie(c, "token") ?? "")) { + return c.redirect("/home"); } const InfoReq = await fetch(`${Config.server.origin}/api/info`, { diff --git a/packages/web/src/routes/pages/mailverify.tsx b/packages/web/src/routes/pages/mailverify.tsx index b9b2924..b45d338 100644 --- a/packages/web/src/routes/pages/mailverify.tsx +++ b/packages/web/src/routes/pages/mailverify.tsx @@ -8,14 +8,13 @@ import { getKeys as getTurnstileKeys, isEnabled as TurnstileIsEnabled } from ".. import Turnstile from "../../components/turnstile.js"; import { html } from "hono/html"; import { getCookie } from "hono/cookie"; -import { getScopeAPIToken } from "../../lib/token.js"; +import { isSignin } from "../../lib/token.js"; const MailVerify = new Hono(); MailVerify.get("/", async (c) => { - if (getCookie(c, "token") !== undefined) { - const scope = await getScopeAPIToken(getCookie(c, "token") ?? ""); - if (scope !== undefined) return c.redirect("/home"); + if (await isSignin(getCookie(c, "token") ?? "")) { + return c.redirect("/home"); } const InfoReq = await fetch(`${Config.server.origin}/api/info`, { diff --git a/packages/web/src/routes/pages/signin.tsx b/packages/web/src/routes/pages/signin.tsx index 11112ef..6c89f01 100644 --- a/packages/web/src/routes/pages/signin.tsx +++ b/packages/web/src/routes/pages/signin.tsx @@ -9,14 +9,13 @@ import Turnstile from "../../components/turnstile.js"; import { getKeys as getTurnstileKeys, isEnabled as TurnstileIsEnabled } from "../../lib/turnstile.js"; import { html } from "hono/html"; import { getCookie } from "hono/cookie"; -import { getScopeAPIToken } from "../../lib/token.js"; +import { isSignin } from "../../lib/token.js"; const SignIn = new Hono(); SignIn.get("/", async (c) => { - if (getCookie(c, "token") !== undefined) { - const scope = await getScopeAPIToken(getCookie(c, "token") ?? ""); - if (scope !== undefined) return c.redirect("/home"); + if (await isSignin(getCookie(c, "token") ?? "")) { + return c.redirect("/home"); } const InfoReq = await fetch(`${Config.server.origin}/api/info`, { diff --git a/packages/web/src/routes/pages/signup.tsx b/packages/web/src/routes/pages/signup.tsx index e6001ee..3e52044 100644 --- a/packages/web/src/routes/pages/signup.tsx +++ b/packages/web/src/routes/pages/signup.tsx @@ -10,14 +10,13 @@ import Turnstile from "../../components/turnstile.js"; import { getKeys as getTurnstileKeys, isEnabled as TurnstileIsEnabled } from "../../lib/turnstile.js"; import { html } from "hono/html"; import { getCookie } from "hono/cookie"; -import { getScopeAPIToken } from "../../lib/token.js"; +import { isSignin } from "../../lib/token.js"; const SignUp = new Hono(); SignUp.get("/", async (c) => { - if (getCookie(c, "token") !== undefined) { - const scope = await getScopeAPIToken(getCookie(c, "token") ?? ""); - if (scope !== undefined) return c.redirect("/home"); + if (await isSignin(getCookie(c, "token") ?? "")) { + return c.redirect("/home"); } const InfoReq = await fetch(`${Config.server.origin}/api/info`, { diff --git a/packages/web/types/api/scope.d.ts b/packages/web/types/api/scope.d.ts index 11ec6c6..895ffb6 100644 --- a/packages/web/types/api/scope.d.ts +++ b/packages/web/types/api/scope.d.ts @@ -1,5 +1,6 @@ type scope = "read:user" | -"client"; +"admin:client" | +"user:client"; export default scope; diff --git a/scripts/peas.sql b/scripts/peas.sql index a5fe27e..3942d06 100644 --- a/scripts/peas.sql +++ b/scripts/peas.sql @@ -88,10 +88,12 @@ DROP TABLE IF EXISTS `users`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `users` ( - `id` text NOT NULL, + `id` VARCHAR(20) NOT NULL, + `name` VARCHAR(50) NOT NULL, `password` text NOT NULL, `email` text NOT NULL, `mailverified` int(1) NOT NULL DEFAULT 0, + `isAdmin` int(1) NOT NULL DEFAULT 0, `time` datetime(3) NOT NULL DEFAULT current_timestamp(3), `num` int(11) NOT NULL AUTO_INCREMENT, PRIMARY KEY (`num`),