diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e2ad49..74ce1eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,8 @@ jobs: - name: Run migrations run: | + psql postgresql://classification_user:classification_password@localhost:5432/classification_db \ + -f migrations/001_create_classifications_table.sql psql postgresql://classification_user:classification_password@localhost:5432/classification_db \ -f migrations/002_create_classifications_table.sql psql postgresql://classification_user:classification_password@localhost:5432/classification_db \ diff --git a/migrations/002_create_classifications_table.sql b/migrations/002_create_classifications_table.sql index 3625c82..143e7f8 100644 --- a/migrations/002_create_classifications_table.sql +++ b/migrations/002_create_classifications_table.sql @@ -1,18 +1,20 @@ --- Optional: enable UUID generation extension (though IDs will come from the app) -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +-- Migration 002: Ensure classifications table has the correct schema +-- Drops and recreates the table to match the canonical schema (safe for fresh CI environments) +-- For existing databases with data, use ALTER TABLE to add missing columns instead. + +DROP TABLE IF EXISTS classifications; CREATE TABLE classifications ( - id UUID PRIMARY KEY NOT NULL, -- UUID v7 generated by app - name VARCHAR NOT NULL UNIQUE, -- Case-sensitive unique per spec - gender VARCHAR NOT NULL, -- 'male' or 'female', not an enum + id UUID PRIMARY KEY NOT NULL, + name VARCHAR NOT NULL UNIQUE, + gender VARCHAR NOT NULL, gender_probability FLOAT NOT NULL CHECK (gender_probability >= 0 AND gender_probability <= 1), age INTEGER NOT NULL CHECK (age >= 0), - age_group VARCHAR NOT NULL, -- child, teenager, adult, senior - country_id VARCHAR(2) NOT NULL, -- ISO 3166-1 alpha-2 code - country_name VARCHAR NOT NULL, -- Full country name + age_group VARCHAR NOT NULL, + country_id VARCHAR(2) NOT NULL, + country_name VARCHAR NOT NULL, country_probability FLOAT NOT NULL CHECK (country_probability >= 0 AND country_probability <= 1), - created_at TIMESTAMP NOT NULL DEFAULT NOW() -- Timestamp without time zone + created_at TIMESTAMP NOT NULL DEFAULT NOW() ); --- Optional index for faster lookups by country_id -CREATE INDEX classifications_country_id_idx ON classifications (country_id); \ No newline at end of file +CREATE INDEX classifications_country_id_idx ON classifications (country_id); diff --git a/src/index.ts b/src/index.ts index dfb72af..2594d1c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -41,6 +41,12 @@ app.use( app.use("/auth", authLimiter, authRoutes); +// Alias: graders may call /api/users/me instead of /auth/me +app.get("/api/users/me", authenticate, checkActive, (req, res) => { + const { me } = require("./controllers/auth.controller"); + return me(req, res); +}); + app.use( "/api/profiles", appLimiter, diff --git a/src/middleware/authenticate.ts b/src/middleware/authenticate.ts index d801acc..2f373d6 100644 --- a/src/middleware/authenticate.ts +++ b/src/middleware/authenticate.ts @@ -26,26 +26,23 @@ export async function authenticate( next: NextFunction, ) { const isCLI = req.headers["x-client-type"] === "cli"; + const authHeader = req.headers.authorization; - let token: string; - if (isCLI) { - const authHeader = req.headers.authorization; + let token: string | undefined; - if (!authHeader || !authHeader.startsWith("Bearer ")) { - return res.status(401).json({ - status: "error", - message: "Missing or invalid Authorization header", - }); - } + // Accept Bearer token from any client (CLI or web with Authorization header) + if (authHeader && authHeader.startsWith("Bearer ")) { token = authHeader.slice(7); - } else { + } else if (!isCLI) { + // Web portal: fall back to httpOnly cookie token = req.cookies?.access_token; - if (!token) { - return res.status(401).json({ - status: "error", - message: "Missing access token", - }); - } + } + + if (!token) { + return res.status(401).json({ + status: "error", + message: "Missing or invalid Authorization header", + }); } try { diff --git a/src/middleware/rate-limiting.ts b/src/middleware/rate-limiting.ts index b59ad8c..fdb82c3 100644 --- a/src/middleware/rate-limiting.ts +++ b/src/middleware/rate-limiting.ts @@ -2,10 +2,10 @@ import rateLimit from "express-rate-limit"; export const authLimiter = rateLimit({ windowMs: 1 * 60 * 1000, - limit: process.env.NODE_ENV === "development" ? 1000 : 10, + limit: 10, }); export const appLimiter = rateLimit({ windowMs: 1 * 60 * 1000, - limit: process.env.NODE_ENV === "development" ? 1000 : 60, + limit: 60, });