Skip to content
Merged
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
15 changes: 5 additions & 10 deletions src/app/[userName]/[projectName]/download/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import { notFound } from 'next/navigation'
import z from 'zod'

import { requireAuth } from '@/lib/server/auth'
import { getDb } from '@/lib/server/db'
import { canAccessProject } from '@/lib/server/util'
import { getProjectForViewer } from '@/lib/server/util'

const zParams = z.object({
userName: zUserName,
Expand Down Expand Up @@ -35,14 +34,10 @@ export async function GET(_req: Request, { params }: { params: Promise<unknown>
const { userName, projectName } = parsed.data

const session = await requireAuth()
const db = getDb()
const owner = await db.user.findUnique({ where: { name: userName } })
if (!owner) notFound()
const project = await db.project.findUnique({
where: { userId_name: { userId: owner.id, name: projectName } },
})
// 404 rather than 403 to avoid leaking the existence of inaccessible projects.
if (!project || !canAccessProject(session.user, project)) notFound()

const proj = await getProjectForViewer(session.user, userName, projectName)
if (!proj) notFound() // 404 rather than 403 to avoid leaking the existence of inaccessible projects.
const { owner, project } = proj

return new Response(iteratorToStream(generateProjectTgz(owner, project.id)), {
headers: {
Expand Down
19 changes: 5 additions & 14 deletions src/app/[userName]/[projectName]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import { connection } from 'next/server'
import z from 'zod'

import { requireAuth } from '@/lib/server/auth'
import { getDb } from '@/lib/server/db'
import { getShardCoordinator } from '@/lib/server/shardConnection'
import { canAccessProject } from '@/lib/server/util'
import { getProjectForViewer } from '@/lib/server/util'

const zParams = z.object({
userName: zUserName,
Expand All @@ -26,22 +25,14 @@ export default async function EditorSession({ params: params_ }: { params: Promi
const params = parsed.data

const session = await requireAuth()
const viewer = session.user

const db = getDb()
const owner = await db.user.findUnique({
where: { name: params.userName },
})
if (!owner) notFound()

const project = await db.project.findUnique({
where: { userId_name: { userId: owner.id, name: params.projectName } },
})
if (!project || !canAccessProject(viewer, project)) notFound()
const proj = await getProjectForViewer(session.user, params.userName, params.projectName)
if (!proj) notFound() // 404 rather than 403 to avoid leaking the existence of inaccessible projects.
const { owner, project } = proj

const coordinator = getShardCoordinator()
// may throw to error boundary (e.g. if the project folder isn't accessible)
const iframeUrl = await coordinator.ensureSession(viewer, owner, project)
const iframeUrl = await coordinator.ensureSession(session.user, owner, project)

// TODO: VSC should be sandboxed but can't be opaque-origin: need a subdomain.
return <iframe id='editor-frame' src={iframeUrl} className='editor-session-iframe' />
Expand Down
14 changes: 4 additions & 10 deletions src/app/[userName]/[projectName]/preview/[...relPath]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import { notFound, redirect } from 'next/navigation'
import z from 'zod'

import { requireAuth } from '@/lib/server/auth'
import { getDb } from '@/lib/server/db'
import { mintSignedDirToken, signedDirFileUrl } from '@/lib/server/dirToken'
import { canAccessProject } from '@/lib/server/util'
import { getProjectForViewer } from '@/lib/server/util'

const zParams = z.object({
userName: zUserName,
Expand All @@ -29,14 +28,9 @@ export async function GET(_req: Request, { params }: { params: Promise<Params> }

const session = await requireAuth()

const db = getDb()
const owner = await db.user.findUnique({ where: { name: userName } })
if (!owner) notFound()
const project = await db.project.findUnique({
where: { userId_name: { userId: owner.id, name: projectName } },
})
// 404 rather than 403 to avoid leaking the existence of inaccessible projects.
if (!project || !canAccessProject(session.user, project)) notFound()
const proj = await getProjectForViewer(session.user, userName, projectName)
if (!proj) notFound() // 404 rather than 403 to avoid leaking the existence of inaccessible projects.
const { owner, project } = proj

// Mint a token with the the project folder as root dir:
// viewer can already read these same files through the editor.
Expand Down
25 changes: 24 additions & 1 deletion src/lib/server/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,34 @@ export function submitAction<S extends z.ZodType, T = void>(
}
}

export function canAccessProject(user: User, project: Project) {
function canAccessProject(user: User, project: Project) {
const isOwner = user.id === project.userId
return isOwner || project.isPublic
}

/**
* If a the given User can access <ownerName>/<projectName>,
* return both the owner and project records.
*
* Otherwise, return null.
*/
export async function getProjectForViewer(
sessionUser: User,
ownerName: string,
projectName: string,
): Promise<{ owner: User; project: Project } | null> {
const db = getDb()

const owner = await db.user.findUnique({ where: { name: ownerName } })
if (!owner) return null

const project = await db.project.findUnique({ where: { userId_name: { userId: owner.id, name: projectName } } })
if (!project) return null

if (canAccessProject(sessionUser, project)) return { owner, project }
return null
}

/** Resolve `<userName>/<projectName>` for an owner-only route,
* where the requesting user must be the project's owner.
*
Expand Down
Loading