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
7 changes: 1 addition & 6 deletions .github/workflows/auto-process.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,7 @@ jobs:
echo "PR #${PR_NUMBER} already counted."
exit 0
fi
if [ ! -f leaderboard.json ]; then
printf '{}\n' > leaderboard.json
fi
tmp_file="$(mktemp)"
jq --arg user "${PR_USER}" '.[$user] = ((.[$user] // 0) + 1)' leaderboard.json > "${tmp_file}"
mv "${tmp_file}" leaderboard.json
node scripts/updateLeaderboard.js leaderboard.json "${PR_USER}"
git add leaderboard.json
if git diff --cached --quiet -- leaderboard.json; then
echo "No leaderboard changes to commit."
Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import usersRouter from "./routes/users";
const app = express();
const port = process.env.PORT || 4000;

app.use(express.json());
// Limit JSON payload size to 100kb
app.use(express.json({ limit: "100kb" }));

app.get("/health", (_req, res) => {
res.json({ status: "ok", service: "taskflow-api" });
Expand Down
6 changes: 6 additions & 0 deletions apps/api/src/routes/users.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Router } from "express";

import { sendApiError } from "../utils/error";

const router = Router();

router.get("/", (_req, res) => {
Expand All @@ -10,6 +12,10 @@ router.get("/", (_req, res) => {
});

router.post("/", (req, res) => {
if (!req.body || Object.keys(req.body).length === 0) {
return sendApiError(res, 400, "User data is required.");
}

res.status(201).json({
data: {
id: "stub-user-id",
Expand Down
14 changes: 14 additions & 0 deletions apps/api/src/utils/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Response } from "express";

/**
* Standardized API error response helper
*/
export function sendApiError(res: Response, statusCode: number, message: string, details?: any) {
return res.status(statusCode).json({
success: false,
error: {
message,
...(details && { details })
}
});
}
5 changes: 5 additions & 0 deletions apps/web/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
30 changes: 30 additions & 0 deletions apps/web/next.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()',
},
],
},
];
},
};

export default nextConfig;
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"description": "Next.js web application for TaskFlow.",
"scripts": {
"dev": "next dev",
"test": "echo \"No web tests configured yet\"",
"test": "node test-security-headers.mjs",
"lint": "next lint"
},
"dependencies": {
Expand Down
16 changes: 16 additions & 0 deletions apps/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const metadata = {
title: 'Next.js',
description: 'Generated by Next.js',
}

export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
61 changes: 61 additions & 0 deletions apps/web/test-security-headers.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { spawn } from "child_process";
import assert from "assert";

async function runTest() {
console.log("Starting Next.js dev server...");
const devProcess = spawn("npm", ["run", "dev", "--", "-p", "3002"], {
stdio: "pipe",
env: { ...process.env, PORT: "3002" },
});

let serverReady = false;

devProcess.stdout.on("data", (data) => {
const output = data.toString();
console.log(output);
if (output.includes("Ready") || output.includes("started server on")) {
serverReady = true;
}
});

devProcess.stderr.on("data", (data) => {
console.error(data.toString());
});

// Wait for server to be ready
let attempts = 0;
while (!serverReady && attempts < 30) {
await new Promise((resolve) => setTimeout(resolve, 1000));
attempts++;
}

if (!serverReady) {
console.error("Server failed to start in time.");
devProcess.kill();
process.exit(1);
}

console.log("Fetching http://localhost:3002...");
try {
const res = await fetch("http://localhost:3002");
console.log("Headers:");
res.headers.forEach((value, key) => {
console.log(`${key}: ${value}`);
});

assert.strictEqual(res.headers.get("x-frame-options"), "DENY", "X-Frame-Options should be DENY");
assert.strictEqual(res.headers.get("x-content-type-options"), "nosniff", "X-Content-Type-Options should be nosniff");
assert.strictEqual(res.headers.get("referrer-policy"), "strict-origin-when-cross-origin", "Referrer-Policy should be strict-origin-when-cross-origin");
assert.ok(res.headers.get("permissions-policy"), "Permissions-Policy should be set");
console.log("SUCCESS: Security headers are correctly set.");
} catch (error) {
console.error("Test failed:", error);
devProcess.kill();
process.exit(1);
}

devProcess.kill();
process.exit(0);
}

runTest();
34 changes: 34 additions & 0 deletions apps/web/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"compilerOptions": {
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"noEmit": true,
"incremental": true,
"module": "esnext",
"esModuleInterop": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"plugins": [
{
"name": "next"
}
]
},
"include": [
"next-env.d.ts",
".next/types/**/*.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
}
Loading
Loading