security: close audit findings #19–#23 and harden Docker setup (#24)
#19 MFA QR code: render locally via qrcode package, remove external qrserver.com request #20 Webhook SSRF: add ssrf-guard.ts with DNS-verified IP blocklist; enforce on create/update/test/dispatch #21 /api/perf: fail-closed when CRON_SECRET missing; remove query-string token auth #22 CSP: remove unsafe-eval and unsafe-inline from script-src in production builds #23 Active session registry: forward jti into session object; validate against ActiveSession on every tRPC request #24 Docker: add missing packages/application to Dockerfile.dev; fix pnpm-lock.yaml glob; run db:migrate:deploy on container start so a fresh checkout boots without manual steps Also: fix pre-existing TS error in e2e/allocations.spec.ts (args.length literal type overlap) Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import QRCode from "qrcode";
|
||||
import { trpc } from "~/lib/trpc/client.js";
|
||||
|
||||
type SetupStep = "idle" | "show-secret" | "verify" | "done";
|
||||
@@ -9,10 +10,20 @@ export function MfaSetup() {
|
||||
const [step, setStep] = useState<SetupStep>("idle");
|
||||
const [secret, setSecret] = useState("");
|
||||
const [uri, setUri] = useState("");
|
||||
const [qrDataUrl, setQrDataUrl] = useState("");
|
||||
const [token, setToken] = useState("");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [success, setSuccess] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!uri) return;
|
||||
let cancelled = false;
|
||||
QRCode.toDataURL(uri, { width: 200, margin: 2 }).then((dataUrl) => {
|
||||
if (!cancelled) setQrDataUrl(dataUrl);
|
||||
}).catch(() => {/* ignore — manual key is shown as fallback */});
|
||||
return () => { cancelled = true; };
|
||||
}, [uri]);
|
||||
|
||||
const { data: mfaStatus, refetch } = trpc.user.getMfaStatus.useQuery();
|
||||
const generateMutation = trpc.user.generateTotpSecret.useMutation();
|
||||
const verifyMutation = trpc.user.verifyAndEnableTotp.useMutation();
|
||||
@@ -110,17 +121,17 @@ export function MfaSetup() {
|
||||
Scan this QR code with your authenticator app (Google Authenticator, Authy, 1Password, etc.).
|
||||
</p>
|
||||
|
||||
{/* QR Code via public Google Charts API (otpauth URI) */}
|
||||
{/* QR Code — rendered locally, no external service */}
|
||||
<div className="flex justify-center">
|
||||
<div className="rounded-lg border border-gray-200 dark:border-gray-700 bg-white p-3">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={`https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(uri)}`}
|
||||
alt="TOTP QR Code"
|
||||
width={200}
|
||||
height={200}
|
||||
className="rounded"
|
||||
/>
|
||||
{qrDataUrl ? (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img src={qrDataUrl} alt="TOTP QR Code" width={200} height={200} className="rounded" />
|
||||
) : (
|
||||
<div className="h-[200px] w-[200px] flex items-center justify-center text-xs text-gray-400">
|
||||
Generating…
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user