diff options
| author | nsfisis <nsfisis@gmail.com> | 2025-12-30 22:08:47 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2025-12-30 22:08:47 +0900 |
| commit | c2eb7513834eeb5adfa53fff897f585de87e4821 (patch) | |
| tree | 9e914051ca67e2f9e1fa301119bdec398ec9e55f /src/server/routes/auth.ts | |
| parent | b839cae49efd4b9d35c2868a4137101a4d71bd7f (diff) | |
| download | kioku-c2eb7513834eeb5adfa53fff897f585de87e4821.tar.gz kioku-c2eb7513834eeb5adfa53fff897f585de87e4821.tar.zst kioku-c2eb7513834eeb5adfa53fff897f585de87e4821.zip | |
feat(security): add rate limiting and CORS middleware
- Add rate limiting to login endpoint (5 requests/minute per IP)
- Configure CORS middleware with environment-based origin control
- Expose rate limit headers in CORS for client visibility
- Update hono to 4.11.3 for rate limiter peer dependency
🤖 Generated with [Claude Code](https://claude.ai/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'src/server/routes/auth.ts')
| -rw-r--r-- | src/server/routes/auth.ts | 116 |
1 files changed, 63 insertions, 53 deletions
diff --git a/src/server/routes/auth.ts b/src/server/routes/auth.ts index 06c88a6..73deb83 100644 --- a/src/server/routes/auth.ts +++ b/src/server/routes/auth.ts @@ -3,7 +3,7 @@ import { zValidator } from "@hono/zod-validator"; import * as argon2 from "argon2"; import { Hono } from "hono"; import { sign } from "hono/jwt"; -import { Errors } from "../middleware/index.js"; +import { Errors, loginRateLimiter } from "../middleware/index.js"; import { type RefreshTokenRepository, refreshTokenRepository, @@ -39,63 +39,73 @@ export function createAuthRouter(deps: AuthDependencies) { const { userRepo, refreshTokenRepo } = deps; return new Hono() - .post("/login", zValidator("json", loginSchema), async (c) => { - const { username, password } = c.req.valid("json"); - - // Find user by username - const user = await userRepo.findByUsername(username); - - if (!user) { - throw Errors.unauthorized( - "Invalid username or password", - "INVALID_CREDENTIALS", + .post( + "/login", + loginRateLimiter, + zValidator("json", loginSchema), + async (c) => { + const { username, password } = c.req.valid("json"); + + // 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, ); - } - - // Verify password - const isPasswordValid = await argon2.verify(user.passwordHash, password); - if (!isPasswordValid) { - throw Errors.unauthorized( - "Invalid username or password", - "INVALID_CREDENTIALS", + 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, + }, + getJwtSecret(), ); - } - - // 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, - }, - getJwtSecret(), - ); - // 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, - }); + // Generate refresh token + const refreshToken = generateRefreshToken(); + const tokenHash = hashToken(refreshToken); + const expiresAt = new Date( + Date.now() + REFRESH_TOKEN_EXPIRES_IN * 1000, + ); - return c.json( - { - accessToken, - refreshToken, - user: { - id: user.id, - username: user.username, + // 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, + }, }, - }, - 200, - ); - }) + 200, + ); + }, + ) .post("/refresh", zValidator("json", refreshTokenSchema), async (c) => { const { refreshToken } = c.req.valid("json"); const tokenHash = hashToken(refreshToken); |
