Skip to content
Open
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
3 changes: 2 additions & 1 deletion src/app/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { BillQuestions } from "@/components/BillDetail/BillQuestions";
import { Separator } from "@/components/ui/separator";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { DEV_OPEN_ACCESS } from "@/lib/auth-guards";
import { BillTenets } from "@/components/BillDetail/BillTenets";
import { JudgementValue } from "@/components/Judgement/judgement.component";
import { buildAbsoluteUrl, buildRelativePath } from "@/utils/basePath";
Expand Down Expand Up @@ -89,7 +90,7 @@ export default async function BillDetail({ params }: Params) {
<Link href="/" className="text-sm underline mb-6">
← Back to bills
</Link>
{session?.user && (
{(session?.user || DEV_OPEN_ACCESS) && (
<Link href={`/${id}/edit`} className="ml-4 text-sm underline">
Edit
</Link>
Expand Down
27 changes: 16 additions & 11 deletions src/app/api/[id]/reprocess/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { connectToDatabase } from "@/lib/mongoose";
import { Bill, type BillDocument } from "@/models/Bill";
import { User } from "@/models/User";
import { authOptions } from "@/lib/auth";
import { DEV_OPEN_ACCESS } from "@/lib/auth-guards";
import {
type ApiBillDetail,
fetchBillMarkdown,
Expand All @@ -24,21 +25,25 @@ export async function POST(
_request: Request,
{ params }: { params: Promise<{ id: string }> },
) {
const session = await getServerSession(authOptions);
if (!session?.user?.email) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
// DEV ONLY: open access — skip the session/allowlist checks entirely.
if (!DEV_OPEN_ACCESS) {
const session = await getServerSession(authOptions);
if (!session?.user?.email) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

// Verify user is on the allowlist; do not create
await connectToDatabase();
const dbUser = await User.findOne({
emailLower: session.user.email.toLowerCase(),
});
if (!dbUser) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
}

await connectToDatabase();

// Verify user is on the allowlist; do not create
const dbUser = await User.findOne({
emailLower: session.user.email.toLowerCase(),
});
if (!dbUser) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

const { id } = await params;

const existing = (await Bill.findOne({ billId: id })
Expand Down
28 changes: 17 additions & 11 deletions src/app/api/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,33 @@ import { connectToDatabase } from "@/lib/mongoose";
import { Bill } from "@/models/Bill";
import { User } from "@/models/User";
import { authOptions } from "@/lib/auth";
import { DEV_OPEN_ACCESS } from "@/lib/auth-guards";
import { BASE_PATH } from "@/utils/basePath";

export async function POST(
request: Request,
{ params }: { params: Promise<{ id: string }> },
) {
const session = await getServerSession(authOptions);
if (!session?.user?.email) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// DEV ONLY: open access — skip the session/allowlist checks entirely.
if (!DEV_OPEN_ACCESS) {
const session = await getServerSession(authOptions);
if (!session?.user?.email) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

await connectToDatabase();
await connectToDatabase();

// Verify user exists in DB; do not create
const dbUser = await User.findOne({
emailLower: session.user.email.toLowerCase(),
});
if (!dbUser) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
// Verify user exists in DB; do not create
const dbUser = await User.findOne({
emailLower: session.user.email.toLowerCase(),
});
if (!dbUser) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
}

await connectToDatabase();

const contentType = request.headers.get("content-type")?.toLowerCase() || "";

let title: string | undefined;
Expand Down
2 changes: 1 addition & 1 deletion src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function optional(
return value && value.trim() !== "" ? value : undefined;
}

const ENDPOINT = "https://civics-project-kiyv.vercel.app";
const ENDPOINT = "https://api.civicsproject.org";

export const env = {
NODE_ENV: process.env.NODE_ENV || "development",
Expand Down
12 changes: 12 additions & 0 deletions src/lib/auth-guards.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { redirect } from "next/navigation";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { env } from "@/env";
import { connectToDatabase } from "@/lib/mongoose";
import { User } from "@/models/User";

/**
* DEV ONLY: when true, admin/edit access is open to everyone — including users
* who are not signed in. Never enabled in production.
*/
export const DEV_OPEN_ACCESS = env.NODE_ENV !== "production";

/**
* Server-side authentication guard that requires a valid authenticated user.
* Redirects to /unauthorized if:
Expand All @@ -15,6 +22,11 @@ import { User } from "@/models/User";
* @throws Redirects to /unauthorized if authentication fails
*/
export async function requireAuthenticatedUser() {
// DEV ONLY: open access — skip the session/allowlist checks entirely.
if (DEV_OPEN_ACCESS) {
return { session: null, dbUser: null };
}

const session = await getServerSession(authOptions);

if (!session?.user?.email) {
Expand Down
14 changes: 13 additions & 1 deletion src/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,29 @@ export const authOptions: NextAuthOptions = {
const now = new Date();
const existing = await User.findOne({ emailLower: email });
if (!existing) {
// DEV ONLY: auto-create + allow any signed-in account so every user
// has admin access locally. Never runs in production.
if (env.NODE_ENV !== "production") {
console.warn(
`[auth] User ${email} not found. No auto-creation. Denying sign-in.`,
`[auth] DEV: auto-creating + allowing ${email} (admin access on for all users).`,
);
await User.create({
email: user?.email,
emailLower: email,
name: user?.name ?? null,
allowed: true,
lastLoginAt: now,
});
return true;
}
return false;
}
existing.name = user?.name ?? existing.name;
(existing as any).image = (user as any)?.image ?? existing.image;
existing.lastLoginAt = now;
await existing.save();
// DEV ONLY: allow regardless of the allowlist flag.
if (env.NODE_ENV !== "production") return true;
return !!existing.allowed;
} catch (err) {
if (env.NODE_ENV !== "production") {
Expand Down
1 change: 1 addition & 0 deletions src/prompt/summary-and-vote-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ You are analyzing Canadian legislation. You must assess whether the bill aligns
- tenet_evaluations.alignment: aligns|conflicts|neutral
- final_judgment: yes|no|abstain
- is_social_issue: yes|no
- Never mention the tenents in the summary, questions, or rationale.

Output format (return valid JSON only):

Expand Down
13 changes: 12 additions & 1 deletion src/services/billApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export async function getBillFromCivicsProjectApi(
billId: string,
): Promise<ApiBillDetail | null> {
const URL = `${env.CIVICS_PROJECT_BASE_URL}/canada/bills/${CANADIAN_PARLIAMENT_NUMBER}/${billId}`;
console.log({ URL });
const response = await fetch(URL, {
// Cache individual bills.
...(process.env.NODE_ENV === "production"
Expand All @@ -72,7 +73,17 @@ export async function getBillFromCivicsProjectApi(
},
});
if (!response.ok) {
throw new Error("Failed to fetch bill details");
const body = await response.text().catch(() => "<unable to read body>");
console.error("Failed to fetch bill details from Civics Project API", {
billId,
url: URL,
status: response.status,
statusText: response.statusText,
body: body.slice(0, 1000),
});
throw new Error(
`Failed to fetch bill details for "${billId}": ${response.status} ${response.statusText} (${URL})`,
);
}
const json = await response.json();
const data = (json?.data?.bill ?? json?.data ?? json) as
Expand Down
Loading