From 811458427593a4172a2cd535cc768db375350dca Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 6 Dec 2025 17:05:21 +0900 Subject: feat(dev): change architecture and directory structure --- pkgs/server/src/routes/auth.ts | 199 ----------------------------------------- 1 file changed, 199 deletions(-) delete mode 100644 pkgs/server/src/routes/auth.ts (limited to 'pkgs/server/src/routes/auth.ts') diff --git a/pkgs/server/src/routes/auth.ts b/pkgs/server/src/routes/auth.ts deleted file mode 100644 index e1f7ebb..0000000 --- a/pkgs/server/src/routes/auth.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { createHash, randomBytes } from "node:crypto"; -import { - createUserSchema, - loginSchema, - refreshTokenSchema, -} from "@kioku/shared"; -import * as argon2 from "argon2"; -import { Hono } from "hono"; -import { sign } from "hono/jwt"; -import { Errors } from "../middleware"; -import { - type RefreshTokenRepository, - refreshTokenRepository, - type UserRepository, - userRepository, -} from "../repositories"; - -const JWT_SECRET = process.env.JWT_SECRET; -if (!JWT_SECRET) { - throw new Error("JWT_SECRET environment variable is required"); -} -const ACCESS_TOKEN_EXPIRES_IN = 60 * 15; // 15 minutes -const REFRESH_TOKEN_EXPIRES_IN = 60 * 60 * 24 * 7; // 7 days - -function generateRefreshToken(): string { - return randomBytes(32).toString("hex"); -} - -function hashToken(token: string): string { - return createHash("sha256").update(token).digest("hex"); -} - -export interface AuthDependencies { - userRepo: UserRepository; - refreshTokenRepo: RefreshTokenRepository; -} - -export function createAuthRouter(deps: AuthDependencies) { - const { userRepo, refreshTokenRepo } = deps; - const auth = new Hono(); - - auth.post("/register", async (c) => { - const body = await c.req.json(); - - const parsed = createUserSchema.safeParse(body); - if (!parsed.success) { - throw Errors.validationError(parsed.error.issues[0]?.message); - } - - const { username, password } = parsed.data; - - // Check if username already exists - const exists = await userRepo.existsByUsername(username); - if (exists) { - throw Errors.conflict("Username already exists", "USERNAME_EXISTS"); - } - - // Hash password with Argon2 - const passwordHash = await argon2.hash(password); - - // Create user - const newUser = await userRepo.create({ username, passwordHash }); - - return c.json({ user: newUser }, 201); - }); - - auth.post("/login", async (c) => { - const body = await c.req.json(); - - const parsed = loginSchema.safeParse(body); - if (!parsed.success) { - throw Errors.validationError(parsed.error.issues[0]?.message); - } - - const { username, password } = parsed.data; - - // Find user by username - const user = await userRepo.findByUsername(username); - - if (!user) { - throw Errors.unauthorized( - "Invalid username or password", - "INVALID_CREDENTIALS", - ); - } - - // Verify password - const isPasswordValid = await argon2.verify(user.passwordHash, password); - if (!isPasswordValid) { - throw Errors.unauthorized( - "Invalid username or password", - "INVALID_CREDENTIALS", - ); - } - - // Generate JWT access token - const now = Math.floor(Date.now() / 1000); - const accessToken = await sign( - { - sub: user.id, - iat: now, - exp: now + ACCESS_TOKEN_EXPIRES_IN, - }, - JWT_SECRET, - ); - - // Generate refresh token - const refreshToken = generateRefreshToken(); - const tokenHash = hashToken(refreshToken); - const expiresAt = new Date(Date.now() + REFRESH_TOKEN_EXPIRES_IN * 1000); - - // Store refresh token in database - await refreshTokenRepo.create({ - userId: user.id, - tokenHash, - expiresAt, - }); - - return c.json({ - accessToken, - refreshToken, - user: { - id: user.id, - username: user.username, - }, - }); - }); - - auth.post("/refresh", async (c) => { - const body = await c.req.json(); - - const parsed = refreshTokenSchema.safeParse(body); - if (!parsed.success) { - throw Errors.validationError(parsed.error.issues[0]?.message); - } - - const { refreshToken } = parsed.data; - const tokenHash = hashToken(refreshToken); - - // Find valid refresh token - const storedToken = await refreshTokenRepo.findValidToken(tokenHash); - - if (!storedToken) { - throw Errors.unauthorized( - "Invalid or expired refresh token", - "INVALID_REFRESH_TOKEN", - ); - } - - // Get user info - const user = await userRepo.findById(storedToken.userId); - - if (!user) { - throw Errors.unauthorized("User not found", "USER_NOT_FOUND"); - } - - // Delete old refresh token (rotation) - await refreshTokenRepo.deleteById(storedToken.id); - - // Generate new access token - const now = Math.floor(Date.now() / 1000); - const accessToken = await sign( - { - sub: user.id, - iat: now, - exp: now + ACCESS_TOKEN_EXPIRES_IN, - }, - JWT_SECRET, - ); - - // Generate new refresh token (rotation) - const newRefreshToken = generateRefreshToken(); - const newTokenHash = hashToken(newRefreshToken); - const expiresAt = new Date(Date.now() + REFRESH_TOKEN_EXPIRES_IN * 1000); - - await refreshTokenRepo.create({ - userId: user.id, - tokenHash: newTokenHash, - expiresAt, - }); - - return c.json({ - accessToken, - refreshToken: newRefreshToken, - user: { - id: user.id, - username: user.username, - }, - }); - }); - - return auth; -} - -// Default auth router with real repositories for production use -export const auth = createAuthRouter({ - userRepo: userRepository, - refreshTokenRepo: refreshTokenRepository, -}); -- cgit v1.2.3-70-g09d2