@blakearoberts/visage 0.0.1-rc.25 → 0.0.1-rc.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Visage
2
2
 
3
- Visage (`/vit·ɛdʒ/`) is a Vite plugin for local development with HMR and OIDC session cookie lifecycle semantics.
3
+ Visage (`/vit·ɛdʒ/`) is a Vite plugin for local development with HMR and OIDC
4
+ session cookie lifecycle semantics.
4
5
 
5
6
  ## Getting Started
6
7
 
@@ -27,21 +28,32 @@ Start Vite normally:
27
28
  vite
28
29
  ```
29
30
 
30
- By default, you can reach the app at `https://localhost:9001`. You will be redirected to Dex to sign in. The default username and password is `user@example.com` and `pass`.
31
+ By default, you can reach the app at `https://localhost:9001`. You will be
32
+ redirected to Dex to sign in. The default username and password is
33
+ `user@example.com` and `pass`.
31
34
 
32
35
  ## Why Visage
33
36
 
34
- Visage is a local development harness for web apps that run behind an auth-protected edge, where browser sessions are represented by secure cookies backed by OIDC tokens.
37
+ Visage is a local development harness for web apps that run behind an
38
+ auth-protected edge, where browser sessions are represented by secure cookies
39
+ backed by OIDC tokens.
35
40
 
36
- Visage narrows the gap between local development, automated tests, and production by bringing production-like session lifecycle semantics to local Vite development without giving up HMR. That makes it practical to iterate on SSR identity injection, session timeout recovery, lock screens, and authenticated API calls.
41
+ Visage narrows the gap between local development, automated tests, and
42
+ production by bringing production-like session lifecycle semantics to local Vite
43
+ development without giving up HMR. That makes it practical to iterate on SSR
44
+ identity injection, session timeout recovery, lock screens, and authenticated
45
+ API calls.
37
46
 
38
- Visage can also use a hosted IdP, so local frontend code can call hosted backend APIs with real credentials. That avoids frontend-only auth mocks or backend-only local bypasses: code can be written for production and still work locally.
47
+ Visage can also use a hosted IdP, so local frontend code can call hosted backend
48
+ APIs with real credentials. That avoids frontend-only auth mocks or backend-only
49
+ local bypasses: code can be written for production and still work locally.
39
50
 
40
51
  ## Configuration
41
52
 
42
53
  Visage is configured through `visage(options?)` in `vite.config.ts`.
43
54
 
44
- The top-level `host` and `port` configure the local Visage origin that the browser visits:
55
+ The top-level `host` and `port` configure the local Visage origin that the
56
+ browser visits:
45
57
 
46
58
  ```ts
47
59
  visage({ host: 'localhost', port: 9001 });
@@ -94,8 +106,7 @@ visage({
94
106
  ```
95
107
 
96
108
  OAuth2 Proxy identity values can also be mapped explicitly through headers such
97
- as `$auth_user`, `$auth_email`, `$auth_groups`, and
98
- `$auth_preferred_username`.
109
+ as `$auth_user`, `$auth_email`, `$auth_groups`, and `$auth_preferred_username`.
99
110
 
100
111
  Authenticated locations also get Fetch Metadata CSRF checks by default. The
101
112
  built-in Vite root location uses `csrf: 'app'`, which allows same-origin
@@ -141,15 +152,12 @@ flowchart LR
141
152
 
142
153
  ## Required Tools
143
154
 
144
- - [Docker](https://docs.docker.com/get-started/get-docker/) with Compose v2 support through `docker compose`.
155
+ - [Docker](https://docs.docker.com/get-started/get-docker/) with Compose v2
156
+ support through `docker compose`.
157
+ - [`mkcert`](https://github.com/FiloSottile/mkcert#installation) installed on
158
+ `PATH`, or configured with `VISAGE_MKCERT=/path/to/mkcert`.
145
159
 
146
- ## Managed Tools
147
-
148
- ### mkcert
149
-
150
- Visage downloads [`mkcert`](https://github.com/FiloSottile/mkcert) from `dl.filippo.io` into `$XDG_CACHE_HOME/visage/bin/mkcert-<platform>-<arch>` when the Vite dev server starts. Visage uses it to install a local certificate authority and generate HTTPS certificates for the local proxy.
151
-
152
- ### Docker Images
160
+ ## Managed Docker Images
153
161
 
154
162
  Visage pulls these as needed based on configuration:
155
163
 
@@ -161,9 +169,15 @@ Visage pulls these as needed based on configuration:
161
169
 
162
170
  ## Security Notes
163
171
 
164
- Visage is local-development tooling. It starts local auth infrastructure, terminates local HTTPS, and forwards authenticated identity or token material to configured upstreams.
172
+ Visage is local-development tooling. It starts local auth infrastructure,
173
+ terminates local HTTPS, and forwards authenticated identity or token material to
174
+ configured upstreams.
175
+
176
+ Please report suspected vulnerabilities through GitHub private vulnerability
177
+ reporting as described in [Security Policy](SECURITY.md).
165
178
 
166
- Do not treat the managed Dex and OAuth2 Proxy defaults as production auth infrastructure.
179
+ Do not treat the managed Dex and OAuth2 Proxy defaults as production auth
180
+ infrastructure.
167
181
 
168
182
  Visage's CSRF policy is an edge request-isolation guard for cookie-backed
169
183
  locations. It is not a replacement for application-owned CSRF tokens where an
@@ -172,14 +186,19 @@ application accepts form posts or other browser-submitted mutations. CSP,
172
186
 
173
187
  ## Troubleshooting
174
188
 
175
- - If startup fails immediately, confirm Docker is running and `docker compose` works.
189
+ - If startup fails immediately, confirm Docker is running and `docker compose`
190
+ works.
176
191
  - If NGINX cannot start, check whether the configured `port` is already in use.
177
- - If the hostname cannot be resolved, Visage may need permission to update `/etc/hosts`.
178
- - If the browser rejects the certificate, allow the local certificate authority prompt from `mkcert`; CI test runners should be configured to ignore local HTTPS errors.
192
+ - If the hostname cannot be resolved, Visage may need permission to update
193
+ `/etc/hosts`.
194
+ - If the browser rejects the certificate, allow the local certificate authority
195
+ prompt from `mkcert`; CI test runners should be configured to ignore local
196
+ HTTPS errors.
179
197
 
180
198
  ## TO-DO
181
199
 
182
- - [ ] Harden the default security posture by addressing the [security hardening backlog](docs/security-hardening.md).
200
+ - [ ] Harden the default security posture by addressing the
201
+ [security hardening backlog](docs/security-hardening.md).
183
202
  - [ ] Support configuring [Dex connectors](https://dexidp.io/docs/connectors/).
184
203
  - [ ] Support configuring Dex on a distinct subdomain, such as `auth.localhost`.
185
204
  - [ ] Support optional [HTTP mode without local TLS](docs/tls-http-mode.md).
@@ -1 +1 @@
1
- {"version":3,"file":"certs.d.ts","sourceRoot":"","sources":["../src/certs.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAI7C,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAuCrE"}
1
+ {"version":3,"file":"certs.d.ts","sourceRoot":"","sources":["../src/certs.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAI7C,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAwCrE"}
package/dist/index.js CHANGED
@@ -2,11 +2,9 @@ import { spawnSync, spawn } from 'node:child_process';
2
2
  import { randomBytes } from 'node:crypto';
3
3
  import { isIP } from 'node:net';
4
4
  import { join } from 'node:path';
5
- import { readFileSync, mkdirSync, chmodSync, openSync, rmSync, existsSync, createWriteStream, appendFileSync, writeFileSync } from 'node:fs';
5
+ import { readFileSync, mkdirSync, chmodSync, openSync, rmSync, appendFileSync, writeFileSync } from 'node:fs';
6
6
  import { parse, stringify } from 'yaml';
7
7
  import { homedir } from 'node:os';
8
- import { Readable } from 'node:stream';
9
- import { pipeline } from 'node:stream/promises';
10
8
  import { hashSync } from 'bcryptjs';
11
9
  import { Eta } from 'eta';
12
10
 
@@ -465,13 +463,14 @@ async function ensureCerts(config) {
465
463
  const CAROOT = join(CACHE_HOME, 'visage/ca');
466
464
  mkdirSync(CAROOT, { recursive: true, mode: 0o700 });
467
465
  chmodSync(CAROOT, 0o700);
468
- const mkcert = await ensureMkCert();
466
+ const mkcert = resolveMkcert();
469
467
  const out = openSync(join(config.cache, 'logs', 'mkcert.log'), 'w');
470
468
  const env = { CAROOT, TRUST_STORES: 'system', ...process.env };
471
469
  const tty = process.stdin.isTTY;
472
470
  const stdio = [tty ? 'inherit' : 'ignore', out, out];
473
471
  if (process.env.CI !== 'true') {
474
- // mkcert -install is idempotent; CA files alone do not prove trust-store state.
472
+ // mkcert -install is idempotent;
473
+ // CA files alone don't prove trust-store state.
475
474
  const result = spawnSync(mkcert, ['-install'], { env, stdio });
476
475
  if (result.error)
477
476
  throw result.error;
@@ -497,23 +496,77 @@ async function ensureCerts(config) {
497
496
  chmodSync(cert, 0o600);
498
497
  chmodSync(key, 0o600);
499
498
  }
500
- async function ensureMkCert() {
501
- const bin = join(CACHE_HOME, 'visage/bin');
502
- const file = join(bin, `mkcert-${process.platform}-${process.arch}`);
503
- if (existsSync(file))
504
- return file;
505
- mkdirSync(bin, { recursive: true });
506
- const base = 'https://dl.filippo.io/mkcert/latest';
507
- const arch = process.arch === 'x64' ? 'amd64' : process.arch;
508
- const params = `?for=${process.platform}/${arch}`;
509
- const url = new URL(params, base);
510
- const response = await fetch(url);
511
- if (!response.ok || !response.body) {
512
- throw new Error('Failed to download mkcert');
499
+ function resolveMkcert() {
500
+ const env = process.env;
501
+ const options = { encoding: 'utf8', env };
502
+ const mkcert = findMkcert();
503
+ const result = spawnSync(mkcert, ['-version'], options);
504
+ if (result.error || result.status !== 0) {
505
+ throw new Error([
506
+ `Visage found mkcert at "${mkcert}", but could not execute it.`,
507
+ '',
508
+ mkcertInstallInstructions(),
509
+ ].join('\n'));
513
510
  }
514
- await pipeline(Readable.fromWeb(response.body), createWriteStream(file));
515
- chmodSync(file, 0o755);
516
- return file;
511
+ return mkcert;
512
+ }
513
+ function findMkcert() {
514
+ const env = process.env;
515
+ const exec = env.VISAGE_MKCERT || 'mkcert';
516
+ const options = { encoding: 'utf8', env };
517
+ const result = process.platform === 'win32'
518
+ ? spawnSync('where', [exec], options)
519
+ : spawnSync('sh', ['-c', `command -v ${exec}`], options);
520
+ const path = result.stdout
521
+ .split(/\r?\n/)
522
+ .map((line) => line.trim())
523
+ .find(Boolean);
524
+ if (result.error || result.status !== 0 || !path) {
525
+ throw new Error([
526
+ 'Visage requires mkcert to configure HTTPS, but mkcert was not found.',
527
+ '',
528
+ mkcertInstallInstructions(),
529
+ ].join('\n'));
530
+ }
531
+ return path;
532
+ }
533
+ function mkcertInstallInstructions() {
534
+ const common = [
535
+ 'After installing mkcert, run `mkcert -install` once when local ' +
536
+ 'certificates should be trusted.',
537
+ 'Install docs: https://github.com/FiloSottile/mkcert#installation',
538
+ 'Set VISAGE_MKCERT=/path/to/mkcert to use a custom executable.',
539
+ ];
540
+ const platform = process.platform;
541
+ if (platform === 'darwin') {
542
+ return [
543
+ 'Install mkcert with Homebrew:',
544
+ ' brew install mkcert',
545
+ ' brew install nss # optional, for Firefox',
546
+ ...common,
547
+ ].join('\n');
548
+ }
549
+ if (platform === 'win32') {
550
+ return [
551
+ 'Install mkcert with Chocolatey or Scoop:',
552
+ ' choco install mkcert',
553
+ ' scoop install mkcert',
554
+ ...common,
555
+ ].join('\n');
556
+ }
557
+ if (platform === 'linux') {
558
+ return [
559
+ 'Install mkcert with your Linux package manager. Common commands:',
560
+ ' sudo apt install mkcert libnss3-tools',
561
+ ' sudo dnf install mkcert nss-tools',
562
+ ' sudo pacman -Syu mkcert nss',
563
+ ...common,
564
+ ].join('\n');
565
+ }
566
+ return [
567
+ 'Install mkcert for your operating system and make it available on PATH.',
568
+ ...common,
569
+ ].join('\n');
517
570
  }
518
571
 
519
572
  let stopRef;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blakearoberts/visage",
3
- "version": "0.0.1-rc.25",
3
+ "version": "0.0.1-rc.27",
4
4
  "description": "Vite plugin for local development with HMR and OIDC session cookie lifecycle semantics.",
5
5
  "type": "module",
6
6
  "author": "Blake Roberts",
@@ -48,14 +48,22 @@
48
48
  "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
49
49
  "example": "npm run example:simple",
50
50
  "example:simple": "cd examples/simple && npm run dev",
51
- "format": "prettier --write \"{docs,examples,src,test}/**/*.{ts,tsx,js,jsx,json,css,html,md}\" \"*.{json,md}\"",
52
- "format:check": "prettier --check \"{docs,examples,src,test}/**/*.{ts,tsx,js,jsx,json,css,html,md}\" \"*.{json,md}\"",
51
+ "format": "prettier --write .",
52
+ "format:check": "prettier --check .",
53
53
  "promote:codex": "scripts/promote-codex.sh",
54
54
  "test": "npm run typecheck && npm run test:unit",
55
55
  "test:all": "npm test && npm run test:e2e",
56
56
  "test:e2e": "playwright test test/e2e",
57
57
  "test:unit": "node --experimental-strip-types --test test/unit/*.test.ts",
58
- "typecheck": "tsc --noEmit && tsc -p tsconfig.test.json"
58
+ "typecheck": "tsc --noEmit"
59
+ },
60
+ "dependencies": {
61
+ "bcryptjs": "^3.0.3",
62
+ "eta": "^4.6.0",
63
+ "yaml": "^2.9.0"
64
+ },
65
+ "peerDependencies": {
66
+ "vite": "^8.0.0"
59
67
  },
60
68
  "devDependencies": {
61
69
  "@playwright/test": "^1.60.0",
@@ -66,10 +74,5 @@
66
74
  "tslib": "^2.8.1",
67
75
  "typescript": "^6.0.3",
68
76
  "vite": "^8.0.13"
69
- },
70
- "dependencies": {
71
- "bcryptjs": "^3.0.3",
72
- "eta": "^4.6.0",
73
- "yaml": "^2.9.0"
74
77
  }
75
78
  }