@blakearoberts/visage 0.0.1-rc.19 → 0.0.1-rc.20
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 +30 -12
- package/dist/certs.d.ts +2 -6
- package/dist/certs.d.ts.map +1 -1
- package/dist/compose.d.ts +2 -1
- package/dist/compose.d.ts.map +1 -1
- package/dist/config.d.ts +27 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/hosts.d.ts +2 -1
- package/dist/hosts.d.ts.map +1 -1
- package/dist/index.d.ts +2 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +339 -265
- package/dist/network.d.ts +3 -0
- package/dist/network.d.ts.map +1 -0
- package/dist/plugin.d.ts +5 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/render/index.d.ts +4 -2
- package/dist/render/index.d.ts.map +1 -1
- package/dist/server.d.ts +5 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/types.d.ts +27 -17
- package/dist/types.d.ts.map +1 -1
- package/docker-compose.images.yml +7 -0
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ Start Vite normally:
|
|
|
27
27
|
vite
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
By default, you can reach the app at
|
|
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
31
|
|
|
32
32
|
## Why Visage
|
|
33
33
|
|
|
@@ -72,17 +72,21 @@ visage({
|
|
|
72
72
|
});
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
Authenticated upstream locations
|
|
76
|
-
`
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
Authenticated upstream locations do not forward bearer tokens by default. Set
|
|
76
|
+
`auth.forward` to `true` to forward the default bearer token for the upstream
|
|
77
|
+
kind: external upstreams receive the OAuth access token, while local service
|
|
78
|
+
upstreams receive the OIDC ID token.
|
|
79
|
+
|
|
80
|
+
Hosted backend APIs that validate bearer auth should generally receive the
|
|
81
|
+
access token, provided the token is issued for that API's issuer, audience, and
|
|
82
|
+
scopes. Use `'access'` or `'id'` when you need to force a specific token kind.
|
|
79
83
|
|
|
80
84
|
```ts
|
|
81
85
|
visage({
|
|
82
86
|
upstreams: {
|
|
83
87
|
api: {
|
|
84
88
|
locations: {
|
|
85
|
-
'/api/': { auth: { forward:
|
|
89
|
+
'/api/': { auth: { forward: true } },
|
|
86
90
|
},
|
|
87
91
|
},
|
|
88
92
|
},
|
|
@@ -93,6 +97,20 @@ OAuth2 Proxy identity values can also be mapped explicitly through headers such
|
|
|
93
97
|
as `$auth_user`, `$auth_email`, `$auth_groups`, and
|
|
94
98
|
`$auth_preferred_username`.
|
|
95
99
|
|
|
100
|
+
### External IdPs
|
|
101
|
+
|
|
102
|
+
External OIDC providers use issuer discovery by default:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
visage({
|
|
106
|
+
idp: { issuer: 'https://idp.example.test/oauth2/default' },
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Configure `authorization`, `token`, or `jwks` only when the provider endpoints
|
|
111
|
+
must be rendered explicitly instead of discovered from the issuer. Configure
|
|
112
|
+
`end_session_endpoint` when the provider supports OIDC end-session redirects.
|
|
113
|
+
|
|
96
114
|
See [`VisageOptions`](src/types.ts) for the full option surface.
|
|
97
115
|
|
|
98
116
|
## Expected Local URLs
|
|
@@ -139,11 +157,11 @@ Visage downloads [`mkcert`](https://github.com/FiloSottile/mkcert) from `dl.fili
|
|
|
139
157
|
|
|
140
158
|
Visage pulls these as needed based on configuration:
|
|
141
159
|
|
|
142
|
-
| Service | Image
|
|
143
|
-
| ------------------------------------------------------------ |
|
|
144
|
-
| [NGINX](https://nginx.org/) | [`nginx
|
|
145
|
-
| [OAuth2 Proxy](https://oauth2-proxy.github.io/oauth2-proxy/) | [`quay.io/oauth2-proxy/oauth2-proxy
|
|
146
|
-
| [Dex](https://dexidp.io/) | [`ghcr.io/dexidp/dex
|
|
160
|
+
| Service | Image | Pin |
|
|
161
|
+
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------- | ------------------------------------- |
|
|
162
|
+
| [NGINX](https://nginx.org/) | [`nginx`](https://hub.docker.com/_/nginx) | [manifest](docker-compose.images.yml) |
|
|
163
|
+
| [OAuth2 Proxy](https://oauth2-proxy.github.io/oauth2-proxy/) | [`quay.io/oauth2-proxy/oauth2-proxy`](https://quay.io/repository/oauth2-proxy/oauth2-proxy) | [manifest](docker-compose.images.yml) |
|
|
164
|
+
| [Dex](https://dexidp.io/) | [`ghcr.io/dexidp/dex`](https://github.com/dexidp/dex/pkgs/container/dex) | [manifest](docker-compose.images.yml) |
|
|
147
165
|
|
|
148
166
|
## Security Notes
|
|
149
167
|
|
|
@@ -160,7 +178,7 @@ Do not treat the managed Dex and OAuth2 Proxy defaults as production auth infras
|
|
|
160
178
|
|
|
161
179
|
## TO-DO
|
|
162
180
|
|
|
163
|
-
- [ ] Support
|
|
181
|
+
- [ ] Support CSRF (click-jacking) mitigations/projections.
|
|
164
182
|
- [ ] Support configuring [Dex connectors](https://dexidp.io/docs/connectors/).
|
|
165
183
|
- [ ] Support configuring Dex on a distinct subdomain, such as `auth.localhost`.
|
|
166
184
|
- [ ] Support optional [HTTP mode without local TLS](docs/tls-http-mode.md).
|
package/dist/certs.d.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
type
|
|
2
|
-
|
|
3
|
-
hostname: string;
|
|
4
|
-
};
|
|
5
|
-
export declare function ensureCerts({ certs, hostname }: Options): Promise<void>;
|
|
6
|
-
export {};
|
|
1
|
+
import type { VisageConfig } from './config';
|
|
2
|
+
export declare function ensureCerts(config: VisageConfig): Promise<void>;
|
|
7
3
|
//# sourceMappingURL=certs.d.ts.map
|
package/dist/certs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"certs.d.ts","sourceRoot":"","sources":["../src/certs.ts"],"names":[],"mappings":"AAcA,
|
|
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,CAqCrE"}
|
package/dist/compose.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
import type { VisageConfig } from './config';
|
|
1
2
|
type StopCompose = () => void;
|
|
2
|
-
export declare function startCompose(
|
|
3
|
+
export declare function startCompose(config: VisageConfig): StopCompose;
|
|
3
4
|
export {};
|
|
4
5
|
//# sourceMappingURL=compose.d.ts.map
|
package/dist/compose.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAIA,KAAK,WAAW,GAAG,MAAM,IAAI,CAAC;AAI9B,wBAAgB,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,KAAK,WAAW,GAAG,MAAM,IAAI,CAAC;AAI9B,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW,CAmC9D"}
|
package/dist/config.d.ts
CHANGED
|
@@ -27,21 +27,33 @@ type ResolvedVisageOptions = {
|
|
|
27
27
|
readonly services: Readonly<Record<string, VisageService>>;
|
|
28
28
|
readonly upstreams: Record<string, VisageUpstream>;
|
|
29
29
|
};
|
|
30
|
-
type
|
|
31
|
-
readonly upstream: string;
|
|
30
|
+
type OIDCEndpointConfig = {
|
|
32
31
|
readonly issuer: string;
|
|
32
|
+
readonly authorization?: string;
|
|
33
|
+
readonly token?: string;
|
|
34
|
+
readonly jwks?: string;
|
|
35
|
+
readonly end_session_endpoint?: string;
|
|
36
|
+
};
|
|
37
|
+
type ManualOIDCEndpointConfig = OIDCEndpointConfig & {
|
|
33
38
|
readonly authorization: string;
|
|
34
39
|
readonly token: string;
|
|
35
40
|
readonly jwks: string;
|
|
36
41
|
};
|
|
37
|
-
type ResolvedDexIdpConfig =
|
|
42
|
+
type ResolvedDexIdpConfig = {
|
|
38
43
|
readonly dex: {
|
|
39
44
|
readonly expiry?: VisageDexExpiry;
|
|
40
45
|
readonly users: readonly VisageDexUser[];
|
|
41
46
|
};
|
|
47
|
+
readonly oidc: ManualOIDCEndpointConfig;
|
|
48
|
+
readonly upstream: {
|
|
49
|
+
readonly dex: ResolvedUpstream;
|
|
50
|
+
};
|
|
42
51
|
};
|
|
43
|
-
type ResolvedExternalIdpConfig =
|
|
44
|
-
readonly
|
|
52
|
+
type ResolvedExternalIdpConfig = {
|
|
53
|
+
readonly oidc: OIDCEndpointConfig;
|
|
54
|
+
readonly upstream: {
|
|
55
|
+
readonly idp: ResolvedUpstream;
|
|
56
|
+
};
|
|
45
57
|
};
|
|
46
58
|
type ResolvedIdpConfig = ResolvedDexIdpConfig | ResolvedExternalIdpConfig;
|
|
47
59
|
type ResolvedService = Omit<VisageService, 'upstream'> & {
|
|
@@ -53,8 +65,13 @@ type ResolvedUpstream = {
|
|
|
53
65
|
readonly port: number;
|
|
54
66
|
readonly locations: Readonly<Record<string, VisageProxyPolicy>>;
|
|
55
67
|
};
|
|
68
|
+
type ResolvedAuthPolicy = {
|
|
69
|
+
readonly enabled: boolean;
|
|
70
|
+
readonly forward: false | 'id' | 'access';
|
|
71
|
+
readonly redirect: boolean;
|
|
72
|
+
};
|
|
56
73
|
type ResolvedProxyPolicy = {
|
|
57
|
-
readonly auth:
|
|
74
|
+
readonly auth: ResolvedAuthPolicy;
|
|
58
75
|
readonly headers: Readonly<Record<string, string>>;
|
|
59
76
|
readonly directives: Readonly<Record<string, readonly string[]>>;
|
|
60
77
|
};
|
|
@@ -78,6 +95,10 @@ export type VisageConfig = {
|
|
|
78
95
|
readonly clientSecret: Volume;
|
|
79
96
|
readonly cookieSecret: Volume;
|
|
80
97
|
};
|
|
98
|
+
readonly network: {
|
|
99
|
+
readonly name: string;
|
|
100
|
+
readonly trustedProxyIps: readonly string[];
|
|
101
|
+
};
|
|
81
102
|
readonly services: Readonly<Record<string, ResolvedService>>;
|
|
82
103
|
readonly upstreams: Readonly<Record<string, ResolvedConfigUpstream>>;
|
|
83
104
|
};
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,wBAAwB,EACxB,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,cAAc,EACf,MAAM,SAAS,CAAC;AAEjB,KAAK,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;AAElD,KAAK,oBAAoB,GAAG;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC,CAAC;AAEF,KAAK,iBAAiB,GAClB;IAAE,QAAQ,CAAC,GAAG,EAAE,gBAAgB,CAAA;CAAE,GAClC,wBAAwB,CAAC;AAE7B,KAAK,oBAAoB,GAAG;IAC1B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,KAAK,qBAAqB,GAAG;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,GAAG,EAAE,iBAAiB,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;IAC3D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CACpD,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACxC,CAAC;AAEF,KAAK,wBAAwB,GAAG,kBAAkB,GAAG;IACnD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,QAAQ,CAAC,GAAG,EAAE;QACZ,QAAQ,CAAC,MAAM,CAAC,EAAE,eAAe,CAAC;QAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;KAC1C,CAAC;IACF,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,gBAAgB,CAAA;KAAE,CAAC;CACvD,CAAC;AACF,KAAK,yBAAyB,GAAG;IAC/B,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,gBAAgB,CAAA;KAAE,CAAC;CACvD,CAAC;AACF,KAAK,iBAAiB,GAAG,oBAAoB,GAAG,yBAAyB,CAAC;AAE1E,KAAK,eAAe,GAAG,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,GAAG;IACvD,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC;CACzD,CAAC;AAEF,KAAK,gBAAgB,GAAG;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;CACjE,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,KAAK,GAAG,IAAI,GAAG,QAAQ,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACzB,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;CAClE,CAAC;AAEF,KAAK,sBAAsB,GAAG,IAAI,CAAC,gBAAgB,EAAE,WAAW,CAAC,GAAG;IAClE,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAClE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,GAAG,EAAE,iBAAiB,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IAEtC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;QAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;KAC/B,CAAC;IACF,QAAQ,CAAC,OAAO,EAAE;QAChB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;KAC7C,CAAC;IAEF,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC;IAC7D,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC,CAAC;CACtE,CAAC;AAuFF,wBAAgB,cAAc,CAAC,OAAO,EAAE,aAAa,GAAG,qBAAqB,CA0D5E;AAuED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,qBAAqB,EAC9B,KAAK,EAAE,MAAM,GACZ,YAAY,CAoGd;AAiGD,wBAAgB,mBAAmB,CACjC,IAAI,GAAE,cAAkC,GACvC,cAAc,CAwBhB"}
|
package/dist/hosts.d.ts
CHANGED
package/dist/hosts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hosts.d.ts","sourceRoot":"","sources":["../src/hosts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hosts.d.ts","sourceRoot":"","sources":["../src/hosts.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAI7C,wBAAgB,eAAe,CAAC,EAAE,IAAI,EAAE,EAAE,YAAY,GAAG,IAAI,CAyD5D"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import type { Plugin } from 'vite';
|
|
2
|
-
import type { VisageOptions, VisageServer } from './types';
|
|
3
1
|
export type { VisageCookiePolicy, VisageDexExpiry, VisageDexOptions, VisageDexUser, VisageExternalIdpOptions, VisageOAuth2Client, VisageOptions, VisageProxyPolicy, VisageServer, VisageService, VisageUpstream, } from './types';
|
|
4
|
-
export
|
|
5
|
-
export
|
|
2
|
+
export { default, visage } from './plugin';
|
|
3
|
+
export { createVisageServer } from './server';
|
|
6
4
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,wBAAwB,EACxB,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,cAAc,GACf,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,111 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { readFileSync, mkdirSync, chmodSync, openSync, rmSync, existsSync, createWriteStream, appendFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { parse, stringify } from 'yaml';
|
|
3
4
|
import { spawnSync, spawn } from 'node:child_process';
|
|
4
5
|
import { homedir } from 'node:os';
|
|
5
6
|
import { Readable } from 'node:stream';
|
|
6
7
|
import { pipeline } from 'node:stream/promises';
|
|
7
|
-
import { stringify } from 'yaml';
|
|
8
8
|
import { hashSync } from 'bcryptjs';
|
|
9
9
|
import { Eta } from 'eta';
|
|
10
10
|
import { randomBytes } from 'node:crypto';
|
|
11
11
|
|
|
12
|
-
const CACHE_HOME = process.env.XDG_CACHE_HOME || join(homedir(), '.cache');
|
|
13
|
-
async function ensureCerts({ certs, hostname }) {
|
|
14
|
-
const CAROOT = join(CACHE_HOME, 'visage/ca');
|
|
15
|
-
mkdirSync(CAROOT, { recursive: true, mode: 0o700 });
|
|
16
|
-
chmodSync(CAROOT, 0o700);
|
|
17
|
-
const mkcert = await ensureMkCert();
|
|
18
|
-
const logs = join(dirname(certs), 'logs');
|
|
19
|
-
mkdirSync(logs, { recursive: true });
|
|
20
|
-
const log = join(logs, 'mkcert.log');
|
|
21
|
-
const output = openSync(log, 'w');
|
|
22
|
-
const env = { CAROOT, TRUST_STORES: 'system', ...process.env };
|
|
23
|
-
const tty = process.stdin.isTTY;
|
|
24
|
-
const stdio = [
|
|
25
|
-
tty ? 'inherit' : 'ignore',
|
|
26
|
-
output,
|
|
27
|
-
output,
|
|
28
|
-
];
|
|
29
|
-
if (process.env.CI !== 'true') {
|
|
30
|
-
// mkcert -install is idempotent; CA files alone do not prove trust-store state.
|
|
31
|
-
const result = spawnSync(mkcert, ['-install'], { env, stdio });
|
|
32
|
-
if (result.error)
|
|
33
|
-
throw result.error;
|
|
34
|
-
if (result.status !== 0) {
|
|
35
|
-
throw new Error('Failed to install CA');
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
const cert = join(certs, 'tls.crt');
|
|
39
|
-
const key = join(certs, 'tls.key');
|
|
40
|
-
mkdirSync(certs, { recursive: true });
|
|
41
|
-
rmSync(cert, { force: true });
|
|
42
|
-
rmSync(key, { force: true });
|
|
43
|
-
const names = [...new Set([hostname, 'localhost', '127.0.0.1', '::1'])];
|
|
44
|
-
const args = ['-cert-file', cert, '-key-file', key, ...names];
|
|
45
|
-
const result = spawnSync(mkcert, args, { env, stdio });
|
|
46
|
-
if (result.error)
|
|
47
|
-
throw result.error;
|
|
48
|
-
if (result.status !== 0) {
|
|
49
|
-
throw new Error('Failed to generate TLS certificates');
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
async function ensureMkCert() {
|
|
53
|
-
const bin = join(CACHE_HOME, 'visage/bin');
|
|
54
|
-
const file = join(bin, `mkcert-${process.platform}-${process.arch}`);
|
|
55
|
-
if (existsSync(file))
|
|
56
|
-
return file;
|
|
57
|
-
mkdirSync(bin, { recursive: true });
|
|
58
|
-
const base = 'https://dl.filippo.io/mkcert/latest';
|
|
59
|
-
const arch = process.arch === 'x64' ? 'amd64' : process.arch;
|
|
60
|
-
const params = `?for=${process.platform}/${arch}`;
|
|
61
|
-
const url = new URL(params, base);
|
|
62
|
-
const response = await fetch(url);
|
|
63
|
-
if (!response.ok || !response.body) {
|
|
64
|
-
throw new Error('Failed to download mkcert');
|
|
65
|
-
}
|
|
66
|
-
await pipeline(Readable.fromWeb(response.body), createWriteStream(file));
|
|
67
|
-
chmodSync(file, 0o755);
|
|
68
|
-
return file;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
let stopRef;
|
|
72
|
-
function startCompose(file) {
|
|
73
|
-
stopRef?.();
|
|
74
|
-
stopRef = undefined;
|
|
75
|
-
const logs = join(dirname(file), 'logs');
|
|
76
|
-
mkdirSync(logs, { recursive: true });
|
|
77
|
-
const output = openSync(join(logs, 'compose.log'), 'w');
|
|
78
|
-
const compose = [
|
|
79
|
-
'compose',
|
|
80
|
-
'--ansi=never',
|
|
81
|
-
`--file=${file}`,
|
|
82
|
-
`--project-name=${process.env.COMPOSE_PROJECT_NAME ?? 'visage'}`,
|
|
83
|
-
];
|
|
84
|
-
const env = { ...process.env, COMPOSE_MENU: 'false' };
|
|
85
|
-
const opts = {
|
|
86
|
-
cwd: dirname(file),
|
|
87
|
-
stdio: ['ignore', output, output],
|
|
88
|
-
env,
|
|
89
|
-
};
|
|
90
|
-
const up = [
|
|
91
|
-
...compose,
|
|
92
|
-
'up',
|
|
93
|
-
'--abort-on-container-failure',
|
|
94
|
-
'--remove-orphans',
|
|
95
|
-
];
|
|
96
|
-
const child = spawn('docker', up, opts);
|
|
97
|
-
const stop = () => {
|
|
98
|
-
if (stopRef !== stop)
|
|
99
|
-
return;
|
|
100
|
-
stopRef = undefined;
|
|
101
|
-
child.kill();
|
|
102
|
-
const down = [...compose, 'down', '--remove-orphans'];
|
|
103
|
-
spawnSync('docker', down, opts);
|
|
104
|
-
};
|
|
105
|
-
stopRef = stop;
|
|
106
|
-
return stop;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
12
|
const BaseFiles = {
|
|
110
13
|
certs: ['./certs', '/etc/nginx/certs'],
|
|
111
14
|
compose: './compose.yaml',
|
|
@@ -115,29 +18,24 @@ const BaseFiles = {
|
|
|
115
18
|
clientSecret: ['./oauth2-client-secret', '/etc/oauth2-proxy/client-secret'],
|
|
116
19
|
cookieSecret: ['./oauth2-cookie-secret', '/etc/oauth2-proxy/cookie-secret'],
|
|
117
20
|
};
|
|
21
|
+
const DockerImages = parse(readFileSync(new URL('../docker-compose.images.yml', import.meta.url), 'utf8')).services;
|
|
118
22
|
const BaseServiceDex = {
|
|
119
|
-
image:
|
|
23
|
+
image: DockerImages.dex.image,
|
|
120
24
|
command: ['dex', 'serve', '/etc/dex/dex.yml'],
|
|
121
25
|
restart: 'always',
|
|
122
26
|
};
|
|
123
27
|
const BaseServiceNginx = {
|
|
124
|
-
image:
|
|
28
|
+
image: DockerImages.nginx.image,
|
|
125
29
|
depends_on: ['oauth2_proxy'],
|
|
126
30
|
extra_hosts: ['host.docker.internal:host-gateway'],
|
|
127
31
|
restart: 'always',
|
|
128
32
|
};
|
|
129
33
|
const BaseServiceOAuth2Proxy = {
|
|
130
|
-
image:
|
|
34
|
+
image: DockerImages.oauth2_proxy.image,
|
|
131
35
|
command: ['--config', '/etc/oauth2-proxy/config.yml'],
|
|
132
36
|
extra_hosts: ['host.docker.internal:host-gateway'],
|
|
133
37
|
restart: 'always',
|
|
134
38
|
};
|
|
135
|
-
const BaseUpstreamDex = {
|
|
136
|
-
host: 'dex',
|
|
137
|
-
scheme: 'http',
|
|
138
|
-
port: 5556,
|
|
139
|
-
locations: { '/dex/': { auth: { enabled: false } } },
|
|
140
|
-
};
|
|
141
39
|
const BaseUpstreamOauth2Proxy = {
|
|
142
40
|
host: 'oauth2_proxy',
|
|
143
41
|
scheme: 'http',
|
|
@@ -159,10 +57,7 @@ const DefaultCookiePolicy = {
|
|
|
159
57
|
cookie_secret_file: BaseFiles.cookieSecret[1],
|
|
160
58
|
};
|
|
161
59
|
const DefaultDexUsers = [
|
|
162
|
-
{
|
|
163
|
-
email: 'user@example.com',
|
|
164
|
-
password: 'pass',
|
|
165
|
-
},
|
|
60
|
+
{ email: 'user@example.com', password: 'pass' },
|
|
166
61
|
];
|
|
167
62
|
const DefaultOAuth2Client = {
|
|
168
63
|
id: 'visage',
|
|
@@ -170,7 +65,6 @@ const DefaultOAuth2Client = {
|
|
|
170
65
|
scopes: ['openid', 'email', 'profile', 'offline_access'],
|
|
171
66
|
emailDomains: ['example.com']};
|
|
172
67
|
const DefaultProxyPolicy = {
|
|
173
|
-
auth: { enabled: true, forward: 'id', redirect: false },
|
|
174
68
|
headers: {
|
|
175
69
|
Cookie: '""', // Don't forward session cookie.
|
|
176
70
|
Host: '$host',
|
|
@@ -183,7 +77,7 @@ const DefaultProxyPolicy = {
|
|
|
183
77
|
},
|
|
184
78
|
};
|
|
185
79
|
function resolveOptions(options) {
|
|
186
|
-
const { host = 'localhost', port = 9001, cookie = {}, oauth2 = {} } = options;
|
|
80
|
+
const { host = 'localhost', port = 9001, cookie = {}, idp = {}, oauth2 = {}, } = options;
|
|
187
81
|
const cookieName = cookie.name ?? 'sess';
|
|
188
82
|
const publicClient = oauth2.clientSecret === null;
|
|
189
83
|
const services = resolveServicesOptions(options.services);
|
|
@@ -207,7 +101,19 @@ function resolveOptions(options) {
|
|
|
207
101
|
: { cookie_domains: cookie.domains }),
|
|
208
102
|
...(cookie.path === undefined ? {} : { cookie_path: cookie.path }),
|
|
209
103
|
},
|
|
210
|
-
idp:
|
|
104
|
+
idp: 'issuer' in idp
|
|
105
|
+
? idp
|
|
106
|
+
: {
|
|
107
|
+
dex: {
|
|
108
|
+
...(idp.expiry ? { expiry: idp.expiry } : {}),
|
|
109
|
+
users: (idp.users ?? DefaultDexUsers).map((user) => ({
|
|
110
|
+
email: user.email,
|
|
111
|
+
password: user.password,
|
|
112
|
+
username: user.username ?? user.email.split('@', 1)[0],
|
|
113
|
+
userID: user.userID ?? user.email,
|
|
114
|
+
})),
|
|
115
|
+
},
|
|
116
|
+
},
|
|
211
117
|
oauth2: {
|
|
212
118
|
id: oauth2.clientId ?? DefaultOAuth2Client.id,
|
|
213
119
|
...(publicClient
|
|
@@ -274,78 +180,28 @@ function resolveUpstreamsOptions(services, upstreams = {}) {
|
|
|
274
180
|
])),
|
|
275
181
|
};
|
|
276
182
|
}
|
|
277
|
-
function resolveIdpOption(idp) {
|
|
278
|
-
if (idp && 'issuer' in idp) {
|
|
279
|
-
return {
|
|
280
|
-
issuer: idp.issuer,
|
|
281
|
-
authorization: idp.authorization ?? '/auth',
|
|
282
|
-
token: idp.token ?? '/token',
|
|
283
|
-
jwks: idp.jwks ?? '/keys',
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
return {
|
|
287
|
-
dex: {
|
|
288
|
-
...(idp?.expiry ? { expiry: idp.expiry } : {}),
|
|
289
|
-
users: (idp?.users ?? DefaultDexUsers).map((user) => ({
|
|
290
|
-
email: user.email,
|
|
291
|
-
password: user.password,
|
|
292
|
-
username: user.username ?? user.email.split('@', 1)[0],
|
|
293
|
-
userID: user.userID ?? user.email,
|
|
294
|
-
})),
|
|
295
|
-
},
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
function resolveDirectives(directives = {}) {
|
|
299
|
-
return Object.fromEntries(Object.entries(directives).map(([name, value]) => [
|
|
300
|
-
name,
|
|
301
|
-
Array.isArray(value) ? value : [value],
|
|
302
|
-
]));
|
|
303
|
-
}
|
|
304
|
-
function resolveIdpConfig({ host, port, idp, }) {
|
|
305
|
-
if ('dex' in idp) {
|
|
306
|
-
const issuer = `https://${host}:${port}/dex`;
|
|
307
|
-
const upstream = `http://dex:5556/dex`;
|
|
308
|
-
return {
|
|
309
|
-
upstream: 'dex',
|
|
310
|
-
issuer,
|
|
311
|
-
authorization: `${issuer}/auth`,
|
|
312
|
-
token: `${upstream}/token`,
|
|
313
|
-
jwks: `${upstream}/keys`,
|
|
314
|
-
dex: {
|
|
315
|
-
expiry: idp.dex.expiry,
|
|
316
|
-
users: (idp.dex?.users ?? DefaultDexUsers).map((user) => ({
|
|
317
|
-
email: user.email,
|
|
318
|
-
password: user.password,
|
|
319
|
-
username: user.username ?? user.email.split('@', 1)[0],
|
|
320
|
-
userID: user.userID ?? user.email,
|
|
321
|
-
})),
|
|
322
|
-
},
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
return {
|
|
326
|
-
upstream: 'idp',
|
|
327
|
-
issuer: idp.issuer,
|
|
328
|
-
authorization: idp.issuer + (idp.authorization ?? '/auth'),
|
|
329
|
-
token: idp.issuer + (idp.token ?? '/token'),
|
|
330
|
-
jwks: idp.issuer + (idp.jwks ?? '/keys'),
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
function resolveExternalIdpUpstream(idp) {
|
|
334
|
-
const issuer = new URL(idp.issuer);
|
|
335
|
-
return {
|
|
336
|
-
host: issuer.hostname,
|
|
337
|
-
locations: {},
|
|
338
|
-
scheme: issuer.protocol === 'https:' ? 'https' : 'http',
|
|
339
|
-
port: Number(issuer.port) || (issuer.protocol === 'https:' ? 443 : 80),
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
183
|
function resolveConfig(options, cache) {
|
|
343
184
|
const idp = resolveIdpConfig(options);
|
|
344
185
|
const upstreams = {
|
|
345
|
-
oauth2_proxy:
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
186
|
+
oauth2_proxy: {
|
|
187
|
+
...BaseUpstreamOauth2Proxy,
|
|
188
|
+
locations: {
|
|
189
|
+
...BaseUpstreamOauth2Proxy.locations,
|
|
190
|
+
'/oauth2/sign_out': {
|
|
191
|
+
auth: { enabled: false },
|
|
192
|
+
headers: {
|
|
193
|
+
Cookie: '$http_cookie', // Forward session cookie.
|
|
194
|
+
'X-Auth-Request-Redirect': idp.oidc.end_session_endpoint
|
|
195
|
+
? JSON.stringify(idp.oidc.end_session_endpoint +
|
|
196
|
+
(idp.oidc.end_session_endpoint.includes('?') ? '&' : '?') +
|
|
197
|
+
'id_token_hint={id_token}&post_logout_redirect_uri=' +
|
|
198
|
+
encodeURIComponent(`https://${options.host}:${options.port}/`))
|
|
199
|
+
: '/',
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
...idp.upstream,
|
|
349
205
|
...options.upstreams,
|
|
350
206
|
};
|
|
351
207
|
return {
|
|
@@ -355,15 +211,19 @@ function resolveConfig(options, cache) {
|
|
|
355
211
|
idp,
|
|
356
212
|
oauth2: options.oauth2,
|
|
357
213
|
cache,
|
|
358
|
-
files:
|
|
214
|
+
files: BaseFiles,
|
|
215
|
+
network: {
|
|
216
|
+
name: `${process.env.COMPOSE_PROJECT_NAME ?? 'visage'}_nginx`,
|
|
217
|
+
trustedProxyIps: [],
|
|
218
|
+
},
|
|
359
219
|
services: {
|
|
360
|
-
...(
|
|
361
|
-
? {
|
|
362
|
-
: {
|
|
220
|
+
...('dex' in idp
|
|
221
|
+
? {
|
|
363
222
|
dex: BaseServiceDex,
|
|
364
223
|
nginx: { ...BaseServiceNginx, depends_on: ['dex', 'oauth2_proxy'] },
|
|
365
224
|
oauth2_proxy: { ...BaseServiceOAuth2Proxy, depends_on: ['dex'] },
|
|
366
|
-
}
|
|
225
|
+
}
|
|
226
|
+
: { nginx: BaseServiceNginx, oauth2_proxy: BaseServiceOAuth2Proxy }),
|
|
367
227
|
...Object.fromEntries(Object.entries(options.services).map(([name, { upstream: _upstream, ...service }]) => [
|
|
368
228
|
name,
|
|
369
229
|
{ restart: 'on-failure', ...service },
|
|
@@ -380,7 +240,7 @@ function resolveConfig(options, cache) {
|
|
|
380
240
|
locations: Object.fromEntries(Object.entries(upstream.locations ?? {}).map(([path, policy]) => [
|
|
381
241
|
path,
|
|
382
242
|
{
|
|
383
|
-
auth:
|
|
243
|
+
auth: resolveAuthPolicy(policy.auth, external && name !== 'vite'),
|
|
384
244
|
headers: {
|
|
385
245
|
...(external
|
|
386
246
|
? { ...DefaultProxyPolicy.headers, Host: upstream.host }
|
|
@@ -389,7 +249,10 @@ function resolveConfig(options, cache) {
|
|
|
389
249
|
},
|
|
390
250
|
directives: {
|
|
391
251
|
...DefaultProxyPolicy.directives,
|
|
392
|
-
...
|
|
252
|
+
...Object.fromEntries(Object.entries(policy.directives ?? {}).map(([name, value]) => [
|
|
253
|
+
name,
|
|
254
|
+
Array.isArray(value) ? value : [value],
|
|
255
|
+
])),
|
|
393
256
|
},
|
|
394
257
|
},
|
|
395
258
|
])),
|
|
@@ -398,12 +261,77 @@ function resolveConfig(options, cache) {
|
|
|
398
261
|
})),
|
|
399
262
|
};
|
|
400
263
|
}
|
|
264
|
+
function resolveIdpConfig({ host, port, idp, }) {
|
|
265
|
+
if ('dex' in idp) {
|
|
266
|
+
return {
|
|
267
|
+
dex: {
|
|
268
|
+
expiry: idp.dex.expiry,
|
|
269
|
+
users: (idp.dex?.users ?? DefaultDexUsers).map((user) => ({
|
|
270
|
+
email: user.email,
|
|
271
|
+
password: user.password,
|
|
272
|
+
username: user.username ?? user.email.split('@', 1)[0],
|
|
273
|
+
userID: user.userID ?? user.email,
|
|
274
|
+
})),
|
|
275
|
+
},
|
|
276
|
+
oidc: {
|
|
277
|
+
issuer: `https://${host}:${port}/dex`,
|
|
278
|
+
authorization: `https://${host}:${port}/dex/auth`,
|
|
279
|
+
token: 'http://dex:5556/dex/token',
|
|
280
|
+
jwks: 'http://dex:5556/dex/keys',
|
|
281
|
+
},
|
|
282
|
+
upstream: {
|
|
283
|
+
dex: {
|
|
284
|
+
host: 'dex',
|
|
285
|
+
scheme: 'http',
|
|
286
|
+
port: 5556,
|
|
287
|
+
locations: { '/dex/': { auth: { enabled: false } } },
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
const issuer = new URL(idp.issuer);
|
|
293
|
+
const oidc = {
|
|
294
|
+
issuer: idp.issuer,
|
|
295
|
+
...(idp.end_session_endpoint === undefined
|
|
296
|
+
? {}
|
|
297
|
+
: { end_session_endpoint: idp.end_session_endpoint }),
|
|
298
|
+
};
|
|
299
|
+
return {
|
|
300
|
+
oidc: !idp.authorization && !idp.token && !idp.jwks
|
|
301
|
+
? oidc
|
|
302
|
+
: {
|
|
303
|
+
...oidc,
|
|
304
|
+
authorization: idp.issuer + (idp.authorization ?? '/auth'),
|
|
305
|
+
token: idp.issuer + (idp.token ?? '/token'),
|
|
306
|
+
jwks: idp.issuer + (idp.jwks ?? '/keys'),
|
|
307
|
+
},
|
|
308
|
+
upstream: {
|
|
309
|
+
idp: {
|
|
310
|
+
scheme: issuer.protocol === 'https:' ? 'https' : 'http',
|
|
311
|
+
host: issuer.hostname,
|
|
312
|
+
port: Number(issuer.port) || (issuer.protocol === 'https:' ? 443 : 80),
|
|
313
|
+
locations: {},
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
function resolveAuthPolicy(auth = {}, external) {
|
|
319
|
+
return {
|
|
320
|
+
enabled: auth.enabled ?? true,
|
|
321
|
+
forward: auth.forward === true
|
|
322
|
+
? external
|
|
323
|
+
? 'access'
|
|
324
|
+
: 'id'
|
|
325
|
+
: (auth.forward ?? false),
|
|
326
|
+
redirect: auth.redirect ?? false,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
401
329
|
const BaseViteUpstream = {
|
|
402
330
|
host: 'host.docker.internal',
|
|
403
331
|
scheme: 'http',
|
|
404
332
|
locations: {
|
|
405
333
|
'/': {
|
|
406
|
-
auth: {
|
|
334
|
+
auth: { redirect: true },
|
|
407
335
|
headers: {
|
|
408
336
|
Host: '$host',
|
|
409
337
|
Upgrade: '$http_upgrade',
|
|
@@ -441,12 +369,99 @@ function resolveViteUpstream(vite = { locations: {} }) {
|
|
|
441
369
|
};
|
|
442
370
|
}
|
|
443
371
|
|
|
372
|
+
const CACHE_HOME = process.env.XDG_CACHE_HOME || join(homedir(), '.cache');
|
|
373
|
+
async function ensureCerts(config) {
|
|
374
|
+
const CAROOT = join(CACHE_HOME, 'visage/ca');
|
|
375
|
+
mkdirSync(CAROOT, { recursive: true, mode: 0o700 });
|
|
376
|
+
chmodSync(CAROOT, 0o700);
|
|
377
|
+
const mkcert = await ensureMkCert();
|
|
378
|
+
mkdirSync(join(config.cache, 'logs'), { recursive: true });
|
|
379
|
+
const out = openSync(join(config.cache, 'logs', 'mkcert.log'), 'w');
|
|
380
|
+
const env = { CAROOT, TRUST_STORES: 'system', ...process.env };
|
|
381
|
+
const tty = process.stdin.isTTY;
|
|
382
|
+
const stdio = [tty ? 'inherit' : 'ignore', out, out];
|
|
383
|
+
if (process.env.CI !== 'true') {
|
|
384
|
+
// mkcert -install is idempotent; CA files alone do not prove trust-store state.
|
|
385
|
+
const result = spawnSync(mkcert, ['-install'], { env, stdio });
|
|
386
|
+
if (result.error)
|
|
387
|
+
throw result.error;
|
|
388
|
+
if (result.status !== 0) {
|
|
389
|
+
throw new Error('Failed to install CA');
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const certs = join(config.cache, config.files.certs[0]);
|
|
393
|
+
const cert = join(certs, 'tls.crt');
|
|
394
|
+
const key = join(certs, 'tls.key');
|
|
395
|
+
mkdirSync(certs, { recursive: true });
|
|
396
|
+
rmSync(cert, { force: true });
|
|
397
|
+
rmSync(key, { force: true });
|
|
398
|
+
const names = [...new Set([config.host, 'localhost', '127.0.0.1', '::1'])];
|
|
399
|
+
const args = ['-cert-file', cert, '-key-file', key, ...names];
|
|
400
|
+
const result = spawnSync(mkcert, args, { env, stdio });
|
|
401
|
+
if (result.error)
|
|
402
|
+
throw result.error;
|
|
403
|
+
if (result.status !== 0) {
|
|
404
|
+
throw new Error('Failed to generate TLS certificates');
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
async function ensureMkCert() {
|
|
408
|
+
const bin = join(CACHE_HOME, 'visage/bin');
|
|
409
|
+
const file = join(bin, `mkcert-${process.platform}-${process.arch}`);
|
|
410
|
+
if (existsSync(file))
|
|
411
|
+
return file;
|
|
412
|
+
mkdirSync(bin, { recursive: true });
|
|
413
|
+
const base = 'https://dl.filippo.io/mkcert/latest';
|
|
414
|
+
const arch = process.arch === 'x64' ? 'amd64' : process.arch;
|
|
415
|
+
const params = `?for=${process.platform}/${arch}`;
|
|
416
|
+
const url = new URL(params, base);
|
|
417
|
+
const response = await fetch(url);
|
|
418
|
+
if (!response.ok || !response.body) {
|
|
419
|
+
throw new Error('Failed to download mkcert');
|
|
420
|
+
}
|
|
421
|
+
await pipeline(Readable.fromWeb(response.body), createWriteStream(file));
|
|
422
|
+
chmodSync(file, 0o755);
|
|
423
|
+
return file;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
let stopRef;
|
|
427
|
+
function startCompose(config) {
|
|
428
|
+
stopRef?.();
|
|
429
|
+
stopRef = undefined;
|
|
430
|
+
const file = join(config.cache, config.files.compose);
|
|
431
|
+
const logs = join(config.cache, 'logs');
|
|
432
|
+
const output = openSync(join(logs, 'compose.log'), 'w');
|
|
433
|
+
const compose = [
|
|
434
|
+
'compose',
|
|
435
|
+
'--ansi=never',
|
|
436
|
+
`--file=${file}`,
|
|
437
|
+
`--project-name=${process.env.COMPOSE_PROJECT_NAME ?? 'visage'}`,
|
|
438
|
+
];
|
|
439
|
+
const env = { ...process.env, COMPOSE_MENU: 'false' };
|
|
440
|
+
const opts = {
|
|
441
|
+
cwd: config.cache,
|
|
442
|
+
stdio: ['ignore', output, output],
|
|
443
|
+
env,
|
|
444
|
+
};
|
|
445
|
+
const up = [...compose, 'up', '--remove-orphans'];
|
|
446
|
+
const child = spawn('docker', up, opts);
|
|
447
|
+
const stop = () => {
|
|
448
|
+
if (stopRef !== stop)
|
|
449
|
+
return;
|
|
450
|
+
stopRef = undefined;
|
|
451
|
+
child.kill();
|
|
452
|
+
const down = [...compose, 'down', '--remove-orphans'];
|
|
453
|
+
spawnSync('docker', down, opts);
|
|
454
|
+
};
|
|
455
|
+
stopRef = stop;
|
|
456
|
+
return stop;
|
|
457
|
+
}
|
|
458
|
+
|
|
444
459
|
const HOSTS_FILE = '/etc/hosts';
|
|
445
|
-
function ensureHostEntry(
|
|
446
|
-
if (!
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
460
|
+
function ensureHostEntry({ host }) {
|
|
461
|
+
if (!host ||
|
|
462
|
+
host.trim() !== host ||
|
|
463
|
+
host.includes('/') ||
|
|
464
|
+
host.includes(':')) {
|
|
450
465
|
throw new Error('Invalid hostname');
|
|
451
466
|
}
|
|
452
467
|
const contents = readFileSync(HOSTS_FILE, 'utf8');
|
|
@@ -456,7 +471,7 @@ function ensureHostEntry(hostname) {
|
|
|
456
471
|
continue;
|
|
457
472
|
}
|
|
458
473
|
const [address, ...names] = uncommented.split(/\s+/);
|
|
459
|
-
if (!names.includes(
|
|
474
|
+
if (!names.includes(host)) {
|
|
460
475
|
continue;
|
|
461
476
|
}
|
|
462
477
|
if (address === '127.0.0.1' || address === '::1') {
|
|
@@ -466,7 +481,7 @@ function ensureHostEntry(hostname) {
|
|
|
466
481
|
throw new Error('Hosts file contains a conflicting entry');
|
|
467
482
|
}
|
|
468
483
|
const prefix = contents.endsWith('\n') ? '' : '\n';
|
|
469
|
-
const entry = `${prefix}127.0.0.1\t${
|
|
484
|
+
const entry = `${prefix}127.0.0.1\t${host} # visage\n`;
|
|
470
485
|
try {
|
|
471
486
|
appendFileSync(HOSTS_FILE, entry);
|
|
472
487
|
return;
|
|
@@ -486,6 +501,65 @@ function ensureHostEntry(hostname) {
|
|
|
486
501
|
}
|
|
487
502
|
}
|
|
488
503
|
|
|
504
|
+
function ensureNginxNetwork(config) {
|
|
505
|
+
const exists = spawnSync('docker', [
|
|
506
|
+
'network',
|
|
507
|
+
'ls',
|
|
508
|
+
'--filter',
|
|
509
|
+
`name=${config.network.name}`,
|
|
510
|
+
'--format',
|
|
511
|
+
'{{ .Name }}',
|
|
512
|
+
], { encoding: 'utf-8' });
|
|
513
|
+
if (exists.error)
|
|
514
|
+
throw exists.error;
|
|
515
|
+
if (exists.status !== 0) {
|
|
516
|
+
console.error(exists.stderr);
|
|
517
|
+
throw new Error('Failed to list Docker network');
|
|
518
|
+
}
|
|
519
|
+
if (exists.stdout) {
|
|
520
|
+
return {
|
|
521
|
+
...config,
|
|
522
|
+
network: {
|
|
523
|
+
...config.network,
|
|
524
|
+
trustedProxyIps: inspectNetwork(config.network.name),
|
|
525
|
+
},
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
const create = spawnSync('docker', ['network', 'create', '--driver', 'bridge', config.network.name], { encoding: 'utf-8' });
|
|
529
|
+
if (create.error)
|
|
530
|
+
throw create.error;
|
|
531
|
+
if (create.status !== 0) {
|
|
532
|
+
console.error(create.stderr);
|
|
533
|
+
throw new Error('Failed to create Docker network');
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
...config,
|
|
537
|
+
network: {
|
|
538
|
+
...config.network,
|
|
539
|
+
trustedProxyIps: inspectNetwork(config.network.name),
|
|
540
|
+
},
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
function inspectNetwork(name) {
|
|
544
|
+
const result = spawnSync('docker', [
|
|
545
|
+
'network',
|
|
546
|
+
'inspect',
|
|
547
|
+
'--format',
|
|
548
|
+
'{{range .IPAM.Config}}{{println .Subnet}}{{end}}',
|
|
549
|
+
name,
|
|
550
|
+
], { encoding: 'utf-8' });
|
|
551
|
+
if (result.error)
|
|
552
|
+
throw result.error;
|
|
553
|
+
if (result.status !== 0) {
|
|
554
|
+
console.error(result.stderr);
|
|
555
|
+
throw new Error('Failed to inspect Docker network');
|
|
556
|
+
}
|
|
557
|
+
return result.stdout
|
|
558
|
+
.split(/\r?\n/)
|
|
559
|
+
.map((line) => line.trim())
|
|
560
|
+
.filter(Boolean);
|
|
561
|
+
}
|
|
562
|
+
|
|
489
563
|
function writeComposeConfig(config) {
|
|
490
564
|
const file = join(config.cache, config.files.compose);
|
|
491
565
|
const render = renderComposeConfig(config);
|
|
@@ -494,8 +568,9 @@ function writeComposeConfig(config) {
|
|
|
494
568
|
function renderComposeConfig(config) {
|
|
495
569
|
const { dex, nginx, oauth2_proxy, ...services } = config.services;
|
|
496
570
|
return stringify({
|
|
571
|
+
networks: { default: { external: true, name: config.network.name } },
|
|
497
572
|
services: {
|
|
498
|
-
...(config.idp
|
|
573
|
+
...('dex' in config.idp
|
|
499
574
|
? {
|
|
500
575
|
dex: {
|
|
501
576
|
...config.services.dex,
|
|
@@ -526,38 +601,34 @@ function renderComposeConfig(config) {
|
|
|
526
601
|
}
|
|
527
602
|
|
|
528
603
|
function writeDexConfig(config) {
|
|
529
|
-
const file = join(config.cache, config.files.dex[0]);
|
|
530
604
|
const render = renderDexConfig(config);
|
|
605
|
+
const file = join(config.cache, config.files.dex[0]);
|
|
531
606
|
writeFileSync(file, render, 'utf-8');
|
|
532
607
|
}
|
|
533
608
|
function renderDexConfig(config) {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
}
|
|
538
|
-
const origin = `https://${config.host}:${config.port}`;
|
|
539
|
-
const redirect = `${origin}/oauth2/callback`;
|
|
540
|
-
const upstream = config.upstreams[idp.upstream];
|
|
609
|
+
if (!('dex' in config.idp))
|
|
610
|
+
throw new Error('Dex config missing');
|
|
611
|
+
const { host, port, oauth2, idp: { dex: { expiry, users }, oidc, upstream, }, } = config;
|
|
541
612
|
return stringify({
|
|
542
|
-
issuer:
|
|
613
|
+
issuer: oidc.issuer,
|
|
543
614
|
storage: { type: 'memory' },
|
|
544
|
-
web: { http: `0.0.0.0:${upstream.port}` },
|
|
615
|
+
web: { http: `0.0.0.0:${upstream.dex.port}` },
|
|
545
616
|
oauth2: { skipApprovalScreen: true },
|
|
546
617
|
staticClients: [
|
|
547
618
|
{
|
|
548
|
-
id:
|
|
619
|
+
id: oauth2.id,
|
|
549
620
|
name: 'Visage',
|
|
550
|
-
...(
|
|
621
|
+
...(oauth2.secret === undefined
|
|
551
622
|
? { public: true }
|
|
552
|
-
: { secret:
|
|
553
|
-
redirectURIs: [
|
|
623
|
+
: { secret: oauth2.secret }),
|
|
624
|
+
redirectURIs: [`https://${host}:${port}/oauth2/callback`],
|
|
554
625
|
},
|
|
555
626
|
],
|
|
556
627
|
enablePasswordDB: true,
|
|
557
|
-
...(
|
|
558
|
-
staticPasswords:
|
|
628
|
+
...(expiry === undefined ? {} : { expiry }),
|
|
629
|
+
staticPasswords: users.map(({ password, ...user }) => ({
|
|
559
630
|
...user,
|
|
560
|
-
hash: hashSync(password
|
|
631
|
+
hash: hashSync(password),
|
|
561
632
|
})),
|
|
562
633
|
});
|
|
563
634
|
}
|
|
@@ -703,11 +774,15 @@ function renderOauth2ProxyConfig(config) {
|
|
|
703
774
|
const data = {
|
|
704
775
|
http_address: `0.0.0.0:${config.upstreams.oauth2_proxy.port}`,
|
|
705
776
|
provider: 'oidc',
|
|
706
|
-
oidc_issuer_url: config.idp.issuer,
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
777
|
+
oidc_issuer_url: config.idp.oidc.issuer,
|
|
778
|
+
...('authorization' in config.idp.oidc
|
|
779
|
+
? {
|
|
780
|
+
skip_oidc_discovery: true,
|
|
781
|
+
login_url: config.idp.oidc.authorization,
|
|
782
|
+
redeem_url: config.idp.oidc.token,
|
|
783
|
+
oidc_jwks_url: config.idp.oidc.jwks,
|
|
784
|
+
}
|
|
785
|
+
: {}),
|
|
711
786
|
redirect_url: `https://${config.host}:${config.port}/oauth2/callback`,
|
|
712
787
|
client_id: config.oauth2.id,
|
|
713
788
|
...(config.oauth2.secret === undefined
|
|
@@ -723,9 +798,16 @@ function renderOauth2ProxyConfig(config) {
|
|
|
723
798
|
cookie_csrf_per_request: true,
|
|
724
799
|
cookie_csrf_per_request_limit: 16,
|
|
725
800
|
email_domains: config.oauth2.emailDomains,
|
|
726
|
-
whitelist_domains: [
|
|
801
|
+
whitelist_domains: [
|
|
802
|
+
config.host,
|
|
803
|
+
`${config.host}:${config.port}`,
|
|
804
|
+
...(!('dex' in config.idp) && config.idp.oidc.end_session_endpoint
|
|
805
|
+
? [new URL(config.idp.oidc.end_session_endpoint).host]
|
|
806
|
+
: []),
|
|
807
|
+
],
|
|
727
808
|
scope: config.oauth2.scopes.join(' '),
|
|
728
809
|
reverse_proxy: true,
|
|
810
|
+
trusted_proxy_ips: config.network.trustedProxyIps,
|
|
729
811
|
set_xauthrequest: true,
|
|
730
812
|
set_authorization_header: true,
|
|
731
813
|
pass_access_token: true,
|
|
@@ -746,36 +828,19 @@ function renderOauth2ProxyConfig(config) {
|
|
|
746
828
|
.join('\n')}\n`;
|
|
747
829
|
}
|
|
748
830
|
|
|
749
|
-
function render(config) {
|
|
750
|
-
writeComposeConfig(config);
|
|
751
|
-
if (config.idp.dex !== undefined) {
|
|
752
|
-
writeDexConfig(config);
|
|
753
|
-
}
|
|
754
|
-
writeNginxConfig(config);
|
|
755
|
-
writeOauth2ProxyConfig(config);
|
|
756
|
-
}
|
|
757
|
-
|
|
758
831
|
function createVisageServer(options) {
|
|
832
|
+
const cache = join(process.cwd(), '.visage');
|
|
759
833
|
const config = resolveConfig(resolveOptions({
|
|
760
834
|
...options,
|
|
761
835
|
upstreams: {
|
|
762
836
|
...options.upstreams,
|
|
763
837
|
vite: resolveViteUpstream(options.upstreams?.vite),
|
|
764
838
|
},
|
|
765
|
-
}),
|
|
839
|
+
}), cache);
|
|
766
840
|
let stop;
|
|
767
841
|
return {
|
|
768
842
|
async listen() {
|
|
769
|
-
|
|
770
|
-
return;
|
|
771
|
-
rmSync(join(config.cache, 'logs'), { recursive: true, force: true });
|
|
772
|
-
await ensureCerts({
|
|
773
|
-
certs: join(config.cache, config.files.certs[0]),
|
|
774
|
-
hostname: config.host,
|
|
775
|
-
});
|
|
776
|
-
ensureHostEntry(config.host);
|
|
777
|
-
render(config);
|
|
778
|
-
stop = await startCompose(join(config.cache, config.files.compose));
|
|
843
|
+
stop ??= await startVisageServer(config);
|
|
779
844
|
},
|
|
780
845
|
close() {
|
|
781
846
|
stop?.();
|
|
@@ -783,6 +848,22 @@ function createVisageServer(options) {
|
|
|
783
848
|
},
|
|
784
849
|
};
|
|
785
850
|
}
|
|
851
|
+
async function startVisageServer(config) {
|
|
852
|
+
const logs = join(config.cache, 'logs');
|
|
853
|
+
rmSync(logs, { recursive: true, force: true });
|
|
854
|
+
mkdirSync(logs, { recursive: true });
|
|
855
|
+
await ensureCerts(config);
|
|
856
|
+
ensureHostEntry(config);
|
|
857
|
+
const renderConfig = ensureNginxNetwork(config);
|
|
858
|
+
writeComposeConfig(renderConfig);
|
|
859
|
+
if ('dex' in renderConfig.idp) {
|
|
860
|
+
writeDexConfig(renderConfig);
|
|
861
|
+
}
|
|
862
|
+
writeNginxConfig(renderConfig);
|
|
863
|
+
writeOauth2ProxyConfig(renderConfig);
|
|
864
|
+
return startCompose(renderConfig);
|
|
865
|
+
}
|
|
866
|
+
|
|
786
867
|
function visage(options = {}) {
|
|
787
868
|
const resolvedOptions = resolveOptions(options);
|
|
788
869
|
let stop;
|
|
@@ -813,21 +894,6 @@ function visage(options = {}) {
|
|
|
813
894
|
printUrls();
|
|
814
895
|
viteDevServer.config.logger.info(visageUrl ?? 'Visage failed to start');
|
|
815
896
|
};
|
|
816
|
-
async function startVisage(vite) {
|
|
817
|
-
const config = resolveConfig(resolveOptions({
|
|
818
|
-
...options,
|
|
819
|
-
upstreams: { ...options.upstreams, vite },
|
|
820
|
-
}), join(viteDevServer.config.cacheDir, 'visage'));
|
|
821
|
-
visageUrl = formatVisageUrlLog(config.host, config.port);
|
|
822
|
-
rmSync(join(config.cache, 'logs'), { recursive: true, force: true });
|
|
823
|
-
await ensureCerts({
|
|
824
|
-
certs: join(config.cache, config.files.certs[0]),
|
|
825
|
-
hostname: config.host,
|
|
826
|
-
});
|
|
827
|
-
ensureHostEntry(config.host);
|
|
828
|
-
render(config);
|
|
829
|
-
return startCompose(join(config.cache, config.files.compose));
|
|
830
|
-
}
|
|
831
897
|
// monkey patch vite's listen to get vite's auto-resolved port
|
|
832
898
|
const listen = viteDevServer.listen.bind(viteDevServer);
|
|
833
899
|
viteDevServer.listen = async (port, isRestart) => {
|
|
@@ -836,11 +902,19 @@ function visage(options = {}) {
|
|
|
836
902
|
if (!address || typeof address === 'string') {
|
|
837
903
|
throw new Error('Failed to resolve port for Visage');
|
|
838
904
|
}
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
...options
|
|
842
|
-
|
|
843
|
-
|
|
905
|
+
const cache = join(viteDevServer.config.cacheDir, 'visage');
|
|
906
|
+
const config = resolveConfig(resolveOptions({
|
|
907
|
+
...options,
|
|
908
|
+
upstreams: {
|
|
909
|
+
...options.upstreams,
|
|
910
|
+
vite: resolveViteUpstream({
|
|
911
|
+
port: address.port,
|
|
912
|
+
...options.upstreams?.vite,
|
|
913
|
+
}),
|
|
914
|
+
},
|
|
915
|
+
}), cache);
|
|
916
|
+
visageUrl = formatVisageUrlLog(config.host, config.port);
|
|
917
|
+
stop = await startVisageServer(config);
|
|
844
918
|
viteDevServer.httpServer?.once('close', closeBundle);
|
|
845
919
|
return result;
|
|
846
920
|
};
|
|
@@ -856,4 +930,4 @@ function formatVisageUrlLog(host, port) {
|
|
|
856
930
|
return ` ${AnsiGreen}➜${AnsiReset} ${AnsiBold}Visage${AnsiReset}: ${AnsiCyan}https://${host}:${AnsiBold}${port}${AnsiReset}${AnsiCyan}/${AnsiReset}`;
|
|
857
931
|
}
|
|
858
932
|
|
|
859
|
-
export { createVisageServer, visage as default };
|
|
933
|
+
export { createVisageServer, visage as default, visage };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../src/network.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CA6CrE"}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAInC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,wBAAgB,MAAM,CAAC,OAAO,GAAE,aAAkB,GAAG,MAAM,CAoE1D;AAED,eAAe,MAAM,CAAC"}
|
package/dist/render/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
export { writeComposeConfig } from './compose';
|
|
2
|
+
export { writeDexConfig } from './dex';
|
|
3
|
+
export { writeNginxConfig } from './nginx';
|
|
4
|
+
export { writeOauth2ProxyConfig } from './oauth2-proxy';
|
|
3
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/render/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/render/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type VisageConfig } from './config';
|
|
2
|
+
import type { VisageOptions, VisageServer } from './types';
|
|
3
|
+
export declare function createVisageServer(options: VisageOptions): VisageServer;
|
|
4
|
+
export declare function startVisageServer(config: VisageConfig): Promise<() => void>;
|
|
5
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAKA,OAAO,EAIL,KAAK,YAAY,EAClB,MAAM,UAAU,CAAC;AASlB,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE3D,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,YAAY,CAsBvE;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,MAAM,IAAI,CAAC,CAkBrB"}
|
package/dist/types.d.ts
CHANGED
|
@@ -100,11 +100,14 @@ export type VisageCookiePolicy = {
|
|
|
100
100
|
readonly path?: string;
|
|
101
101
|
};
|
|
102
102
|
/**
|
|
103
|
-
* Managed Dex identity provider options.
|
|
103
|
+
* Managed Dex identity provider options. Dex is the default identity provider
|
|
104
|
+
* for Visage.
|
|
104
105
|
*/
|
|
105
106
|
export type VisageDexOptions = {
|
|
106
107
|
/**
|
|
107
108
|
* Token expiration and rotation settings rendered into the Dex config.
|
|
109
|
+
*
|
|
110
|
+
* @see {@link https://dexidp.io/docs/configuration/tokens/#expiration-and-rotation-settings}
|
|
108
111
|
*/
|
|
109
112
|
readonly expiry?: VisageDexExpiry;
|
|
110
113
|
/**
|
|
@@ -190,30 +193,36 @@ export type VisageDexUser = {
|
|
|
190
193
|
*/
|
|
191
194
|
export type VisageExternalIdpOptions = {
|
|
192
195
|
/**
|
|
193
|
-
* OIDC issuer URL used by OAuth2 Proxy.
|
|
196
|
+
* OIDC issuer URL used by OAuth2 Proxy. When no endpoint paths are
|
|
197
|
+
* configured, OAuth2 Proxy discovers provider endpoints from this issuer.
|
|
194
198
|
*/
|
|
195
199
|
readonly issuer: string;
|
|
196
200
|
/**
|
|
197
201
|
* OIDC authorization path appended to
|
|
198
|
-
* {@link VisageExternalIdpOptions.issuer}.
|
|
199
|
-
*
|
|
200
|
-
* @defaultValue '/auth'
|
|
202
|
+
* {@link VisageExternalIdpOptions.issuer}. Configure this, `token`, or `jwks`
|
|
203
|
+
* to disable OIDC discovery and render explicit provider endpoints.
|
|
201
204
|
*/
|
|
202
205
|
readonly authorization?: string;
|
|
203
206
|
/**
|
|
204
207
|
* OIDC token endpoint path appended to
|
|
205
|
-
* {@link VisageExternalIdpOptions.issuer}.
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
+
* {@link VisageExternalIdpOptions.issuer}. Configure this, `authorization`,
|
|
209
|
+
* or `jwks` to disable OIDC discovery and render explicit provider
|
|
210
|
+
* endpoints.
|
|
208
211
|
*/
|
|
209
212
|
readonly token?: string;
|
|
210
213
|
/**
|
|
211
214
|
* OIDC JWKS endpoint path appended to
|
|
212
|
-
* {@link VisageExternalIdpOptions.issuer}.
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
+
* {@link VisageExternalIdpOptions.issuer}. Configure this, `authorization`,
|
|
216
|
+
* or `token` to disable OIDC discovery and render explicit provider
|
|
217
|
+
* endpoints.
|
|
215
218
|
*/
|
|
216
219
|
readonly jwks?: string;
|
|
220
|
+
/**
|
|
221
|
+
* OIDC end-session endpoint URL. When configured, Visage routes OAuth2 Proxy
|
|
222
|
+
* sign-out redirects through this endpoint so the provider session can be
|
|
223
|
+
* ended as part of the sign-out flow.
|
|
224
|
+
*/
|
|
225
|
+
readonly end_session_endpoint?: string;
|
|
217
226
|
};
|
|
218
227
|
/**
|
|
219
228
|
* OAuth2 client configuration used by OAuth2 Proxy and, for managed Dex, the
|
|
@@ -333,15 +342,16 @@ export type VisageProxyPolicy = {
|
|
|
333
342
|
*/
|
|
334
343
|
readonly redirect?: boolean;
|
|
335
344
|
/**
|
|
336
|
-
* Token forwarding behavior for the upstream `Authorization` header.
|
|
345
|
+
* Token forwarding behavior for the upstream `Authorization` header. Set
|
|
346
|
+
* to `false` to omit a bearer token. Set to `true` to forward the
|
|
347
|
+
* default bearer token for the upstream kind: an OAuth access token for
|
|
348
|
+
* external upstreams, or an OIDC ID token for local service upstreams.
|
|
337
349
|
*
|
|
338
|
-
* `'id'`
|
|
339
|
-
* OAuth access token for legacy/resource-server integrations that
|
|
340
|
-
* explicitly require it.
|
|
350
|
+
* Use `'id'` or `'access'` to force a specific token kind.
|
|
341
351
|
*
|
|
342
|
-
* @defaultValue `
|
|
352
|
+
* @defaultValue `false`
|
|
343
353
|
*/
|
|
344
|
-
readonly forward?: 'id' | 'access';
|
|
354
|
+
readonly forward?: boolean | 'id' | 'access';
|
|
345
355
|
};
|
|
346
356
|
/**
|
|
347
357
|
* Request headers to set when proxying to the upstream. Values may include
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB;;OAEG;IACH,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB;;OAEG;IACH,KAAK,IAAI,IAAI,CAAC;CACf,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,kBAAkB,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,gBAAgB,GAAG,wBAAwB,CAAC;IAC3D;;OAEG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,kBAAkB,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAClD;;OAEG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CACrD,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;OAOG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF
|
|
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.1-rc.
|
|
3
|
+
"version": "0.0.1-rc.20",
|
|
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",
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"files": [
|
|
36
|
-
"dist"
|
|
36
|
+
"dist",
|
|
37
|
+
"docker-compose.images.yml"
|
|
37
38
|
],
|
|
38
39
|
"engines": {
|
|
39
40
|
"node": ">=20"
|
|
@@ -45,7 +46,8 @@
|
|
|
45
46
|
"scripts": {
|
|
46
47
|
"build": "npm run clean && rollup -c",
|
|
47
48
|
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
48
|
-
"
|
|
49
|
+
"example": "npm run example:simple",
|
|
50
|
+
"example:simple": "cd examples/simple && npm run dev",
|
|
49
51
|
"format": "prettier --write \"{docs,examples,src,test}/**/*.{ts,tsx,js,jsx,json,css,html,md}\" \"*.{json,md}\"",
|
|
50
52
|
"format:check": "prettier --check \"{docs,examples,src,test}/**/*.{ts,tsx,js,jsx,json,css,html,md}\" \"*.{json,md}\"",
|
|
51
53
|
"promote:codex": "scripts/promote-codex.sh",
|
|
@@ -63,7 +65,7 @@
|
|
|
63
65
|
"rollup": "^4.60.4",
|
|
64
66
|
"tslib": "^2.8.1",
|
|
65
67
|
"typescript": "^5.9.3",
|
|
66
|
-
"vite": "^
|
|
68
|
+
"vite": "^8.0.13"
|
|
67
69
|
},
|
|
68
70
|
"dependencies": {
|
|
69
71
|
"bcryptjs": "^3.0.3",
|