Puesta a punto

This commit is contained in:
2025-09-22 16:59:29 +00:00
parent 69f5860b7f
commit b4c5d2af4f
45 changed files with 7709 additions and 3631 deletions
+8 -67
View File
@@ -5,6 +5,7 @@ PORT=4040
# ===== Session (usa el Redis del stack) =====
# Para DEV podemos reutilizar el Redis de Authentik. En prod conviene uno separado.
SESSION_SECRET=Neon*Mammal*Boaster*Ludicrous*Fender8*Crablike
SESSION_COOKIE_NAME=sc.sid
REDIS_URL=redis://ak-redis:6379
# ===== DB principal (metadatos de SuiteCoffee) =====
@@ -22,82 +23,22 @@ TENANTS_USER=dev-user-postgres
TENANTS_PASS=dev-pass-postgres
TENANTS_PORT=5432
TENANT_INIT_SQL=/app/src/db/initTenant.sql
# ===== (Opcional) Colores UI, si alguna vista los lee =====
COL_PRI=452D19 # Marrón oscuro
COL_SEC=D7A666 # Crema / Café
COL_BG=FFA500 # Naranja
TENANT_INIT_SQL=/app/src/db/initTenant_v2.sql
# ===== Authentik — Admin API (server-to-server dentro de la red) =====
# Usa el alias de red del servicio 'authentik' y su puerto interno 9000
AUTHENTIK_BASE_URL=http://dev-authentik:9000
AUTHENTIK_TOKEN=eE3bFTLd4Rpt3ZkcidTC1EppDYMIr023ev3SXt4ImHynOfAGRVtAZVBXSNxj
AUTHENTIK_DEFAULT_GROUP_NAME=suitecoffee-users
AUTHENTIK_TOKEN=h2apVHbd3ApMcnnSwfQPXbvximkvP8HnUE25ot3zXWuEEtJFaNCcOzDHB6Xw
AUTH_CALLBACK_URL=https://suitecoffee.uy/auth/callback
# ===== OIDC (DEBE coincidir con el Provider) =====
# DEV (todo dentro de la red de Docker):
# - El auth service redirige al navegador a este issuer. Si NO tenés reverse proxy hacia Authentik,
# esta URL interna NO será accesible desde el navegador del host. En ese caso, ver nota más abajo.
# OIDC_ISSUER=https://authentik.suitecoffee.mateosaldain.uy/application/o/suitecoffee/
OIDC_ISSUER=https://sso.suitecoffee.uy/application/o/suitecoffee/
# AUTHENTIK_ISSUER=https://sso.suitecoffee.mateosaldain.uy/application/o/suitecoffee/
AUTHENTIK_ISSUER=https://sso.suitecoffee.uy/application/o/suitecoffee/
OIDC_CLIENT_ID=1orMM8vOvf3WkN2FejXYvUFpPtONG0Lx1eMlwIpW
OIDC_CLIENT_SECRET=t5wx13qBcM0EFQ3cGnUIAmLzvbdsQrUVPv1OGWjszWkEp35pJQ55t7vZeeShqG49kuRAaiXv6PSGJLhRfGaponGaJl8gH1uCL7KIxdmm7UihgYoAXB2dFhZV4zRxfze2
# Redirect URI que definiste en el Provider. Usa el alias de red del servicio 'auth' (dev-auth)
# Si accedés desde el host sin proxy, usa mejor http://localhost:4040/auth/callback y añadilo al Provider.
# OIDC_REDIRECT_URI=https://suitecoffee.mateosaldain.uy/auth/callback
OIDC_REDIRECT_URI=https://suitecoffee.uy/auth/callback
# Cómo querés que maneje la contraseña Authentik para usuarios NUEVOS creados por tu backend:
# - TEMP_FORCE_CHANGE: crea un password temporal y obliga a cambiar en el primer login (recomendado si usás login con usuario/clave)
# - INVITE_LINK: envías/entregás un link de “establecer contraseña” (necesita flow de Enrollment/Recovery y SMTP configurado)
# - SSO_ONLY: no setea password local; login solo por Google/Microsoft/WebAuthn
AK_PASSWORD_MODE=TEMP_FORCE_CHANGE
# (Opcional) longitud del password temporal
AK_TEMP_PW_LENGTH=12
# 3) Configuración en Authentik (por modo)
# A) TEMP_FORCE_CHANGE (password temporal + cambio obligado)
# Flow de Autenticación
# Entra al Admin de Authentik → Flows → tu Authentication Flow (el que usa tu Provider OIDC).
# Asegurate de que tenga:
# Identification Stage (identifica por email/username),
# Password Stage (para escribir contraseña).
# Con eso, cuando el usuario entre con la clave temporal, Authentik le pedirá cambiarla.
# Provider OIDC (suitecoffee)
# Admin → Applications → Providers → tu provider de SuiteCoffee → Flow settings
# Authentication flow: seleccioná el de arriba.
# (Opcional) Email SMTP
# Si querés notificar o enviar contraseñas temporales/enlaces desde Authentik, configura SMTP en Admin → System → Email.
# Resultado: el usuario se registra en tu app → lo redirigís a /auth/login → Authentik pide email+clave → entra con la temporal → obliga a cambiarla → vuelve a tu app.
# B) INVITE_LINK (enlace de “establecer contraseña”)
# SMTP
# Admin → System → Email: configura SMTP (host, puerto, credenciales, remitente).
# Flow de Enrollment/Recovery
# Admin → Flows → clona/crea un flow de Enrollment/Recovery con:
# Identification Stage (email/username),
# Email Stage (envía el link con token),
# Password Stage (para que defina su clave),
# (opcional) Prompt/ User Write para confirmar.
# Guardalo con un Slug fácil (ej. enroll-set-password).
# Cómo usarlo
# Caminos:
# Manual desde UI: Admin → Directory → Invitations → crear invitación, elegir Flow enroll-set-password, seleccionar usuario, copiar link y enviar.
# Automático (más adelante): podemos automatizar por API la creación de una Invitation y envío de mail. (Si querés, te armo el helper akCreateInvitation(userUUID, flowSlug).)
# Resultado: el registro en tu app no pone password; el usuario recibe un link para establecer la clave y desde ahí inicia normalmente.
# C) SSO_ONLY (sin contraseñas locales)
# Configura un Source (Google Workspace / Microsoft Entra / WebAuthn):
# Admin → Directory → Sources: crea el Source (por ejemplo, Google OAuth o Entra ID).
# Activa Create users (para que se creen en Authentik si no existen).
# Mapea email y name.
# Authentication Flow
# Agrega una Source Stage del proveedor (Google/Microsoft/WebAuthn) en tu Authentication Flow.
# (Podés dejar Password Stage deshabilitado si querés solo SSO.)
# Provider OIDC
# En tu Provider suitecoffee, seleccioná ese Authentication Flow.
# Resultado: el usuario se registra en tu app → al entrar a /auth/login ve botón Iniciar con Google/Microsoft → hace click, vuelve con sesión, tu backend setea sc.sid.
OIDC_ENROLLMENT_URL=https://sso.suitecoffee.uy/if/flow/registro-suitecoffee/
+382 -12
View File
@@ -21,6 +21,10 @@
"express-ejs-layouts": "^2.5.1",
"express-session": "^1.18.2",
"ioredis": "^5.7.0",
"jose": "^6.1.0",
"jsonwebtoken": "^9.0.2",
"jwks-rsa": "^3.2.0",
"node-fetch": "^3.3.2",
"openid-client": "^5.7.1",
"pg": "^8.16.3",
"pg-format": "^1.0.4",
@@ -64,6 +68,26 @@
"node-pre-gyp": "bin/node-pre-gyp"
}
},
"node_modules/@mapbox/node-pre-gyp/node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/@redis/bloom": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.8.2.tgz",
@@ -124,6 +148,119 @@
"@redis/client": "^5.8.2"
}
},
"node_modules/@types/body-parser": {
"version": "1.19.6",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
"integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
"license": "MIT",
"dependencies": {
"@types/connect": "*",
"@types/node": "*"
}
},
"node_modules/@types/connect": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/express": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz",
"integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==",
"license": "MIT",
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^4.17.33",
"@types/qs": "*",
"@types/serve-static": "*"
}
},
"node_modules/@types/express-serve-static-core": {
"version": "4.19.6",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz",
"integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==",
"license": "MIT",
"dependencies": {
"@types/node": "*",
"@types/qs": "*",
"@types/range-parser": "*",
"@types/send": "*"
}
},
"node_modules/@types/http-errors": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
"integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
"license": "MIT"
},
"node_modules/@types/jsonwebtoken": {
"version": "9.0.10",
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz",
"integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==",
"license": "MIT",
"dependencies": {
"@types/ms": "*",
"@types/node": "*"
}
},
"node_modules/@types/mime": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
"license": "MIT"
},
"node_modules/@types/ms": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.3.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.1.tgz",
"integrity": "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.10.0"
}
},
"node_modules/@types/qs": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
"integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
"license": "MIT"
},
"node_modules/@types/range-parser": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
"license": "MIT"
},
"node_modules/@types/send": {
"version": "0.17.5",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz",
"integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==",
"license": "MIT",
"dependencies": {
"@types/mime": "^1",
"@types/node": "*"
}
},
"node_modules/@types/serve-static": {
"version": "1.15.8",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz",
"integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==",
"license": "MIT",
"dependencies": {
"@types/http-errors": "*",
"@types/node": "*",
"@types/send": "*"
}
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -297,6 +434,12 @@
"node": ">=8"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
"license": "BSD-3-Clause"
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -558,6 +701,15 @@
"node": ">= 8"
}
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
@@ -643,6 +795,15 @@
"node": ">= 0.4"
}
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -826,6 +987,29 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/filelist": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
@@ -943,6 +1127,18 @@
"node": ">= 0.6"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"license": "MIT",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -1367,6 +1563,65 @@
}
},
"node_modules/jose": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.1.0.tgz",
"integrity": "sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
"license": "MIT",
"dependencies": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^7.5.4"
},
"engines": {
"node": ">=12",
"npm": ">=6"
}
},
"node_modules/jwa": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
"integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jwks-rsa": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.0.tgz",
"integrity": "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww==",
"license": "MIT",
"dependencies": {
"@types/express": "^4.17.20",
"@types/jsonwebtoken": "^9.0.4",
"debug": "^4.3.4",
"jose": "^4.15.4",
"limiter": "^1.1.5",
"lru-memoizer": "^2.2.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/jwks-rsa/node_modules/jose": {
"version": "4.15.9",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
"integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
@@ -1375,6 +1630,16 @@
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"license": "MIT",
"dependencies": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/keygrip": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
@@ -1387,18 +1652,71 @@
"node": ">= 0.6"
}
},
"node_modules/limiter": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
"integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA=="
},
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
"license": "MIT"
},
"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.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
"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/lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
"license": "MIT"
},
"node_modules/lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
"license": "MIT"
},
"node_modules/lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
"license": "MIT"
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
"license": "MIT"
},
"node_modules/lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
"license": "MIT"
},
"node_modules/lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
"license": "MIT"
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -1411,6 +1729,16 @@
"node": ">=10"
}
},
"node_modules/lru-memoizer": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz",
"integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==",
"license": "MIT",
"dependencies": {
"lodash.clonedeep": "^4.5.0",
"lru-cache": "6.0.0"
}
},
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -1565,24 +1893,42 @@
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==",
"license": "MIT"
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"deprecated": "Use your platform's native DOMException instead",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "4.x || >=6.0.0"
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/nodemon": {
@@ -1736,6 +2082,15 @@
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/openid-client/node_modules/jose": {
"version": "4.15.9",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
"integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -2469,6 +2824,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/undici-types": {
"version": "7.10.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
"license": "MIT"
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -2493,6 +2854,15 @@
"node": ">= 0.8"
}
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+4
View File
@@ -27,6 +27,10 @@
"express-ejs-layouts": "^2.5.1",
"express-session": "^1.18.2",
"ioredis": "^5.7.0",
"jose": "^6.1.0",
"jsonwebtoken": "^9.0.2",
"jwks-rsa": "^3.2.0",
"node-fetch": "^3.3.2",
"openid-client": "^5.7.1",
"pg": "^8.16.3",
"pg-format": "^1.0.4",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+240 -907
View File
File diff suppressed because it is too large Load Diff
-164
View File
@@ -1,164 +0,0 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title><%= typeof pageTitle !== 'undefined' ? pageTitle : 'Iniciar sesión · SuiteCoffee' %></title>
<!-- Bootstrap 5 (minimal) -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<style>
:root {
--col-pri: #<%= (process.env.COL_PRI || '452D19') %>;
--col-sec: #<%= (process.env.COL_SEC || 'D7A666') %>;
--col-bg: #<%= (process.env.COL_BG || 'FFA500') %>33; /* con alpha */
}
body { background: radial-gradient(1200px 600px at 10% -10%, var(--col-bg), transparent), #f8f9fa; }
.brand { color: var(--col-pri); }
.btn-sso { background: var(--col-pri); color: #fff; border-color: var(--col-pri); }
.btn-sso:hover { filter: brightness(1.05); color: #fff; }
.card { border-radius: 14px; }
.form-hint { font-size: .875rem; color: #6c757d; }
.divider { display:flex; align-items:center; text-transform:uppercase; font-size:.8rem; color:#6c757d; }
.divider::before, .divider::after { content:""; height:1px; background:#dee2e6; flex:1; }
.divider:not(:empty)::before { margin-right:.75rem; }
.divider:not(:empty)::after { margin-left:.75rem; }
</style>
</head>
<body>
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-12 col-sm-10 col-md-8 col-lg-6 col-xl-5">
<div class="text-center mb-4">
<h1 class="brand fw-bold">SuiteCoffee</h1>
<p class="text-secondary mb-0">Accedé a tu cuenta</p>
</div>
<!-- Mensajes (query ?msg= / ?error=) -->
<div id="flash" class="mb-3" style="display:none"></div>
<div class="card shadow-sm">
<div class="card-body p-4 p-md-5">
<!-- SSO con Authentik -->
<div class="d-grid gap-2 mb-3">
<a href="/auth/login" class="btn btn-sso btn-lg" id="btn-sso">
Ingresar con SSO (Authentik)
</a>
</div>
<div class="divider my-3">o</div>
<!-- Registro mínimo (usa POST /auth/api/users/register) -->
<form id="form-register" class="needs-validation" novalidate>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email" placeholder="tu@correo.com" required>
<div class="invalid-feedback">Ingresá un email válido.</div>
</div>
<div class="mb-3">
<label for="display_name" class="form-label">Nombre a mostrar</label>
<input type="text" class="form-control" id="display_name" name="display_name" placeholder="Ej.: Juan Pérez" required>
<div class="invalid-feedback">Ingresá tu nombre.</div>
</div>
<div class="mb-3">
<label for="tenant_uuid" class="form-label">Código de organización (tenant UUID)</label>
<input type="text" class="form-control" id="tenant_uuid" name="tenant_uuid" placeholder="Ej.: 4b8d0f6a-...">
<div class="form-hint">Si te invitaron a una organización existente, pegá aquí su UUID. Si sos el primero de tu empresa, dejalo vacío y el equipo te asignará uno.</div>
</div>
<div class="mb-3">
<label for="role" class="form-label">Rol</label>
<select id="role" name="role" class="form-select">
<option value="owner">Owner</option>
<option value="admin">Admin</option>
<option value="staff">Staff</option>
</select>
</div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-outline-dark">Crear cuenta</button>
</div>
</form>
<p class="text-center text-muted mt-3 mb-0" style="font-size:.9rem;">
Al continuar aceptás nuestros términos y políticas.
</p>
</div>
</div>
<p class="text-center text-secondary mt-3" style="font-size:.9rem;">
¿Ya tenés cuenta? <a href="/auth/login" class="link-dark">Iniciá sesión con SSO</a>
</p>
</div>
</div>
</div>
<script>
// Mostrar mensajes por querystring (?msg=... / ?error=...)
(function() {
const params = new URLSearchParams(location.search);
const el = document.getElementById('flash');
const msg = params.get('msg');
const err = params.get('error');
if (msg) {
el.innerHTML = `<div class="alert alert-success mb-0" role="alert">${decodeURIComponent(msg)}</div>`;
el.style.display = '';
} else if (err) {
el.innerHTML = `<div class="alert alert-danger mb-0" role="alert">${decodeURIComponent(err)}</div>`;
el.style.display = '';
}
})();
// Validación Bootstrap + envío del registro contra /auth/api/users/register
(function() {
const form = document.getElementById('form-register');
form.addEventListener('submit', async function(e) {
e.preventDefault();
e.stopPropagation();
form.classList.add('was-validated');
if (!form.checkValidity()) return;
const btn = form.querySelector('button[type="submit"]');
btn.disabled = true; btn.innerText = 'Creando...';
try {
const payload = {
email: document.getElementById('email').value.trim(),
display_name: document.getElementById('display_name').value.trim(),
tenant_uuid: document.getElementById('tenant_uuid').value.trim() || undefined,
role: document.getElementById('role').value
};
const res = await fetch('/auth/api/users/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
credentials: 'include'
});
const data = await res.json().catch(() => ({}));
if (!res.ok) {
throw new Error(data?.error || data?.message || 'No se pudo registrar');
}
// Registro OK → redirigimos a login SSO
const redir = '/auth/login';
location.href = redir + '?msg=' + encodeURIComponent('Registro exitoso. Iniciá sesión con SSO.');
} catch (err) {
alert(err.message || String(err));
} finally {
btn.disabled = false; btn.innerText = 'Crear cuenta';
}
});
})();
</script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</body>
</html>