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