@@ -6,32 +6,40 @@ import { canAccess, type Role } from "@/lib/roles"
66
77const LOGS_DIR = path . join ( process . cwd ( ) , "logs" )
88const LOG_FILE = path . join ( LOGS_DIR , "query-history.jsonl" )
9+ const SIS_ID_PARAM = process . env . SIS_ID_PARAM || "id"
10+
11+ let logDirReady = false
12+
13+ function writeAuditLog ( entry : Record < string , unknown > ) {
14+ const doWrite = async ( ) => {
15+ if ( ! logDirReady ) {
16+ await mkdir ( LOGS_DIR , { recursive : true } )
17+ logDirReady = true
18+ }
19+ await appendFile ( LOG_FILE , JSON . stringify ( entry ) + "\n" , "utf8" )
20+ }
21+ doWrite ( ) . catch ( err => console . error ( "SIS audit log write failed:" , err ) )
22+ }
923
1024export async function GET (
1125 request : NextRequest ,
1226 { params } : { params : Promise < { guid : string } > }
1327) {
14- // Feature disabled if SIS_BASE_URL is not configured
1528 const sisBaseUrl = process . env . SIS_BASE_URL
1629 if ( ! sisBaseUrl ) {
1730 return NextResponse . json ( { url : null } , { status : 404 } )
1831 }
1932
20- // Role check
2133 const role = request . headers . get ( "x-user-role" ) as Role | null
2234 if ( ! role || ! canAccess ( "/api/students" , role ) ) {
2335 return NextResponse . json ( { error : "Forbidden" } , { status : 403 } )
2436 }
2537
2638 const { guid } = await params
27- if ( ! guid ) {
28- return NextResponse . json ( { error : "Missing student GUID" } , { status : 400 } )
29- }
3039
3140 let url : string
3241
3342 try {
34- // Look up SIS ID from mapping table
3543 const pool = getPool ( )
3644 const result = await pool . query (
3745 "SELECT sis_id FROM guid_sis_map WHERE student_guid = $1 LIMIT 1" ,
@@ -42,10 +50,9 @@ export async function GET(
4250 return NextResponse . json ( { url : null } , { status : 404 } )
4351 }
4452
45- // Build URL server-side — SIS ID never reaches the client
46- const sisIdParam = process . env . SIS_ID_PARAM || "id"
53+ // SIS ID is embedded in the URL but never returned as a standalone field
4754 const sisId = result . rows [ 0 ] . sis_id
48- url = `${ sisBaseUrl } ?${ encodeURIComponent ( sisIdParam ) } =${ encodeURIComponent ( sisId ) } `
55+ url = `${ sisBaseUrl } ?${ encodeURIComponent ( SIS_ID_PARAM ) } =${ encodeURIComponent ( sisId ) } `
4956 } catch ( error ) {
5057 console . error ( "SIS link lookup error:" , error )
5158 return NextResponse . json (
@@ -54,19 +61,7 @@ export async function GET(
5461 )
5562 }
5663
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- }
64+ writeAuditLog ( { event : "sis_link_accessed" , guid, role, timestamp : new Date ( ) . toISOString ( ) } )
7065
7166 return NextResponse . json ( { url } )
7267}
0 commit comments