Skip to content

Commit c06d4f8

Browse files
committed
fix(#78): use canAccess pattern and isolate audit log writes
1 parent b70cdda commit c06d4f8

1 file changed

Lines changed: 21 additions & 17 deletions

File tree

codebenders-dashboard/app/api/students/[guid]/sis-link/route.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ import { type NextRequest, NextResponse } from "next/server"
22
import { mkdir, appendFile } from "fs/promises"
33
import path from "path"
44
import { getPool } from "@/lib/db"
5-
import type { Role } from "@/lib/roles"
6-
7-
const ALLOWED_ROLES: Role[] = ["admin", "advisor", "ir"]
5+
import { canAccess, type Role } from "@/lib/roles"
86

97
const LOGS_DIR = path.join(process.cwd(), "logs")
108
const LOG_FILE = path.join(LOGS_DIR, "query-history.jsonl")
@@ -21,7 +19,7 @@ export async function GET(
2119

2220
// Role check
2321
const role = request.headers.get("x-user-role") as Role | null
24-
if (!role || !ALLOWED_ROLES.includes(role)) {
22+
if (!role || !canAccess("/api/students", role)) {
2523
return NextResponse.json({ error: "Forbidden" }, { status: 403 })
2624
}
2725

@@ -30,6 +28,8 @@ export async function GET(
3028
return NextResponse.json({ error: "Missing student GUID" }, { status: 400 })
3129
}
3230

31+
let url: string
32+
3333
try {
3434
// Look up SIS ID from mapping table
3535
const pool = getPool()
@@ -45,24 +45,28 @@ export async function GET(
4545
// Build URL server-side — SIS ID never reaches the client
4646
const sisIdParam = process.env.SIS_ID_PARAM || "id"
4747
const sisId = result.rows[0].sis_id
48-
const url = `${sisBaseUrl}?${encodeURIComponent(sisIdParam)}=${encodeURIComponent(sisId)}`
49-
50-
// Audit log — GUID and role only, never the SIS ID
51-
const logEntry = {
52-
event: "sis_link_accessed",
53-
guid,
54-
role,
55-
timestamp: new Date().toISOString(),
56-
}
57-
await mkdir(LOGS_DIR, { recursive: true })
58-
await appendFile(LOG_FILE, JSON.stringify(logEntry) + "\n", "utf8")
59-
60-
return NextResponse.json({ url })
48+
url = `${sisBaseUrl}?${encodeURIComponent(sisIdParam)}=${encodeURIComponent(sisId)}`
6149
} catch (error) {
6250
console.error("SIS link lookup error:", error)
6351
return NextResponse.json(
6452
{ error: "Failed to look up SIS link" },
6553
{ status: 500 }
6654
)
6755
}
56+
57+
// Audit log — GUID and role only, never the SIS ID
58+
const logEntry = {
59+
event: "sis_link_accessed",
60+
guid,
61+
role,
62+
timestamp: new Date().toISOString(),
63+
}
64+
try {
65+
await mkdir(LOGS_DIR, { recursive: true })
66+
await appendFile(LOG_FILE, JSON.stringify(logEntry) + "\n", "utf8")
67+
} catch (auditErr) {
68+
console.error("SIS audit log write failed:", auditErr)
69+
}
70+
71+
return NextResponse.json({ url })
6872
}

0 commit comments

Comments
 (0)