From 61642312a1a1e941c890cc4016f858ba69163be0 Mon Sep 17 00:00:00 2001 From: ccocoa <270871570+ccocoa@users.noreply.github.com> Date: Sun, 28 Jun 2026 15:14:15 +0000 Subject: [PATCH] feat: refactor API routes to src/pages/api and implement HMAC logic - Remove legacy functions/ directory. - Implement HMAC SHA-256 using crypto.subtle in get-flag.js and verify-flag.js. - Use context.locals.runtime.env.FLAG_SECRET for security. - Ensure normalizedUsername (trim + toLowerCase) is used. - Set prerender = false for API routes. Co-authored-by: thirapi <132630759+thirapi@users.noreply.github.com> --- functions/api/verify.js | 20 ---------------- src/pages/api/get-flag.js | 37 +++++++++++++++++++++++++++++ src/pages/api/verify-flag.js | 46 ++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 20 deletions(-) delete mode 100644 functions/api/verify.js create mode 100644 src/pages/api/get-flag.js create mode 100644 src/pages/api/verify-flag.js diff --git a/functions/api/verify.js b/functions/api/verify.js deleted file mode 100644 index a525612..0000000 --- a/functions/api/verify.js +++ /dev/null @@ -1,20 +0,0 @@ -export async function onRequestPost(context) { - const { request } = context; - const body = await request.json(); - const { levelId, flag } = body; - - // Logika validasi sederhana - // Dalam produksi, gunakan database atau secret - const expectedFlag = `CTF{SECRET_${levelId}}`; - - if (flag === expectedFlag) { - return new Response(JSON.stringify({ success: true, message: 'Flag benar!' }), { - headers: { 'Content-Type': 'application/json' }, - }); - } - - return new Response(JSON.stringify({ success: false, message: 'Flag salah!' }), { - status: 400, - headers: { 'Content-Type': 'application/json' }, - }); -} diff --git a/src/pages/api/get-flag.js b/src/pages/api/get-flag.js new file mode 100644 index 0000000..18f2d2b --- /dev/null +++ b/src/pages/api/get-flag.js @@ -0,0 +1,37 @@ +export const prerender = false; + +export async function POST({ request, locals }) { + const body = await request.json(); + const { username } = body; + + if (!username) { + return new Response(JSON.stringify({ error: 'Username is required' }), { status: 400 }); + } + + const normalizedUsername = username.trim().toLowerCase(); + const secret = locals.runtime.env.FLAG_SECRET; + + if (!secret) { + return new Response(JSON.stringify({ error: 'Server configuration error' }), { status: 500 }); + } + + const encoder = new TextEncoder(); + const keyData = encoder.encode(secret); + const messageData = encoder.encode(normalizedUsername); + + const cryptoKey = await crypto.subtle.importKey( + 'raw', + keyData, + { name: 'HMAC', hash: 'SHA-256' }, + false, + ['sign'] + ); + + const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageData); + const hashArray = Array.from(new Uint8Array(signature)); + const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + + return new Response(JSON.stringify({ flag: `CTF{${hashHex}}` }), { + headers: { 'Content-Type': 'application/json' }, + }); +} diff --git a/src/pages/api/verify-flag.js b/src/pages/api/verify-flag.js new file mode 100644 index 0000000..f874f64 --- /dev/null +++ b/src/pages/api/verify-flag.js @@ -0,0 +1,46 @@ +export const prerender = false; + +export async function POST({ request, locals }) { + const body = await request.json(); + const { username, flag } = body; + + if (!username || !flag) { + return new Response(JSON.stringify({ error: 'Username and flag are required' }), { status: 400 }); + } + + const normalizedUsername = username.trim().toLowerCase(); + const secret = locals.runtime.env.FLAG_SECRET; + + if (!secret) { + return new Response(JSON.stringify({ error: 'Server configuration error' }), { status: 500 }); + } + + const encoder = new TextEncoder(); + const keyData = encoder.encode(secret); + const messageData = encoder.encode(normalizedUsername); + + const cryptoKey = await crypto.subtle.importKey( + 'raw', + keyData, + { name: 'HMAC', hash: 'SHA-256' }, + false, + ['sign'] + ); + + const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageData); + const hashArray = Array.from(new Uint8Array(signature)); + const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + + const expectedFlag = `CTF{${hashHex}}`; + + if (flag === expectedFlag) { + return new Response(JSON.stringify({ success: true, message: 'Flag valid!' }), { + headers: { 'Content-Type': 'application/json' }, + }); + } + + return new Response(JSON.stringify({ success: false, message: 'Flag invalid!' }), { + status: 400, + headers: { 'Content-Type': 'application/json' }, + }); +}