aboutsummaryrefslogtreecommitdiffhomepage
path: root/pkgs/server/src/routes/auth.ts
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-12-06 17:05:21 +0900
committernsfisis <nsfisis@gmail.com>2025-12-06 17:37:04 +0900
commit811458427593a4172a2cd535cc768db375350dca (patch)
tree6c4f46c96b6f29392dc19d591e39e03c187033a1 /pkgs/server/src/routes/auth.ts
parent9736a8981fbd6c6defbd67517ca23904fc844629 (diff)
downloadkioku-811458427593a4172a2cd535cc768db375350dca.tar.gz
kioku-811458427593a4172a2cd535cc768db375350dca.tar.zst
kioku-811458427593a4172a2cd535cc768db375350dca.zip
feat(dev): change architecture and directory structure
Diffstat (limited to 'pkgs/server/src/routes/auth.ts')
-rw-r--r--pkgs/server/src/routes/auth.ts199
1 files changed, 0 insertions, 199 deletions
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,
-});