[READ-ONLY] One Calendar is a privacy-first calendar web app built with Next.js. It has modern security features, including e2ee, password-protected sharing, and self-destructing share links 📅 calendar.xyehr.cn
nextjs
0

Configure Feed

Select the types of activity you want to include in your feed.

One-Calendar / app / api / share / list / route.ts
2.5 kB 94 lines
1import { type NextRequest, NextResponse } from 'next/server' 2import { withEvlog, useLogger } from '@/lib/evlog' 3import { getServerSession } from '@/lib/auth-server' 4import crypto from 'crypto' 5import { db } from '@/lib/drizzle/client' 6import { shares } from '@/lib/drizzle/schema' 7import { eq, desc } from 'drizzle-orm' 8 9export const runtime = 'nodejs' 10 11const ALGORITHM = 'aes-256-gcm' 12 13function keyV2Unprotected(shareId: string) { 14 return crypto.createHash('sha256').update(shareId, 'utf8').digest() 15} 16 17function decryptWithKey( 18 encryptedData: string, 19 iv: string, 20 authTag: string, 21 key: Buffer, 22) { 23 const decipher = crypto.createDecipheriv( 24 ALGORITHM, 25 key, 26 Buffer.from(iv, 'hex'), 27 ) 28 decipher.setAuthTag(Buffer.from(authTag, 'hex')) 29 let decrypted = decipher.update(encryptedData, 'hex', 'utf8') 30 decrypted += decipher.final('utf8') 31 return decrypted 32} 33 34export const GET = withEvlog(async function GET(_req: NextRequest) { 35 const log = useLogger() 36 const session = await getServerSession() 37 const user = session?.user 38 if (!user) { 39 log.audit?.({ 40 action: 'share.list', 41 actor: { type: 'system', id: 'anonymous' }, 42 target: { type: 'share_collection', id: 'unknown' }, 43 outcome: 'denied', 44 reason: 'Authentication required', 45 }) 46 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) 47 } 48 49 const result = await db 50 .select() 51 .from(shares) 52 .where(eq(shares.userId, user.id)) 53 .orderBy(desc(shares.timestamp)) 54 55 const shareList = result.map((row) => { 56 let eventId = '' 57 let eventTitle = '' 58 if (!row.isProtected) { 59 try { 60 const decrypted = decryptWithKey( 61 row.encryptedData, 62 row.iv, 63 row.authTag, 64 keyV2Unprotected(row.shareId), 65 ) 66 const dataObj = JSON.parse(decrypted) 67 eventId = dataObj.id ?? '' 68 eventTitle = dataObj.title ?? '' 69 } catch {} 70 } else { 71 eventId = '受保护' 72 eventTitle = '受保护' 73 } 74 return { 75 id: row.shareId, 76 eventId, 77 eventTitle, 78 sharedBy: user.id, 79 shareDate: row.timestamp.toISOString(), 80 shareLink: `/share/${row.shareId}`, 81 isProtected: row.isProtected, 82 } 83 }) 84 85 log.audit?.({ 86 action: 'share.list', 87 actor: { type: 'user', id: user.id, email: user.email }, 88 target: { type: 'share_collection', id: user.id }, 89 outcome: 'success', 90 reason: 'User listed shares', 91 }) 92 93 return NextResponse.json({ shares: shareList }) 94})