mirror of
https://github.com/beilunyang/moemail.git
synced 2026-09-07 00:06:44 +08:00
feat: Implement OpenAPI with API Key authentication and role-based access control
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import { generateAvatarUrl } from "./avatar"
|
||||
|
||||
const ROLE_DESCRIPTIONS: Record<Role, string> = {
|
||||
[ROLES.EMPEROR]: "皇帝(网站所有者)",
|
||||
[ROLES.DUKE]: "公爵(超级用户)",
|
||||
[ROLES.KNIGHT]: "骑士(高级用户)",
|
||||
[ROLES.CIVILIAN]: "平民(普通用户)",
|
||||
}
|
||||
|
||||
@@ -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
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user