From 0e615faa7f27bff44088ab1804c6f10e70702eec Mon Sep 17 00:00:00 2001 From: Last2014 Date: Fri, 20 Mar 2026 17:44:46 +0900 Subject: [PATCH] =?UTF-8?q?Chg:=20=E3=83=88=E3=83=BC=E3=82=AF=E3=83=B3?= =?UTF-8?q?=E3=81=AE=E5=85=A5=E5=8A=9B=E5=BD=A2=E5=BC=8F=E3=82=92Bearer?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4=20/=20Feat:=20=E3=83=A6=E3=83=8B?= =?UTF-8?q?=E3=83=BC=E3=82=AF=E3=81=AAID=E3=82=92=E7=94=9F=E6=88=90?= =?UTF-8?q?=E3=81=99=E3=82=8B=E9=96=A2=E6=95=B0=E3=82=92=E4=BD=9C=E6=88=90?= =?UTF-8?q?=20/=20Feat:=20user=E3=83=86=E3=83=BC=E3=83=96=E3=83=AB?= =?UTF-8?q?=E3=81=AB=E3=83=97=E3=83=AD=E3=83=95=E3=82=A3=E3=83=BC=E3=83=AB?= =?UTF-8?q?=E3=82=92=E4=BD=9C=E6=88=90=20/=20Chg:=20=E3=83=A6=E3=83=BC?= =?UTF-8?q?=E3=82=B6=E3=83=BC=E4=BD=9C=E6=88=90=E3=81=A7=E3=81=AE=E3=83=A6?= =?UTF-8?q?=E3=83=BC=E3=82=B6=E3=83=BC=E5=90=8D=E3=82=92=E4=BB=BB=E6=84=8F?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4=20/=20Fix:=20me=E3=82=A8=E3=83=B3?= =?UTF-8?q?=E3=83=89=E3=83=9D=E3=82=A4=E3=83=B3=E3=83=88=E3=81=A7=E6=88=90?= =?UTF-8?q?=E5=8A=9F=E6=99=82=E3=81=AB=E3=82=82success=E3=82=92=E3=83=AC?= =?UTF-8?q?=E3=82=B9=E3=83=9D=E3=83=B3=E3=82=B9=E3=81=AB=E5=90=AB=E3=82=81?= =?UTF-8?q?=E3=82=8B=20/=20Fix:=20signup=E3=82=A8=E3=83=B3=E3=83=89?= =?UTF-8?q?=E3=83=9D=E3=82=A4=E3=83=B3=E3=83=88=E3=81=A7=E5=85=A5=E5=8A=9B?= =?UTF-8?q?=E3=81=8C=E9=87=8D=E8=A4=87=E3=81=97=E3=81=9F=E3=81=A8=E3=81=8D?= =?UTF-8?q?=E3=81=AB=E3=83=87=E3=83=BC=E3=82=BF=E3=83=99=E3=83=BC=E3=82=B9?= =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E3=81=A7=E3=81=AF=E3=81=AA=E3=81=8F?= =?UTF-8?q?=E5=85=A5=E5=8A=9B=E3=82=A8=E3=83=A9=E3=83=BC=E3=81=A8=E3=81=97?= =?UTF-8?q?=E3=81=A6=E3=83=AC=E3=82=B9=E3=83=9D=E3=83=B3=E3=82=B9=20/=20Fi?= =?UTF-8?q?x:=20setup/initialization=E3=82=A8=E3=83=B3=E3=83=89=E3=83=9D?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=83=88=E3=81=A7=E3=83=87=E3=83=BC=E3=82=BF?= =?UTF-8?q?=E3=83=99=E3=83=BC=E3=82=B9=E3=82=A8=E3=83=A9=E3=83=BC=E3=81=8C?= =?UTF-8?q?=E5=87=BA=E3=82=8B=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=20/=20Fix:=20L.js=E3=81=AE=E5=9E=8B=E3=82=92=E5=AE=9F=E9=9A=9B?= =?UTF-8?q?=E3=81=AE=E5=80=A4=E3=81=AB=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/lib/auth.ts | 14 ++++++++- packages/backend/src/lib/id.ts | 13 +++++++++ packages/backend/src/modules/entities/User.ts | 18 ++++++++---- .../backend/src/modules/repositories/User.ts | 4 ++- packages/backend/src/routes/me/index.ts | 5 +++- packages/backend/src/routes/primary/signup.ts | 29 +++++++++++++++++-- .../backend/src/routes/setup/create-admin.ts | 9 ++++-- .../src/routes/setup/initialization.ts | 2 +- .../src/1.0.0-alpha.0/api/me/index.d.ts | 4 ++- .../src/1.0.0-alpha.0/api/primary/signup.d.ts | 2 +- .../1.0.0-alpha.0/api/setup/create-admin.d.ts | 2 +- .../src/1.0.0-alpha.0/modules/user.d.ts | 4 +++ 12 files changed, 89 insertions(+), 17 deletions(-) create mode 100644 packages/backend/src/lib/id.ts diff --git a/packages/backend/src/lib/auth.ts b/packages/backend/src/lib/auth.ts index 95218db..2e90208 100644 --- a/packages/backend/src/lib/auth.ts +++ b/packages/backend/src/lib/auth.ts @@ -14,7 +14,7 @@ const logger = new Logger("Lib | auth.ts"); const Authorization: FastifyPluginCallback = (fastify) => { fastify.addHook("onRequest", async (req, res) => { - const token = req.headers["authorization"]; + let token = req.headers["authorization"]; if (typeof token !== "string") { return req.token = ErrorBase({ bad: "client", @@ -23,6 +23,18 @@ const Authorization: FastifyPluginCallback = (fastify) => { }); } + if (!token.startsWith("Bearer ")) { + req.token = ErrorBase({ + bad: "client", + code: "token_invalid", + message: "トークンが不正です。", + }); + + return; + } + + token = token.replace("Bearer ", ""); + try { const result = await fastify.orm.em.getRepository(TokenEntity).authToken(token); diff --git a/packages/backend/src/lib/id.ts b/packages/backend/src/lib/id.ts new file mode 100644 index 0000000..3570111 --- /dev/null +++ b/packages/backend/src/lib/id.ts @@ -0,0 +1,13 @@ +import { randomBytes } from "node:crypto"; + +export default function generateUniqueId(length = 10) { + const guide = Math.ceil(length * 5 / 8); + const bytes = randomBytes(guide); + const bigint = BigInt("0x" + bytes.toString("hex")); + + let id = bigint.toString(36); + id = id.padStart(length, "0") + id = id.slice(-length); + + return id; +} \ No newline at end of file diff --git a/packages/backend/src/modules/entities/User.ts b/packages/backend/src/modules/entities/User.ts index db8205a..c2b367c 100644 --- a/packages/backend/src/modules/entities/User.ts +++ b/packages/backend/src/modules/entities/User.ts @@ -1,6 +1,6 @@ import { Entity, EntityRepositoryType, OptionalProps, PrimaryKey, Property } from "@mikro-orm/core"; import { UserRepository } from "@/modules/repositories/User"; -import { v4 as uuid } from "uuid"; +import generateUniqueId from "@/lib/id"; @Entity({ tableName: "user", @@ -8,13 +8,14 @@ import { v4 as uuid } from "uuid"; }) export class UserEntity { [EntityRepositoryType]?: UserRepository; - [OptionalProps]?: "uuid" | "isSuspended" | "createdAt"; + [OptionalProps]?: "profile" | "isSuspended" | "createdAt"; @PrimaryKey({ type: "string", - length: 36 + length: 10, + onCreate: () => generateUniqueId(), }) - uuid: string = uuid(); + id!: string; @Property({ type: "string", @@ -29,6 +30,13 @@ export class UserEntity { }) username!: string; + @Property({ + type: "string", + length: 4096, + default: "", + }) + profile!: string; + @Property({ type: "string", unique: true, @@ -52,7 +60,7 @@ export class UserEntity { isSuspended: boolean = false; @Property({ - type: "date", + type: "datetime", onCreate: () => new Date(), }) createdAt!: Date; diff --git a/packages/backend/src/modules/repositories/User.ts b/packages/backend/src/modules/repositories/User.ts index 1ec10f4..8fbe742 100644 --- a/packages/backend/src/modules/repositories/User.ts +++ b/packages/backend/src/modules/repositories/User.ts @@ -8,12 +8,13 @@ export class UserRepository extends EntityRepository { public static schema = z.object({ userid: z.string().trim().min(3).max(20), username: z.string().trim().min(3).max(30), + profile: z.string().max(4096).optional(), email: z.string().min(6).trim().max(254).regex(EmailRegex), password: z.string().trim().min(8), isAdmin: z.boolean(), }); - async createUser(data: z.infer) { + async createUser(data: Pick, "userid" | "email" | "password" | "isAdmin">) { const hashed = await hash(data.password, { type: argon2id, memoryCost: 2 ** 16, @@ -23,6 +24,7 @@ export class UserRepository extends EntityRepository { const user = this.create({ ...data, + username: data.userid, password: hashed, }); diff --git a/packages/backend/src/routes/me/index.ts b/packages/backend/src/routes/me/index.ts index ffac7a6..c345b53 100644 --- a/packages/backend/src/routes/me/index.ts +++ b/packages/backend/src/routes/me/index.ts @@ -6,6 +6,9 @@ export default async function Me(fastify: FastifyInstance) { return res.code(400).send(req.token); const { password, email, ...safeUser } = req.token.user; - return res.send(safeUser); + return res.send({ + ...safeUser, + success: true, + }); }); } diff --git a/packages/backend/src/routes/primary/signup.ts b/packages/backend/src/routes/primary/signup.ts index a7c56d5..3bbf758 100644 --- a/packages/backend/src/routes/primary/signup.ts +++ b/packages/backend/src/routes/primary/signup.ts @@ -10,19 +10,42 @@ export default function SignUp(fastify: FastifyInstance) { fastify.post("/", async (req, res) => { res.header("Content-Type", "application/json"); - const result = UserRepository.schema.safeParse(req.body); + const result = UserRepository.schema.pick({ + userid: true, + email: true, + password: true, + }).safeParse(req.body); if (!result.success) { + console.log(result.error.issues) return res.code(400).send(InputError(result.error.issues)); } try { - await fastify.orm.em.getRepository(UserEntity).createUser(result.data); + await fastify.orm.em.getRepository(UserEntity).createUser({ + ...result.data, + isAdmin: false, + }); return res.code(200).send({ success: true, }); - } catch (err) { + } catch (err: any) { + if (err.name === "UniqueConstraintViolationException") { + const duplicate = err.constraint.replace("user_", "").replace("_unique", ""); + + if (duplicate !== "id") { + return res.code(400).send(InputError([ + { + validation: "regex", + code: "invalid_string", + message: "Duplicate", + path: [duplicate] + } + ])) + } + } + logger.error("Database Error: User create failed.", err); return res.code(500).send(DatabaseError()); diff --git a/packages/backend/src/routes/setup/create-admin.ts b/packages/backend/src/routes/setup/create-admin.ts index 4cec9f6..91eba84 100755 --- a/packages/backend/src/routes/setup/create-admin.ts +++ b/packages/backend/src/routes/setup/create-admin.ts @@ -3,6 +3,7 @@ import Logger from "@/lib/logger"; import type { FastifyInstance } from "fastify"; import { UserRepository } from "@/modules/repositories/User"; import { DatabaseError, ErrorBase, InputError } from "@/errors"; +import { UniqueConstraintViolationException } from "@mikro-orm/core"; export default function CreateAdmin(fastify: FastifyInstance) { const logger = new Logger("Endpoint | setup/create-admin"); @@ -10,7 +11,11 @@ export default function CreateAdmin(fastify: FastifyInstance) { fastify.post("/", async (req, res) => { res.header("Content-Type", "application/json"); - const result = UserRepository.schema.omit({ isAdmin: true }).safeParse(req.body); + const result = UserRepository.schema.pick({ + userid: true, + email: true, + password: true, + }).safeParse(req.body); if (!result.success) { return res.code(400).send(InputError(result.error.issues)); @@ -38,7 +43,7 @@ export default function CreateAdmin(fastify: FastifyInstance) { isAdmin: true, }); - logger.warn("First administrator account has been created.") + logger.warn("First administrator account has been created."); return res.code(200).send({ success: true, diff --git a/packages/backend/src/routes/setup/initialization.ts b/packages/backend/src/routes/setup/initialization.ts index 5330345..2fef43b 100644 --- a/packages/backend/src/routes/setup/initialization.ts +++ b/packages/backend/src/routes/setup/initialization.ts @@ -48,7 +48,7 @@ export default function Initialization(fastify: FastifyInstance) { const entries = Object.entries(result.data).filter(([key]) => key !== "force"); for (const [key, value] of entries) { - const entity = fastify.orm.em.getRepository(ConfigEntity).set( + const entity = await fastify.orm.em.getRepository(ConfigEntity).set( key, typeof value === "string" ? value diff --git a/packages/lynqchat-js/src/1.0.0-alpha.0/api/me/index.d.ts b/packages/lynqchat-js/src/1.0.0-alpha.0/api/me/index.d.ts index f07f85a..765632b 100644 --- a/packages/lynqchat-js/src/1.0.0-alpha.0/api/me/index.d.ts +++ b/packages/lynqchat-js/src/1.0.0-alpha.0/api/me/index.d.ts @@ -3,10 +3,12 @@ import ErrorBase from "../../modules/error"; import DatabaseError from "../../modules/error/database"; import Success from "../../modules/response/success"; import YetInitializationError from "../../modules/error/yet_init"; +import UserSchema from "../../modules/user"; export default interface Me { "me": { body: never; - response: Success | DatabaseError | InputError | InputNoneError | YetInitializationError; + response: (Success & Omit) + | DatabaseError | InputError | InputNoneError | YetInitializationError; }; } \ No newline at end of file diff --git a/packages/lynqchat-js/src/1.0.0-alpha.0/api/primary/signup.d.ts b/packages/lynqchat-js/src/1.0.0-alpha.0/api/primary/signup.d.ts index 75c02ac..e80ba94 100644 --- a/packages/lynqchat-js/src/1.0.0-alpha.0/api/primary/signup.d.ts +++ b/packages/lynqchat-js/src/1.0.0-alpha.0/api/primary/signup.d.ts @@ -7,7 +7,7 @@ import UserSchema from "../../modules/user"; export default interface PrimarySignup { "primary/signup": { - body: UserSchema; + body: Pick; response: Success | DatabaseError | InputError | InputNoneError | YetInitializationError; }; } \ No newline at end of file diff --git a/packages/lynqchat-js/src/1.0.0-alpha.0/api/setup/create-admin.d.ts b/packages/lynqchat-js/src/1.0.0-alpha.0/api/setup/create-admin.d.ts index 501e46e..9837296 100644 --- a/packages/lynqchat-js/src/1.0.0-alpha.0/api/setup/create-admin.d.ts +++ b/packages/lynqchat-js/src/1.0.0-alpha.0/api/setup/create-admin.d.ts @@ -7,7 +7,7 @@ import { UserSchema } from "../primary/signup"; export default interface SetupCreateAdmin { "setup/create-admin": { - body: UserSchema; + body: Pick; response: Success | DatabaseError | ErrorBase<{ bad: "client", code: "first_admin_already_exists", diff --git a/packages/lynqchat-js/src/1.0.0-alpha.0/modules/user.d.ts b/packages/lynqchat-js/src/1.0.0-alpha.0/modules/user.d.ts index 3d89d48..5b152c6 100644 --- a/packages/lynqchat-js/src/1.0.0-alpha.0/modules/user.d.ts +++ b/packages/lynqchat-js/src/1.0.0-alpha.0/modules/user.d.ts @@ -1,6 +1,10 @@ export default interface UserSchema { userid: string; username: string; + profile: string; email: string; password: string; + isSuspended: boolean; + isAdmin: boolean; + createdAt: string; } \ No newline at end of file