First commit

This commit is contained in:
2026-03-18 22:42:33 +09:00
commit 50657066a6
64 changed files with 5290 additions and 0 deletions
@@ -0,0 +1,10 @@
import { Entity, PrimaryKey, Property } from "@mikro-orm/core";
@Entity({ tableName: "config" })
export class ConfigEntity {
@PrimaryKey({ type: "string" })
name!: string;
@Property({ type: "text" })
value!: string;
}
@@ -0,0 +1,31 @@
import { Entity, EntityRepositoryType, Index, ManyToOne, OptionalProps, PrimaryKey, Property } from "@mikro-orm/core";
import { UserEntity } from "@/modules/entities/User";
import { TokenRepository } from "@/modules/repositories/Token";
@Entity({
tableName: "token",
repository: () => TokenRepository,
})
export class TokenEntity {
[EntityRepositoryType]?: TokenRepository;
[OptionalProps]?: "createdAt";
@PrimaryKey({ type: "string", length: 64 })
name!: string;
@Property({ type: "text" })
passphrase!: string;
@ManyToOne(() => UserEntity)
@Index()
user!: UserEntity;
@Property()
isNative!: boolean;
@Property({ onCreate: () => new Date() })
createdAt!: Date;
@Property({ nullable: true })
lastUsedAt?: Date;
}
@@ -0,0 +1,36 @@
import { Entity, EntityRepositoryType, OptionalProps, PrimaryKey, Property } from "@mikro-orm/core";
import { UserRepository } from "@/modules/repositories/User";
import { v4 as uuid } from "uuid";
@Entity({
tableName: "user",
repository: () => UserRepository,
})
export class UserEntity {
[EntityRepositoryType]?: UserRepository;
[OptionalProps]?: "uuid" | "isSuspended" | "createdAt";
@PrimaryKey({ length: 36 })
uuid: string = uuid();
@Property({ unique: true, length: 20 })
userid!: string;
@Property({ length: 30 })
username!: string;
@Property({ unique: true, length: 256 })
email!: string;
@Property({ type: "text" })
password!: string;
@Property({ default: false })
isAdmin: boolean = false;
@Property({ default: false })
isSuspended: boolean = false;
@Property({ onCreate: () => new Date() })
createdAt!: Date;
}
@@ -0,0 +1,68 @@
import { EntityRepository } from "@mikro-orm/postgresql";
import type { TokenEntity } from "@/modules/entities/Token";
import { hash, argon2id, verify as argon2Verify } from "argon2";
import { randomBytes } from "node:crypto";
import { UserEntity } from "@/modules/entities/User";
import { ErrorBase } from "@/errors";
export class TokenRepository extends EntityRepository<TokenEntity> {
async createToken(user: UserEntity, isNative: boolean) {
const name = randomBytes(32).toString("hex");
const passphrase = randomBytes(32).toString("hex");
const hashed = await hash(passphrase, {
type: argon2id,
memoryCost: 2 ** 16,
timeCost: 3,
parallelism: 1,
});
const token = this.create({
name,
passphrase: hashed,
isNative,
user,
});
await this.em.persist(token).flush();
return `${name}_${passphrase}`;
}
async authToken(
tokenStr: string
): Promise<TokenEntity | ReturnType<typeof ErrorBase>> {
const tokenArr = tokenStr.split("_");
if (
tokenArr[0]?.length !== 64 ||
tokenArr[1]?.length !== 64
)
return ErrorBase({
bad: "client",
code: "token_length_wrong",
message: "トークンの文字数が不正です。",
});
const token = await this.findOne({ name: tokenArr[0] }, { populate: ["user"] });
if (!token)
return ErrorBase({
bad: "client",
code: "token_invalid",
message: "トークンが不正です。",
});
const isOk = await argon2Verify(token.passphrase, tokenArr[1]);
if (!isOk)
return ErrorBase({
bad: "client",
code: "token_invalid",
message: "トークンが不正です。",
});
token.lastUsedAt = new Date()
await this.em.persist(token).flush();
return token;
}
}
@@ -0,0 +1,55 @@
import { EntityRepository } from "@mikro-orm/postgresql";
import { UserEntity } from "@/modules/entities/User";
import { hash, argon2id, verify as argon2Verify } from "argon2";
import z from "zod/v3";
import EmailRegex from "@/regexs/email";
export class UserRepository extends EntityRepository<UserEntity> {
public static schema = z.object({
userid: z.string().trim().min(3).max(20),
username: z.string().trim().min(3).max(30),
email: z.string().min(6).trim().max(254).regex(EmailRegex),
password: z.string().trim().min(8),
isAdmin: z.boolean(),
});
async createUser(data: z.infer<typeof UserRepository.schema>) {
const hashed = await hash(data.password, {
type: argon2id,
memoryCost: 2 ** 16,
timeCost: 3,
parallelism: 1,
});
const user = this.create({
...data,
password: hashed,
});
await this.em.persist(user).flush();
return;
}
async findByUserId(userid: string) {
return this.findOne({ userid });
}
async findByEmail(email: string) {
return this.findOne({ email });
}
async authUser(data: {
userid: string;
password: string;
}) {
const user = await this.findByUserId(data.userid);
if (!user)
return "userid_wrong";
const isOk = await argon2Verify(user.password, data.password);
if (!isOk)
return "password_wrong";
return user;
}
}