feat: Implement OpenAPI with API Key authentication and role-based access control

This commit is contained in:
beilunyang
2025-02-10 11:25:25 +08:00
parent 1bc0369b83
commit 9ad3115833
28 changed files with 2339 additions and 144 deletions
+57
View File
@@ -0,0 +1,57 @@
import { createDb } from "./db"
import { apiKeys } from "./schema"
import { eq, and, gt } from "drizzle-orm"
import { NextResponse } from "next/server"
import type { User } from "next-auth"
import { auth } from "./auth"
import { headers } from "next/headers"
async function getUserByApiKey(key: string): Promise<User | null> {
const db = createDb()
const apiKey = await db.query.apiKeys.findFirst({
where: and(
eq(apiKeys.key, key),
eq(apiKeys.enabled, true),
gt(apiKeys.expiresAt, new Date())
),
with: {
user: true
}
})
if (!apiKey) return null
return apiKey.user
}
export async function handleApiKeyAuth(apiKey: string, pathname: string) {
if (!pathname.startsWith('/api/emails')) {
return NextResponse.json(
{ error: "无权限查看" },
{ status: 403 }
)
}
const user = await getUserByApiKey(apiKey)
if (!user?.id) {
return NextResponse.json(
{ error: "无效的 API Key" },
{ status: 401 }
)
}
const response = NextResponse.next()
response.headers.set("X-User-Id", user.id)
return response
}
export const getUserId = async () => {
const headersList = await headers()
const userId = headersList.get("X-User-Id")
if (userId) return userId
const session = await auth()
return session?.user.id
}
+1
View File
@@ -13,6 +13,7 @@ import { generateAvatarUrl } from "./avatar"
const ROLE_DESCRIPTIONS: Record<Role, string> = {
[ROLES.EMPEROR]: "皇帝(网站所有者)",
[ROLES.DUKE]: "公爵(超级用户)",
[ROLES.KNIGHT]: "骑士(高级用户)",
[ROLES.CIVILIAN]: "平民(普通用户)",
}
+7
View File
@@ -1,5 +1,6 @@
export const ROLES = {
EMPEROR: 'emperor',
DUKE: 'duke',
KNIGHT: 'knight',
CIVILIAN: 'civilian',
} as const;
@@ -11,12 +12,18 @@ export const PERMISSIONS = {
MANAGE_WEBHOOK: 'manage_webhook',
PROMOTE_USER: 'promote_user',
MANAGE_CONFIG: 'manage_config',
MANAGE_API_KEY: 'manage_api_key',
} as const;
export type Permission = typeof PERMISSIONS[keyof typeof PERMISSIONS];
export const ROLE_PERMISSIONS: Record<Role, Permission[]> = {
[ROLES.EMPEROR]: Object.values(PERMISSIONS),
[ROLES.DUKE]: [
PERMISSIONS.MANAGE_EMAIL,
PERMISSIONS.MANAGE_WEBHOOK,
PERMISSIONS.MANAGE_API_KEY,
],
[ROLES.KNIGHT]: [
PERMISSIONS.MANAGE_EMAIL,
PERMISSIONS.MANAGE_WEBHOOK,
+19 -1
View File
@@ -93,6 +93,23 @@ export const userRoles = sqliteTable("user_role", {
pk: primaryKey({ columns: [table.userId, table.roleId] }),
}));
export const apiKeys = sqliteTable('api_keys', {
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
userId: text('user_id').notNull().references(() => users.id),
name: text('name').notNull().unique(),
key: text('key').notNull().unique(),
createdAt: integer('created_at', { mode: 'timestamp' }).$defaultFn(() => new Date()),
expiresAt: integer('expires_at', { mode: 'timestamp' }),
enabled: integer('enabled', { mode: 'boolean' }).notNull().default(true),
});
export const apiKeysRelations = relations(apiKeys, ({ one }) => ({
user: one(users, {
fields: [apiKeys.userId],
references: [users.id],
}),
}));
export const userRolesRelations = relations(userRoles, ({ one }) => ({
user: one(users, {
fields: [userRoles.userId],
@@ -106,7 +123,8 @@ export const userRolesRelations = relations(userRoles, ({ one }) => ({
export const usersRelations = relations(users, ({ many }) => ({
userRoles: many(userRoles),
}));
apiKeys: many(apiKeys),
}));
export const rolesRelations = relations(roles, ({ many }) => ({
userRoles: many(userRoles),