chore(deps): upgrade all dependencies to latest and pin exact
Build & Push Docker Image / build (push) Failing after 1m18s
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:
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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` });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user