@blakearoberts/visage 0.0.5-rc.2 → 0.0.5-rc.21

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
@@ -108,13 +108,14 @@ visage({
108
108
  OAuth2 Proxy identity values can also be mapped explicitly through headers such
109
109
  as `$auth_user`, `$auth_email`, `$auth_groups`, and `$auth_preferred_username`.
110
110
 
111
- Authenticated locations also get Fetch Metadata CSRF checks by default. The
112
- built-in Vite root location uses `csrf: 'app'`, which allows same-origin
113
- requests and top-level `GET` document navigations. Other authenticated upstream
114
- locations use `csrf: 'api'`, which blocks same-site and cross-site browser
115
- requests when modern Fetch Metadata headers are present. Set `csrf: 'app'` for
116
- an upstream that serves browser pages, or `csrf: false` when the upstream
117
- intentionally handles cross-site browser requests itself.
111
+ Authenticated locations also get CSRF checks by default. The policy allows
112
+ same-origin requests and top-level `GET` document navigations, blocks other
113
+ same-site and cross-site browser requests when modern Fetch Metadata headers are
114
+ present, and falls back to an exact `Origin` match or `Referer` origin-prefix
115
+ match against the configured browser origin for unsafe methods when Fetch
116
+ Metadata is absent or unrecognized. Set `csrf: false` when the upstream
117
+ intentionally handles cross-site browser requests itself. See
118
+ [CSRF Policy](../../docs/csrf-policy.md).
118
119
 
119
120
  ### External IdPs
120
121
 
@@ -175,15 +176,17 @@ terminates local HTTPS, and forwards authenticated identity or token material to
175
176
  configured upstreams.
176
177
 
177
178
  Please report suspected vulnerabilities through GitHub private vulnerability
178
- reporting as described in [Security Policy](SECURITY.md).
179
+ reporting as described in [Security Policy](../../SECURITY.md).
179
180
 
180
181
  Do not treat the managed Dex and OAuth2 Proxy defaults as production auth
181
182
  infrastructure.
182
183
 
183
- Visage's CSRF policy is an edge request-isolation guard for cookie-backed
184
- locations. It is not a replacement for application-owned CSRF tokens where an
185
- application accepts form posts or other browser-submitted mutations. CSP,
186
- `frame-ancestors`, and other click-jacking controls remain application policy.
184
+ Visage's CSRF policy is the edge CSRF control for cookie-backed locations.
185
+ Application CSRF tokens are not required for ordinary same-origin SPA mutations
186
+ under this policy. App-level tokens may still be useful when a location disables
187
+ the edge policy, intentionally accepts cross-site browser requests, or needs a
188
+ token that encodes additional authorization semantics. CSP, `frame-ancestors`,
189
+ and other click-jacking controls remain application policy.
187
190
 
188
191
  ## Troubleshooting
189
192
 
@@ -192,12 +195,3 @@ application accepts form posts or other browser-submitted mutations. CSP,
192
195
  - If NGINX cannot start, check whether the configured `port` is already in use.
193
196
  - If the hostname cannot be resolved, Visage may need permission to update
194
197
  `/etc/hosts`.
195
-
196
- ## TO-DO
197
-
198
- - [ ] Harden the default security posture by addressing the
199
- [security hardening backlog](docs/security-hardening.md).
200
- - [ ] Support [runtime config reloads](docs/config-reload.md).
201
- - [ ] Support [Dex connectors](https://dexidp.io/docs/connectors/).
202
- - [ ] Support Dex on a distinct subdomain, such as `auth.localhost`.
203
- - [ ] Support [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":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAI7C,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAwCrE"}
1
+ {"version":3,"file":"certs.d.ts","sourceRoot":"","sources":["../src/certs.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCrE"}
@@ -1 +1 @@
1
- {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,IAAI,CAmD7D"}
1
+ {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,IAAI,CA6E7D"}
package/dist/config.d.ts CHANGED
@@ -21,9 +21,8 @@ type ResolvedProxyPolicy = {
21
21
  readonly auth: {
22
22
  readonly enabled: boolean;
23
23
  readonly forward: false | 'id' | 'access';
24
- readonly redirect: boolean;
25
24
  };
26
- readonly csrf: false | 'app' | 'api';
25
+ readonly csrf: boolean;
27
26
  readonly headers: Readonly<Record<string, string>>;
28
27
  readonly directives: Readonly<Record<string, readonly string[]>>;
29
28
  };
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { randomBytes } from 'node:crypto';
2
2
  import { basename, resolve, join } from 'node:path';
3
- import { readFileSync, mkdirSync, chmodSync, openSync, rmSync, appendFileSync, writeFileSync } from 'node:fs';
3
+ import { readFileSync, openSync, mkdirSync, chmodSync, rmSync, appendFileSync, writeFileSync } from 'node:fs';
4
4
  import { parse, stringify } from 'yaml';
5
- import { spawnSync } from 'node:child_process';
6
- import { homedir } from 'node:os';
5
+ import { spawnSync, spawn } from 'node:child_process';
7
6
  import { hashSync } from 'bcryptjs';
8
7
  import { Eta } from 'eta';
8
+ import { isIP } from 'node:net';
9
9
 
10
10
  const VisageEdgeKeyHeader$1 = 'X-Visage-Edge-Key';
11
11
  const DockerImages = parse(readFileSync(new URL('../docker-compose.images.yml', import.meta.url), 'utf8')).services;
@@ -27,8 +27,8 @@ const BaseServiceOAuth2Proxy = {
27
27
  restart: 'always',
28
28
  };
29
29
  const DefaultProxyPolicy = {
30
- auth: { enabled: true, forward: false, redirect: false },
31
- csrf: 'api',
30
+ auth: { enabled: true, forward: false },
31
+ csrf: true,
32
32
  headers: {
33
33
  Host: '$host',
34
34
  // Mitigate header injection by clearing auth headers.
@@ -53,7 +53,7 @@ const BaseUpstreamOauth2Proxy = {
53
53
  port: 4180,
54
54
  locations: {
55
55
  '/oauth2/': {
56
- auth: { enabled: false, forward: false, redirect: false },
56
+ auth: { enabled: false, forward: false },
57
57
  csrf: false,
58
58
  headers: {
59
59
  ...DefaultProxyPolicy.headers,
@@ -63,7 +63,7 @@ const BaseUpstreamOauth2Proxy = {
63
63
  directives: { ...DefaultProxyPolicy.directives },
64
64
  },
65
65
  '= /oauth2/auth': {
66
- auth: { enabled: false, forward: false, redirect: false },
66
+ auth: { enabled: false, forward: false },
67
67
  csrf: false,
68
68
  headers: {
69
69
  ...DefaultProxyPolicy.headers,
@@ -77,7 +77,7 @@ const BaseUpstreamOauth2Proxy = {
77
77
  },
78
78
  },
79
79
  '/oauth2/sign_out': {
80
- auth: { enabled: false, forward: false, redirect: false },
80
+ auth: { enabled: false, forward: false },
81
81
  csrf: false,
82
82
  headers: {
83
83
  ...DefaultProxyPolicy.headers,
@@ -193,19 +193,14 @@ function resolveUpstreamsOptions(services, upstreams = {}) {
193
193
  };
194
194
  }
195
195
  const BaseViteUpstreamRootLocation = {
196
- auth: { enabled: true, forward: false, redirect: true },
197
- csrf: 'app',
196
+ auth: { enabled: true, forward: false },
197
+ csrf: true,
198
+ ws: true,
198
199
  headers: {
199
200
  Host: '$host',
200
- Upgrade: '$http_upgrade',
201
- Connection: '$connection_upgrade',
202
201
  'X-Auth-Request-User': '$auth_user',
203
202
  'X-Auth-Request-Email': '$auth_email',
204
203
  },
205
- directives: {
206
- proxy_http_version: ['1.1'],
207
- proxy_read_timeout: ['1h'],
208
- },
209
204
  };
210
205
  function resolveViteUpstreamOptions(upstream) {
211
206
  const base = BaseViteUpstreamRootLocation;
@@ -221,6 +216,7 @@ function resolveViteUpstreamOptions(upstream) {
221
216
  : {
222
217
  auth: { ...base.auth, ...root.auth },
223
218
  csrf: root.csrf ?? base.csrf,
219
+ ws: root.ws ?? base.ws,
224
220
  headers: {
225
221
  ...base.headers,
226
222
  ...root.headers,
@@ -244,18 +240,22 @@ function resolveUpstreamOptions(name, upstream = {}, external) {
244
240
  locations: {
245
241
  ...Object.fromEntries(Object.entries(upstream.locations ?? { [`/${name}/`]: {} }).map(([path, policy]) => [
246
242
  path,
247
- resolveUpstreamLocationOptions(name, host, policy, external),
243
+ resolveUpstreamLocationOptions(name, host, {
244
+ ...policy,
245
+ headers: { ...(upstream.headers ?? {}), ...policy.headers },
246
+ }, external),
248
247
  ])),
249
248
  },
250
249
  };
251
250
  }
252
251
  function resolveUpstreamLocationOptions(name, host, location, external) {
253
252
  const auth = resolveAuthPolicy(location.auth, external && name !== 'vite');
253
+ const { ws, ...policy } = location;
254
254
  return {
255
255
  ...DefaultProxyPolicy,
256
- ...location,
256
+ ...policy,
257
257
  auth,
258
- csrf: location.csrf ?? (auth.enabled ? 'api' : false),
258
+ csrf: location.csrf ?? auth.enabled,
259
259
  headers: {
260
260
  ...DefaultProxyPolicy.headers,
261
261
  ...(external ? { Host: host } : {}),
@@ -265,10 +265,22 @@ function resolveUpstreamLocationOptions(name, host, location, external) {
265
265
  ...(auth.enabled && auth.forward === 'access'
266
266
  ? { Authorization: '"Bearer $access_token"' }
267
267
  : {}),
268
+ ...(ws
269
+ ? {
270
+ Connection: '$connection_upgrade',
271
+ Upgrade: '$http_upgrade',
272
+ }
273
+ : {}),
268
274
  ...(location.headers ?? {}),
269
275
  },
270
276
  directives: {
271
277
  ...DefaultProxyPolicy.directives,
278
+ ...(ws
279
+ ? {
280
+ proxy_http_version: ['1.1'],
281
+ proxy_read_timeout: ['1h'],
282
+ }
283
+ : {}),
272
284
  ...Object.fromEntries(Object.entries(location.directives ?? {}).map(([name, value]) => [
273
285
  name,
274
286
  Array.isArray(value) ? value : [value],
@@ -284,7 +296,6 @@ function resolveAuthPolicy(auth = {}, external) {
284
296
  ? 'access'
285
297
  : 'id'
286
298
  : (auth.forward ?? false),
287
- redirect: auth.redirect ?? false,
288
299
  };
289
300
  }
290
301
  function resolveConfig(options) {
@@ -402,7 +413,7 @@ function resolveIdpConfig({ host, port, idp, }) {
402
413
  port: 5556,
403
414
  locations: {
404
415
  '/dex/': {
405
- auth: { enabled: false, forward: false, redirect: false },
416
+ auth: { enabled: false, forward: false },
406
417
  csrf: false,
407
418
  headers: { ...DefaultProxyPolicy.headers },
408
419
  directives: { ...DefaultProxyPolicy.directives },
@@ -465,20 +476,15 @@ function isVisageEdgeRequest(request, edgeKey) {
465
476
  return typeof header === 'string' && header === edgeKey;
466
477
  }
467
478
 
468
- const CACHE_HOME = process.env.XDG_CACHE_HOME || join(homedir(), '.cache');
469
479
  async function ensureCerts(config) {
470
- const CAROOT = join(CACHE_HOME, 'visage/ca');
471
- mkdirSync(CAROOT, { recursive: true, mode: 0o700 });
472
- chmodSync(CAROOT, 0o700);
473
480
  const mkcert = resolveMkcert();
474
481
  const out = openSync(join(config.cache, 'logs', 'mkcert.log'), 'w');
475
- const env = { CAROOT, TRUST_STORES: 'system', ...process.env };
476
482
  const tty = process.stdin.isTTY;
477
483
  const stdio = [tty ? 'inherit' : 'ignore', out, out];
478
- if (process.env.CI !== 'true') {
484
+ {
479
485
  // mkcert -install is idempotent;
480
486
  // CA files alone don't prove trust-store state.
481
- const result = spawnSync(mkcert, ['-install'], { env, stdio });
487
+ const result = spawnSync(mkcert, ['-install'], { stdio });
482
488
  if (result.error)
483
489
  throw result.error;
484
490
  if (result.status !== 0) {
@@ -494,7 +500,7 @@ async function ensureCerts(config) {
494
500
  rmSync(key, { force: true });
495
501
  const names = [...new Set([config.host, 'localhost', '127.0.0.1', '::1'])];
496
502
  const args = ['-cert-file', cert, '-key-file', key, ...names];
497
- const result = spawnSync(mkcert, args, { env, stdio });
503
+ const result = spawnSync(mkcert, args, { stdio });
498
504
  if (result.error)
499
505
  throw result.error;
500
506
  if (result.status !== 0) {
@@ -570,8 +576,6 @@ function mkcertInstallInstructions() {
570
576
  }
571
577
 
572
578
  function startCompose(config) {
573
- const logs = join(config.cache, 'logs');
574
- const output = openSync(join(logs, 'compose.log'), 'w');
575
579
  const compose = [
576
580
  'compose',
577
581
  '--ansi=never',
@@ -579,6 +583,7 @@ function startCompose(config) {
579
583
  `--project-name=${config.compose.name}`,
580
584
  `--profile=${process.platform}`,
581
585
  ];
586
+ const dir = join(config.cache, 'logs');
582
587
  const env = {
583
588
  COMPOSE_MENU: 'false',
584
589
  ...(config.oauth2.public
@@ -588,12 +593,32 @@ function startCompose(config) {
588
593
  [config.secrets.edgeKey]: config.edgeKey,
589
594
  [config.secrets.cookieSecret]: randomBytes(32).toString('base64url'),
590
595
  };
591
- const opts = {
592
- cwd: config.cache,
593
- stdio: ['ignore', output, output],
594
- env,
595
- };
596
+ const opts = { cwd: config.cache, env };
597
+ function validate() {
598
+ const args = [
599
+ ...compose,
600
+ 'run',
601
+ '--build',
602
+ '--quiet-build',
603
+ '--rm',
604
+ '--no-deps',
605
+ 'nginx',
606
+ 'nginx',
607
+ '-t',
608
+ '-q',
609
+ ];
610
+ const result = spawnSync('docker', args, {
611
+ ...opts,
612
+ stdio: ['ignore', 'ignore', 'inherit'],
613
+ });
614
+ if (result.error)
615
+ throw result.error;
616
+ if (result.status !== 0) {
617
+ throw new Error('Failed to validate NGINX configuration');
618
+ }
619
+ }
596
620
  function up() {
621
+ const out = openSync(join(dir, 'compose.log'), 'w');
597
622
  const args = [
598
623
  ...compose,
599
624
  'up',
@@ -601,13 +626,20 @@ function startCompose(config) {
601
626
  '--force-recreate',
602
627
  '--remove-orphans',
603
628
  ];
604
- return spawnSync('docker', args, opts);
629
+ return spawnSync('docker', args, { ...opts, stdio: ['ignore', out, out] });
605
630
  }
606
631
  function down() {
632
+ const out = openSync(join(dir, 'compose.log'), 'a');
607
633
  const args = [...compose, 'down', '--remove-orphans'];
608
- return spawnSync('docker', args, opts);
634
+ return spawnSync('docker', args, { ...opts, stdio: ['ignore', out, out] });
635
+ }
636
+ function follow() {
637
+ const out = openSync(join(dir, 'container.log'), 'w');
638
+ const args = [...compose, 'logs', '--follow'];
639
+ return spawn('docker', args, { ...opts, stdio: ['ignore', out, out] });
609
640
  }
610
641
  down();
642
+ validate();
611
643
  const result = up();
612
644
  if (result.error)
613
645
  throw result.error;
@@ -615,7 +647,11 @@ function startCompose(config) {
615
647
  down();
616
648
  throw new Error('Failed to start Docker Compose');
617
649
  }
618
- return down;
650
+ const logs = follow();
651
+ return () => {
652
+ down();
653
+ logs.kill('SIGINT');
654
+ };
619
655
  }
620
656
 
621
657
  const HOSTS_FILE = '/etc/hosts';
@@ -670,12 +706,6 @@ function writeComposeConfig(config) {
670
706
  }
671
707
  function renderComposeConfig(config) {
672
708
  const { dex, nginx, oauth2_proxy, ...services } = config.services;
673
- const bridge = spawnSync('docker', [
674
- 'network',
675
- 'inspect',
676
- 'bridge',
677
- '--format={{ (index .IPAM.Config 0).Gateway }}',
678
- ], { encoding: 'utf8' }).stdout.trim();
679
709
  return stringify({
680
710
  secrets: {
681
711
  [config.secrets.cookieSecret]: {
@@ -711,7 +741,9 @@ function renderComposeConfig(config) {
711
741
  ? {
712
742
  vite_loopback: {
713
743
  image: DockerImages.socat.image,
714
- command: `tcp-listen:${config.upstreams.vite.port},fork,bind=${bridge} tcp-connect:127.0.0.1:${config.upstreams.vite.port}`,
744
+ command: `tcp-listen:${config.upstreams.vite.port},fork,bind=host.docker.internal ` +
745
+ `tcp-connect:127.0.0.1:${config.upstreams.vite.port}`,
746
+ extra_hosts: ['host.docker.internal:host-gateway'],
715
747
  network_mode: 'host',
716
748
  profiles: ['linux'],
717
749
  restart: 'always',
@@ -784,6 +816,7 @@ function renderDexConfig(config) {
784
816
 
785
817
  const template = `
786
818
  load_module modules/ngx_http_js_module.so;
819
+ include /etc/nginx/modules/*.conf;
787
820
 
788
821
  events {}
789
822
 
@@ -792,6 +825,8 @@ http {
792
825
  js_shared_dict_zone zone=edge_key:32k;
793
826
  js_set $edge_key edge_key;
794
827
 
828
+ include /etc/nginx/http.d/*.conf;
829
+
795
830
  # Disable IPv6 DNS lookup. Docker Desktop (com.docker.backend), doesn't
796
831
  # support IPv6 traffic translation to host loopback.
797
832
  resolver 127.0.0.11 ipv6=off;
@@ -809,16 +844,49 @@ http {
809
844
  '' close;
810
845
  }
811
846
 
812
- # Fetch Metadata CSRF guards for cookie-backed locations.
813
- map $http_sec_fetch_site $csrf_api {
814
- default 0;
815
- same-site 1;
816
- cross-site 1;
847
+ # Fetch Metadata CSRF guard for cookie auth locations.
848
+ map $request_method $csrf_method {
849
+ default unsafe;
850
+ GET safe;
851
+ HEAD safe;
852
+ OPTIONS safe;
853
+ }
854
+ map $http_origin $csrf_origin {
855
+ default invalid;
856
+ '' absent;
857
+ "<%~ it.csrf.origin %>" match;
858
+ }
859
+ map $http_referer $csrf_referer {
860
+ default invalid;
861
+ '' absent;
862
+ "<%~ it.csrf.referer %>" match;
817
863
  }
818
- map "$http_sec_fetch_site:$request_method:$http_sec_fetch_mode:$http_sec_fetch_dest" $csrf_app {
819
- default 0;
820
- ~^(cross-site|same-site):GET:navigate:document$ 0;
821
- ~^(cross-site|same-site): 1;
864
+ map "$csrf_method:$csrf_origin:$csrf_referer" $csrf_fallback {
865
+ default deny;
866
+ "~^safe:.*:.*$" allow;
867
+ unsafe:match:match allow;
868
+ unsafe:match:absent allow;
869
+ unsafe:absent:match allow;
870
+ }
871
+ map "$request_method:$http_sec_fetch_mode:$http_sec_fetch_dest" $csrf_doc_nav {
872
+ default 0;
873
+ GET:navigate:document 1;
874
+ }
875
+ map "$http_sec_fetch_site:$csrf_doc_nav" $csrf_fetch_metadata {
876
+ default fallback;
877
+ "~^(same-origin|none):(0|1)$" allow;
878
+ "~^(same-site|cross-site):1$" allow;
879
+ "~^(same-site|cross-site):0$" deny;
880
+ }
881
+ map "$csrf_fetch_metadata:$csrf_fallback" $csrf_reject {
882
+ default 1;
883
+ allow:deny 0;
884
+ allow:allow 0;
885
+ fallback:allow 0;
886
+ }
887
+ map "$request_method:$http_sec_fetch_mode:$http_sec_fetch_dest" $auth_error_page {
888
+ default @auth_401;
889
+ ~^(GET|HEAD):navigate:document$ @auth_redirect;
822
890
  }
823
891
 
824
892
  <%_ for (const [name, upstream] of Object.entries(it.upstreams)) { %>
@@ -841,25 +909,20 @@ http {
841
909
  ssl_certificate_key <%~ it.ssl.key %>;
842
910
 
843
911
  access_log /var/log/nginx/access.log access_log_format;
844
- set $auth_email "";
845
912
 
846
913
  # Redirect HTTP to HTTPS.
847
914
  error_page 497 =301 https://$http_host$request_uri;
848
915
 
916
+ set $auth_email "";
917
+
849
918
  <%_ for (const [name, upstream] of Object.entries(it.upstreams)) { %>
850
919
  <%_ for (const [path, location] of Object.entries(upstream.locations)) { %>
851
920
  location <%~ path %> {
852
921
  <%_ if (location.csrf) { %>
853
- add_header Vary "Sec-Fetch-Site, Sec-Fetch-Mode, Sec-Fetch-Dest" always;
854
- <%_ if (location.csrf === 'app') { %>
855
- if ($csrf_app) {
922
+ add_header Vary "Sec-Fetch-Site, Sec-Fetch-Mode, Sec-Fetch-Dest, Origin, Referer" always;
923
+ if ($csrf_reject) {
856
924
  return 403;
857
925
  }
858
- <%_ } else { %>
859
- if ($csrf_api) {
860
- return 403;
861
- }
862
- <%_ } %>
863
926
 
864
927
  <%_ } %>
865
928
  <%_ if (location.auth?.enabled) { %>
@@ -874,10 +937,7 @@ http {
874
937
  # Propagate refreshed session cookie.
875
938
  auth_request_set $auth_cookie $upstream_http_set_cookie;
876
939
  add_header Set-Cookie $auth_cookie;
877
-
878
- <%_ if (location.auth.redirect) { %>
879
- error_page 401 =302 /oauth2/start?rd=$scheme://$http_host$request_uri;
880
- <%_ } %>
940
+ error_page 401 = $auth_error_page;
881
941
  <%_ } %>
882
942
  <%_ if (name === 'vite') { %>
883
943
  proxy_set_header <%~ it.edgeKey.header %> $edge_key;
@@ -892,11 +952,11 @@ http {
892
952
  <%_ } %>
893
953
  <%_ if (upstream.scheme === 'https') { %>
894
954
  proxy_ssl_server_name on;
895
- proxy_ssl_name <%~ upstream.host %>;
955
+ proxy_ssl_name <%~ upstream.host %>;
896
956
  <%_ if (upstream.external) { %>
897
957
  proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
898
- proxy_ssl_verify on;
899
- proxy_ssl_verify_depth 3;
958
+ proxy_ssl_verify on;
959
+ proxy_ssl_verify_depth 3;
900
960
  <%_ } %>
901
961
  <%_ } %>
902
962
  proxy_pass <%~ upstream.scheme %>://<%~ name %>;
@@ -904,6 +964,12 @@ http {
904
964
  <%_ } %>
905
965
 
906
966
  <%_ } %>
967
+ location @auth_redirect {
968
+ return 302 /oauth2/start?rd=$scheme://$http_host$request_uri;
969
+ }
970
+ location @auth_401 {
971
+ return 401;
972
+ }
907
973
  }
908
974
  }
909
975
  `;
@@ -926,9 +992,12 @@ function writeNginxConfig(config) {
926
992
  writeFileSync(edgeKeyFile, edgeKeyRender, 'utf-8');
927
993
  }
928
994
  function renderNginxConfig(config) {
995
+ const origin = `https://${config.host}:${config.port}`;
996
+ const referer = `~^${origin.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([/?#]|$)`;
929
997
  const data = {
930
998
  host: config.host,
931
999
  port: config.port,
1000
+ csrf: { origin, referer },
932
1001
  ssl: {
933
1002
  cert: join(config.files.certs[1], 'tls.crt'),
934
1003
  key: join(config.files.certs[1], 'tls.key'),
@@ -943,7 +1012,7 @@ function renderNginxConfig(config) {
943
1012
  ...upstream,
944
1013
  resolve: upstream.host === 'host.docker.internal'
945
1014
  ? process.platform !== 'linux'
946
- : upstream.external,
1015
+ : isIP(upstream.host) === 0,
947
1016
  },
948
1017
  ])),
949
1018
  };
@@ -965,6 +1034,7 @@ function writeOauth2ProxyConfig(config) {
965
1034
  function renderOauth2ProxyConfig(config) {
966
1035
  const data = {
967
1036
  http_address: `127.0.0.1:${config.upstreams.oauth2_proxy.port}`,
1037
+ metrics_address: '0.0.0.0:4181',
968
1038
  provider: 'oidc',
969
1039
  oidc_issuer_url: config.idp.oidc.issuer,
970
1040
  ...(config.idp.oidc.algs === undefined
@@ -1078,7 +1148,7 @@ function visage(options = {}) {
1078
1148
  server: {
1079
1149
  // Configure Vite to only allow traffic from the intended host.
1080
1150
  allowedHosts: [resolvedOptions.host],
1081
- hmr: {
1151
+ ws: {
1082
1152
  protocol: 'wss',
1083
1153
  host: resolvedOptions.host,
1084
1154
  clientPort: resolvedOptions.port,
@@ -1 +1 @@
1
- {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/render/compose.ts"],"names":[],"mappings":"AAMA,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAE5D,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAI7D"}
1
+ {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/render/compose.ts"],"names":[],"mappings":"AAKA,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAE5D,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAI7D"}
@@ -1 +1 @@
1
- {"version":3,"file":"nginx.d.ts","sourceRoot":"","sources":["../../src/render/nginx.ts"],"names":[],"mappings":"AAIA,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AA2InE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAQ3D"}
1
+ {"version":3,"file":"nginx.d.ts","sourceRoot":"","sources":["../../src/render/nginx.ts"],"names":[],"mappings":"AAKA,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AA6KnE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAQ3D"}
package/dist/types.d.ts CHANGED
@@ -256,6 +256,10 @@ export type VisageOAuth2Client = {
256
256
  * Subset of a Docker Compose service definition managed by Visage.
257
257
  */
258
258
  export type VisageService = {
259
+ /**
260
+ * Docker build context for the service.
261
+ */
262
+ readonly build?: string;
259
263
  /**
260
264
  * Container image reference used for the service. Required for additional
261
265
  * services; defaults to the managed image when overriding `nginx` or
@@ -280,6 +284,11 @@ export type VisageService = {
280
284
  * Docker network mode rendered into the Compose service.
281
285
  */
282
286
  readonly network_mode?: string;
287
+ /**
288
+ * Compose image pull policy.
289
+ * @defaultValue `'missing'`
290
+ */
291
+ readonly pull_policy?: 'always' | 'build' | 'missing' | 'never';
283
292
  /**
284
293
  * Additional host-to-IP mappings rendered into the Compose service.
285
294
  */
@@ -321,6 +330,13 @@ export type VisageUpstream = {
321
330
  * @defaultValue `80`, or `443` when {@link VisageUpstream.scheme} is `'https'`.
322
331
  */
323
332
  readonly port?: number;
333
+ /**
334
+ * Request headers to apply to every location for this upstream. Per-location
335
+ * headers override matching values from this map.
336
+ */
337
+ readonly headers?: {
338
+ readonly [key: string]: string;
339
+ };
324
340
  /**
325
341
  * Path-location policies for this upstream, keyed by NGINX location path.
326
342
  *
@@ -344,12 +360,6 @@ export type VisageProxyPolicy = {
344
360
  * @defaultValue `true`
345
361
  */
346
362
  readonly enabled?: boolean;
347
- /**
348
- * Whether unauthenticated browser requests should redirect to sign-in.
349
- *
350
- * @defaultValue `false`
351
- */
352
- readonly redirect?: boolean;
353
363
  /**
354
364
  * Token forwarding behavior for the upstream `Authorization` header. Set
355
365
  * to `false` to omit a bearer token. Set to `true` to forward the
@@ -367,10 +377,20 @@ export type VisageProxyPolicy = {
367
377
  * locations. Set this to `false` when an upstream handles CSRF itself or
368
378
  * intentionally accepts cross-site browser requests.
369
379
  *
370
- * @defaultValue `'app'` for the built-in Vite root location; `'api'` for
371
- * authenticated upstream locations; `false` for unauthenticated locations.
380
+ * @defaultValue `true` for authenticated locations; `false` for
381
+ * unauthenticated locations.
382
+ */
383
+ readonly csrf?: boolean;
384
+ /**
385
+ * Whether to add WebSocket proxy support for this location.
386
+ *
387
+ * When enabled, Visage adds `Connection` and `Upgrade` proxy headers plus
388
+ * `proxy_http_version` and `proxy_read_timeout` directives unless the
389
+ * location explicitly overrides them.
390
+ *
391
+ * @defaultValue `false`
372
392
  */
373
- readonly csrf?: false | 'app' | 'api';
393
+ readonly ws?: boolean;
374
394
  /**
375
395
  * Request headers to set when proxying to the upstream. Values may include
376
396
  * NGINX variables. These are merged with Visage's default proxy headers:
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blakearoberts/visage",
3
- "version": "0.0.5-rc.2",
3
+ "version": "0.0.5-rc.21",
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",
@@ -61,11 +61,11 @@
61
61
  "devDependencies": {
62
62
  "@playwright/test": "^1.61.1",
63
63
  "@rollup/plugin-typescript": "^12.3.0",
64
- "@types/node": "^26.0.1",
64
+ "@types/node": "^26.1.1",
65
65
  "rollup": "^4.62.2",
66
66
  "tslib": "^2.8.1",
67
- "tsx": "^4.22.3",
67
+ "tsx": "^4.23.0",
68
68
  "typescript": "^6.0.3",
69
- "vite": "^8.1.2"
69
+ "vite": "^8.1.4"
70
70
  }
71
71
  }