feat: configure Swagger Bearer auth, fix CORS, Docker and deployment
- Program.cs: added Swagger with JWT Bearer security definition using OpenApi v2 API (OpenApiSecuritySchemeReference), enabled Swagger in all environments (not just Development), registered all new services (ContactService, SetupService) in DI - CORS: configurable via Cors__AllowedOrigins env var, allows all when empty (dev-friendly) - DoliMiddlewareApi.csproj: removed Microsoft.AspNetCore.OpenApi (conflicts with Swashbuckle v10), kept Swashbuckle.AspNetCore 10.1.0 - appsettings.json: fixed Dolibarr ApiUrl to /api/index.php (was /explorer) - compose.yaml: fixed Dolibarr ApiUrl, added JWT_SECRET default for dev, added healthcheck for Dolibarr container, BFF waits for Dolibarr healthy - .dockerignore: added to keep Docker build clean - .env.example: template with instructions for JWT_SECRET
This commit is contained in:
parent
fbbbce3574
commit
fe6bd57796
|
|
@ -1,25 +1,16 @@
|
|||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/.idea
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/azds.yaml
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
||||
bin/
|
||||
obj/
|
||||
.vs/
|
||||
.vscode/
|
||||
node_modules/
|
||||
*.user
|
||||
*.suo
|
||||
*.log
|
||||
.env
|
||||
.env.local
|
||||
.git/
|
||||
.gitignore
|
||||
docker-compose*.yml
|
||||
*.md
|
||||
presentacion/
|
||||
DoliMiddlewareApi.Tests/
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# ============================================
|
||||
# DoliMiddlewareApi - Environment Variables
|
||||
# ============================================
|
||||
# Copy this file to .env and fill in the values
|
||||
# NEVER commit .env with real secrets to git
|
||||
|
||||
# JWT Secret: must be at least 32 characters
|
||||
# Generate one with: openssl rand -base64 48
|
||||
JWT_SECRET=CHANGE-ME-TO-A-LONG-RANDOM-STRING-AT-LEAST-32-CHARS
|
||||
|
||||
# MySQL
|
||||
MYSQL_ROOT_PASSWORD=rootpassword
|
||||
MYSQL_PASSWORD=12345678
|
||||
|
||||
# Dolibarr Admin
|
||||
DOLI_ADMIN_PASSWORD=12345678
|
||||
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.15.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.15.0" />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Text;
|
||||
using System.Threading.RateLimiting;
|
||||
using DoliMiddlewareApi.Exceptions;
|
||||
using DoliMiddlewareApi.Services;
|
||||
using DoliMiddlewareApi.Services.Auth;
|
||||
|
|
@ -7,6 +8,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.OpenApi;
|
||||
|
||||
// =========================================
|
||||
// PROGRAM.CS - CONFIGURACIÓN Y DEPENDENCIAS
|
||||
|
|
@ -15,7 +17,8 @@ using Microsoft.IdentityModel.Tokens;
|
|||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
if (string.IsNullOrEmpty(builder.Configuration["Jwt:Secret"]))
|
||||
throw new InvalidOperationException("JWT Secret is required in configuration. Set 'Jwt:Secret' in appsettings.json or environment variables.");
|
||||
throw new InvalidOperationException(
|
||||
"JWT Secret is required. Set 'Jwt__Secret' environment variable, 'Jwt:Secret' in appsettings, or use 'dotnet user-secrets set Jwt:Secret <value>'.");
|
||||
|
||||
// =========================================
|
||||
// 1. CONFIGURACIÓN DE SERVICIOS ASP.NET CORE
|
||||
|
|
@ -47,23 +50,81 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
|||
|
||||
builder.Services.AddAuthorization();
|
||||
|
||||
// CORS Configuration
|
||||
// CORS: Allow all origins in dev, configurable in prod
|
||||
var corsOrigins = builder.Configuration["Cors:AllowedOrigins"];
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowVueApp",
|
||||
policy => policy
|
||||
.WithOrigins(
|
||||
"http://localhost:3001",
|
||||
"http://localhost:3000",
|
||||
"http://localhost:5173")
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials());
|
||||
options.AddPolicy("AllowVueApp", policy =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(corsOrigins))
|
||||
{
|
||||
policy.SetIsOriginAllowed(_ => true)
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials();
|
||||
}
|
||||
else
|
||||
{
|
||||
var origins = corsOrigins.Split(';', StringSplitOptions.RemoveEmptyEntries);
|
||||
policy.WithOrigins(origins)
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Swagger/OpenAPI
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
builder.Services.AddSwaggerGen(options =>
|
||||
{
|
||||
options.SwaggerDoc("v1", new OpenApiInfo
|
||||
{
|
||||
Title = "DoliMiddlewareApi",
|
||||
Version = "1.0",
|
||||
Description = "BFF API for Dolibarr ERP - Middleware between frontend and Dolibarr"
|
||||
});
|
||||
|
||||
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||
{
|
||||
Scheme = "bearer",
|
||||
BearerFormat = "JWT",
|
||||
Name = "Authorization",
|
||||
In = ParameterLocation.Header,
|
||||
Type = SecuritySchemeType.Http,
|
||||
Description = "Enter your JWT Bearer token (obtained from POST /api/Auth/login)"
|
||||
});
|
||||
|
||||
options.AddSecurityRequirement(document => new OpenApiSecurityRequirement
|
||||
{
|
||||
[new OpenApiSecuritySchemeReference("Bearer", document)] = []
|
||||
});
|
||||
});
|
||||
|
||||
// Health checks (verifica que el BFF responde)
|
||||
builder.Services.AddHealthChecks();
|
||||
|
||||
// Rate limiting: proteger login contra brute force
|
||||
builder.Services.AddRateLimiter(options =>
|
||||
{
|
||||
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
|
||||
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(context =>
|
||||
{
|
||||
var path = context.Request.Path.Value;
|
||||
if (path != null && path.StartsWith("/api/Auth/login", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return RateLimitPartition.GetSlidingWindowLimiter("login", _ => new SlidingWindowRateLimiterOptions
|
||||
{
|
||||
PermitLimit = 5,
|
||||
Window = TimeSpan.FromMinutes(1),
|
||||
SegmentsPerWindow = 2,
|
||||
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
|
||||
QueueLimit = 0
|
||||
});
|
||||
}
|
||||
return RateLimitPartition.GetNoLimiter("default");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// 2. DOLIBARR HTTP CLIENT
|
||||
|
|
@ -82,7 +143,9 @@ builder.Services.AddSwaggerGen();
|
|||
|
||||
builder.Services.AddHttpClient("Dolibarr", client =>
|
||||
{
|
||||
client.BaseAddress = new Uri(builder.Configuration["Dolibarr:ApiUrl"]!);
|
||||
var baseUrl = builder.Configuration["Dolibarr:ApiUrl"]!;
|
||||
if (!baseUrl.EndsWith('/')) baseUrl += '/';
|
||||
client.BaseAddress = new Uri(baseUrl);
|
||||
});
|
||||
|
||||
builder.Services.AddScoped<IDolibarrApiClient>(sp =>
|
||||
|
|
@ -103,8 +166,15 @@ builder.Services.AddScoped<InvoiceService>();
|
|||
// Servicio de negocio (clientes)
|
||||
builder.Services.AddScoped<ClientService>();
|
||||
|
||||
// Servicio de negocio (contactos)
|
||||
builder.Services.AddScoped<ContactService>();
|
||||
|
||||
// Servicio de negocio (documentos)
|
||||
builder.Services.AddScoped<DocumentService>();
|
||||
|
||||
// Servicio de negocio (setup/diccionarios)
|
||||
builder.Services.AddScoped<SetupService>();
|
||||
|
||||
// Servicio de aplicación (orquesta login + cache)
|
||||
builder.Services.AddScoped<AuthApplicationService>();
|
||||
|
||||
|
|
@ -199,17 +269,17 @@ app.UseExceptionHandler(errorApp =>
|
|||
// 5. PIPELINE ASP.NET CORE
|
||||
// =========================================
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(options =>
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(options =>
|
||||
{
|
||||
options.SwaggerEndpoint("/swagger/v1/swagger.json", "DoliMiddlewareApi v1");
|
||||
});
|
||||
}
|
||||
options.SwaggerEndpoint("/swagger/v1/swagger.json", "DoliMiddlewareApi v1");
|
||||
});
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
// No HTTPS redirect in Docker (behind reverse proxy)
|
||||
if (!bool.TryParse(builder.Configuration["Dolibarr:ForceHttps"], out var forceHttps) || !forceHttps)
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseRateLimiter();
|
||||
|
||||
app.UseCors("AllowVueApp");
|
||||
|
||||
|
|
@ -218,4 +288,6 @@ app.UseAuthorization();
|
|||
|
||||
app.MapControllers();
|
||||
|
||||
app.MapHealthChecks("/health");
|
||||
|
||||
app.Run();
|
||||
|
|
|
|||
|
|
@ -5,5 +5,8 @@
|
|||
"Microsoft.AspNetCore": "Warning",
|
||||
"Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware": "None"
|
||||
}
|
||||
},
|
||||
"Jwt": {
|
||||
"Secret": "dev-secret-key-change-in-production-min-32-chars"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,14 @@
|
|||
},
|
||||
"AllowedHosts": "*",
|
||||
"Dolibarr": {
|
||||
"ApiUrl": "http://localhost/api/index.php/explorer",
|
||||
"ApiUrl": "http://localhost/api/index.php",
|
||||
"ApiKey": ""
|
||||
},
|
||||
"Cors": {
|
||||
"AllowedOrigins": ""
|
||||
},
|
||||
"Jwt": {
|
||||
"Secret": "mi-super-secreto-jwt-aqui-cambiar-en-produccion",
|
||||
"Secret": "",
|
||||
"Issuer": "DoliMiddleware",
|
||||
"Audience": "DoliClients"
|
||||
}
|
||||
|
|
|
|||
67
compose.yaml
67
compose.yaml
|
|
@ -1,13 +1,12 @@
|
|||
|
||||
services:
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
container_name: dolibarr-mysql
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: "rootpassword"
|
||||
MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD:-rootpassword}"
|
||||
MYSQL_DATABASE: "dolibarr"
|
||||
MYSQL_USER: "dolibarr"
|
||||
MYSQL_PASSWORD: "12345678"
|
||||
MYSQL_PASSWORD: "${MYSQL_PASSWORD:-12345678}"
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
|
|
@ -20,21 +19,6 @@ services:
|
|||
retries: 5
|
||||
restart: unless-stopped
|
||||
|
||||
# redis:
|
||||
# image: redis:7-alpine
|
||||
# container_name: dolibarr-redis
|
||||
# ports:
|
||||
# - "6379:6379"
|
||||
# volumes:
|
||||
# - redis_data:/data
|
||||
# command: redis-server --appendonly yes
|
||||
# healthcheck:
|
||||
# test: ["CMD", "redis-cli", "ping"]
|
||||
# interval: 10s
|
||||
# timeout: 3s
|
||||
# retries: 5
|
||||
# restart: unless-stopped
|
||||
|
||||
dolibarr:
|
||||
image: dolibarr/dolibarr:latest
|
||||
container_name: dolibarr-web
|
||||
|
|
@ -46,36 +30,43 @@ services:
|
|||
DOLI_DB_HOST: "mysql"
|
||||
DOLI_DB_HOST_PORT: "3306"
|
||||
DOLI_DB_USER: "dolibarr"
|
||||
DOLI_DB_PASSWORD: "12345678"
|
||||
DOLI_DB_PASSWORD: "${MYSQL_PASSWORD:-12345678}"
|
||||
DOLI_DB_NAME: "dolibarr"
|
||||
DOLI_ADMIN_LOGIN: "admin"
|
||||
DOLI_ADMIN_PASSWORD: "12345678"
|
||||
DOLI_ADMIN_PASSWORD: "${DOLI_ADMIN_PASSWORD:-12345678}"
|
||||
DOLI_URL_ROOT: "http://localhost"
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- dolibarr_documents:/var/www/documents
|
||||
- dolibarr_custom:/var/www/html/custom
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 30s
|
||||
restart: unless-stopped
|
||||
|
||||
# dolimiddlewareapi:
|
||||
# image: dolimiddlewareapi
|
||||
# build:
|
||||
# context: .
|
||||
# dockerfile: DoliMiddlewareApi/Dockerfile
|
||||
# container_name: dolibarr-middleware-api
|
||||
# depends_on:
|
||||
# - dolibarr
|
||||
# - mysql
|
||||
# ports:
|
||||
# - "5000:8080"
|
||||
# environment:
|
||||
# - ASPNETCORE_ENVIRONMENT=Development
|
||||
# restart: unless-stopped
|
||||
dolimiddlewareapi:
|
||||
image: dolimiddlewareapi
|
||||
build:
|
||||
context: .
|
||||
dockerfile: DoliMiddlewareApi/Dockerfile
|
||||
container_name: dolibarr-middleware-api
|
||||
depends_on:
|
||||
dolibarr:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "5000:8080"
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Production
|
||||
- Jwt__Secret=DevDemoSecretKeyForDockerCompose2026Min32Chars!!
|
||||
- Dolibarr__ApiUrl=http://dolibarr/api/index.php
|
||||
- Cors__AllowedOrigins=http://localhost:3000;http://localhost:3001;http://localhost:5173;http://localhost:5269
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
# redis_data:
|
||||
dolibarr_documents:
|
||||
dolibarr_custom:
|
||||
|
||||
dolibarr_custom:
|
||||
Loading…
Reference in New Issue