From 0763153865e2157e0d06c946993dd8b235b06c83 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Wed, 3 Dec 2025 05:45:41 +0900 Subject: feat(auth): add refresh token endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement refresh token functionality for authentication: - Add refresh_tokens table to database schema with user reference - Generate migration for the new table - Login endpoint now returns both access token and refresh token - Add POST /api/auth/refresh endpoint with token rotation - Refresh tokens are hashed (SHA256) before storage for security - Tokens expire after 7 days, access tokens after 15 minutes - Update tests to cover new functionality 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- pkgs/server/src/routes/auth.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'pkgs/server/src/routes/auth.test.ts') diff --git a/pkgs/server/src/routes/auth.test.ts b/pkgs/server/src/routes/auth.test.ts index 1dfba46..28bd558 100644 --- a/pkgs/server/src/routes/auth.test.ts +++ b/pkgs/server/src/routes/auth.test.ts @@ -43,6 +43,13 @@ vi.mock("../db", () => { username: "username", createdAt: "created_at", }, + refreshTokens: { + id: "id", + userId: "user_id", + tokenHash: "token_hash", + expiresAt: "expires_at", + createdAt: "created_at", + }, }; }); @@ -67,6 +74,7 @@ interface RegisterResponse { interface LoginResponse { accessToken?: string; + refreshToken?: string; user?: { id: string; username: string; @@ -188,6 +196,11 @@ describe("POST /login", () => { }), } as unknown as ReturnType); + // Mock the insert call for refresh token + vi.mocked(db.insert).mockReturnValueOnce({ + values: vi.fn().mockResolvedValue(undefined), + } as unknown as ReturnType); + const res = await app.request("/api/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, @@ -201,6 +214,8 @@ describe("POST /login", () => { const body = (await res.json()) as LoginResponse; expect(body.accessToken).toBeDefined(); expect(typeof body.accessToken).toBe("string"); + expect(body.refreshToken).toBeDefined(); + expect(typeof body.refreshToken).toBe("string"); expect(body.user).toEqual({ id: "user-uuid-123", username: "testuser", -- cgit v1.2.3-70-g09d2