chore(deps): upgrade all dependencies to latest and pin exact
Build & Push Docker Image / build (push) Failing after 1m18s

Policy: every direct dependency is now an exact pin; lockfile + --frozen-lockfile keep builds reproducible; save-exact=true enforced.

Toolchain:
- pnpm 10.26 -> 11.18 (lockfile migrated; supportedArchitectures moved to pnpm-workspace.yaml)
- typescript 5.9 -> 7.0, vite 7 -> 8, @vitejs/plugin-react 5 -> 6
- esbuild override 0.27.3 -> 0.28.1 (closes GHSA-g7r4-m6w7-qqqr); added @esbuild/darwin-arm64 for local dev
- orval 8.9 -> 8.23, regenerated clients

Backend (prod image):
- openid-client 5.7 -> 6.8 (rewritten OIDC flow in routes/auth.ts: discovery + functional API, PKCE/state, fetchUserInfo, buildEndSessionUrl)
- pino 9 -> 10, pino-http 10 -> 11, thread-stream 3 -> 4
- zod 3.25 -> 4.4 (catalog; supported by drizzle-zod 0.8.3)
- pg 8.20 -> 8.22; removed deprecated @types/bcryptjs

Frontend:
- react/react-dom 19.1.0 -> 19.2.8 (catalog pin lifted; expo note removed)
- react-day-picker 9 -> 10 (table classname -> month_grid)
- recharts 2 -> 3 (TooltipContentProps + DefaultLegendContentProps typing; safe keys)
- react-resizable-panels 2 -> 4 (Group/Separator rename)
- date-fns 3 -> 4, @hookform/resolvers 3 -> 5, lucide-react 0.545 -> 1.28
- all @radix-ui/*, tailwind, types, and remaining patch/minor deps bumped to latest

Security/process:
- overrides for body-parser >=2.3.0 (GHSA-v422-hmwv-36x6) + markdown-it/linkify-it/brace-expansion/fast-uri (dev tooling)
- pnpm audit now reports 0 vulnerabilities (prod and full)
- CI audit gate added to build.yaml; docs/dependency-policy.md; renovate.json
- Dockerfile pins node:24.18.1-alpine and pnpm@11.18.0
This commit is contained in:
opencode
2026-08-03 12:56:00 +02:00
parent bcae59626f
commit 2f66fff993
75 changed files with 3102 additions and 2357 deletions
+19 -20
View File
@@ -12,30 +12,29 @@
"dependencies": {
"@workspace/api-zod": "workspace:*",
"@workspace/db": "workspace:*",
"bcryptjs": "^3.0.3",
"connect-pg-simple": "^10.0.0",
"cookie-parser": "^1.4.7",
"cors": "^2.8.6",
"bcryptjs": "3.0.3",
"connect-pg-simple": "10.0.0",
"cookie-parser": "1.4.7",
"cors": "2.8.6",
"drizzle-orm": "catalog:",
"express": "^5.2.1",
"express-rate-limit": "^8.6.1",
"express-session": "^1.19.0",
"openid-client": "^5.7.1",
"pino": "^9.14.0",
"pino-http": "^10.5.0",
"express": "5.2.1",
"express-rate-limit": "8.6.1",
"express-session": "1.19.0",
"openid-client": "6.8.4",
"pino": "10.3.1",
"pino-http": "11.0.0",
"zod": "catalog:"
},
"devDependencies": {
"@types/bcryptjs": "^3.0.0",
"@types/connect-pg-simple": "^7.0.3",
"@types/cookie-parser": "^1.4.10",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/express-session": "^1.19.0",
"@types/connect-pg-simple": "7.0.3",
"@types/cookie-parser": "1.4.10",
"@types/cors": "2.8.19",
"@types/express": "5.0.6",
"@types/express-session": "1.19.0",
"@types/node": "catalog:",
"esbuild": "0.27.3",
"esbuild-plugin-pino": "^2.3.3",
"pino-pretty": "^13.1.3",
"thread-stream": "3.1.0"
"esbuild": "0.28.1",
"esbuild-plugin-pino": "2.3.3",
"pino-pretty": "13.1.3",
"thread-stream": "4.2.0"
}
}
+33 -27
View File
@@ -1,5 +1,16 @@
import { Router, type IRouter, type Request } from "express";
import { Issuer, generators, type Client } from "openid-client";
import {
discovery,
randomPKCECodeVerifier,
calculatePKCECodeChallenge,
randomState,
buildAuthorizationUrl,
authorizationCodeGrant,
fetchUserInfo,
buildEndSessionUrl,
skipSubjectCheck,
type Configuration,
} from "openid-client";
import bcrypt from "bcryptjs";
import { eq, and, inArray, isNull } from "drizzle-orm";
import { z } from "zod";
@@ -12,7 +23,7 @@ import { getCsrfToken } from "../middleware/csrf";
const router: IRouter = Router();
let cachedClient: Client | null = null;
let cachedConfig: Configuration | null = null;
function isOidcConfigured(): boolean {
return !!(
@@ -39,8 +50,8 @@ function isSafeReturnTo(value: string): boolean {
}
}
async function getClient(): Promise<Client | null> {
if (cachedClient) return cachedClient;
async function getClient(): Promise<Configuration | null> {
if (cachedConfig) return cachedConfig;
const keycloakUrl = process.env.KEYCLOAK_URL;
const realm = process.env.KEYCLOAK_REALM;
@@ -52,14 +63,9 @@ async function getClient(): Promise<Client | null> {
}
try {
const issuerUrl = `${keycloakUrl}/realms/${realm}`;
const issuer = await Issuer.discover(issuerUrl);
cachedClient = new issuer.Client({
client_id: clientId,
client_secret: clientSecret,
response_types: ["code"],
});
return cachedClient;
const issuerUrl = new URL(`${keycloakUrl}/realms/${realm}`);
cachedConfig = await discovery(issuerUrl, clientId, clientSecret);
return cachedConfig;
} catch (err) {
logger.error({ err }, "Failed to discover Keycloak issuer");
return null;
@@ -176,9 +182,9 @@ router.get("/auth/login", async (req, res): Promise<void> => {
return;
}
const codeVerifier = generators.codeVerifier();
const codeChallenge = generators.codeChallenge(codeVerifier);
const state = generators.state();
const codeVerifier = randomPKCECodeVerifier();
const codeChallenge = await calculatePKCECodeChallenge(codeVerifier);
const state = randomState();
req.session.codeVerifier = codeVerifier;
req.session.oidcState = state;
@@ -187,7 +193,7 @@ router.get("/auth/login", async (req, res): Promise<void> => {
}
const redirectUri = `${getBaseUrl(req)}/api/auth/callback`;
const url = client.authorizationUrl({
const url = buildAuthorizationUrl(client, {
scope: "openid email profile",
code_challenge: codeChallenge,
code_challenge_method: "S256",
@@ -195,7 +201,7 @@ router.get("/auth/login", async (req, res): Promise<void> => {
state,
});
res.redirect(url);
res.redirect(url.href);
});
router.get("/auth/callback", async (req, res): Promise<void> => {
@@ -221,13 +227,13 @@ router.get("/auth/callback", async (req, res): Promise<void> => {
const redirectUri = `${getBaseUrl(req)}/api/auth/callback`;
try {
const params = client.callbackParams(req);
const tokenSet = await client.callback(redirectUri, params, {
code_verifier: codeVerifier,
state,
});
const tokens = await authorizationCodeGrant(
client,
new URL(req.originalUrl ?? "/", getBaseUrl(req)),
{ pkceCodeVerifier: codeVerifier, expectedState: state },
);
const userinfo = await client.userinfo(tokenSet.access_token!);
const userinfo = await fetchUserInfo(client, tokens.access_token, skipSubjectCheck);
const dbUser = await upsertUserFromOidc(userinfo);
await new Promise<void>((resolve, reject) => {
@@ -259,9 +265,9 @@ router.get("/auth/logout", async (req, res): Promise<void> => {
req.session.destroy(() => {});
const client = await getClient();
if (client && client.issuer.metadata.end_session_endpoint) {
const logoutUrl = client.endSessionUrl({ post_logout_redirect_uri: getBaseUrl(req) });
res.redirect(logoutUrl);
if (client && client.serverMetadata().end_session_endpoint) {
const logoutUrl = buildEndSessionUrl(client, { post_logout_redirect_uri: getBaseUrl(req) });
res.redirect(logoutUrl.href);
return;
}
@@ -292,7 +298,7 @@ router.get("/auth/password-redirect", async (req, res): Promise<void> => {
res.json({ url: null });
return;
}
const realm = client.issuer.metadata.issuer ?? "";
const realm = client.serverMetadata().issuer ?? "";
res.json({ url: `${realm}/account/password` });
});