mirror of
https://github.com/nfonteyne/octane-website.git
synced 2026-09-03 23:24:48 +02:00
update: create production env
This commit is contained in:
parent
d8d4a45483
commit
c3380179ce
6 changed files with 135 additions and 15 deletions
|
|
@ -8,7 +8,12 @@ POSTGRES_PASSWORD=changeme
|
||||||
# Sessions
|
# Sessions
|
||||||
SESSION_SECRET=change-me-to-a-long-random-string
|
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/
|
AUTHENTIK_ISSUER_URL=https://auth.example.com/application/o/octane-website/
|
||||||
OIDC_CLIENT_ID=
|
OIDC_CLIENT_ID=
|
||||||
OIDC_CLIENT_SECRET=
|
OIDC_CLIENT_SECRET=
|
||||||
|
|
|
||||||
31
README.md
31
README.md
|
|
@ -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).
|
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
|
```bash
|
||||||
npm install
|
npm install
|
||||||
|
|
@ -163,6 +188,10 @@ npm run migrate
|
||||||
npm start
|
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
|
## Structure du projet
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
||||||
25
docker-compose.dev.yml
Normal file
25
docker-compose.dev.yml
Normal 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:
|
||||||
|
|
@ -6,9 +6,63 @@ const asyncHandler = require('../lib/asyncHandler');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
function escapeHtml(str) {
|
||||||
|
return String(str ?? '').replace(/[&<>"']/g, (c) => ({
|
||||||
|
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
|
||||||
|
}[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(
|
router.get(
|
||||||
'/login',
|
'/login',
|
||||||
asyncHandler(async (req, res) => {
|
asyncHandler(async (req, res) => {
|
||||||
|
if (config.devBypassAuth) {
|
||||||
|
return devLogin(req, res);
|
||||||
|
}
|
||||||
|
|
||||||
const oidcConfig = getOidcConfig();
|
const oidcConfig = getOidcConfig();
|
||||||
const codeVerifier = client.randomPKCECodeVerifier();
|
const codeVerifier = client.randomPKCECodeVerifier();
|
||||||
const codeChallenge = await client.calculatePKCECodeChallenge(codeVerifier);
|
const codeChallenge = await client.calculatePKCECodeChallenge(codeVerifier);
|
||||||
|
|
@ -36,6 +90,10 @@ router.get(
|
||||||
router.get(
|
router.get(
|
||||||
'/callback',
|
'/callback',
|
||||||
asyncHandler(async (req, res) => {
|
asyncHandler(async (req, res) => {
|
||||||
|
if (config.devBypassAuth) {
|
||||||
|
return res.redirect('/auth/login');
|
||||||
|
}
|
||||||
|
|
||||||
const oidcConfig = getOidcConfig();
|
const oidcConfig = getOidcConfig();
|
||||||
const pending = req.session.oidc;
|
const pending = req.session.oidc;
|
||||||
if (!pending) {
|
if (!pending) {
|
||||||
|
|
@ -68,14 +126,7 @@ router.get(
|
||||||
delete req.session.oidc;
|
delete req.session.oidc;
|
||||||
delete req.session.returnTo;
|
delete req.session.returnTo;
|
||||||
|
|
||||||
req.session.regenerate((err) => {
|
establishSession(req, res, user, returnTo);
|
||||||
if (err) throw err;
|
|
||||||
req.session.userId = user.id;
|
|
||||||
req.session.save((saveErr) => {
|
|
||||||
if (saveErr) throw saveErr;
|
|
||||||
res.redirect(returnTo);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
|
|
||||||
|
const devBypassAuth = process.env.DEV_BYPASS_AUTH === 'true';
|
||||||
|
|
||||||
function required(name) {
|
function required(name) {
|
||||||
const value = process.env[name];
|
const value = process.env[name];
|
||||||
if (!value && process.env.NODE_ENV !== 'test') {
|
if (!value && process.env.NODE_ENV !== 'test') {
|
||||||
|
|
@ -13,9 +15,13 @@ module.exports = {
|
||||||
port: parseInt(process.env.PORT, 10) || 3000,
|
port: parseInt(process.env.PORT, 10) || 3000,
|
||||||
databaseUrl: required('DATABASE_URL'),
|
databaseUrl: required('DATABASE_URL'),
|
||||||
sessionSecret: required('SESSION_SECRET'),
|
sessionSecret: required('SESSION_SECRET'),
|
||||||
authentikIssuerUrl: required('AUTHENTIK_ISSUER_URL'),
|
// DEV_BYPASS_AUTH lets you run the app locally without a real Authentik
|
||||||
oidcClientId: required('OIDC_CLIENT_ID'),
|
// instance: /auth/login prompts for a name instead of redirecting to OIDC.
|
||||||
oidcClientSecret: required('OIDC_CLIENT_SECRET'),
|
// Never enable this outside local development.
|
||||||
oidcRedirectUri: required('OIDC_REDIRECT_URI'),
|
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',
|
adminGroupName: process.env.ADMIN_GROUP_NAME || 'octane-admins',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,11 @@ const { runMigrations } = require('./db/migrate');
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
await runMigrations();
|
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();
|
const app = createApp();
|
||||||
app.listen(config.port, () => {
|
app.listen(config.port, () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue