Skip to content

Commit

Permalink
feat: return more detailed info instead of just message (#25)
Browse files Browse the repository at this point in the history
Now /auth/status will return 
{
  user_id: z.number(),
  username: z.string(),
  user_role_id: z.number(),
  created_at: z.string(),
}

Test updated accordingly.
  • Loading branch information
ZL-Asica authored Nov 2, 2024
1 parent e3fc24a commit 7e60c25
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 19 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/label-pr-based-on-paths.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ jobs:
const labelsToColor = {
auth: 'f66a0a', // Orange
utils: '1f77b4', // Blue
lib: '1f77b4', // Blue
settings: '8c564b', // Brown
dependency: '2ca02c', // Green
db: '9467bd', // Purple
test: 'd62728', // Red
};
const labels = new Set();
Expand All @@ -39,9 +41,15 @@ jobs:
if (file.filename.startsWith('src/auth')) {
labels.add('auth');
}
if (file.filename.endsWith('.test.ts')) {
labels.add('test');
}
if (file.filename.startsWith('src/utils')) {
labels.add('utils');
}
if (file.filename.startsWith('src/lib')) {
labels.add('lib');
}
if (file.filename.startsWith('src/settings')) {
labels.add('settings');
}
Expand Down
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@

> Kumo - 日语中的雲☁️ - 一个基于 Cloudflare Workers、D1 数据库和 Hono 框架构建的高效身份认证系统
![Test by Github Action](https://img.shields.io/github/actions/workflow/status/ZL-Asica/KumoAuth/auto-test.yml?logo=github&label=Test) ![GitHub License](https://img.shields.io/github/license/ZL-Asica/KumoAuth) ![Yarn Version](https://img.shields.io/github/package-json/packageManager/ZL-Asica/KumoAuth?label=&logo=yarn&logoColor=fff)

![Hono](https://img.shields.io/badge/Hono-E36002?logo=hono&logoColor=fff) ![Cloudflare](https://img.shields.io/badge/Cloudflare-F38020?logo=Cloudflare&logoColor=white) ![Eslint](https://img.shields.io/badge/eslint-4B32C3?logo=eslint&logoColor=white) ![Prettier](https://img.shields.io/badge/Prettier-F7B93E?logo=Prettier&logoColor=white)
![Test by Github Action](https://img.shields.io/github/actions/workflow/status/ZL-Asica/KumoAuth/auto-test.yml?logo=github&label=Test) ![GitHub License](https://img.shields.io/github/license/ZL-Asica/KumoAuth) ![Yarn Version](https://img.shields.io/github/package-json/packageManager/ZL-Asica/KumoAuth?label=&logo=yarn&logoColor=fff) | ![Hono](https://img.shields.io/badge/Hono-E36002?logo=hono&logoColor=fff) ![Cloudflare](https://img.shields.io/badge/Cloudflare-F38020?logo=Cloudflare&logoColor=white) ![Eslint](https://img.shields.io/badge/eslint-4B32C3?logo=eslint&logoColor=white) ![Prettier](https://img.shields.io/badge/Prettier-F7B93E?logo=Prettier&logoColor=white)

此项目旨在利用 Cloudflare 的无服务器架构搭建一个简单、轻量的身份认证系统。项目使用了 JWT 来实现用户的无状态认证和访问保护功能,未来计划加入更多功能,如双因素认证、刷新令牌等。

Expand Down
4 changes: 1 addition & 3 deletions README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@

> Kumo - means cloud (雲☁️) in Japanese - is a lightweight and efficient authentication system built with Cloudflare Workers, D1 Database, and the Hono framework.
![Test by Github Action](https://img.shields.io/github/actions/workflow/status/ZL-Asica/KumoAuth/auto-test.yml?logo=github&label=Test) ![GitHub License](https://img.shields.io/github/license/ZL-Asica/KumoAuth) ![Yarn Version](https://img.shields.io/github/package-json/packageManager/ZL-Asica/KumoAuth?label=&logo=yarn&logoColor=fff)

![Hono](https://img.shields.io/badge/Hono-E36002?logo=hono&logoColor=fff) ![Cloudflare](https://img.shields.io/badge/Cloudflare-F38020?logo=Cloudflare&logoColor=white) ![Eslint](https://img.shields.io/badge/eslint-4B32C3?logo=eslint&logoColor=white) ![Prettier](https://img.shields.io/badge/Prettier-F7B93E?logo=Prettier&logoColor=white)
![Test by Github Action](https://img.shields.io/github/actions/workflow/status/ZL-Asica/KumoAuth/auto-test.yml?logo=github&label=Test) ![GitHub License](https://img.shields.io/github/license/ZL-Asica/KumoAuth) ![Yarn Version](https://img.shields.io/github/package-json/packageManager/ZL-Asica/KumoAuth?label=&logo=yarn&logoColor=fff) | ![Hono](https://img.shields.io/badge/Hono-E36002?logo=hono&logoColor=fff) ![Cloudflare](https://img.shields.io/badge/Cloudflare-F38020?logo=Cloudflare&logoColor=white) ![Eslint](https://img.shields.io/badge/eslint-4B32C3?logo=eslint&logoColor=white) ![Prettier](https://img.shields.io/badge/Prettier-F7B93E?logo=Prettier&logoColor=white)

This project leverages Cloudflare's serverless architecture to build a simple, lightweight authentication system. It uses JWTs for stateless authentication and access protection, with plans for additional features like two-factor authentication and refresh tokens.

Expand Down
44 changes: 39 additions & 5 deletions src/auth/status.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { authStatusHandler } from '@/auth/status'
import { getUserByUserId } from '@/lib/db'
import type { Context } from 'hono'
import { getSignedCookie } from 'hono/cookie'
import { verify } from 'hono/jwt'
Expand All @@ -7,10 +8,11 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
// Mock dependencies
vi.mock('hono/cookie')
vi.mock('hono/jwt')
vi.mock('@/lib/db')

// Mock Context
const mockContext = {
env: { JWT_SECRET: 'testSecret' },
env: { JWT_SECRET: 'testSecret', DB: 'testDB' },
req: { json: vi.fn() },
json: vi.fn(),
header: vi.fn(),
Expand All @@ -21,18 +23,37 @@ describe('authStatusHandler', () => {
vi.clearAllMocks()
})

it('should return 200 if user is logged in', async () => {
// Mock a valid token
it('should return 200 with user details if user is logged in', async () => {
vi.mocked(getSignedCookie).mockResolvedValueOnce('valid.token')
vi.mocked(verify).mockResolvedValueOnce({ user_id: 1 })
vi.mocked(verify).mockResolvedValueOnce({
user_id: 1,
user_role_id: 1,
exp: 1234567890,
})
vi.mocked(getUserByUserId).mockResolvedValueOnce({
user_id: 1,
username: 'testUser',
user_role_id: 1,
password_hash: 'testHash',
created_at: '2023-11-01T12:00:00Z',
})

await authStatusHandler(mockContext)

expect(verify).toHaveBeenCalledWith(
'valid.token',
mockContext.env.JWT_SECRET
)
expect(mockContext.json).toHaveBeenCalledWith({ message: 'Logged in' }, 200)
expect(getUserByUserId).toHaveBeenCalledWith(mockContext.env.DB, 1)
expect(mockContext.json).toHaveBeenCalledWith(
{
user_id: 1,
username: 'testUser',
user_role_id: 1,
created_at: '2023-11-01T12:00:00Z',
},
200
)
})

it('should return 401 if user is not logged in (no token)', async () => {
Expand Down Expand Up @@ -72,4 +93,17 @@ describe('authStatusHandler', () => {
403
)
})

it('should return 403 Token verification failed when a non-Error is thrown', async () => {
// Mock error
vi.mocked(getSignedCookie).mockResolvedValueOnce('invalid.token.test')
vi.mocked(verify).mockRejectedValueOnce('Token verification failed')

await authStatusHandler(mockContext)

expect(mockContext.json).toHaveBeenCalledWith(
{ error: 'Token verification failed' },
403
)
})
})
42 changes: 34 additions & 8 deletions src/auth/status.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import { errorResponse, jsonMessageContent } from '@/lib/helper'
import { createRoute } from '@hono/zod-openapi'
import { getUserByUserId } from '@/lib/db'
import { errorResponse, jsonContent } from '@/lib/helper'
import { createRoute, z } from '@hono/zod-openapi'
import type { Context } from 'hono'
import { getSignedCookie } from 'hono/cookie'
import { verify } from 'hono/jwt'

// authStatusRoute
const authStatusSchema = z.object({
user_id: z.number(),
username: z.string(),
user_role_id: z.number(),
created_at: z.string(),
})

export const authStatusRoute = createRoute({
method: 'get',
path: '/auth/status',
responses: {
200: jsonMessageContent('Logged in'),
401: errorResponse('Not logged in or token expired'),
403: errorResponse('Invalid token or token verification failed'),
200: jsonContent(authStatusSchema, 'User details'),
401: errorResponse('Token expired'),
403: errorResponse('Invalid token'),
},
})

Expand All @@ -23,8 +30,27 @@ export const authStatusHandler = async (c: Context) => {
}

try {
await verify(token, c.env.JWT_SECRET)
return c.json({ message: 'Logged in' }, 200)
const decoded = await verify(token, c.env.JWT_SECRET)

if (!decoded || typeof decoded.user_id !== 'number') {
return c.json({ error: 'Invalid token' }, 403)
}

const user = await getUserByUserId(c.env.DB, decoded.user_id)

if (!user) {
return c.json({ error: 'User not found' }, 403)
}

return c.json(
{
user_id: user.user_id,
username: user.username,
user_role_id: user.user_role_id,
created_at: user.created_at,
},
200
)
} catch (error) {
if (error instanceof Error) {
if (error.message === 'TokenExpiredError') {
Expand Down

0 comments on commit 7e60c25

Please sign in to comment.