Files
gitea-mirror/src/lib/api.ts
ARUNAVO RAY 299659eca2 fix: resolve CVEs, upgrade to Astro v6, and harden API security (#227)
* fix: resolve CVEs, upgrade to Astro v6, and harden API security

Docker image CVE fixes:
- Install git-lfs v3.7.1 from GitHub releases (Go 1.25) instead of
  Debian apt (Go 1.23.12), fixing CVE-2025-68121 and 8 other Go stdlib CVEs
- Strip build-only packages (esbuild, vite, rollup, svgo, tailwindcss)
  from production image, eliminating 9 esbuild Go stdlib CVEs

Dependency upgrades:
- Astro v5 → v6 (includes Vite 7, Zod 4)
- Remove legacy content config (src/content/config.ts)
- Update HealthResponse type for simplified health endpoint
- npm overrides for fast-xml-parser ≥5.3.6, devalue ≥5.6.2,
  node-forge ≥1.3.2, svgo ≥4.0.1, rollup ≥4.59.0

API security hardening:
- /api/auth/debug: dev-only, require auth, remove user-creation POST,
  strip trustedOrigins/databaseConfig from response
- /api/auth/check-users: return boolean hasUsers instead of exact count
- /api/cleanup/auto: require authentication, remove per-user details
- /api/health: remove OS version, memory, uptime from response
- /api/config: validate Gitea URL protocol (http/https only)
- BETTER_AUTH_SECRET: log security warning when using insecure defaults
- generateRandomString: replace Math.random() with crypto.getRandomValues()
- hashValue: add random salt and timing-safe verification

* repositories: migrate table to tanstack

* Revert "repositories: migrate table to tanstack"

This reverts commit a544b29e6d.

* fixed lock file
2026-03-15 09:19:24 +05:30

154 lines
3.8 KiB
TypeScript

// Base API URL
const API_BASE = "/api";
// Helper function for API requests
async function apiRequest<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const url = `${API_BASE}${endpoint}`;
const headers = {
"Content-Type": "application/json",
...options.headers,
};
const response = await fetch(url, {
...options,
headers,
});
if (!response.ok) {
const error = await response.json().catch(() => ({
message: "An unknown error occurred",
}));
throw new Error(error.message || "An unknown error occurred");
}
return response.json();
}
// Auth API
export const authApi = {
login: async (username: string, password: string) => {
const res = await fetch(`${API_BASE}/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include", // Send cookies
body: JSON.stringify({ username, password }),
});
if (!res.ok) throw new Error("Login failed");
return await res.json(); // returns user
},
register: async (username: string, email: string, password: string) => {
const res = await fetch(`${API_BASE}/auth/register`, {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ username, email, password }),
});
if (!res.ok) throw new Error("Registration failed");
return await res.json(); // returns user
},
getCurrentUser: async () => {
const res = await fetch(`${API_BASE}/auth`, {
method: "GET",
credentials: "include", // Send cookies
});
if (!res.ok) throw new Error("Not authenticated");
return await res.json();
},
logout: async () => {
await fetch(`${API_BASE}/auth/logout`, {
method: "POST",
credentials: "include",
});
},
};
// GitHub API
export const githubApi = {
testConnection: (token: string) =>
apiRequest<{ success: boolean }>("/github/test-connection", {
method: "POST",
body: JSON.stringify({ token }),
}),
};
// Gitea API
export const giteaApi = {
testConnection: (url: string, token: string) =>
apiRequest<{ success: boolean }>("/gitea/test-connection", {
method: "POST",
body: JSON.stringify({ url, token }),
}),
};
// Health API
export interface HealthResponse {
status: "ok" | "error" | "degraded";
timestamp: string;
version: string;
latestVersion: string;
updateAvailable: boolean;
database: {
connected: boolean;
};
recovery?: {
status: string;
jobsNeedingRecovery: number;
};
error?: string;
}
export const healthApi = {
check: async (): Promise<HealthResponse> => {
try {
const response = await fetch(`${API_BASE}/health`);
if (!response.ok) {
const errorData = await response.json().catch(() => ({
status: "error",
error: "Failed to parse error response",
}));
return {
...errorData,
status: "error",
timestamp: new Date().toISOString(),
} as HealthResponse;
}
return await response.json();
} catch (error) {
return {
status: "error",
timestamp: new Date().toISOString(),
error: error instanceof Error ? error.message : "Unknown error checking health",
version: "unknown",
latestVersion: "unknown",
updateAvailable: false,
database: { connected: false, message: "Failed to connect to API" },
system: {
uptime: { startTime: "", uptimeMs: 0, formatted: "N/A" },
memory: {
rss: "N/A",
heapTotal: "N/A",
heapUsed: "N/A",
external: "N/A",
systemTotal: "N/A",
systemFree: "N/A",
},
os: { platform: "", version: "", arch: "" },
env: "",
},
};
}
},
};