Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: '22'
cache: 'pnpm'

- name: Install Dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: '22'
registry-url: 'https://registry.npmjs.org'
cache: 'pnpm'

Expand Down
31 changes: 22 additions & 9 deletions app/api/emails/[id]/[messageId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,29 @@ import { createDb } from "@/lib/db"
import { messages, emails } from "@/lib/schema"
import { and, eq } from "drizzle-orm"
import { getUserId } from "@/lib/apiKey"
import { canUserAccessAllEmails } from "@/lib/email-access"
export const runtime = "edge"

export async function DELETE(
request: Request,
{ params }: { params: Promise<{ id: string; messageId: string }> }
) {
const userId = await getUserId()
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

try {
const db = createDb()
const { id, messageId } = await params
const canAccessAll = await canUserAccessAllEmails(userId)
const email = await db.query.emails.findFirst({
where: and(
eq(emails.id, id),
eq(emails.userId, userId!)
)
where: canAccessAll
? eq(emails.id, id)
: and(
eq(emails.id, id),
eq(emails.userId, userId)
)
})

if (!email) {
Expand Down Expand Up @@ -60,12 +67,18 @@ export async function GET(_request: Request, { params }: { params: Promise<{ id:
const { id, messageId } = await params
const db = createDb()
const userId = await getUserId()
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

const canAccessAll = await canUserAccessAllEmails(userId)
const email = await db.query.emails.findFirst({
where: and(
eq(emails.id, id),
eq(emails.userId, userId!)
)
where: canAccessAll
? eq(emails.id, id)
: and(
eq(emails.id, id),
eq(emails.userId, userId)
)
})

if (!email) {
Expand Down Expand Up @@ -109,4 +122,4 @@ export async function GET(_request: Request, { params }: { params: Promise<{ id:
{ status: 500 }
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { messageShares, messages, emails } from "@/lib/schema"
import { eq, and } from "drizzle-orm"
import { NextResponse } from "next/server"
import { getUserId } from "@/lib/apiKey"
import { canUserAccessAllEmails } from "@/lib/email-access"

export const runtime = "edge"

Expand All @@ -20,9 +21,11 @@ export async function DELETE(
const db = createDb()

try {
// 验证邮箱所有权
const canAccessAll = await canUserAccessAllEmails(userId)
const email = await db.query.emails.findFirst({
where: and(eq(emails.id, emailId), eq(emails.userId, userId))
where: canAccessAll
? eq(emails.id, emailId)
: and(eq(emails.id, emailId), eq(emails.userId, userId))
})

if (!email) {
Expand Down Expand Up @@ -52,4 +55,3 @@ export async function DELETE(
)
}
}

14 changes: 9 additions & 5 deletions app/api/emails/[id]/messages/[messageId]/share/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { eq, and } from "drizzle-orm"
import { NextResponse } from "next/server"
import { getUserId } from "@/lib/apiKey"
import { nanoid } from "nanoid"
import { canUserAccessAllEmails } from "@/lib/email-access"

export const runtime = "edge"

Expand All @@ -21,9 +22,11 @@ export async function GET(
const db = createDb()

try {
// 验证邮箱所有权
const canAccessAll = await canUserAccessAllEmails(userId)
const email = await db.query.emails.findFirst({
where: and(eq(emails.id, emailId), eq(emails.userId, userId))
where: canAccessAll
? eq(emails.id, emailId)
: and(eq(emails.id, emailId), eq(emails.userId, userId))
})

if (!email) {
Expand Down Expand Up @@ -69,9 +72,11 @@ export async function POST(
const db = createDb()

try {
// 验证邮箱所有权
const canAccessAll = await canUserAccessAllEmails(userId)
const email = await db.query.emails.findFirst({
where: and(eq(emails.id, emailId), eq(emails.userId, userId))
where: canAccessAll
? eq(emails.id, emailId)
: and(eq(emails.id, emailId), eq(emails.userId, userId))
})

if (!email) {
Expand Down Expand Up @@ -116,4 +121,3 @@ export async function POST(
)
}
}

34 changes: 24 additions & 10 deletions app/api/emails/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { eq, and, lt, or, sql, ne, isNull } from "drizzle-orm"
import { encodeCursor, decodeCursor } from "@/lib/cursor"
import { getUserId } from "@/lib/apiKey"
import { checkBasicSendPermission } from "@/lib/send-permissions"
import { canUserAccessAllEmails } from "@/lib/email-access"

export const runtime = "edge"

Expand All @@ -13,15 +14,21 @@ export async function DELETE(
{ params }: { params: Promise<{ id: string }> }
) {
const userId = await getUserId()
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

try {
const db = createDb()
const { id } = await params
const canAccessAll = await canUserAccessAllEmails(userId)
const email = await db.query.emails.findFirst({
where: and(
eq(emails.id, id),
eq(emails.userId, userId!)
)
where: canAccessAll
? eq(emails.id, id)
: and(
eq(emails.id, id),
eq(emails.userId, userId)
)
})

if (!email) {
Expand Down Expand Up @@ -61,8 +68,12 @@ export async function GET(
const { id } = await params

const userId = await getUserId()
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

if (messageType === 'sent') {
const permissionResult = await checkBasicSendPermission(userId!)
const permissionResult = await checkBasicSendPermission(userId)
if (!permissionResult.canSend) {
return NextResponse.json(
{ error: permissionResult.error || "您没有查看发送邮件的权限" },
Expand All @@ -71,11 +82,14 @@ export async function GET(
}
}

const canAccessAll = await canUserAccessAllEmails(userId)
const email = await db.query.emails.findFirst({
where: and(
eq(emails.id, id),
eq(emails.userId, userId!)
)
where: canAccessAll
? eq(emails.id, id)
: and(
eq(emails.id, id),
eq(emails.userId, userId)
)
})

if (!email) {
Expand Down Expand Up @@ -159,4 +173,4 @@ export async function GET(
{ status: 500 }
)
}
}
}
6 changes: 4 additions & 2 deletions app/api/emails/[id]/send/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { emails, messages } from "@/lib/schema"
import { eq } from "drizzle-orm"
import { getRequestContext } from "@cloudflare/next-on-pages"
import { checkSendPermission } from "@/lib/send-permissions"
import { canUserAccessAllEmails } from "@/lib/email-access"

export const runtime = "edge"

Expand Down Expand Up @@ -90,7 +91,8 @@ export async function POST(
)
}

if (email.userId !== userId) {
const canAccessAll = await canUserAccessAllEmails(userId)
if (!canAccessAll && email.userId !== userId) {
return NextResponse.json(
{ error: "无权访问此邮箱" },
{ status: 403 }
Expand Down Expand Up @@ -131,4 +133,4 @@ export async function POST(
{ status: 500 }
)
}
}
}
8 changes: 5 additions & 3 deletions app/api/emails/[id]/share/[shareId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { emailShares, emails } from "@/lib/schema"
import { eq, and } from "drizzle-orm"
import { NextResponse } from "next/server"
import { getUserId } from "@/lib/apiKey"
import { canUserAccessAllEmails } from "@/lib/email-access"

export const runtime = "edge"

Expand All @@ -20,9 +21,11 @@ export async function DELETE(
const db = createDb()

try {
// 验证邮箱所有权
const canAccessAll = await canUserAccessAllEmails(userId)
const email = await db.query.emails.findFirst({
where: and(eq(emails.id, emailId), eq(emails.userId, userId))
where: canAccessAll
? eq(emails.id, emailId)
: and(eq(emails.id, emailId), eq(emails.userId, userId))
})

if (!email) {
Expand All @@ -43,4 +46,3 @@ export async function DELETE(
)
}
}

14 changes: 9 additions & 5 deletions app/api/emails/[id]/share/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { eq, and } from "drizzle-orm"
import { NextResponse } from "next/server"
import { getUserId } from "@/lib/apiKey"
import { nanoid } from "nanoid"
import { canUserAccessAllEmails } from "@/lib/email-access"

export const runtime = "edge"

Expand All @@ -21,9 +22,11 @@ export async function GET(
const db = createDb()

try {
// 验证邮箱所有权
const canAccessAll = await canUserAccessAllEmails(userId)
const email = await db.query.emails.findFirst({
where: and(eq(emails.id, emailId), eq(emails.userId, userId))
where: canAccessAll
? eq(emails.id, emailId)
: and(eq(emails.id, emailId), eq(emails.userId, userId))
})

if (!email) {
Expand Down Expand Up @@ -60,9 +63,11 @@ export async function POST(
const db = createDb()

try {
// 验证邮箱所有权
const canAccessAll = await canUserAccessAllEmails(userId)
const email = await db.query.emails.findFirst({
where: and(eq(emails.id, emailId), eq(emails.userId, userId))
where: canAccessAll
? eq(emails.id, emailId)
: and(eq(emails.id, emailId), eq(emails.userId, userId))
})

if (!email) {
Expand Down Expand Up @@ -98,4 +103,3 @@ export async function POST(
)
}
}

17 changes: 12 additions & 5 deletions app/api/emails/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,31 @@ import { NextResponse } from "next/server"
import { emails } from "@/lib/schema"
import { encodeCursor, decodeCursor } from "@/lib/cursor"
import { getUserId } from "@/lib/apiKey"
import { canUserAccessAllEmails } from "@/lib/email-access"

export const runtime = "edge"

const PAGE_SIZE = 20

export async function GET(request: Request) {
const userId = await getUserId()
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

const { searchParams } = new URL(request.url)
const cursor = searchParams.get('cursor')

const db = createDb()

try {
const baseConditions = and(
eq(emails.userId, userId!),
gt(emails.expiresAt, new Date())
)
const canAccessAll = await canUserAccessAllEmails(userId)
const baseConditions = canAccessAll
? gt(emails.expiresAt, new Date())
: and(
eq(emails.userId, userId),
gt(emails.expiresAt, new Date())
)

const totalResult = await db.select({ count: sql<number>`count(*)` })
.from(emails)
Expand Down Expand Up @@ -73,4 +80,4 @@ export async function GET(request: Request) {
{ status: 500 }
)
}
}
}
15 changes: 15 additions & 0 deletions app/lib/email-access.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { eq } from "drizzle-orm"
import { createDb } from "./db"
import { roles, userRoles } from "./schema"
import { canAccessAllEmails, type Role } from "./permissions"

export async function canUserAccessAllEmails(userId: string) {
const db = createDb()
const currentUserRole = await db.select({ roleName: roles.name })
.from(userRoles)
.innerJoin(roles, eq(userRoles.roleId, roles.id))
.where(eq(userRoles.userId, userId))
.get()

return canAccessAllEmails(currentUserRole?.roleName as Role | null | undefined)
}
6 changes: 5 additions & 1 deletion app/lib/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,8 @@ export const ROLE_PERMISSIONS: Record<Role, Permission[]> = {

export function hasPermission(userRoles: Role[], permission: Permission): boolean {
return userRoles.some(role => ROLE_PERMISSIONS[role]?.includes(permission));
}
}

export function canAccessAllEmails(role: Role | null | undefined): boolean {
return role === ROLES.EMPEROR;
}
11 changes: 11 additions & 0 deletions tests/email-access.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import assert from "node:assert/strict"
import test from "node:test"
import { canAccessAllEmails, ROLES } from "../app/lib/permissions"

test("only emperor can access all emails", () => {
assert.equal(canAccessAllEmails(ROLES.EMPEROR), true)
assert.equal(canAccessAllEmails(ROLES.DUKE), false)
assert.equal(canAccessAllEmails(ROLES.KNIGHT), false)
assert.equal(canAccessAllEmails(ROLES.CIVILIAN), false)
assert.equal(canAccessAllEmails(null), false)
})