Pre-reordenación
This commit is contained in:
parent
8522d02170
commit
80778c0ed9
@ -69,38 +69,26 @@ services:
|
||||
#################
|
||||
# --- Authentik db (solo interno)
|
||||
authentik-db:
|
||||
# image: postgres:16-alpine
|
||||
environment:
|
||||
POSTGRES_DB: authentik
|
||||
POSTGRES_USER: authentik
|
||||
POSTGRES_PASSWORD: ${AUTHENTIK_DB_PASS}
|
||||
# healthcheck:
|
||||
# test: ["CMD-SHELL", "pg_isready -U authentik -d authentik"]
|
||||
# interval: 10s
|
||||
# timeout: 3s
|
||||
# retries: 10
|
||||
volumes:
|
||||
- authentik-db:/var/lib/postgresql/data
|
||||
networks:
|
||||
net:
|
||||
aliases: [ak-db]
|
||||
# restart: unless-stopped
|
||||
|
||||
# --- Authentik Redis (solo interno)
|
||||
authentik-redis:
|
||||
# image: redis:7-alpine
|
||||
command: ["redis-server", "--save", "", "--appendonly", "no"]
|
||||
networks:
|
||||
net:
|
||||
aliases: [ak-redis]
|
||||
# restart: unless-stopped
|
||||
|
||||
# --- Authentik Server (sin puertos públicos)
|
||||
authentik:
|
||||
# image: ghcr.io/goauthentik/server:latest
|
||||
# depends_on:
|
||||
# authentik-db: { condition: service_healthy }
|
||||
# authentik-redis: { condition: service_started }
|
||||
command: server
|
||||
environment:
|
||||
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
|
||||
AUTHENTIK_DEBUG: "false"
|
||||
@ -112,22 +100,12 @@ services:
|
||||
# Opcional: bootstrap automático del admin
|
||||
AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD}
|
||||
AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL}
|
||||
# expose:
|
||||
# - "9000" # HTTP interno
|
||||
# - "9443" # HTTPS interno
|
||||
networks:
|
||||
net:
|
||||
aliases: [authentik]
|
||||
# restart: unless-stopped
|
||||
# Habilitá ESTO SOLO si querés abrir la UI local:
|
||||
profiles: ["ak-ui"]
|
||||
ports:
|
||||
- 9000:9000
|
||||
- 9443:9443
|
||||
aliases: [authentik]
|
||||
|
||||
# --- Authentik Worker
|
||||
authentik-worker:
|
||||
# image: ghcr.io/goauthentik/server:latest
|
||||
command: worker
|
||||
depends_on:
|
||||
authentik-db: { condition: service_healthy }
|
||||
|
||||
@ -6,13 +6,13 @@ services:
|
||||
|
||||
manso:
|
||||
image: node:20-bookworm
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
tenants:
|
||||
condition: service_healthy
|
||||
expose:
|
||||
- ${APP_LOCAL_PORT}
|
||||
# depends_on:
|
||||
# db:
|
||||
# condition: service_healthy
|
||||
# tenants:
|
||||
# condition: service_healthy
|
||||
# expose:
|
||||
# - ${APP_LOCAL_PORT}
|
||||
working_dir: /app
|
||||
user: "${UID:-1000}:${GID:-1000}"
|
||||
volumes:
|
||||
@ -35,29 +35,29 @@ services:
|
||||
profiles: [manso]
|
||||
restart: unless-stopped
|
||||
|
||||
db:
|
||||
image: postgres:16
|
||||
environment:
|
||||
POSTGRES_DB: ${DB_NAME}
|
||||
POSTGRES_USER: ${DB_USER}
|
||||
POSTGRES_PASSWORD: ${DB_PASS}
|
||||
volumes:
|
||||
- suitecoffee-db:/var/lib/postgresql/data
|
||||
networks:
|
||||
net:
|
||||
aliases: [dev-db]
|
||||
# db:
|
||||
# image: postgres:16
|
||||
# environment:
|
||||
# POSTGRES_DB: ${DB_NAME}
|
||||
# POSTGRES_USER: ${DB_USER}
|
||||
# POSTGRES_PASSWORD: ${DB_PASS}
|
||||
# volumes:
|
||||
# - suitecoffee-db:/var/lib/postgresql/data
|
||||
# networks:
|
||||
# net:
|
||||
# aliases: [dev-db]
|
||||
|
||||
tenants:
|
||||
image: postgres:16
|
||||
environment:
|
||||
POSTGRES_DB: ${TENANTS_DB_NAME}
|
||||
POSTGRES_USER: ${TENANTS_DB_USER}
|
||||
POSTGRES_PASSWORD: ${TENANTS_DB_PASS}
|
||||
volumes:
|
||||
- tenants-db:/var/lib/postgresql/data
|
||||
networks:
|
||||
net:
|
||||
aliases: [dev-tenants]
|
||||
# tenants:
|
||||
# image: postgres:16
|
||||
# environment:
|
||||
# POSTGRES_DB: ${TENANTS_DB_NAME}
|
||||
# POSTGRES_USER: ${TENANTS_DB_USER}
|
||||
# POSTGRES_PASSWORD: ${TENANTS_DB_PASS}
|
||||
# volumes:
|
||||
# - tenants-db:/var/lib/postgresql/data
|
||||
# networks:
|
||||
# net:
|
||||
# aliases: [dev-tenants]
|
||||
|
||||
volumes:
|
||||
tenants-db:
|
||||
|
||||
250
services/app/package-lock.json
generated
250
services/app/package-lock.json
generated
@ -10,13 +10,17 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"chalk": "^5.6.0",
|
||||
"connect-redis": "^9.0.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.1",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^5.1.0",
|
||||
"express-ejs-layouts": "^2.5.1",
|
||||
"express-session": "^1.18.2",
|
||||
"ioredis": "^5.7.0",
|
||||
"pg": "^8.16.3",
|
||||
"pg-format": "^1.0.4",
|
||||
"redis": "^5.8.2",
|
||||
"serve-favicon": "^2.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -29,6 +33,72 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@ioredis/commands": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.3.1.tgz",
|
||||
"integrity": "sha512-bYtU8avhGIcje3IhvF9aSjsa5URMZBHnwKtOvXsT4sfYy9gppW11gLPT/9oNqlJZD47yPKveQFTAFWpHjKvUoQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@redis/bloom": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.8.2.tgz",
|
||||
"integrity": "sha512-855DR0ChetZLarblio5eM0yLwxA9Dqq50t8StXKp5bAtLT0G+rZ+eRzzqxl37sPqQKjUudSYypz55o6nNhbz0A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^5.8.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/client": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@redis/client/-/client-5.8.2.tgz",
|
||||
"integrity": "sha512-WtMScno3+eBpTac1Uav2zugXEoXqaU23YznwvFgkPwBQVwEHTDgOG7uEAObtZ/Nyn8SmAMbqkEubJaMOvnqdsQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cluster-key-slot": "1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/json": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@redis/json/-/json-5.8.2.tgz",
|
||||
"integrity": "sha512-uxpVfas3I0LccBX9rIfDgJ0dBrUa3+0Gc8sEwmQQH0vHi7C1Rx1Qn8Nv1QWz5bohoeIXMICFZRcyDONvum2l/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^5.8.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/search": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@redis/search/-/search-5.8.2.tgz",
|
||||
"integrity": "sha512-cNv7HlgayavCBXqPXgaS97DRPVWFznuzsAmmuemi2TMCx5scwLiP50TeZvUS06h/MG96YNPe6A0Zt57yayfxwA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^5.8.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/time-series": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.8.2.tgz",
|
||||
"integrity": "sha512-g2NlHM07fK8H4k+613NBsk3y70R2JIM2dPMSkhIjl2Z17SYvaYKdusz85d7VYOrZBWtDrHV/WD2E3vGu+zni8A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^5.8.2"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "2.0.0",
|
||||
"license": "MIT",
|
||||
@ -174,11 +244,33 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/cluster-key-slot": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
|
||||
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/connect-redis": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-9.0.0.tgz",
|
||||
"integrity": "sha512-QwzyvUePTMvEzG1hy45gZYw3X3YHrjmEdSkayURlcZft7hqadQ3X39wYkmCqblK2rGlw+XItELYt6GnyG6DEIQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"express-session": ">=1",
|
||||
"redis": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
@ -265,6 +357,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/denque": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"license": "MIT",
|
||||
@ -396,6 +497,46 @@
|
||||
"node_modules/express-ejs-layouts": {
|
||||
"version": "2.5.1"
|
||||
},
|
||||
"node_modules/express-session": {
|
||||
"version": "1.18.2",
|
||||
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz",
|
||||
"integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "0.7.2",
|
||||
"cookie-signature": "1.0.7",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~2.0.0",
|
||||
"on-headers": "~1.1.0",
|
||||
"parseurl": "~1.3.3",
|
||||
"safe-buffer": "5.2.1",
|
||||
"uid-safe": "~2.1.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session/node_modules/cookie-signature": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
||||
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/express-session/node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session/node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/filelist": {
|
||||
"version": "1.0.4",
|
||||
"license": "Apache-2.0",
|
||||
@ -589,6 +730,30 @@
|
||||
"version": "2.0.4",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ioredis": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.7.0.tgz",
|
||||
"integrity": "sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ioredis/commands": "^1.3.0",
|
||||
"cluster-key-slot": "^1.1.0",
|
||||
"debug": "^4.3.4",
|
||||
"denque": "^2.1.0",
|
||||
"lodash.defaults": "^4.2.0",
|
||||
"lodash.isarguments": "^3.1.0",
|
||||
"redis-errors": "^1.2.0",
|
||||
"redis-parser": "^3.0.0",
|
||||
"standard-as-callback": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.22.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/ioredis"
|
||||
}
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"license": "MIT",
|
||||
@ -658,6 +823,18 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash.defaults": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isarguments": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
||||
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"license": "MIT",
|
||||
@ -783,6 +960,15 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/on-headers": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
|
||||
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"license": "ISC",
|
||||
@ -967,6 +1153,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/random-bytes": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
||||
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"license": "MIT",
|
||||
@ -998,6 +1193,43 @@
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/redis": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-5.8.2.tgz",
|
||||
"integrity": "sha512-31vunZj07++Y1vcFGcnNWEf5jPoTkGARgfWI4+Tk55vdwHxhAvug8VEtW7Cx+/h47NuJTEg/JL77zAwC6E0OeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@redis/bloom": "5.8.2",
|
||||
"@redis/client": "5.8.2",
|
||||
"@redis/json": "5.8.2",
|
||||
"@redis/search": "5.8.2",
|
||||
"@redis/time-series": "5.8.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/redis-errors": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
||||
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/redis-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"redis-errors": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/router": {
|
||||
"version": "2.2.0",
|
||||
"license": "MIT",
|
||||
@ -1204,6 +1436,12 @@
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/standard-as-callback": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
|
||||
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.2",
|
||||
"license": "MIT",
|
||||
@ -1260,6 +1498,18 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/uid-safe": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"random-bytes": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/undefsafe": {
|
||||
"version": "2.0.5",
|
||||
"dev": true,
|
||||
|
||||
@ -16,13 +16,17 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "^5.6.0",
|
||||
"connect-redis": "^9.0.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.1",
|
||||
"ejs": "^3.1.10",
|
||||
"express": "^5.1.0",
|
||||
"express-ejs-layouts": "^2.5.1",
|
||||
"express-session": "^1.18.2",
|
||||
"ioredis": "^5.7.0",
|
||||
"pg": "^8.16.3",
|
||||
"pg-format": "^1.0.4",
|
||||
"redis": "^5.8.2",
|
||||
"serve-favicon": "^2.5.1"
|
||||
},
|
||||
"keywords": [],
|
||||
|
||||
@ -12,6 +12,14 @@ import { fileURLToPath } from 'url';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
//Redis
|
||||
import session from 'express-session';
|
||||
import { createClient } from 'redis';
|
||||
import * as connectRedis from 'connect-redis';
|
||||
const RedisStore = connectRedis.default || connectRedis.RedisStore;
|
||||
|
||||
const redis = createClient({ url: process.env.REDIS_URL || 'redis://authentik-redis:6379' });
|
||||
await redis.connect();
|
||||
// Variables de Entorno
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
@ -36,6 +44,18 @@ app.use(express.json());
|
||||
app.use(express.json({ limit: '1mb' }));
|
||||
app.use(express.static(path.join(__dirname, 'pages')));
|
||||
|
||||
app.use(session({
|
||||
name: 'sc.sid',
|
||||
store: new RedisStore({ client: redis, prefix: 'sess:' }),
|
||||
secret: process.env.SESSION_SECRET || 'change-me',
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: {
|
||||
httpOnly: true,
|
||||
sameSite: 'lax',
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
},
|
||||
}));
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Motor de vistas EJS
|
||||
@ -72,6 +92,14 @@ const dbConfig = {
|
||||
|
||||
const pool = new Pool(dbConfig);
|
||||
|
||||
const tenantsPool = new Pool({
|
||||
host: process.env.TENANTS_HOST || 'dev-tenants',
|
||||
port: Number(process.env.TENANTS_PORT || 5432),
|
||||
user: process.env.TENANTS_USER || 'postgres',
|
||||
password: process.env.TENANTS_PASS || 'postgres',
|
||||
database: process.env.TENANTS_DB || 'dev-postgres',
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Seguridad: Tablas permitidas
|
||||
// ----------------------------------------------------------
|
||||
@ -103,6 +131,63 @@ async function getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
export async function withTenant(req, res, next) {
|
||||
const client = await tenantsPool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
const uuid = getTenantUuid(req);
|
||||
const schema = `schema_tenant_${uuid}`;
|
||||
|
||||
// Usa la función helper si la creaste en la DB (recomendado)
|
||||
// await client.query('SELECT public.f_set_search_path($1)', [schema]);
|
||||
|
||||
// Alternativa directa si aún no tienes la función:
|
||||
await client.query(`SET LOCAL search_path TO ${schema.replace(/"/g, '')}`);
|
||||
|
||||
req.pg = client;
|
||||
req.pgSchema = schema;
|
||||
next();
|
||||
} catch (e) {
|
||||
try { if (client) await client.query('ROLLBACK'); } catch {}
|
||||
if (client) client.release();
|
||||
return res.status(400).json({ error: e.message });
|
||||
}
|
||||
}
|
||||
|
||||
// Cierra la transacción y libera la conexión
|
||||
export async function done(req, res, next) {
|
||||
try {
|
||||
if (req.pg) await req.pg.query('COMMIT');
|
||||
} catch (e) {
|
||||
try { if (req.pg) await req.pg.query('ROLLBACK'); } catch {}
|
||||
} finally {
|
||||
if (req.pg) req.pg.release();
|
||||
}
|
||||
next?.();
|
||||
}
|
||||
|
||||
function requireAuth(req, res, next) {
|
||||
if (!req.session?.user) return res.status(401).json({ error: 'no-auth' });
|
||||
next();
|
||||
}
|
||||
|
||||
function getTenantUuid(req) {
|
||||
// 1) header enviado por el front (fetchWithTenant)
|
||||
const h = req.get('x-tenant-uuid');
|
||||
if (h) return String(h).replace(/-/g, '');
|
||||
// 2) sesión del login OIDC
|
||||
const s = req.session?.user?.tenant_uuid;
|
||||
if (s) return String(s).replace(/-/g, '');
|
||||
throw new Error('Tenant no especificado');
|
||||
}
|
||||
|
||||
app.get('/api/productos', requireAuth, withTenant, async (req, res, next) => {
|
||||
const { rows } = await req.pg.query('SELECT * FROM productos ORDER BY id');
|
||||
res.json(rows);
|
||||
}, done);
|
||||
|
||||
app.use((req,res,next)=>{ res.locals.user = req.session?.user || null; next(); });
|
||||
|
||||
// ----------------------------------------------------------
|
||||
// Introspección de esquema
|
||||
// ----------------------------------------------------------
|
||||
|
||||
377
services/auth/package-lock.json
generated
377
services/auth/package-lock.json
generated
@ -9,16 +9,21 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^1.11.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"chalk": "^5.6.0",
|
||||
"connect-redis": "^9.0.0",
|
||||
"cookie-session": "^2.0.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.1",
|
||||
"express": "^5.1.0",
|
||||
"express-ejs-layouts": "^2.5.1",
|
||||
"openid-client": "^5.6.5",
|
||||
"express-session": "^1.18.2",
|
||||
"ioredis": "^5.7.0",
|
||||
"openid-client": "^5.7.1",
|
||||
"pg": "^8.16.3",
|
||||
"pg-format": "^1.0.4"
|
||||
"pg-format": "^1.0.4",
|
||||
"redis": "^5.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^10.0.0",
|
||||
@ -32,6 +37,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@ioredis/commands": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.3.1.tgz",
|
||||
"integrity": "sha512-bYtU8avhGIcje3IhvF9aSjsa5URMZBHnwKtOvXsT4sfYy9gppW11gLPT/9oNqlJZD47yPKveQFTAFWpHjKvUoQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
|
||||
@ -52,6 +63,66 @@
|
||||
"node-pre-gyp": "bin/node-pre-gyp"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/bloom": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.8.2.tgz",
|
||||
"integrity": "sha512-855DR0ChetZLarblio5eM0yLwxA9Dqq50t8StXKp5bAtLT0G+rZ+eRzzqxl37sPqQKjUudSYypz55o6nNhbz0A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^5.8.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/client": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@redis/client/-/client-5.8.2.tgz",
|
||||
"integrity": "sha512-WtMScno3+eBpTac1Uav2zugXEoXqaU23YznwvFgkPwBQVwEHTDgOG7uEAObtZ/Nyn8SmAMbqkEubJaMOvnqdsQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cluster-key-slot": "1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/json": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@redis/json/-/json-5.8.2.tgz",
|
||||
"integrity": "sha512-uxpVfas3I0LccBX9rIfDgJ0dBrUa3+0Gc8sEwmQQH0vHi7C1Rx1Qn8Nv1QWz5bohoeIXMICFZRcyDONvum2l/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^5.8.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/search": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@redis/search/-/search-5.8.2.tgz",
|
||||
"integrity": "sha512-cNv7HlgayavCBXqPXgaS97DRPVWFznuzsAmmuemi2TMCx5scwLiP50TeZvUS06h/MG96YNPe6A0Zt57yayfxwA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^5.8.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/time-series": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.8.2.tgz",
|
||||
"integrity": "sha512-g2NlHM07fK8H4k+613NBsk3y70R2JIM2dPMSkhIjl2Z17SYvaYKdusz85d7VYOrZBWtDrHV/WD2E3vGu+zni8A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^5.8.2"
|
||||
}
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
@ -126,6 +197,23 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
|
||||
"integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@ -286,6 +374,15 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/cluster-key-slot": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
|
||||
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-support": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
|
||||
@ -295,12 +392,37 @@
|
||||
"color-support": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/connect-redis": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-9.0.0.tgz",
|
||||
"integrity": "sha512-QwzyvUePTMvEzG1hy45gZYw3X3YHrjmEdSkayURlcZft7hqadQ3X39wYkmCqblK2rGlw+XItELYt6GnyG6DEIQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"express-session": ">=1",
|
||||
"redis": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||
@ -446,12 +568,30 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delegates": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/denque": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
@ -547,6 +687,21 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
@ -609,6 +764,46 @@
|
||||
"resolved": "https://registry.npmjs.org/express-ejs-layouts/-/express-ejs-layouts-2.5.1.tgz",
|
||||
"integrity": "sha512-IXROv9n3xKga7FowT06n1Qn927JR8ZWDn5Dc9CJQoiiaaDqbhW5PDmWShzbpAa2wjWT1vJqaIM1S6vJwwX11gA=="
|
||||
},
|
||||
"node_modules/express-session": {
|
||||
"version": "1.18.2",
|
||||
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz",
|
||||
"integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "0.7.2",
|
||||
"cookie-signature": "1.0.7",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~2.0.0",
|
||||
"on-headers": "~1.1.0",
|
||||
"parseurl": "~1.3.3",
|
||||
"safe-buffer": "5.2.1",
|
||||
"uid-safe": "~2.1.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session/node_modules/cookie-signature": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
||||
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/express-session/node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session/node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
@ -639,6 +834,63 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data/node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data/node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@ -837,6 +1089,21 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-unicode": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||
@ -929,6 +1196,30 @@
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ioredis": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.7.0.tgz",
|
||||
"integrity": "sha512-NUcA93i1lukyXU+riqEyPtSEkyFq8tX90uL659J+qpCZ3rEdViB/APC58oAhIh3+bJln2hzdlZbBZsGNrlsR8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ioredis/commands": "^1.3.0",
|
||||
"cluster-key-slot": "^1.1.0",
|
||||
"debug": "^4.3.4",
|
||||
"denque": "^2.1.0",
|
||||
"lodash.defaults": "^4.2.0",
|
||||
"lodash.isarguments": "^3.1.0",
|
||||
"redis-errors": "^1.2.0",
|
||||
"redis-parser": "^3.0.0",
|
||||
"standard-as-callback": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.22.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/ioredis"
|
||||
}
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
@ -1027,6 +1318,18 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash.defaults": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isarguments": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
||||
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
@ -1564,6 +1867,12 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pstree.remy": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
||||
@ -1586,6 +1895,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/random-bytes": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
||||
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
@ -1637,6 +1955,43 @@
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/redis": {
|
||||
"version": "5.8.2",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-5.8.2.tgz",
|
||||
"integrity": "sha512-31vunZj07++Y1vcFGcnNWEf5jPoTkGARgfWI4+Tk55vdwHxhAvug8VEtW7Cx+/h47NuJTEg/JL77zAwC6E0OeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@redis/bloom": "5.8.2",
|
||||
"@redis/client": "5.8.2",
|
||||
"@redis/json": "5.8.2",
|
||||
"@redis/search": "5.8.2",
|
||||
"@redis/time-series": "5.8.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/redis-errors": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
||||
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/redis-parser": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
||||
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"redis-errors": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
@ -1879,6 +2234,12 @@
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/standard-as-callback": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
|
||||
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||
@ -2014,6 +2375,18 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/uid-safe": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"random-bytes": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/undefsafe": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
|
||||
|
||||
@ -15,16 +15,21 @@
|
||||
"nodemon": "^3.1.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.11.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"chalk": "^5.6.0",
|
||||
"connect-redis": "^9.0.0",
|
||||
"cookie-session": "^2.0.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^17.2.1",
|
||||
"express": "^5.1.0",
|
||||
"express-ejs-layouts": "^2.5.1",
|
||||
"express-session": "^1.18.2",
|
||||
"ioredis": "^5.7.0",
|
||||
"openid-client": "^5.7.1",
|
||||
"pg": "^8.16.3",
|
||||
"pg-format": "^1.0.4",
|
||||
"openid-client": "^5.6.5",
|
||||
"cookie-session": "^2.0.0"
|
||||
"redis": "^5.8.2"
|
||||
},
|
||||
"keywords": [],
|
||||
"description": ""
|
||||
|
||||
46
services/auth/src/ak.js
Normal file
46
services/auth/src/ak.js
Normal file
@ -0,0 +1,46 @@
|
||||
// services/auth/src/ak.js
|
||||
import axios from 'axios';
|
||||
|
||||
const AK = axios.create({
|
||||
baseURL: `${process.env.AUTHENTIK_BASE_URL}/api/v3`,
|
||||
headers: { Authorization: `Bearer ${process.env.AUTHENTIK_TOKEN}` },
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
// Busca usuario por email (case-insensitive)
|
||||
export async function akFindUserByEmail(email) {
|
||||
const { data } = await AK.get('/core/users/', { params: { search: email }});
|
||||
// filtra exacto por email si querés evitar colisiones de 'search'
|
||||
return data.results?.find(u => (u.email || '').toLowerCase() === email.toLowerCase()) || null;
|
||||
}
|
||||
|
||||
// Crea usuario en Authentik con atributo tenant_uuid y lo agrega a un grupo (opcional)
|
||||
export async function akCreateUser({ email, displayName, tenantUuid, addToGroupId }) {
|
||||
// 1) crear usuario
|
||||
const { data: user } = await AK.post('/core/users/', {
|
||||
username: email, // en Authentik el username puede ser el email
|
||||
name: displayName || email,
|
||||
email,
|
||||
is_active: true,
|
||||
attributes: { tenant_uuid: tenantUuid }, // <-- para tu claim custom
|
||||
});
|
||||
|
||||
// 2) agregar a grupo por defecto (opcional)
|
||||
if (addToGroupId) {
|
||||
await AK.post(`/core/users/${user.pk}/groups/`, { group: addToGroupId });
|
||||
}
|
||||
|
||||
return user; // contiene pk y uuid
|
||||
}
|
||||
|
||||
// Opcional: setear/forzar password inicial (si querés flujo con password local en Authentik)
|
||||
export async function akSetPassword(userPk, password, requireChange = true) {
|
||||
try {
|
||||
await AK.post(`/core/users/${userPk}/set_password/`, {
|
||||
password, require_change: requireChange,
|
||||
});
|
||||
} catch (e) {
|
||||
// Si tu instancia no permite setear password por API, capturá y usá un flow de "reset password"
|
||||
throw new Error('No se pudo establecer la contraseña en Authentik por API');
|
||||
}
|
||||
}
|
||||
@ -5,10 +5,22 @@ import expressLayouts from 'express-ejs-layouts';
|
||||
import cors from 'cors';
|
||||
import { Pool } from 'pg';
|
||||
import bcrypt from'bcrypt';
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
import session from 'express-session';
|
||||
import { createClient } from 'redis';
|
||||
import * as connectRedis from 'connect-redis';
|
||||
const RedisStore = connectRedis.default || connectRedis.RedisStore;
|
||||
|
||||
const redis = createClient({ url: process.env.REDIS_URL || 'redis://authentik-redis:6379' });
|
||||
await redis.connect();
|
||||
|
||||
import { Issuer, generators } from 'openid-client';
|
||||
import cookieSession from 'cookie-session';
|
||||
|
||||
import { akFindUserByEmail, akCreateUser, akSetPassword } from './ak.js';
|
||||
|
||||
|
||||
// Rutas
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
@ -41,6 +53,39 @@ app.use(express.json());
|
||||
app.set('trust proxy', true);
|
||||
app.use(express.static(path.join(__dirname, 'pages')));
|
||||
|
||||
/* 1) Motor de vistas apuntando a /auth/src/views */
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
/* 2) Estáticos si usás /css/main.css dentro de /auth/src/public */
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
/* 3) Exponer user a las vistas (opcional, cómodo) */
|
||||
app.use((req, res, next) => {
|
||||
res.locals.user = req.session?.user || null;
|
||||
next();
|
||||
});
|
||||
|
||||
|
||||
/* 4) Página de login (renderiza el EJS de arriba)
|
||||
- Mantén /auth/login para iniciar OIDC (redirección a Authentik)
|
||||
- Usa /login para mostrar la página con el botón */
|
||||
app.get('/login', (req, res) => {
|
||||
res.render('login'); // -> /auth/src/views/login.ejs
|
||||
});
|
||||
|
||||
app.use(session({
|
||||
name: 'sc.sid',
|
||||
store: new RedisStore({ client: redis, prefix: 'sess:' }),
|
||||
secret: process.env.SESSION_SECRET || 'change-me',
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: {
|
||||
httpOnly: true,
|
||||
sameSite: 'lax',
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
},
|
||||
}));
|
||||
|
||||
app.use(cookieSession({
|
||||
name: 'sid',
|
||||
@ -52,7 +97,7 @@ app.use(cookieSession({
|
||||
|
||||
// Configuración de conexión PostgreSQL
|
||||
|
||||
const dbConfig = {
|
||||
const poolMeta = {
|
||||
host: process.env.DB_HOST,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASS,
|
||||
@ -60,8 +105,23 @@ const dbConfig = {
|
||||
port: process.env.DB_LOCAL_PORT
|
||||
};
|
||||
|
||||
const pool = new Pool(dbConfig);
|
||||
const pool = new Pool(poolMeta);
|
||||
|
||||
const poolTenants = new Pool({ // apunta al servidor/base multi-tenant
|
||||
host: process.env.TENANTS_HOST, // dev-tenants
|
||||
user: process.env.TENANTS_USER,
|
||||
password: process.env.TENANTS_PASS,
|
||||
database: process.env.TENANTS_DB, // dev-postgres
|
||||
port: process.env.TENANTS_PORT,
|
||||
});
|
||||
|
||||
const tenantsPool = new Pool({
|
||||
host: process.env.TENANTS_HOST, // dev-tenants
|
||||
user: process.env.TENANTS_USER,
|
||||
password: process.env.TENANTS_PASS,
|
||||
database: process.env.TENANTS_DB, // dev-postgres
|
||||
port: process.env.TENANTS_PORT
|
||||
});
|
||||
|
||||
async function verificarConexion() {
|
||||
try {
|
||||
@ -78,17 +138,27 @@ async function verificarConexion() {
|
||||
|
||||
// Descubrimiento OIDC (una sola vez)
|
||||
let oidcClient;
|
||||
async function getClient() {
|
||||
if (oidcClient) return oidcClient;
|
||||
const ISSUER = process.env.OIDC_ISSUER_INTERNAL; // ej: http://authentik:9000/application/o/suitecoffee/
|
||||
const issuer = await Issuer.discover(`${ISSUER}.well-known/openid-configuration`);
|
||||
(async () => {
|
||||
const issuer = await Issuer.discover(process.env.OIDC_ISSUER); // debe coincidir EXACTO
|
||||
oidcClient = new issuer.Client({
|
||||
client_id: process.env.OIDC_CLIENT_ID,
|
||||
client_secret: process.env.OIDC_CLIENT_SECRET,
|
||||
redirect_uris: [`${process.env.BASE_URL}${process.env.OIDC_REDIRECT_PATH}`],
|
||||
response_types: ['code']
|
||||
redirect_uris: [process.env.OIDC_REDIRECT_URI],
|
||||
response_types: ['code'],
|
||||
});
|
||||
return oidcClient;
|
||||
})().catch(err => {
|
||||
console.error('Error inicializando OIDC:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
|
||||
// util para resolver tenant si aún no usás claim tenant_uuid
|
||||
async function lookupTenantByEmail(email) {
|
||||
const { rows } = await poolMeta.query(
|
||||
`SELECT tenant_uuid FROM app_user WHERE email = $1 ORDER BY id LIMIT 1`,
|
||||
[email.toLowerCase()]
|
||||
);
|
||||
return rows[0]?.tenant_uuid || null;
|
||||
}
|
||||
|
||||
// === Servir páginas estáticas ===
|
||||
@ -108,62 +178,125 @@ app.get('/planes', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/users/register', async (req, res, next) => {
|
||||
const { email, display_name, tenant_uuid, role, password } = req.body;
|
||||
|
||||
app.post('/api/registro', async (req, res) => {
|
||||
const {
|
||||
nombre_empresa,
|
||||
rut,
|
||||
correo,
|
||||
telefono,
|
||||
direccion,
|
||||
logo,
|
||||
clave_acceso,
|
||||
plan_id
|
||||
} = req.body;
|
||||
if (!email || !tenant_uuid) {
|
||||
return res.status(400).json({ error: 'email y tenant_uuid son obligatorios' });
|
||||
}
|
||||
|
||||
const client = await poolMeta.connect();
|
||||
try {
|
||||
const client = await pool.connect();
|
||||
await client.query('BEGIN');
|
||||
|
||||
// 1. Hashear la contraseña
|
||||
const hash = await bcrypt.hash(clave_acceso, 10);
|
||||
// 0) idempotencia: si ya existe en tu DB, devolvés 409 o retornás el existente
|
||||
const { rows: existing } = await client.query(
|
||||
`SELECT id, email, ak_sub FROM app_user WHERE email = $1`, [email]
|
||||
);
|
||||
if (existing.length) {
|
||||
await client.query('ROLLBACK');
|
||||
return res.status(409).json({ error: 'El usuario ya existe en SuiteCoffee' });
|
||||
}
|
||||
|
||||
// 2. Insertar el tenant
|
||||
const result = await client.query(`
|
||||
INSERT INTO tenant (
|
||||
nombre_empresa, rut, correo, telefono, direccion, logo,
|
||||
clave_acceso, plan_id, nombre_base_datos
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6,
|
||||
$7, $8, 'TEMPORAL'
|
||||
)
|
||||
RETURNING uuid;
|
||||
`, [
|
||||
nombre_empresa, rut, correo, telefono, direccion, logo,
|
||||
hash, plan_id
|
||||
]);
|
||||
// 1) crear/obtener usuario en Authentik
|
||||
let akUser = await akFindUserByEmail(email);
|
||||
if (!akUser) {
|
||||
akUser = await akCreateUser({
|
||||
email,
|
||||
displayName: display_name,
|
||||
tenantUuid: tenant_uuid.replace(/-/g, ''),
|
||||
addToGroupId: process.env.AUTHENTIK_DEFAULT_GROUP_ID || null,
|
||||
});
|
||||
// Si querés asignar una clave inicial (no recomendado en prod), descomentá:
|
||||
// if (password) await akSetPassword(akUser.pk, password, true);
|
||||
}
|
||||
|
||||
const uuid = result.rows[0].uuid;
|
||||
const nombre_base_datos = `tenantdb_${uuid}`.replace(/-/g, '').substring(0, 24); // ajustamos para longitud segura
|
||||
// el 'sub' lo tendrás recién tras login OIDC; guardamos el uuid interno si te sirve
|
||||
const _role = role || 'owner';
|
||||
|
||||
// 3. Actualizar el campo nombre_base_datos
|
||||
await client.query(`
|
||||
UPDATE tenant SET nombre_base_datos = $1 WHERE uuid = $2
|
||||
`, [nombre_base_datos, uuid]);
|
||||
// 2) crear usuario local (sin password, dependemos del SSO)
|
||||
await client.query(
|
||||
`INSERT INTO app_user (email, display_name, tenant_uuid, ak_user_uuid, role)
|
||||
VALUES ($1, $2, $3, $4, $5)`,
|
||||
[email, display_name || null, tenant_uuid.replace(/-/g, ''), akUser.uuid, _role]
|
||||
);
|
||||
|
||||
client.release();
|
||||
await client.query('COMMIT');
|
||||
|
||||
return res.status(201).json({
|
||||
message: 'Tenant registrado correctamente',
|
||||
uuid,
|
||||
nombre_base_datos
|
||||
message: 'Usuario registrado',
|
||||
email, tenant_uuid, role: _role,
|
||||
authentik_user_uuid: akUser.uuid,
|
||||
next: '/auth/login' // redirigí a OIDC
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return res.status(500).json({ error: 'Error al registrar tenant' });
|
||||
await client.query('ROLLBACK');
|
||||
next(err);
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// app.post('/api/registro', async (req, res) => {
|
||||
// const {
|
||||
// nombre_empresa, rut, correo, telefono, direccion, logo,
|
||||
// clave_acceso, plan_id
|
||||
// } = req.body;
|
||||
|
||||
// const clientMeta = await poolMeta.connect();
|
||||
// const clientTen = await poolTenants.connect();
|
||||
|
||||
// try {
|
||||
// await clientMeta.query('BEGIN');
|
||||
|
||||
// // 1) Generar UUID sin guiones
|
||||
// const uuid = crypto.randomUUID().replace(/-/g, '');
|
||||
// const hash = await bcrypt.hash(clave_acceso, 10);
|
||||
|
||||
// // 2) Provisionar en PG (dev-tenants/dev-postgres)
|
||||
// const { rows: [prov] } = await clientTen.query(
|
||||
// `SELECT public.f_tenant_provision($1::text, $2::text) AS data`,
|
||||
// [uuid, `tenant_${uuid}`]
|
||||
// );
|
||||
|
||||
// const info = prov.data; // { tenant_uuid, schema, role, user, password, ... }
|
||||
|
||||
// // 3) Guardar metadatos en suitecoffee_db (tu tabla 'tenant')
|
||||
// await clientMeta.query(`
|
||||
// INSERT INTO tenant (
|
||||
// uuid, nombre_empresa, rut, correo, telefono, direccion, logo,
|
||||
// clave_acceso, plan_id,
|
||||
// schema_name, role_name, user_name
|
||||
// ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)
|
||||
// `, [
|
||||
// uuid, nombre_empresa, rut, correo, telefono, direccion, logo,
|
||||
// hash, plan_id,
|
||||
// info.schema, info.role, info.user
|
||||
// ]);
|
||||
|
||||
// await clientMeta.query('COMMIT');
|
||||
|
||||
// // 4) Devolver credenciales del usuario de DB si las necesitás (mejor *no* persistir la password)
|
||||
// return res.status(201).json({
|
||||
// message: 'Tenant registrado correctamente',
|
||||
// uuid,
|
||||
// schema: info.schema,
|
||||
// db_user: info.user,
|
||||
// db_password: info.password // muéstrala *una vez* y recomendación: NO guardarla
|
||||
// });
|
||||
|
||||
// } catch (err) {
|
||||
// await clientMeta.query('ROLLBACK');
|
||||
// console.error(err);
|
||||
// return res.status(500).json({ error: 'Error al registrar tenant' });
|
||||
// } finally {
|
||||
// clientMeta.release();
|
||||
// clientTen.release();
|
||||
// }
|
||||
// });
|
||||
|
||||
app.post('/api/login', async (req, res) => {
|
||||
const { correo, clave_acceso } = req.body;
|
||||
|
||||
@ -202,6 +335,56 @@ app.post('/api/login', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/auth/login', (req, res) => {
|
||||
const code_verifier = generators.codeVerifier();
|
||||
const code_challenge = generators.codeChallenge(code_verifier);
|
||||
req.session.code_verifier = code_verifier;
|
||||
|
||||
const url = oidcClient.authorizationUrl({
|
||||
scope: 'openid profile email offline_access tenant', // incluye tu scope custom “tenant”
|
||||
code_challenge: code_challenge,
|
||||
code_challenge_method: 'S256',
|
||||
});
|
||||
res.redirect(url);
|
||||
});
|
||||
|
||||
|
||||
// ------------- Middleware ----------------
|
||||
function getTenantUuid(req) {
|
||||
// Ejemplo 1: header
|
||||
if (req.headers['x-tenant-uuid']) return String(req.headers['x-tenant-uuid']);
|
||||
// Ejemplo 2: si más adelante usás JWT:
|
||||
// return req.user?.tenantUuid;
|
||||
throw new Error('Tenant no especificado');
|
||||
}
|
||||
|
||||
async function withTenant(req, res, next) {
|
||||
const client = await tenantsPool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
const uuid = getTenantUuid(req).replace(/-/g, '');
|
||||
const schema = `schema_tenant_${uuid}`;
|
||||
await client.query(`SELECT public.f_set_search_path($1)`, [schema]);
|
||||
|
||||
// guardamos el cliente en req para reutilizar en los handlers
|
||||
req.pg = client;
|
||||
req.pgSchema = schema;
|
||||
next();
|
||||
} catch (e) {
|
||||
if (req.pg) await req.pg.query('ROLLBACK');
|
||||
if (req.pg) req.pg.release();
|
||||
return res.status(400).json({ error: e.message });
|
||||
}
|
||||
}
|
||||
|
||||
// Al final de cada handler, hacé COMMIT y release
|
||||
async function done(req, res, next) {
|
||||
try { if (req.pg) await req.pg.query('COMMIT'); }
|
||||
finally { if (req.pg) req.pg.release(); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// --- login: redirige a Authentik con PKCE
|
||||
app.get('/auth/login', async (req, res) => {
|
||||
@ -223,24 +406,70 @@ app.get('/auth/login', async (req, res) => {
|
||||
res.redirect(authUrl);
|
||||
});
|
||||
|
||||
app.use((req,res,next)=>{ res.locals.user = req.session?.user || null; next(); });
|
||||
|
||||
|
||||
// --- callback: intercambia code por tokens y guarda sesión mínima
|
||||
app.get(process.env.OIDC_REDIRECT_PATH || '/auth/callback', async (req, res) => {
|
||||
const client = await getClient();
|
||||
const { state, code } = req.query;
|
||||
// --- RUTA: callback (enlaza la sesión con tu usuario local)
|
||||
app.get('/auth/callback', async (req, res, next) => {
|
||||
try {
|
||||
const params = oidcClient.callbackParams(req);
|
||||
const tokenSet = await oidcClient.callback(
|
||||
process.env.OIDC_REDIRECT_URI,
|
||||
params,
|
||||
{ code_verifier: req.session.code_verifier }
|
||||
);
|
||||
const claims = tokenSet.claims(); // { sub, email, tenant_uuid?, ... }
|
||||
|
||||
if (!state || state !== req.session.state) {
|
||||
return res.status(400).send('state inválido');
|
||||
}
|
||||
const params = { state, code, code_verifier: req.session.code_verifier };
|
||||
const tokenSet = await client.callback(`${process.env.BASE_URL}${process.env.OIDC_REDIRECT_PATH}`, params, { state });
|
||||
const email = (claims.email || '').toLowerCase();
|
||||
const sub = claims.sub;
|
||||
const tenantUuid = (claims.tenant_uuid || await lookupTenantByEmail(email))?.replace(/-/g, '');
|
||||
|
||||
// Guarda lo que necesites para pruebas (id_token y claims)
|
||||
req.session.user = tokenSet.claims();
|
||||
req.session.id_token = tokenSet.id_token;
|
||||
req.session.access_token = tokenSet.access_token;
|
||||
if (!tenantUuid) {
|
||||
return res.status(403).send('No se pudo determinar el tenant del usuario.');
|
||||
}
|
||||
|
||||
// Redirigí a donde quieras (página de bienvenida)
|
||||
res.redirect('/auth/me');
|
||||
// Asegurar presencia del usuario en tu DB y enlazar el sub de OIDC
|
||||
const { rows } = await poolMeta.query(
|
||||
`SELECT id, ak_sub FROM app_user WHERE email=$1 AND tenant_uuid=$2`,
|
||||
[email, tenantUuid]
|
||||
);
|
||||
let userId;
|
||||
if (rows.length) {
|
||||
userId = rows[0].id;
|
||||
if (!rows[0].ak_sub) {
|
||||
await poolMeta.query(`UPDATE app_user SET ak_sub=$1 WHERE id=$2`, [sub, userId]);
|
||||
}
|
||||
} else {
|
||||
// “just in time” create (opcional): lo das de alta si no existe aún en tu app
|
||||
const ins = await poolMeta.query(
|
||||
`INSERT INTO app_user (email, tenant_uuid, ak_sub, role)
|
||||
VALUES ($1,$2,$3,'staff') RETURNING id`,
|
||||
[email, tenantUuid, sub]
|
||||
);
|
||||
userId = ins.rows[0].id;
|
||||
}
|
||||
|
||||
// Sesión de aplicación (lo que el resto del backend necesita)
|
||||
req.session.user = { id: userId, email, tenant_uuid: tenantUuid, sub };
|
||||
|
||||
req.session.regenerate(err => {
|
||||
if (err) return next(err);
|
||||
req.session.user = { id: userId, email, tenant_uuid: tenantUuid, sub };
|
||||
req.session.save(err2 => {
|
||||
if (err2) return next(err2);
|
||||
return res.redirect('/');
|
||||
});
|
||||
});
|
||||
|
||||
// redirige a la app (home o dashboard)
|
||||
res.redirect('/');
|
||||
} catch (e) { next(e); }
|
||||
});
|
||||
|
||||
// (Opcional) logout “local”
|
||||
app.post('/auth/logout', (req, res) => {
|
||||
req.session.destroy(() => res.clearCookie('sc.sid').status(204).end());
|
||||
});
|
||||
|
||||
// --- ver quién soy (para probar)
|
||||
|
||||
25
services/auth/src/views/login.ejs
Normal file
25
services/auth/src/views/login.ejs
Normal file
@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Iniciar sesión | SuiteCoffee</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<link rel="stylesheet" href="/css/main.css"/>
|
||||
</head>
|
||||
<body class="container">
|
||||
<header class="my-4">
|
||||
<h1>SuiteCoffee — Acceso</h1>
|
||||
</header>
|
||||
|
||||
<% if (user) { %>
|
||||
<p>Ya iniciaste sesión como <strong><%= user.email %></strong>.</p>
|
||||
<p>Continuar a <a href="/">la aplicación</a></p>
|
||||
<% } else { %>
|
||||
<div class="card p-4">
|
||||
<p>Usamos inicio de sesión único (SSO) con nuestro Identity Provider.</p>
|
||||
<!-- Esta URL dispara el flujo OIDC hacia Authentik -->
|
||||
<a class="btn btn-primary btn-lg" href="/auth/login">Iniciar sesión con SuiteCoffee SSO</a>
|
||||
</div>
|
||||
<% } %>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user