| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,4 +1,5 @@ | |||
| 1 | 1 | node_modules | |
| 2 | + .env | ||
| 2 | 3 | {{#if sqliteFile}} | |
| 3 | 4 | {{ sqliteFile }} | |
| 4 | 5 | {{/if}} | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,4 @@ | |||
| 1 | + # Secrets live in the gitignored .env; this committed file only lists which ones each developer must create. | ||
| 2 | + # create-app already wrote a random ADMINFORTH_SECRET into .env on the machine that scaffolded the project. | ||
| 3 | + # On a fresh clone, generate your own — see "First-time setup" in README.md (openssl rand -hex 32). | ||
| 4 | + ADMINFORTH_SECRET= | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1 +1,4 @@ | |||
| 1 | - # Add only sensitive local environment variables here; non-sensitive local variables should go to .env.local | ||
| 1 | + # Add only sensitive local environment variables here; non-sensitive local variables should go to .env.local | ||
| 2 | + # This file is gitignored. Each developer and each deployment needs its own ADMINFORTH_SECRET. | ||
| 3 | + | ||
| 4 | + ADMINFORTH_SECRET={{{adminforthSecret}}} | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,7 +1,6 @@ | |||
| 1 | 1 | # Add only non-sensitive local environment variables here so all team members can use them with minimal setup | |
| 2 | 2 | # For sensitive local environment variables, use .env and explain to team members how to set them, ideally with a .env.example | |
| 3 | 3 | ||
| 4 | - ADMINFORTH_SECRET=123 | ||
| 5 | 4 | NODE_ENV=development | |
| 6 | 5 | DEBUG_LEVEL=info | |
| 7 | 6 | AF_DEBUG_LEVEL=info | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -23,6 +23,13 @@ Create the initial migration and apply it to the database: | |||
| 23 | 23 | ``` | |
| 24 | 24 | {{/if}} | |
| 25 | 25 | ||
| 26 | + First-time setup on a fresh clone: `create-app` wrote a random `ADMINFORTH_SECRET` into `.env`, but that file is gitignored, so every developer and every deployment creates their own (see `.env.example`). The command below only creates `.env` when it does not exist yet: | ||
| 27 | + | ||
| 28 | + ```bash | ||
| 29 | + # On Windows (PowerShell): node -e "require('fs').writeFileSync('.env', 'ADMINFORTH_SECRET=' + require('crypto').randomBytes(32).toString('hex') + '\n', { flag: 'wx' })" | ||
| 30 | + [ -f .env ] || (umask 077; echo "ADMINFORTH_SECRET=$(openssl rand -hex 32)" > .env) | ||
| 31 | + ``` | ||
| 32 | + | ||
| 26 | 33 | Start the server: | |
| 27 | 34 | ||
| 28 | 35 | ```bash | |
@@ -48,8 +55,12 @@ Your colleagues will need to pull the changes and run `{{packageManagerRun}} mig | |||
| 48 | 55 | You have Dockerfile ready for production deployment. You can test the build with: | |
| 49 | 56 | ||
| 50 | 57 | ```bash | |
| 58 | + # Generate a signing key once and keep it in your secret store (never commit it). | ||
| 59 | + # On Windows without openssl: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" | ||
| 60 | + export ADMINFORTH_SECRET="$(openssl rand -hex 32)" | ||
| 61 | + | ||
| 51 | 62 | docker build -t {{appName}}-image . | |
| 52 | - docker run -p 3500:3500 -e ADMINFORTH_SECRET=123 {{#if sqliteFile}}-v $(pwd)/db:/code/db {{/if}}{{appName}}-image | ||
| 63 | + docker run -p 3500:3500 -e ADMINFORTH_SECRET="$ADMINFORTH_SECRET" {{#if sqliteFile}}-v $(pwd)/db:/code/db {{/if}}{{appName}}-image | ||
| 53 | 64 | ``` | |
| 54 | 65 | ||
| 55 | 66 | To set non-sensitive environment variables in production, use `.env.prod` file. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,6 +9,7 @@ import { Listr } from 'listr2' | |||
| 9 | 9 | import { fileURLToPath, pathToFileURL } from 'url'; | |
| 10 | 10 | import {ConnectionString} from 'connection-string'; | |
| 11 | 11 | import { exec } from 'child_process'; | |
| 12 | + import crypto from 'crypto'; | ||
| 12 | 13 | ||
| 13 | 14 | import Handlebars from 'handlebars'; | |
| 14 | 15 | import { promisify } from 'util'; | |
@@ -454,7 +455,7 @@ function getPackageManagerTemplateData(useNpm, nodeMajor) { | |||
| 454 | 455 | }; | |
| 455 | 456 | } | |
| 456 | 457 | ||
| 457 | - async function writeTemplateFiles(dirname, cwd, useNpm, includePrismaMigrations, options) { | ||
| 458 | + export async function writeTemplateFiles(dirname, cwd, useNpm, includePrismaMigrations, options) { | ||
| 458 | 459 | const { | |
| 459 | 460 | dbUrl, prismaDbUrl, appName, provider, existingDb, nodeMajor, | |
| 460 | 461 | dbUrlProd, prismaDbUrlProd, sqliteFile | |
@@ -540,10 +541,17 @@ async function writeTemplateFiles(dirname, cwd, useNpm, includePrismaMigrations, | |||
| 540 | 541 | data: {}, | |
| 541 | 542 | }, | |
| 542 | 543 | { | |
| 543 | - // We'll write .env using the same content as .env.sample | ||
| 544 | + // gitignored: holds the JWT signing key, unique per developer and per deployment | ||
| 544 | 545 | src: '.env.hbs', | |
| 545 | 546 | dest: '.env', | |
| 546 | - data: { dbUrl, prismaDbUrl: resolvedPrismaDbUrl }, | ||
| 547 | + data: { adminforthSecret: crypto.randomBytes(32).toString('hex') }, | ||
| 548 | + mode: 0o600, | ||
| 549 | + }, | ||
| 550 | + { | ||
| 551 | + // committed: tells the next developer which secrets to create locally | ||
| 552 | + src: '.env.example.hbs', | ||
| 553 | + dest: '.env.example', | ||
| 554 | + data: {}, | ||
| 547 | 555 | }, | |
| 548 | 556 | { | |
| 549 | 557 | src: 'adminuser.ts.hbs', | |
@@ -635,7 +643,11 @@ async function writeTemplateFiles(dirname, cwd, useNpm, includePrismaMigrations, | |||
| 635 | 643 | ...packageManagerTemplateData, | |
| 636 | 644 | ...task.data, | |
| 637 | 645 | }); | |
| 638 | - await fs.promises.writeFile(destPath, compiled); | ||
| 646 | + await fs.promises.writeFile(destPath, compiled, task.mode ? { mode: task.mode } : undefined); | ||
| 647 | + if (task.mode) { | ||
| 648 | + // writeFile's mode is masked by the umask and ignored on overwrite; enforce it unconditionally | ||
| 649 | + await fs.promises.chmod(destPath, task.mode); | ||
| 650 | + } | ||
| 639 | 651 | } | |
| 640 | 652 | } | |
| 641 | 653 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -100,7 +100,8 @@ myadmin/ | |||
| 100 | 100 | ├── package.json # Project dependencies | |
| 101 | 101 | ├── pnpm-workspace.yaml | |
| 102 | 102 | ├── tsconfig.json # TypeScript configuration | |
| 103 | - ├── .env # Env vars like tokens, secrets that should not be in version control | ||
| 103 | + ├── .env # Env vars like tokens, secrets that should not be in version control (ADMINFORTH_SECRET is generated here for you) | ||
| 104 | + ├── .env.example # Committed template listing the secrets each developer must create locally | ||
| 104 | 105 | ├── .env.local # General local environment variables | |
| 105 | 106 | └── .gitignore | |
| 106 | 107 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -47,12 +47,24 @@ Create two files in your project's root directory: | |||
| 47 | 47 | Put the following content to the `.env.local` file: | |
| 48 | 48 | ||
| 49 | 49 | ```bash title="./.env.local" | |
| 50 | - ADMINFORTH_SECRET=123 | ||
| 51 | 50 | NODE_ENV=development | |
| 52 | 51 | DATABASE_URL=sqlite://.db.sqlite | |
| 53 | 52 | PRISMA_DATABASE_URL=file:.db.sqlite | |
| 54 | 53 | ``` | |
| 55 | 54 | ||
| 55 | + Generate a signing key into the gitignored `.env` file, readable by you only (AdminForth logs a warning at startup if the secret is shorter than 16 characters; on Windows without openssl use `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`): | ||
| 56 | + | ||
| 57 | + ```bash | ||
| 58 | + (umask 077; echo "ADMINFORTH_SECRET=$(openssl rand -hex 32)" > .env) | ||
| 59 | + ``` | ||
| 60 | + | ||
| 61 | + Make sure `.env` never reaches the repository or a Docker image: | ||
| 62 | + | ||
| 63 | + ```bash | ||
| 64 | + echo ".env" >> .gitignore | ||
| 65 | + echo ".env" >> .dockerignore | ||
| 66 | + ``` | ||
| 67 | + | ||
| 56 | 68 | > ☝️ Production best practices: | |
| 57 | 69 | > | |
| 58 | 70 | > 1) Most likely you not need `.env` file at all, instead you should use environment variables (from Docker, Kubernetes, Operating System, etc.) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -33,8 +33,12 @@ docker build -t myadminapp . | |||
| 33 | 33 | And run container with: | |
| 34 | 34 | ||
| 35 | 35 | ```bash | |
| 36 | + # Generate a signing key once and keep it in your secret store (never commit it). | ||
| 37 | + # On Windows without openssl: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" | ||
| 38 | + export ADMINFORTH_SECRET="$(openssl rand -hex 32)" | ||
| 39 | + | ||
| 36 | 40 | docker run -p 3500:3500 \ | |
| 37 | - -e ADMINFORTH_SECRET=CHANGEME \ | ||
| 41 | + -e ADMINFORTH_SECRET="$ADMINFORTH_SECRET" \ | ||
| 38 | 42 | -v $(pwd)/db:/code/db \ | |
| 39 | 43 | myadminapp | |
| 40 | 44 | ``` | |
@@ -115,7 +119,7 @@ services: | |||
| 115 | 119 | build: ./adminforth-app | |
| 116 | 120 | environment: | |
| 117 | 121 | - NODE_ENV=production | |
| 118 | - - ADMINFORTH_SECRET=!CHANGEME! # ☝️ replace with your secret | ||
| 122 | + - ADMINFORTH_SECRET=${ADMINFORTH_SECRET:?generate one with openssl rand -hex 32} # ☝️ export it in the shell or put it in a gitignored .env next to this compose file; compose refuses to start when it is unset | ||
| 119 | 123 | labels: | |
| 120 | 124 | - "traefik.enable=true" | |
| 121 | 125 | - "traefik.http.routers.adminforth.tls=true" | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -620,6 +620,11 @@ class AdminForth implements IAdminForth { | |||
| 620 | 620 | ADMINFORTH_SECRET variable is used to sign JWT tokens | |
| 621 | 621 | `); | |
| 622 | 622 | } | |
| 623 | + if (adminforthSecret.length < 16) { | ||
| 624 | + afLogger.warn(`ADMINFORTH_SECRET is too short (${adminforthSecret.length} characters). ` + | ||
| 625 | + 'It is the key that signs every auth cookie: a guessable value lets anyone forge a session for any user. ' + | ||
| 626 | + 'Generate one with: openssl rand -hex 32'); | ||
| 627 | + } | ||
| 623 | 628 | } | |
| 624 | 629 | ||
| 625 | 630 | async getAllTables(): Promise<{ [dataSourceId: string]: string[] }> { | |
| Back | FazBrowse Home | New Git URL |
0 commit comments