Importación de feature/registration
This commit is contained in:
parent
25876e733b
commit
cbcea72848
@ -1,6 +1,6 @@
|
||||
# SuiteCoffee — Sistema de gestión para cafeterías (Dockerizado y multi‑servicio)
|
||||
|
||||
SuiteCoffee es un sistema modular pensado para la **gestión de cafeterías** (y negocios afines), con servicios Node.js para **aplicación** y **autenticación**, bases de datos **PostgreSQL** separadas para negocio y multi‑tenencia, y un **stack Docker Compose** que facilita levantar entornos de **desarrollo** y **producción**. Incluye herramientas auxiliares como **Nginx Proxy Manager (NPM)** y **CloudBeaver** para administrar bases de datos desde el navegador.
|
||||
SuiteCoffee es un sistema modular pensado para la **gestión de cafeterías** (y negocios afines), con servicios Node.js para **aplicación** y Authentik **autenticación**, bases de datos **PostgreSQL** separadas para negocio y multi‑tenencia, y un **stack Docker Compose** que facilita levantar entornos de **desarrollo** y **producción**. Incluye herramientas auxiliares como **Nginx Proxy Manager (NPM)** y **CloudBeaver** para administrar bases de datos desde el navegador.
|
||||
|
||||
> Repositorio: https://gitea.mateosaldain.uy/msaldain/SuiteCoffee.git
|
||||
|
||||
|
||||
@ -63,10 +63,95 @@ services:
|
||||
networks:
|
||||
net:
|
||||
aliases: [dev-tenants]
|
||||
|
||||
|
||||
#################
|
||||
### Authentik ###
|
||||
#################
|
||||
# --- 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 }
|
||||
environment:
|
||||
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
|
||||
AUTHENTIK_DEBUG: "false"
|
||||
AUTHENTIK_POSTGRESQL__HOST: authentik-db
|
||||
AUTHENTIK_POSTGRESQL__USER: authentik
|
||||
AUTHENTIK_POSTGRESQL__NAME: authentik
|
||||
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASS}
|
||||
AUTHENTIK_REDIS__HOST: authentik-redis
|
||||
# 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:
|
||||
- "127.0.0.1:9000:9000" # SOLO localhost
|
||||
|
||||
# --- Authentik Worker
|
||||
authentik-worker:
|
||||
image: ghcr.io/goauthentik/server:latest
|
||||
command: worker
|
||||
depends_on:
|
||||
authentik-db: { condition: service_healthy }
|
||||
authentik-redis: { condition: service_started }
|
||||
environment:
|
||||
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
|
||||
AUTHENTIK_POSTGRESQL__HOST: authentik-db
|
||||
AUTHENTIK_POSTGRESQL__USER: authentik
|
||||
AUTHENTIK_POSTGRESQL__NAME: authentik
|
||||
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASS}
|
||||
AUTHENTIK_REDIS__HOST: authentik-redis
|
||||
profiles: ["ak-ui"]
|
||||
networks: {
|
||||
net: {
|
||||
|
||||
}
|
||||
}
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
tenants-db:
|
||||
suitecoffee-db:
|
||||
authentik-db:
|
||||
|
||||
networks:
|
||||
net:
|
||||
|
||||
22
compose.yaml
22
compose.yaml
@ -31,17 +31,17 @@ services:
|
||||
# start_period: 20s
|
||||
# restart: unless-stopped
|
||||
|
||||
# auth:
|
||||
# depends_on:
|
||||
# db:
|
||||
# condition: service_healthy
|
||||
# healthcheck:
|
||||
# test: ["CMD-SHELL", "curl -fsS http://localhost:${AUTH_DOCKER_PORT}/health || exit 1"]
|
||||
# interval: 10s
|
||||
# timeout: 3s
|
||||
# retries: 10
|
||||
# start_period: 15s
|
||||
# restart: unless-stopped
|
||||
auth:
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fsS http://localhost:${AUTH_DOCKER_PORT}/health || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
start_period: 15s
|
||||
restart: unless-stopped
|
||||
|
||||
db:
|
||||
image: postgres:16
|
||||
|
||||
@ -22,7 +22,9 @@
|
||||
"express": "^5.1.0",
|
||||
"express-ejs-layouts": "^2.5.1",
|
||||
"pg": "^8.16.3",
|
||||
"pg-format": "^1.0.4"
|
||||
"pg-format": "^1.0.4",
|
||||
"openid-client": "^5.6.5",
|
||||
"cookie-session": "^2.0.0"
|
||||
},
|
||||
"keywords": [],
|
||||
"description": ""
|
||||
|
||||
@ -6,6 +6,9 @@ import cors from 'cors';
|
||||
import { Pool } from 'pg';
|
||||
import bcrypt from'bcrypt';
|
||||
|
||||
import { Issuer, generators } from 'openid-client';
|
||||
import cookieSession from 'cookie-session';
|
||||
|
||||
// Rutas
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
@ -39,6 +42,14 @@ app.set('trust proxy', true);
|
||||
app.use(express.static(path.join(__dirname, 'pages')));
|
||||
|
||||
|
||||
app.use(cookieSession({
|
||||
name: 'sid',
|
||||
secret: process.env.SESSION_SECRET,
|
||||
httpOnly: true,
|
||||
sameSite: 'lax',
|
||||
secure: false // en prod detrás de https: true
|
||||
}));
|
||||
|
||||
// Configuración de conexión PostgreSQL
|
||||
|
||||
const dbConfig = {
|
||||
@ -65,6 +76,20 @@ 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`);
|
||||
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']
|
||||
});
|
||||
return oidcClient;
|
||||
}
|
||||
|
||||
// === Servir páginas estáticas ===
|
||||
|
||||
@ -178,6 +203,58 @@ app.post('/api/login', async (req, res) => {
|
||||
});
|
||||
|
||||
|
||||
// --- login: redirige a Authentik con PKCE
|
||||
app.get('/auth/login', async (req, res) => {
|
||||
const client = await getClient();
|
||||
const state = generators.state();
|
||||
const code_verifier = generators.codeVerifier();
|
||||
const code_challenge = generators.codeChallenge(code_verifier);
|
||||
|
||||
req.session.state = state;
|
||||
req.session.code_verifier = code_verifier;
|
||||
|
||||
const authUrl = client.authorizationUrl({
|
||||
scope: 'openid profile email',
|
||||
state,
|
||||
code_challenge,
|
||||
code_challenge_method: 'S256'
|
||||
});
|
||||
|
||||
res.redirect(authUrl);
|
||||
});
|
||||
|
||||
// --- 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;
|
||||
|
||||
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 });
|
||||
|
||||
// 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;
|
||||
|
||||
// Redirigí a donde quieras (página de bienvenida)
|
||||
res.redirect('/auth/me');
|
||||
});
|
||||
|
||||
// --- ver quién soy (para probar)
|
||||
app.get('/auth/me', (req, res) => {
|
||||
if (!req.session?.user) return res.status(401).json({ error: 'no autenticado' });
|
||||
res.json({ user: req.session.user });
|
||||
});
|
||||
|
||||
// --- logout simple (borra cookie)
|
||||
app.post('/auth/logout', (req, res) => {
|
||||
req.session = null;
|
||||
res.status(204).end();
|
||||
});
|
||||
|
||||
// Colores personalizados
|
||||
let primaryColor = chalk.hex('#'+`${process.env.COL_PRI}`);
|
||||
let secondaryColor = chalk.hex('#'+`${process.env.COL_SEC}`);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user