3 Commits

Author SHA1 Message Date
Felipe Coutinho
43697b4fd2 fix(csp): mover CSP para proxy.ts para leitura em runtime
Content-Security-Policy estava em next.config.ts (build time),
então S3_ENDPOINT nunca era incluído no connect-src ao buildar
via Docker no CI. Movido para proxy.ts que avalia em runtime.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 13:49:23 +00:00
Felipe Coutinho
27e3ba5f0d Update version badge from 2.1.2 to 2.3.4 2026-04-05 20:33:55 -03:00
Felipe Coutinho
31485eec8f fix(csp): permitir upload de anexos para o storage externo
connect-src bloqueava fetch para o Supabase Storage desde o commit
de segurança (10afef9). Adiciona a origin do S3_ENDPOINT na política.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 13:47:23 +00:00
5 changed files with 49 additions and 17 deletions

View File

@@ -7,6 +7,18 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
## [Unreleased] ## [Unreleased]
## [2.3.5] - 2026-04-07
### Corrigido
- CSP: movido `Content-Security-Policy` do `next.config.ts` (build time) para `proxy.ts` (runtime), corrigindo bloqueio de upload de anexos quando `S3_ENDPOINT` não estava disponível durante o build do Docker
## [2.3.4] - 2026-04-05
### Corrigido
- Anexos: corrigido upload que falhava com `NetworkError` — CSP `connect-src` bloqueava fetch para o Storage
## [2.3.3] - 2026-04-05 ## [2.3.3] - 2026-04-05
### Corrigido ### Corrigido

View File

@@ -8,7 +8,7 @@
> **⚠️ Não há versão online hospedada.** Você precisa clonar o repositório e rodar localmente ou no seu próprio servidor. > **⚠️ Não há versão online hospedada.** Você precisa clonar o repositório e rodar localmente ou no seu próprio servidor.
[![Version](https://img.shields.io/badge/version-2.1.2-blue?style=flat-square)](CHANGELOG.md) [![Version](https://img.shields.io/badge/version-2.3.4-blue?style=flat-square)](CHANGELOG.md)
[![Next.js](https://img.shields.io/badge/Next.js-black?style=flat-square&logo=next.js)](https://nextjs.org/) [![Next.js](https://img.shields.io/badge/Next.js-black?style=flat-square&logo=next.js)](https://nextjs.org/)
[![TypeScript](https://img.shields.io/badge/TypeScript-blue?style=flat-square&logo=typescript)](https://www.typescriptlang.org/) [![TypeScript](https://img.shields.io/badge/TypeScript-blue?style=flat-square&logo=typescript)](https://www.typescriptlang.org/)
[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-blue?style=flat-square&logo=postgresql)](https://www.postgresql.org/) [![PostgreSQL](https://img.shields.io/badge/PostgreSQL-blue?style=flat-square&logo=postgresql)](https://www.postgresql.org/)

View File

@@ -4,8 +4,6 @@ import type { NextConfig } from "next";
// Carregar variáveis de ambiente explicitamente // Carregar variáveis de ambiente explicitamente
dotenv.config(); dotenv.config();
const isDev = process.env.NODE_ENV === "development";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
output: "standalone", output: "standalone",
cacheComponents: true, cacheComponents: true,
@@ -44,18 +42,6 @@ const nextConfig: NextConfig = {
key: "X-Frame-Options", key: "X-Frame-Options",
value: "DENY", value: "DENY",
}, },
{
key: "Content-Security-Policy",
value: [
"default-src 'self'",
`script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ""} https://umami.felipecoutinho.com`,
"style-src 'self' 'unsafe-inline'",
"img-src 'self' https://lh3.googleusercontent.com data: blob:",
"font-src 'self'",
"connect-src 'self' https://umami.felipecoutinho.com",
"frame-ancestors 'none'",
].join("; "),
},
{ {
key: "Referrer-Policy", key: "Referrer-Policy",
value: "strict-origin-when-cross-origin", value: "strict-origin-when-cross-origin",

View File

@@ -1,6 +1,6 @@
{ {
"name": "openmonetis", "name": "openmonetis",
"version": "2.3.3", "version": "2.3.5",
"private": true, "private": true,
"packageManager": "pnpm@10.33.0", "packageManager": "pnpm@10.33.0",
"scripts": { "scripts": {

View File

@@ -21,6 +21,38 @@ const PROTECTED_ROUTES = [
// Rotas públicas (não requerem autenticação) // Rotas públicas (não requerem autenticação)
const PUBLIC_AUTH_ROUTES = ["/login", "/signup"]; const PUBLIC_AUTH_ROUTES = ["/login", "/signup"];
function buildCsp(): string {
const isDev = process.env.NODE_ENV === "development";
const s3Origin = (() => {
try {
return process.env.S3_ENDPOINT
? new URL(process.env.S3_ENDPOINT).origin
: "";
} catch {
return "";
}
})();
const connectExtras = ["https://umami.felipecoutinho.com", s3Origin]
.filter(Boolean)
.join(" ");
const imgExtras = ["https://lh3.googleusercontent.com", s3Origin]
.filter(Boolean)
.join(" ");
return [
"default-src 'self'",
`script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ""} https://umami.felipecoutinho.com`,
"style-src 'self' 'unsafe-inline'",
`img-src 'self' ${imgExtras} data: blob:`,
"font-src 'self'",
`connect-src 'self' ${connectExtras}`,
"frame-ancestors 'none'",
].join("; ");
}
export default async function proxy(request: NextRequest) { export default async function proxy(request: NextRequest) {
const { pathname } = request.nextUrl; const { pathname } = request.nextUrl;
@@ -63,7 +95,9 @@ export default async function proxy(request: NextRequest) {
return NextResponse.redirect(new URL("/login", request.url)); return NextResponse.redirect(new URL("/login", request.url));
} }
return NextResponse.next(); const response = NextResponse.next();
response.headers.set("Content-Security-Policy", buildCsp());
return response;
} }
export const config = { export const config = {