Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: redirect unauthorized tip CRUD to /tips #43

Merged
merged 3 commits into from
Jan 4, 2024
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
4 changes: 1 addition & 3 deletions apps/course-builder-web/src/app/[article]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,13 @@ export default async function ArticlePage({
const ability = getAbility({user: session?.user})
const article = await getArticle(params.article)

console.log('******', {article})

if (!article) {
notFound()
}

return (
<div>
{ability.can('edit', 'Article') ? (
{ability.can('update', 'Content') ? (
<div className="flex h-9 w-full items-center justify-between bg-muted px-1">
<div />
<Button asChild className="h-7">
Expand Down
8 changes: 4 additions & 4 deletions apps/course-builder-web/src/app/admin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {getServerAuthSession} from '@/server/auth'
import {getAbility} from '@/lib/ability'
import {redirect} from 'next/navigation'
import {notFound} from 'next/navigation'

export default async function AdminPage() {
const session = await getServerAuthSession()
const ability = getAbility({user: session?.user})

if (ability.can('view', 'Anything')) {
if (ability.can('manage', 'all')) {
return <div>Admin</div>
} else {
notFound()
}

redirect('/')
}
4 changes: 2 additions & 2 deletions apps/course-builder-web/src/app/api/uploadthing/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const ourFileRouter = {
const session = await getServerAuthSession()
const ability = getAbility({user: session?.user})

if (!session || !ability.can('upload', 'Media')) {
if (!session || !ability.can('create', 'Content')) {
throw new Error('Unauthorized')
}

Expand Down Expand Up @@ -51,7 +51,7 @@ export const ourFileRouter = {
const session = await getServerAuthSession()
const ability = getAbility({user: session?.user})

if (!session || !ability.can('upload', 'Media')) {
if (!session || !ability.can('create', 'Content')) {
throw new Error('Unauthorized')
}

Expand Down
11 changes: 9 additions & 2 deletions apps/course-builder-web/src/app/articles/[slug]/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import {getServerAuthSession} from '@/server/auth'
import {getAbility} from '@/lib/ability'
import {EditArticleForm} from '@/app/articles/_components/edit-article-form'
import {getArticle} from '@/lib/articles'
import {notFound} from 'next/navigation'

export const dynamic = 'force-dynamic'

export default async function ArticleEditPage({
params,
Expand All @@ -13,9 +16,13 @@ export default async function ArticleEditPage({
const ability = getAbility({user: session?.user})
const article = await getArticle(params.slug)

return article && ability.can('upload', 'Media') ? (
if (!article || !ability.can('create', 'Content')) {
notFound()
}

return (
<div className="relative mx-auto flex h-full w-full flex-grow flex-col items-center justify-center">
<EditArticleForm key={article.slug} article={article} />
</div>
) : null
)
}
9 changes: 8 additions & 1 deletion apps/course-builder-web/src/app/articles/new/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@ import {getServerAuthSession} from '@/server/auth'
import {getAbility} from '@/lib/ability'
import * as React from 'react'
import {CreateArticle} from '../_components/create-article'
import {notFound} from 'next/navigation'

export const dynamic = 'force-dynamic'

export default async function NewTipPage() {
const session = await getServerAuthSession()
const ability = getAbility({user: session?.user})

if (!ability.can('create', 'Content')) {
notFound()
}

return (
<div className="flex flex-col">
{ability.can('upload', 'Media') ? <CreateArticle /> : null}
<CreateArticle />
</div>
)
}
2 changes: 1 addition & 1 deletion apps/course-builder-web/src/app/profile/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default async function ProfilePage() {
const session = await getServerAuthSession()
const ability = getAbility({user: session?.user})

if (ability.can('view', 'Profile', session?.user?.id)) {
if (ability.can('read', 'User', session?.user?.id)) {
return <div>Profile</div>
}

Expand Down
11 changes: 9 additions & 2 deletions apps/course-builder-web/src/app/tips/[slug]/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@ import {getServerAuthSession} from '@/server/auth'
import {getAbility} from '@/lib/ability'
import {getTip} from '@/lib/tips'
import {EditTipForm} from '../../_components/edit-tip-form'
import {notFound} from 'next/navigation'

export const dynamic = 'force-dynamic'

export default async function TipEditPage({params}: {params: {slug: string}}) {
const session = await getServerAuthSession()
const ability = getAbility({user: session?.user})
const tip = await getTip(params.slug)

return tip && ability.can('upload', 'Media') ? (
if (!tip || !ability.can('create', 'Content')) {
notFound()
}

return (
<div className="relative mx-auto flex h-full w-full flex-grow flex-col items-center justify-center">
<EditTipForm key={tip.slug} tip={tip} />
</div>
) : null
)
}
2 changes: 1 addition & 1 deletion apps/course-builder-web/src/app/tips/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default async function TipPage({params}: {params: {slug: string}}) {

return (
<div>
{ability.can('edit', 'Tip') ? (
{ability.can('update', 'Content') ? (
<div className="flex h-9 w-full items-center justify-between bg-muted px-1">
<div />
<Button asChild className="h-7">
Expand Down
9 changes: 8 additions & 1 deletion apps/course-builder-web/src/app/tips/new/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@ import {getServerAuthSession} from '@/server/auth'
import {getAbility} from '@/lib/ability'
import {CreateTip} from '@/app/tips/_components/create-tip'
import * as React from 'react'
import {notFound} from 'next/navigation'

export const dynamic = 'force-dynamic'

export default async function NewTipPage() {
const session = await getServerAuthSession()
const ability = getAbility({user: session?.user})

if (!ability.can('create', 'Content')) {
notFound()
}

return (
<div className="flex flex-col">
{ability.can('upload', 'Media') ? <CreateTip /> : null}
<CreateTip />
</div>
)
}
2 changes: 1 addition & 1 deletion apps/course-builder-web/src/app/tips/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default async function TipsListPage() {
</div>
<div className="col-span-2 h-full flex-grow">
<h1 className="pb-2 text-lg font-bold">Create Tip</h1>
{ability.can('upload', 'Media') ? <CreateTip /> : null}
{ability.can('create', 'Content') ? <CreateTip /> : null}
</div>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import {sanityQuery} from '@/server/sanity.server'
import {getServerAuthSession} from '@/server/auth'
import {getAbility} from '@/lib/ability'
import {notFound} from 'next/navigation'

export const dynamic = 'force-dynamic'

export default async function EditTutorialLessonPage({
params,
Expand All @@ -7,6 +12,9 @@ export default async function EditTutorialLessonPage({
}) {
const {module, lesson} = params

const session = await getServerAuthSession()
const ability = getAbility({user: session?.user})

const lessonData = await sanityQuery<{
_id: string
title: string
Expand All @@ -26,6 +34,10 @@ export default async function EditTutorialLessonPage({
}
}`)

if (!lessonData || !ability.can('update', 'Content')) {
notFound()
}

return (
<div className="flex flex-col">
<div>Edit Tutorial Lesson Form</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import VideoUploader from '@/components/video-uploader'
import Tree from '@/components/lesson-list/tree'
import ModuleEdit from '@/components/module-edit'

export const dynamic = 'force-dynamic'

export default async function EditTutorialPage({
params,
}: {
Expand All @@ -15,7 +17,7 @@ export default async function EditTutorialPage({
const session = await getServerAuthSession()
const ability = getAbility({user: session?.user})

if (!ability.can('edit', 'Module')) {
if (!ability.can('update', 'Content')) {
redirect('/login')
}

Expand Down
7 changes: 2 additions & 5 deletions apps/course-builder-web/src/app/tutorials/[module]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {getServerAuthSession} from '@/server/auth'
import {getAbility} from '@/lib/ability'
import {notFound, redirect} from 'next/navigation'
import {Separator} from '@coursebuilder/ui'
import VideoUploader from '@/components/video-uploader'
import * as React from 'react'
import {sanityQuery} from '@/server/sanity.server'
import Link from 'next/link'
Expand All @@ -11,7 +10,7 @@ export default async function ModulePage({params}: {params: {module: string}}) {
const session = await getServerAuthSession()
const ability = getAbility({user: session?.user})

if (!ability.can('view', 'Anything')) {
if (!ability.can('read', 'Content')) {
redirect('/login')
}

Expand All @@ -23,14 +22,12 @@ export default async function ModulePage({params}: {params: {module: string}}) {
notFound()
}

console.log('course', course)

return (
<div className="hidden h-full flex-col md:flex">
<div className="container flex flex-col items-start justify-between space-y-2 py-4 sm:flex-row sm:items-center sm:space-y-0 md:h-16">
<h2 className="text-lg font-semibold">{course.title}</h2>
<p>{course.description}</p>
{ability.can('edit', 'Module') && (
{ability.can('update', 'Content') && (
<Link href={`/tutorials/${params.module}/edit`}>edit module</Link>
)}
</div>
Expand Down
9 changes: 8 additions & 1 deletion apps/course-builder-web/src/app/tutorials/new/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import {getServerAuthSession} from '@/server/auth'
import {getAbility} from '@/lib/ability'
import * as React from 'react'
import {notFound} from 'next/navigation'

export const dynamic = 'force-dynamic'

export default async function NewTutorialPage() {
const session = await getServerAuthSession()
const ability = getAbility({user: session?.user})

if (!ability.can('create', 'Content')) {
notFound()
}

return (
<div className="flex flex-col">
{ability.can('upload', 'Media') ? <div>New Tutorial Form</div> : null}
<div>New Tutorial Form</div>
</div>
)
}
2 changes: 1 addition & 1 deletion apps/course-builder-web/src/app/tutorials/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default async function Tutorials() {

return (
<div className="flex flex-col">
{ability.can('upload', 'Media') ? (
{ability.can('create', 'Content') ? (
<Link href="/tutorials/new">New Tutorial</Link>
) : null}
{tutorials.map((tutorial) => (
Expand Down
2 changes: 1 addition & 1 deletion apps/course-builder-web/src/components/navigation/user.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const User: React.FC<{className?: string}> = ({className}) => {
const ability = createAppAbility(abilityRules)
const isLoadingUserInfo = sessionStatus === 'loading'

const canCreateContent = ability.can('upload', 'Media')
const canCreateContent = ability.can('create', 'Content')

return (
<>
Expand Down
20 changes: 5 additions & 15 deletions apps/course-builder-web/src/lib/ability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
type MongoAbility,
} from '@casl/ability'
import z from 'zod'
import {Tip} from '@/lib/tips'

export const UserSchema = z.object({
role: z.string().optional(),
Expand All @@ -16,15 +15,10 @@ export const UserSchema = z.object({

export type User = z.infer<typeof UserSchema>

type Abilities =
| ['view', 'Anything']
| ['view', 'Profile' | User]
| ['upload', 'Media']
| ['edit', 'Tip']
| ['edit', 'Article']
| ['edit', 'Module']
type Actions = 'create' | 'read' | 'update' | 'delete' | 'manage'
type Subjects = 'Content' | 'User' | User | 'all'
Comment on lines +18 to +19
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

had "refine these" on my todo list. Probably want to go back through CASL docs since we are using older style for some stuff.


export type AppAbility = MongoAbility<Abilities>
export type AppAbility = MongoAbility<[Actions, Subjects]>

export const createAppAbility = createMongoAbility as CreateAbility<AppAbility>

Expand All @@ -40,15 +34,11 @@ export function getAbilityRules(options: GetAbilityOptions = {}) {
const {can, rules} = new AbilityBuilder<AppAbility>(createMongoAbility)

if (options.user?.role === 'admin') {
can('view', 'Anything')
can('upload', 'Media')
can('edit', 'Tip')
can('edit', 'Module')
can('edit', 'Article')
can('manage', 'all')
}

if (options.user) {
can('view', 'Profile', {id: options.user.id})
can('read', 'User', {id: options.user.id})
}

return rules
Expand Down
6 changes: 3 additions & 3 deletions apps/course-builder-web/src/trpc/api/routers/articles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const articlesRouter = createTRPCRouter({
.mutation(async ({ctx, input}) => {
const session = await getServerAuthSession()
const ability = getAbility({user: session?.user})
if (!ability.can('upload', 'Media')) {
if (!ability.can('create', 'Content')) {
throw new Error('Unauthorized')
}

Expand All @@ -60,7 +60,7 @@ export const articlesRouter = createTRPCRouter({
.mutation(async ({ctx, input}) => {
const session = await getServerAuthSession()
const ability = getAbility({user: session?.user})
if (!ability.can('upload', 'Media')) {
if (!ability.can('create', 'Content')) {
throw new Error('Unauthorized')
}

Expand Down Expand Up @@ -93,7 +93,7 @@ export const articlesRouter = createTRPCRouter({
.mutation(async ({ctx, input}) => {
const session = await getServerAuthSession()
const ability = getAbility({user: session?.user})
if (!ability.can('upload', 'Media')) {
if (!ability.can('update', 'Content')) {
throw new Error('Unauthorized')
}

Expand Down
6 changes: 3 additions & 3 deletions apps/course-builder-web/src/trpc/api/routers/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const moduleRouter = createTRPCRouter({
const session = await getServerAuthSession()
const ability = getAbility({user: session?.user})

if (!ability.can('upload', 'Media')) {
if (!ability.can('read', 'Content')) {
throw new TRPCError({code: 'UNAUTHORIZED'})
}

Expand All @@ -48,7 +48,7 @@ export const moduleRouter = createTRPCRouter({
const session = await getServerAuthSession()
const ability = getAbility({user: session?.user})

if (!ability.can('upload', 'Media')) {
if (!ability.can('update', 'Content')) {
throw new TRPCError({code: 'UNAUTHORIZED'})
}

Expand Down Expand Up @@ -98,7 +98,7 @@ export const moduleRouter = createTRPCRouter({
const session = await getServerAuthSession()
const ability = getAbility({user: session?.user})

if (!ability.can('upload', 'Media')) {
if (!ability.can('read', 'Content')) {
throw new TRPCError({code: 'UNAUTHORIZED'})
}

Expand Down
Loading