Voters (Authorization)
Voters control who can access which routes.
json
{
"name": "ArticleVoter",
"rules": [
{
"methods": ["GET"],
"resource": "Article"
},
{
"methods": ["POST", "PUT", "DELETE"],
"resource": "Article",
"condition": {
"self": true
}
}
]
}This means:
- Anyone can
GETarticles - Only the article's owner can
POST,PUT, orDELETE
Rule fields
| Field | Type | Description |
|---|---|---|
methods | HttpMethod[] | Allowed HTTP methods |
resource | string | Model name (resource being accessed) |
condition.self | boolean | User must be the owner |
condition.field | string | Field to compare |
condition.operator | string | Comparison operator (==, !=, …) |
condition.value | any | Value to compare against |
Using a voter on a route
Add "voter": "ArticleVoter" (and optionally "auth": true) to a route definition:
json
{
"method": "DELETE",
"path": "/api/articles/:id",
"auth": true,
"voter": "ArticleVoter"
}Generated output
src/lib/auth.ts
ts
// Auto-generated by Codabra — do not edit manually
import { NextRequest } from 'next/server';
export function getAuthUser(req: NextRequest): string | null {
const auth = req.headers.get('authorization');
if (!auth || !auth.startsWith('Bearer ')) return null;
return auth.slice(7) || null;
}src/lib/voters/ArticleVoter.ts
ts
// Auto-generated by Codabra — do not edit manually
import { NextRequest } from 'next/server';
export function checkArticleVoter(
_req: NextRequest,
method: string,
resource: unknown,
userId: string | null,
): boolean {
if (['GET'].includes(method as never)) {
return true;
}
if (['POST', 'PUT', 'DELETE'].includes(method as never)) {
return (
resource != null &&
(resource as Record<string, unknown>)['userId'] === userId
);
}
return false;
}Route handler (excerpt)
Drizzle
ts
export async function DELETE(
_req: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
const { id } = await params;
const userId = getAuthUser(_req);
if (!userId)
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const resource = await db
.select()
.from(articles)
.where(eq(articles.id, id))
.then((r) => r[0] ?? null);
if (!checkArticleVoter(_req, 'DELETE', resource, userId)) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
await db.delete(articles).where(eq(articles.id, id));
return NextResponse.json({ success: true });
}Prisma
ts
export async function DELETE(
_req: NextRequest,
{ params }: { params: Promise<{ id: string }> },
) {
const { id } = await params;
const userId = getAuthUser(_req);
if (!userId)
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const resource = await prisma.article.findUnique({ where: { id } });
if (!checkArticleVoter(_req, 'DELETE', resource, userId)) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const data = await prisma.article.delete({ where: { id } });
return NextResponse.json({ success: true });
}