mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 17:46:06 +00:00
refactor(dashboard): centralize access guards and split interactive pages into client components
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
import { notFound, redirect } from 'next/navigation';
|
||||
import { auth, checkProjectAccess, checkWorkspaceAccess } from '@/lib/auth';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
type AccessIntent = 'view' | 'manage';
|
||||
|
||||
const LOGIN_REDIRECT = '/login';
|
||||
const FORBIDDEN_REDIRECT = '/dashboard';
|
||||
|
||||
function redirectForMissingAuth() {
|
||||
redirect(LOGIN_REDIRECT);
|
||||
}
|
||||
|
||||
function redirectForForbidden() {
|
||||
redirect(FORBIDDEN_REDIRECT);
|
||||
}
|
||||
|
||||
function ensureGuestPolicy(options: { userId?: string; intent: AccessIntent; allowPublicView: boolean }) {
|
||||
const { userId, intent, allowPublicView } = options;
|
||||
if (userId) return;
|
||||
|
||||
if (intent !== 'view' || !allowPublicView) {
|
||||
redirectForMissingAuth();
|
||||
}
|
||||
}
|
||||
|
||||
async function assertProjectAccessOrRedirect(
|
||||
project: { id: string; ownerId: string; workspaceId: string; visibility: string },
|
||||
options: {
|
||||
userId?: string;
|
||||
intent: AccessIntent;
|
||||
allowPublicView: boolean;
|
||||
}
|
||||
) {
|
||||
const { userId, intent, allowPublicView } = options;
|
||||
|
||||
ensureGuestPolicy({ userId, intent, allowPublicView });
|
||||
|
||||
const access = await checkProjectAccess(project, userId, { intent });
|
||||
|
||||
if (!access.hasAccess) {
|
||||
if (!userId) {
|
||||
redirectForMissingAuth();
|
||||
}
|
||||
redirectForForbidden();
|
||||
}
|
||||
|
||||
if (intent === 'manage' && !access.canEdit) {
|
||||
redirectForForbidden();
|
||||
}
|
||||
|
||||
return access;
|
||||
}
|
||||
|
||||
export async function requireAuthOrRedirect() {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) {
|
||||
redirectForMissingAuth();
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
export async function requireWorkspaceAccessOrRedirect(options: {
|
||||
workspaceId: string;
|
||||
userId?: string;
|
||||
intent?: AccessIntent;
|
||||
}) {
|
||||
const { workspaceId, userId, intent = 'view' } = options;
|
||||
const resolvedUserId = userId ?? (await auth())?.user?.id;
|
||||
|
||||
if (!resolvedUserId) {
|
||||
redirectForMissingAuth();
|
||||
}
|
||||
|
||||
const workspace = await db.workspace.findUnique({
|
||||
where: { id: workspaceId },
|
||||
select: { id: true, ownerId: true },
|
||||
});
|
||||
|
||||
if (!workspace) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const access = await checkWorkspaceAccess(workspace, resolvedUserId);
|
||||
|
||||
if (!access.hasAccess) {
|
||||
redirectForForbidden();
|
||||
}
|
||||
|
||||
if (intent === 'manage' && !access.canEdit) {
|
||||
redirectForForbidden();
|
||||
}
|
||||
|
||||
return { workspace, access };
|
||||
}
|
||||
|
||||
export async function requireProjectAccessOrRedirect(options: {
|
||||
projectId: string;
|
||||
userId?: string;
|
||||
intent?: AccessIntent;
|
||||
allowPublicView?: boolean;
|
||||
}) {
|
||||
const { projectId, userId, intent = 'view', allowPublicView = false } = options;
|
||||
const resolvedUserId = userId ?? (await auth())?.user?.id;
|
||||
|
||||
// Fail closed before resource lookup when the route is not public.
|
||||
if (!resolvedUserId && !allowPublicView) {
|
||||
redirectForMissingAuth();
|
||||
}
|
||||
|
||||
const project = await db.project.findUnique({
|
||||
where: { id: projectId },
|
||||
select: { id: true, ownerId: true, workspaceId: true, visibility: true },
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
if (!resolvedUserId) {
|
||||
redirectForMissingAuth();
|
||||
}
|
||||
notFound();
|
||||
}
|
||||
|
||||
const access = await assertProjectAccessOrRedirect(project, {
|
||||
userId: resolvedUserId,
|
||||
intent,
|
||||
allowPublicView,
|
||||
});
|
||||
|
||||
return { project, access };
|
||||
}
|
||||
|
||||
export async function requireVideoProjectAccessOrRedirect(options: {
|
||||
projectId: string;
|
||||
videoId: string;
|
||||
userId?: string;
|
||||
intent?: AccessIntent;
|
||||
allowPublicView?: boolean;
|
||||
}) {
|
||||
const { projectId, videoId, userId, intent = 'view', allowPublicView = false } = options;
|
||||
const resolvedUserId = userId ?? (await auth())?.user?.id;
|
||||
|
||||
// Fail closed before resource lookup when the route is not public.
|
||||
if (!resolvedUserId && !allowPublicView) {
|
||||
redirectForMissingAuth();
|
||||
}
|
||||
|
||||
const video = await db.video.findFirst({
|
||||
where: { id: videoId, projectId },
|
||||
select: {
|
||||
id: true,
|
||||
project: {
|
||||
select: { id: true, ownerId: true, workspaceId: true, visibility: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!video) {
|
||||
if (!resolvedUserId) {
|
||||
redirectForMissingAuth();
|
||||
}
|
||||
notFound();
|
||||
}
|
||||
|
||||
const access = await assertProjectAccessOrRedirect(video.project, {
|
||||
userId: resolvedUserId,
|
||||
intent,
|
||||
allowPublicView,
|
||||
});
|
||||
|
||||
return { video, access, project: video.project };
|
||||
}
|
||||
Reference in New Issue
Block a user