Chg: トークンの入力形式をBearerに変更 / Feat: ユニークなIDを生成する関数を作成 / Feat: userテーブルにプロフィールを作成 / Chg: ユーザー作成でのユーザー名を任意に変更 / Fix: meエンドポイントで成功時にもsuccessをレスポンスに含める / Fix: signupエンドポイントで入力が重複したときにデータベースエラーではなく入力エラーとしてレスポンス / Fix: setup/initializationエンドポイントでデータベースエラーが出る問題を修正 / Fix: L.jsの型を実際の値に更新
This commit is contained in:
@@ -14,7 +14,7 @@ const logger = new Logger("Lib | auth.ts");
|
|||||||
|
|
||||||
const Authorization: FastifyPluginCallback = (fastify) => {
|
const Authorization: FastifyPluginCallback = (fastify) => {
|
||||||
fastify.addHook("onRequest", async (req, res) => {
|
fastify.addHook("onRequest", async (req, res) => {
|
||||||
const token = req.headers["authorization"];
|
let token = req.headers["authorization"];
|
||||||
if (typeof token !== "string") {
|
if (typeof token !== "string") {
|
||||||
return req.token = ErrorBase({
|
return req.token = ErrorBase({
|
||||||
bad: "client",
|
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 {
|
try {
|
||||||
const result = await fastify.orm.em.getRepository(TokenEntity).authToken(token);
|
const result = await fastify.orm.em.getRepository(TokenEntity).authToken(token);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Entity, EntityRepositoryType, OptionalProps, PrimaryKey, Property } from "@mikro-orm/core";
|
import { Entity, EntityRepositoryType, OptionalProps, PrimaryKey, Property } from "@mikro-orm/core";
|
||||||
import { UserRepository } from "@/modules/repositories/User";
|
import { UserRepository } from "@/modules/repositories/User";
|
||||||
import { v4 as uuid } from "uuid";
|
import generateUniqueId from "@/lib/id";
|
||||||
|
|
||||||
@Entity({
|
@Entity({
|
||||||
tableName: "user",
|
tableName: "user",
|
||||||
@@ -8,13 +8,14 @@ import { v4 as uuid } from "uuid";
|
|||||||
})
|
})
|
||||||
export class UserEntity {
|
export class UserEntity {
|
||||||
[EntityRepositoryType]?: UserRepository;
|
[EntityRepositoryType]?: UserRepository;
|
||||||
[OptionalProps]?: "uuid" | "isSuspended" | "createdAt";
|
[OptionalProps]?: "profile" | "isSuspended" | "createdAt";
|
||||||
|
|
||||||
@PrimaryKey({
|
@PrimaryKey({
|
||||||
type: "string",
|
type: "string",
|
||||||
length: 36
|
length: 10,
|
||||||
|
onCreate: () => generateUniqueId(),
|
||||||
})
|
})
|
||||||
uuid: string = uuid();
|
id!: string;
|
||||||
|
|
||||||
@Property({
|
@Property({
|
||||||
type: "string",
|
type: "string",
|
||||||
@@ -29,6 +30,13 @@ export class UserEntity {
|
|||||||
})
|
})
|
||||||
username!: string;
|
username!: string;
|
||||||
|
|
||||||
|
@Property({
|
||||||
|
type: "string",
|
||||||
|
length: 4096,
|
||||||
|
default: "",
|
||||||
|
})
|
||||||
|
profile!: string;
|
||||||
|
|
||||||
@Property({
|
@Property({
|
||||||
type: "string",
|
type: "string",
|
||||||
unique: true,
|
unique: true,
|
||||||
@@ -52,7 +60,7 @@ export class UserEntity {
|
|||||||
isSuspended: boolean = false;
|
isSuspended: boolean = false;
|
||||||
|
|
||||||
@Property({
|
@Property({
|
||||||
type: "date",
|
type: "datetime",
|
||||||
onCreate: () => new Date(),
|
onCreate: () => new Date(),
|
||||||
})
|
})
|
||||||
createdAt!: Date;
|
createdAt!: Date;
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ export class UserRepository extends EntityRepository<UserEntity> {
|
|||||||
public static schema = z.object({
|
public static schema = z.object({
|
||||||
userid: z.string().trim().min(3).max(20),
|
userid: z.string().trim().min(3).max(20),
|
||||||
username: z.string().trim().min(3).max(30),
|
username: z.string().trim().min(3).max(30),
|
||||||
|
profile: z.string().max(4096).optional(),
|
||||||
email: z.string().min(6).trim().max(254).regex(EmailRegex),
|
email: z.string().min(6).trim().max(254).regex(EmailRegex),
|
||||||
password: z.string().trim().min(8),
|
password: z.string().trim().min(8),
|
||||||
isAdmin: z.boolean(),
|
isAdmin: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
async createUser(data: z.infer<typeof UserRepository.schema>) {
|
async createUser(data: Pick<z.infer<typeof UserRepository.schema>, "userid" | "email" | "password" | "isAdmin">) {
|
||||||
const hashed = await hash(data.password, {
|
const hashed = await hash(data.password, {
|
||||||
type: argon2id,
|
type: argon2id,
|
||||||
memoryCost: 2 ** 16,
|
memoryCost: 2 ** 16,
|
||||||
@@ -23,6 +24,7 @@ export class UserRepository extends EntityRepository<UserEntity> {
|
|||||||
|
|
||||||
const user = this.create({
|
const user = this.create({
|
||||||
...data,
|
...data,
|
||||||
|
username: data.userid,
|
||||||
password: hashed,
|
password: hashed,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ export default async function Me(fastify: FastifyInstance) {
|
|||||||
return res.code(400).send(req.token);
|
return res.code(400).send(req.token);
|
||||||
|
|
||||||
const { password, email, ...safeUser } = req.token.user;
|
const { password, email, ...safeUser } = req.token.user;
|
||||||
return res.send(safeUser);
|
return res.send({
|
||||||
|
...safeUser,
|
||||||
|
success: true,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,19 +10,42 @@ export default function SignUp(fastify: FastifyInstance) {
|
|||||||
fastify.post("/", async (req, res) => {
|
fastify.post("/", async (req, res) => {
|
||||||
res.header("Content-Type", "application/json");
|
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) {
|
if (!result.success) {
|
||||||
|
console.log(result.error.issues)
|
||||||
return res.code(400).send(InputError(result.error.issues));
|
return res.code(400).send(InputError(result.error.issues));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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({
|
return res.code(200).send({
|
||||||
success: true,
|
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);
|
logger.error("Database Error: User create failed.", err);
|
||||||
|
|
||||||
return res.code(500).send(DatabaseError());
|
return res.code(500).send(DatabaseError());
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import Logger from "@/lib/logger";
|
|||||||
import type { FastifyInstance } from "fastify";
|
import type { FastifyInstance } from "fastify";
|
||||||
import { UserRepository } from "@/modules/repositories/User";
|
import { UserRepository } from "@/modules/repositories/User";
|
||||||
import { DatabaseError, ErrorBase, InputError } from "@/errors";
|
import { DatabaseError, ErrorBase, InputError } from "@/errors";
|
||||||
|
import { UniqueConstraintViolationException } from "@mikro-orm/core";
|
||||||
|
|
||||||
export default function CreateAdmin(fastify: FastifyInstance) {
|
export default function CreateAdmin(fastify: FastifyInstance) {
|
||||||
const logger = new Logger("Endpoint | setup/create-admin");
|
const logger = new Logger("Endpoint | setup/create-admin");
|
||||||
@@ -10,7 +11,11 @@ export default function CreateAdmin(fastify: FastifyInstance) {
|
|||||||
fastify.post("/", async (req, res) => {
|
fastify.post("/", async (req, res) => {
|
||||||
res.header("Content-Type", "application/json");
|
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) {
|
if (!result.success) {
|
||||||
return res.code(400).send(InputError(result.error.issues));
|
return res.code(400).send(InputError(result.error.issues));
|
||||||
@@ -38,7 +43,7 @@ export default function CreateAdmin(fastify: FastifyInstance) {
|
|||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.warn("First administrator account has been created.")
|
logger.warn("First administrator account has been created.");
|
||||||
|
|
||||||
return res.code(200).send({
|
return res.code(200).send({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export default function Initialization(fastify: FastifyInstance) {
|
|||||||
const entries = Object.entries(result.data).filter(([key]) => key !== "force");
|
const entries = Object.entries(result.data).filter(([key]) => key !== "force");
|
||||||
|
|
||||||
for (const [key, value] of entries) {
|
for (const [key, value] of entries) {
|
||||||
const entity = fastify.orm.em.getRepository(ConfigEntity).set(
|
const entity = await fastify.orm.em.getRepository(ConfigEntity).set(
|
||||||
key,
|
key,
|
||||||
typeof value === "string"
|
typeof value === "string"
|
||||||
? value
|
? value
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ import ErrorBase from "../../modules/error";
|
|||||||
import DatabaseError from "../../modules/error/database";
|
import DatabaseError from "../../modules/error/database";
|
||||||
import Success from "../../modules/response/success";
|
import Success from "../../modules/response/success";
|
||||||
import YetInitializationError from "../../modules/error/yet_init";
|
import YetInitializationError from "../../modules/error/yet_init";
|
||||||
|
import UserSchema from "../../modules/user";
|
||||||
|
|
||||||
export default interface Me {
|
export default interface Me {
|
||||||
"me": {
|
"me": {
|
||||||
body: never;
|
body: never;
|
||||||
response: Success | DatabaseError | InputError | InputNoneError | YetInitializationError;
|
response: (Success & Omit<UserSchema, "password" | "email">)
|
||||||
|
| DatabaseError | InputError | InputNoneError | YetInitializationError;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ import UserSchema from "../../modules/user";
|
|||||||
|
|
||||||
export default interface PrimarySignup {
|
export default interface PrimarySignup {
|
||||||
"primary/signup": {
|
"primary/signup": {
|
||||||
body: UserSchema;
|
body: Pick<UserSchema, "userid" | "email" | "password">;
|
||||||
response: Success | DatabaseError | InputError | InputNoneError | YetInitializationError;
|
response: Success | DatabaseError | InputError | InputNoneError | YetInitializationError;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ import { UserSchema } from "../primary/signup";
|
|||||||
|
|
||||||
export default interface SetupCreateAdmin {
|
export default interface SetupCreateAdmin {
|
||||||
"setup/create-admin": {
|
"setup/create-admin": {
|
||||||
body: UserSchema;
|
body: Pick<UserSchema, "userid" | "email" | "password">;
|
||||||
response: Success | DatabaseError | ErrorBase<{
|
response: Success | DatabaseError | ErrorBase<{
|
||||||
bad: "client",
|
bad: "client",
|
||||||
code: "first_admin_already_exists",
|
code: "first_admin_already_exists",
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
export default interface UserSchema {
|
export default interface UserSchema {
|
||||||
userid: string;
|
userid: string;
|
||||||
username: string;
|
username: string;
|
||||||
|
profile: string;
|
||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
isSuspended: boolean;
|
||||||
|
isAdmin: boolean;
|
||||||
|
createdAt: string;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user