mirror of
https://github.com/LogicLabs-OU/OpenArchiver.git
synced 2026-04-06 00:31:57 +02:00
feat(docker): Fix CORS errors
This commit fixes CORS errors when running the app in Docker by introducing the `APP_URL` environment variable. A CORS policy is set up for the backend to only allow origin from the `APP_URL`. Key changes include: - New `APP_URL` and `ORIGIN` environment variables have been added to properly configure CORS and the SvelteKit adapter, making the application's public URL easily configurable. - Dockerfiles are updated to copy the entrypoint script, Drizzle config, and migration files into the final image. - Documentation and example files (`.env.example`, `docker-compose.yml`) have been updated to reflect these changes.
This commit is contained in:
@@ -4,6 +4,11 @@
|
||||
NODE_ENV=development
|
||||
PORT_BACKEND=4000
|
||||
PORT_FRONTEND=3000
|
||||
# The public-facing URL of your application. This is used by the backend to configure CORS.
|
||||
APP_URL=http://localhost:3000
|
||||
# This is used by the SvelteKit Node adapter to determine the server's public-facing URL.
|
||||
# It should always be set to the value of APP_URL.
|
||||
ORIGIN=$APP_URL
|
||||
# The frequency of continuous email syncing. Default is every minutes, but you can change it to another value based on your needs.
|
||||
SYNC_FREQUENCY='* * * * *'
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ COPY packages/backend/package.json ./packages/backend/
|
||||
COPY packages/frontend/package.json ./packages/frontend/
|
||||
COPY packages/types/package.json ./packages/types/
|
||||
COPY packages/enterprise/package.json ./packages/enterprise/
|
||||
COPY packages/frontend-enterprise/package.json ./packages/frontend-enterprise/
|
||||
COPY apps/open-archiver-enterprise/package.json ./apps/open-archiver-enterprise/
|
||||
|
||||
# 1. Build Stage: Install all dependencies and build the project
|
||||
@@ -26,7 +25,7 @@ COPY packages/frontend/svelte.config.js ./packages/frontend/
|
||||
# Install all dependencies.
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
|
||||
pnpm install --frozen-lockfile --prod=false
|
||||
pnpm install --shamefully-hoist --frozen-lockfile --prod=false
|
||||
|
||||
# Copy the rest of the source code
|
||||
COPY . .
|
||||
@@ -39,10 +38,11 @@ FROM base AS production
|
||||
|
||||
# Copy built application from build stage
|
||||
COPY --from=build /app/packages/backend/dist ./packages/backend/dist
|
||||
COPY --from=build /app/packages/backend/drizzle.config.ts ./packages/backend/drizzle.config.ts
|
||||
COPY --from=build /app/packages/backend/src/database/migrations ./packages/backend/src/database/migrations
|
||||
COPY --from=build /app/packages/frontend/build ./packages/frontend/build
|
||||
COPY --from=build /app/packages/types/dist ./packages/types/dist
|
||||
COPY --from=build /app/packages/enterprise/dist ./packages/enterprise/dist
|
||||
COPY --from=build /app/packages/frontend-enterprise/dist ./packages/frontend-enterprise/dist
|
||||
COPY --from=build /app/apps/open-archiver-enterprise/dist ./apps/open-archiver-enterprise/dist
|
||||
|
||||
# Copy the entrypoint script and make it executable
|
||||
@@ -56,4 +56,4 @@ EXPOSE 3000
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
|
||||
# Start the application
|
||||
CMD ["pnpm", "start:enterprise"]
|
||||
CMD ["pnpm", "docker-start:enterprise"]
|
||||
|
||||
@@ -24,7 +24,7 @@ COPY packages/frontend/svelte.config.js ./packages/frontend/
|
||||
# Install all dependencies.
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
|
||||
pnpm install --frozen-lockfile --prod=false
|
||||
pnpm install --shamefully-hoist --frozen-lockfile --prod=false
|
||||
|
||||
# Copy the rest of the source code
|
||||
COPY . .
|
||||
@@ -37,6 +37,8 @@ FROM base AS production
|
||||
|
||||
# Copy built application from build stage
|
||||
COPY --from=build /app/packages/backend/dist ./packages/backend/dist
|
||||
COPY --from=build /app/packages/backend/drizzle.config.ts ./packages/backend/drizzle.config.ts
|
||||
COPY --from=build /app/packages/backend/src/database/migrations ./packages/backend/src/database/migrations
|
||||
COPY --from=build /app/packages/frontend/build ./packages/frontend/build
|
||||
COPY --from=build /app/packages/types/dist ./packages/types/dist
|
||||
COPY --from=build /app/apps/open-archiver/dist ./apps/open-archiver/dist
|
||||
@@ -52,4 +54,4 @@ EXPOSE 3000
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
|
||||
# Start the application
|
||||
CMD ["pnpm", "start:oss"]
|
||||
CMD ["pnpm", "docker-start:oss"]
|
||||
|
||||
@@ -90,12 +90,14 @@ Here is a complete list of environment variables available for configuration:
|
||||
|
||||
#### Application Settings
|
||||
|
||||
| Variable | Description | Default Value |
|
||||
| ---------------- | ----------------------------------------------------------------------------------------------------- | ------------- |
|
||||
| `NODE_ENV` | The application environment. | `development` |
|
||||
| `PORT_BACKEND` | The port for the backend service. | `4000` |
|
||||
| `PORT_FRONTEND` | The port for the frontend service. | `3000` |
|
||||
| `SYNC_FREQUENCY` | The frequency of continuous email syncing. See [cron syntax](https://crontab.guru/) for more details. | `* * * * *` |
|
||||
| Variable | Description | Default Value |
|
||||
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- |
|
||||
| `NODE_ENV` | The application environment. | `development` |
|
||||
| `PORT_BACKEND` | The port for the backend service. | `4000` |
|
||||
| `PORT_FRONTEND` | The port for the frontend service. | `3000` |
|
||||
| `APP_URL` | The public-facing URL of your application. This is used by the backend to configure CORS. | `http://localhost:3000` |
|
||||
| `ORIGIN` | Used by the SvelteKit Node adapter to determine the server's public-facing URL. It should always be set to the value of `APP_URL` (e.g., `ORIGIN=$APP_URL`). | `http://localhost:3000` |
|
||||
| `SYNC_FREQUENCY` | The frequency of continuous email syncing. See [cron syntax](https://crontab.guru/) for more details. | `* * * * *` |
|
||||
|
||||
#### Docker Compose Service Configuration
|
||||
|
||||
@@ -335,31 +337,3 @@ docker-compose up -d --force-recreate
|
||||
```
|
||||
|
||||
After this, any new data will be saved directly into the `./data/open-archiver` folder in your project directory.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### 403 Cross-Site POST Forbidden Error
|
||||
|
||||
If you are running the application behind a reverse proxy or have mapped the application to a different port (e.g., `3005:3000`), you may encounter a `403 Cross-site POST from submissions are forbidden` error when uploading files.
|
||||
|
||||
To resolve this, you must set the `ORIGIN` environment variable to the URL of your application. This ensures that the backend can verify the origin of requests and prevent cross-site request forgery (CSRF) attacks.
|
||||
|
||||
Add the following line to your `.env` file, replacing `<your_host>` and `<your_port>` with your specific values:
|
||||
|
||||
```bash
|
||||
ORIGIN=http://<your_host>:<your_port>
|
||||
```
|
||||
|
||||
For example, if your application is accessible at `http://localhost:3005`, you would set the variable as follows:
|
||||
|
||||
```bash
|
||||
ORIGIN=http://localhost:3005
|
||||
```
|
||||
|
||||
After adding the `ORIGIN` variable, restart your Docker containers for the changes to take effect:
|
||||
|
||||
```bash
|
||||
docker-compose up -d --force-recreate
|
||||
```
|
||||
|
||||
This will ensure that your file uploads are correctly authorized.
|
||||
|
||||
75
docs/user-guides/troubleshooting/cors-errors.md
Normal file
75
docs/user-guides/troubleshooting/cors-errors.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Troubleshooting CORS Errors
|
||||
|
||||
Cross-Origin Resource Sharing (CORS) is a security feature that controls how web applications in one domain can request and interact with resources in another. If not configured correctly, you may encounter errors when performing actions like uploading files.
|
||||
|
||||
This guide will help you diagnose and resolve common CORS-related issues.
|
||||
|
||||
## Symptoms
|
||||
|
||||
You may be experiencing a CORS issue if you see one of the following errors in your browser's developer console or in the application's logs:
|
||||
|
||||
- `TypeError: fetch failed`
|
||||
- `Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource.`
|
||||
- `Unexpected token 'C', "Cross-site"... is not valid JSON`
|
||||
- A JSON error response similar to the following:
|
||||
```json
|
||||
{
|
||||
"message": "CORS Error: This origin is not allowed.",
|
||||
"requiredOrigin": "http://localhost:3000",
|
||||
"receivedOrigin": "https://localhost:3000"
|
||||
}
|
||||
```
|
||||
|
||||
## Root Cause
|
||||
|
||||
These errors typically occur when the URL you are using to access the application in your browser does not exactly match the `APP_URL` configured in your `.env` file.
|
||||
|
||||
This can happen for several reasons:
|
||||
|
||||
- You are accessing the application via a different port.
|
||||
- You are using a reverse proxy that changes the protocol (e.g., from `http` to `https`).
|
||||
- The SvelteKit server, in a production build, is incorrectly guessing its public-facing URL.
|
||||
|
||||
## Solution
|
||||
|
||||
The solution is to ensure that the application's frontend and backend are correctly configured with the public-facing URL of your instance. This is done by setting two environment variables: `APP_URL` and `ORIGIN`.
|
||||
|
||||
1. **Open your `.env` file** in a text editor.
|
||||
|
||||
2. **Set `APP_URL`**: Define the `APP_URL` variable with the exact URL you use to access the application in your browser.
|
||||
|
||||
```env
|
||||
APP_URL=http://your-domain-or-ip:3000
|
||||
```
|
||||
|
||||
3. **Set `ORIGIN`**: The SvelteKit server requires a specific `ORIGIN` variable to correctly identify itself. This should always be set to the value of your `APP_URL`.
|
||||
|
||||
```env
|
||||
ORIGIN=$APP_URL
|
||||
```
|
||||
|
||||
By using `$APP_URL`, you ensure that both variables are always in sync.
|
||||
|
||||
### Example Configuration
|
||||
|
||||
If you are running the application locally on port `3000`, your configuration should look like this:
|
||||
|
||||
```env
|
||||
APP_URL=http://localhost:3000
|
||||
ORIGIN=$APP_URL
|
||||
```
|
||||
|
||||
If your application is behind a reverse proxy and is accessible at `https://archive.mycompany.com`, your configuration should be:
|
||||
|
||||
```env
|
||||
APP_URL=https://archive.mycompany.com
|
||||
ORIGIN=$APP_URL
|
||||
```
|
||||
|
||||
After making these changes to your `.env` file, you must restart the application for them to take effect:
|
||||
|
||||
```bash
|
||||
docker compose up -d --force-recreate
|
||||
```
|
||||
|
||||
This will ensure that the backend's CORS policy and the frontend server's origin are correctly aligned, resolving the errors.
|
||||
@@ -17,7 +17,8 @@
|
||||
"db:generate": "dotenv -- pnpm --filter @open-archiver/backend db:generate",
|
||||
"db:migrate": "dotenv -- pnpm --filter @open-archiver/backend db:migrate",
|
||||
"db:migrate:dev": "dotenv -- pnpm --filter @open-archiver/backend db:migrate:dev",
|
||||
"docker-start": "concurrently \"pnpm start:workers\" \"pnpm start\"",
|
||||
"docker-start:oss": "concurrently \"pnpm start:workers\" \"pnpm start:oss\"",
|
||||
"docker-start:enterprise": "concurrently \"pnpm start:workers\" \"pnpm start:enterprise\"",
|
||||
"docs:dev": "vitepress dev docs --port 3009",
|
||||
"docs:build": "vitepress build docs",
|
||||
"docs:preview": "vitepress preview docs",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"bcryptjs": "^3.0.2",
|
||||
"bullmq": "^5.56.3",
|
||||
"busboy": "^1.6.0",
|
||||
"cors": "^2.8.5",
|
||||
"cross-fetch": "^4.1.0",
|
||||
"deepmerge-ts": "^7.1.5",
|
||||
"dotenv": "^17.2.0",
|
||||
@@ -68,6 +69,7 @@
|
||||
"@bull-board/express": "^6.11.0",
|
||||
"@types/archiver": "^6.0.3",
|
||||
"@types/busboy": "^1.5.4",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/mailparser": "^3.4.6",
|
||||
"@types/microsoft-graph": "^2.40.1",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import express, { Express } from 'express';
|
||||
import cors from 'cors';
|
||||
import dotenv from 'dotenv';
|
||||
import { AuthController } from './controllers/auth.controller';
|
||||
import { IngestionController } from './controllers/ingestion.controller';
|
||||
@@ -94,6 +95,14 @@ export async function createServer(modules: ArchiverModule[] = []): Promise<Expr
|
||||
|
||||
const app = express();
|
||||
|
||||
// --- CORS ---
|
||||
app.use(
|
||||
cors({
|
||||
origin: process.env.APP_URL || 'http://localhost:3000',
|
||||
credentials: true,
|
||||
})
|
||||
);
|
||||
|
||||
// Trust the proxy to get the real IP address of the client.
|
||||
// This is important for audit logging and security.
|
||||
app.set('trust proxy', true);
|
||||
@@ -111,8 +120,6 @@ export async function createServer(modules: ArchiverModule[] = []): Promise<Expr
|
||||
const settingsRouter = createSettingsRouter(authService);
|
||||
const apiKeyRouter = apiKeyRoutes(authService);
|
||||
const integrityRouter = integrityRoutes(authService);
|
||||
// upload route is added before middleware because it doesn't use the json middleware.
|
||||
app.use(`/${config.api.version}/upload`, uploadRouter);
|
||||
|
||||
// Middleware for all other routes
|
||||
app.use((req, res, next) => {
|
||||
@@ -133,6 +140,7 @@ export async function createServer(modules: ArchiverModule[] = []): Promise<Expr
|
||||
|
||||
app.use(`/${config.api.version}/auth`, authRouter);
|
||||
app.use(`/${config.api.version}/iam`, iamRouter);
|
||||
app.use(`/${config.api.version}/upload`, uploadRouter);
|
||||
app.use(`/${config.api.version}/ingestion-sources`, ingestionRouter);
|
||||
app.use(`/${config.api.version}/archived-emails`, archivedEmailRouter);
|
||||
app.use(`/${config.api.version}/storage`, storageRouter);
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok) {
|
||||
throw new Error(`File upload failed + ${result}`);
|
||||
throw new Error(result.message || 'File upload failed');
|
||||
}
|
||||
|
||||
formData.providerConfig.uploadedFilePath = result.filePath;
|
||||
@@ -107,10 +107,11 @@
|
||||
fileUploading = false;
|
||||
} catch (error) {
|
||||
fileUploading = false;
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
setAlert({
|
||||
type: 'error',
|
||||
title: $t('app.components.ingestion_source_form.upload_failed'),
|
||||
message: JSON.stringify(error),
|
||||
message,
|
||||
duration: 5000,
|
||||
show: true,
|
||||
});
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
import { env } from '$env/dynamic/private';
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { json } from '@sveltejs/kit';
|
||||
|
||||
const BACKEND_URL = `http://localhost:${env.PORT_BACKEND || 4000}`;
|
||||
|
||||
const handleRequest: RequestHandler = async ({ request, params }) => {
|
||||
const handleRequest: RequestHandler = async ({ request, params, fetch }) => {
|
||||
const url = new URL(request.url);
|
||||
const slug = params.slug || '';
|
||||
const targetUrl = `${BACKEND_URL}/${slug}${url.search}`;
|
||||
|
||||
// Create a new request with the same method, headers, and body
|
||||
const proxyRequest = new Request(targetUrl, {
|
||||
method: request.method,
|
||||
headers: request.headers,
|
||||
body: request.body,
|
||||
duplex: 'half', // Required for streaming request bodies
|
||||
} as RequestInit);
|
||||
try {
|
||||
const proxyRequest = new Request(targetUrl, {
|
||||
method: request.method,
|
||||
headers: request.headers,
|
||||
body: request.body,
|
||||
duplex: 'half',
|
||||
} as RequestInit);
|
||||
|
||||
// Forward the request to the backend
|
||||
const response = await fetch(proxyRequest);
|
||||
const response = await fetch(proxyRequest);
|
||||
|
||||
// Return the response from the backend
|
||||
return response;
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Proxy request failed:', error);
|
||||
return json({ message: `Failed to connect to the backend service. ${JSON.stringify(error)}` }, { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
export const GET = handleRequest;
|
||||
|
||||
22
pnpm-lock.yaml
generated
22
pnpm-lock.yaml
generated
@@ -107,6 +107,9 @@ importers:
|
||||
busboy:
|
||||
specifier: ^1.6.0
|
||||
version: 1.6.0
|
||||
cors:
|
||||
specifier: ^2.8.5
|
||||
version: 2.8.5
|
||||
cross-fetch:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0(encoding@0.1.13)
|
||||
@@ -210,6 +213,9 @@ importers:
|
||||
'@types/busboy':
|
||||
specifier: ^1.5.4
|
||||
version: 1.5.4
|
||||
'@types/cors':
|
||||
specifier: ^2.8.19
|
||||
version: 2.8.19
|
||||
'@types/express':
|
||||
specifier: ^5.0.3
|
||||
version: 5.0.3
|
||||
@@ -1821,6 +1827,9 @@ packages:
|
||||
'@types/cookie@0.6.0':
|
||||
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
||||
|
||||
'@types/cors@2.8.19':
|
||||
resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==}
|
||||
|
||||
'@types/d3-path@3.1.1':
|
||||
resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==}
|
||||
|
||||
@@ -2364,6 +2373,10 @@ packages:
|
||||
core-util-is@1.0.3:
|
||||
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
|
||||
|
||||
cors@2.8.5:
|
||||
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
crc-32@1.2.2:
|
||||
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
|
||||
engines: {node: '>=0.8'}
|
||||
@@ -6413,6 +6426,10 @@ snapshots:
|
||||
|
||||
'@types/cookie@0.6.0': {}
|
||||
|
||||
'@types/cors@2.8.19':
|
||||
dependencies:
|
||||
'@types/node': 24.0.13
|
||||
|
||||
'@types/d3-path@3.1.1': {}
|
||||
|
||||
'@types/d3-shape@3.1.7':
|
||||
@@ -7026,6 +7043,11 @@ snapshots:
|
||||
|
||||
core-util-is@1.0.3: {}
|
||||
|
||||
cors@2.8.5:
|
||||
dependencies:
|
||||
object-assign: 4.1.1
|
||||
vary: 1.1.2
|
||||
|
||||
crc-32@1.2.2: {}
|
||||
|
||||
crc32-stream@6.0.0:
|
||||
|
||||
Reference in New Issue
Block a user