update: create production env

This commit is contained in:
Nathan FONTEYNE 2026-07-08 11:19:35 +02:00
parent d8d4a45483
commit c3380179ce
6 changed files with 135 additions and 15 deletions

View file

@ -8,7 +8,12 @@ POSTGRES_PASSWORD=changeme
# Sessions
SESSION_SECRET=change-me-to-a-long-random-string
# Authentik OIDC
# Set to true to test locally without a real Authentik instance:
# /auth/login shows a simple form to pick a test name instead of redirecting to OIDC.
# NEVER set this to true outside local development.
DEV_BYPASS_AUTH=false
# Authentik OIDC (not needed if DEV_BYPASS_AUTH=true)
AUTHENTIK_ISSUER_URL=https://auth.example.com/application/o/octane-website/
OIDC_CLIENT_ID=
OIDC_CLIENT_SECRET=

View file

@ -154,7 +154,32 @@ docker compose up --build
Les migrations SQL (`src/db/migrations/*.sql`) sont exécutées automatiquement au démarrage du conteneur `app`, de façon idempotente (une table `schema_migrations` garde la trace des fichiers déjà appliqués).
## Développement local (sans Docker)
## Tester en local sans Authentik (ex: dans WSL)
Pas besoin d'avoir Authentik pour essayer l'application en premier lieu. Un mode `DEV_BYPASS_AUTH` remplace la redirection OIDC par un simple formulaire "choisissez un nom" — **à n'utiliser qu'en local, jamais en production**.
```bash
cp .env.example .env
```
Dans `.env`, mettre :
```
DEV_BYPASS_AUTH=true
DATABASE_URL=postgres://octane:changeme@postgres:5432/octane
POSTGRES_PASSWORD=changeme
SESSION_SECRET=une-longue-chaine-aleatoire
```
(Les variables `AUTHENTIK_*` / `OIDC_*` peuvent rester vides tant que `DEV_BYPASS_AUTH=true`.)
Puis, avec Docker Compose (fichier séparé `docker-compose.dev.yml`, sans dépendance au réseau Authentik) :
```bash
docker compose -f docker-compose.dev.yml up --build
```
Ou sans Docker du tout, avec un Postgres local :
```bash
npm install
@ -163,6 +188,10 @@ npm run migrate
npm start
```
Ouvrir `http://localhost:3000` : vous serez redirigé vers `/auth/login`, qui affiche un formulaire pour choisir un nom (et cocher "Compte admin" si besoin) au lieu de passer par Authentik. Chaque nom saisi crée un utilisateur distinct et persistant en base — pratique pour tester le vote sur les suggestions avec plusieurs "personnes" (ouvrez un autre navigateur ou une fenêtre de navigation privée pour vous connecter sous un second nom).
Une fois satisfait, repassez `DEV_BYPASS_AUTH=false` et configurez les variables `AUTHENTIK_*`/`OIDC_*` avant de déployer avec `docker-compose.yml` (celui avec le réseau Authentik).
## Structure du projet
```

25
docker-compose.dev.yml Normal file
View file

@ -0,0 +1,25 @@
# Standalone compose file for local testing (e.g. in WSL) without a real Authentik instance.
# Use with DEV_BYPASS_AUTH=true in .env. Run with:
# docker compose -f docker-compose.dev.yml up --build
services:
app:
build: .
restart: unless-stopped
env_file: .env
ports:
- "${APP_PORT:-3000}:3000"
depends_on:
- postgres
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: octane
POSTGRES_USER: octane
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pgdata_dev:/var/lib/postgresql/data
volumes:
pgdata_dev:

View file

@ -6,9 +6,63 @@ const asyncHandler = require('../lib/asyncHandler');
const router = express.Router();
function escapeHtml(str) {
return String(str ?? '').replace(/[&<>"']/g, (c) => ({
'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
}[c]));
}
function establishSession(req, res, user, returnTo) {
req.session.regenerate((err) => {
if (err) throw err;
req.session.userId = user.id;
req.session.save((saveErr) => {
if (saveErr) throw saveErr;
res.redirect(returnTo || '/');
});
});
}
// Dev-only login: no Authentik involved, lets you pick a name/admin flag
// to test the app locally. Only active when DEV_BYPASS_AUTH=true.
async function devLogin(req, res) {
const { name, admin, returnTo } = req.query;
if (!name) {
res.set('Content-Type', 'text/html');
return res.send(`<!doctype html>
<html lang="fr">
<head><meta charset="utf-8"><title>Connexion (mode dev)</title></head>
<body style="font-family: system-ui, sans-serif; max-width: 400px; margin: 60px auto;">
<h1>Connexion (mode dev)</h1>
<p>Authentik n'est pas connecté (DEV_BYPASS_AUTH=true). Choisissez une identité de test.</p>
<form method="get" action="/auth/login">
<input type="hidden" name="returnTo" value="${escapeHtml(returnTo || '/')}">
<p><label>Nom : <input name="name" required autofocus></label></p>
<p><label><input type="checkbox" name="admin" value="1"> Compte admin</label></p>
<button type="submit">Se connecter</button>
</form>
</body>
</html>`);
}
const user = await usersRepo.upsertFromClaims({
sub: `dev:${name}`,
name,
email: `${String(name).toLowerCase().replace(/\s+/g, '.')}@dev.local`,
isAdmin: admin === '1' || admin === 'true',
});
establishSession(req, res, user, returnTo);
}
router.get(
'/login',
asyncHandler(async (req, res) => {
if (config.devBypassAuth) {
return devLogin(req, res);
}
const oidcConfig = getOidcConfig();
const codeVerifier = client.randomPKCECodeVerifier();
const codeChallenge = await client.calculatePKCECodeChallenge(codeVerifier);
@ -36,6 +90,10 @@ router.get(
router.get(
'/callback',
asyncHandler(async (req, res) => {
if (config.devBypassAuth) {
return res.redirect('/auth/login');
}
const oidcConfig = getOidcConfig();
const pending = req.session.oidc;
if (!pending) {
@ -68,14 +126,7 @@ router.get(
delete req.session.oidc;
delete req.session.returnTo;
req.session.regenerate((err) => {
if (err) throw err;
req.session.userId = user.id;
req.session.save((saveErr) => {
if (saveErr) throw saveErr;
res.redirect(returnTo);
});
});
establishSession(req, res, user, returnTo);
})
);

View file

@ -1,5 +1,7 @@
require('dotenv').config();
const devBypassAuth = process.env.DEV_BYPASS_AUTH === 'true';
function required(name) {
const value = process.env[name];
if (!value && process.env.NODE_ENV !== 'test') {
@ -13,9 +15,13 @@ module.exports = {
port: parseInt(process.env.PORT, 10) || 3000,
databaseUrl: required('DATABASE_URL'),
sessionSecret: required('SESSION_SECRET'),
authentikIssuerUrl: required('AUTHENTIK_ISSUER_URL'),
oidcClientId: required('OIDC_CLIENT_ID'),
oidcClientSecret: required('OIDC_CLIENT_SECRET'),
oidcRedirectUri: required('OIDC_REDIRECT_URI'),
// DEV_BYPASS_AUTH lets you run the app locally without a real Authentik
// instance: /auth/login prompts for a name instead of redirecting to OIDC.
// Never enable this outside local development.
devBypassAuth,
authentikIssuerUrl: devBypassAuth ? process.env.AUTHENTIK_ISSUER_URL : required('AUTHENTIK_ISSUER_URL'),
oidcClientId: devBypassAuth ? process.env.OIDC_CLIENT_ID : required('OIDC_CLIENT_ID'),
oidcClientSecret: devBypassAuth ? process.env.OIDC_CLIENT_SECRET : required('OIDC_CLIENT_SECRET'),
oidcRedirectUri: devBypassAuth ? process.env.OIDC_REDIRECT_URI : required('OIDC_REDIRECT_URI'),
adminGroupName: process.env.ADMIN_GROUP_NAME || 'octane-admins',
};

View file

@ -5,7 +5,11 @@ const { runMigrations } = require('./db/migrate');
async function main() {
await runMigrations();
await initOidc();
if (config.devBypassAuth) {
console.warn('DEV_BYPASS_AUTH is enabled — Authentik/OIDC is skipped. Do not use this in production.');
} else {
await initOidc();
}
const app = createApp();
app.listen(config.port, () => {