Voters (Authorization)
Voters control who can access which routes.
config/voters/ArticleVoter.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:
{
"method": "DELETE",
"path": "/api/articles/:id",
"auth": true,
"voter": "ArticleVoter"
}
Generated output
src/lib/auth.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
// 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 (default)
- Prisma
export async function DELETE(_req: NextRequest, { params }: { params: { id: string } }) {
const id = params.id;
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 });
}
export async function DELETE(_req: NextRequest, { params }: { params: { id: string } }) {
const id = params.id;
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 });
}