1
- import type { ReverseProxyOption , ReverseProxyOptions , TlsConfig } from './types'
1
+ import type { CustomTlsConfig , MultiReverseProxyConfig , ReverseProxyConfigs , TlsConfig } from './types'
2
2
import os from 'node:os'
3
3
import path from 'node:path'
4
4
import { log } from '@stacksjs/cli'
5
5
import { addCertToSystemTrustStoreAndSaveCert , createRootCA , generateCertificate as generateCert } from '@stacksjs/tlsx'
6
+ import { config } from './config'
6
7
import { debugLog } from './utils'
7
8
8
9
let cachedSSLConfig : { key : string , cert : string , ca ?: string } | null = null
9
10
10
- function extractDomains ( options : ReverseProxyOptions ) : string [ ] {
11
- if ( Array . isArray ( options ) ) {
12
- return options . map ( ( opt ) => {
13
- const domain = opt . to || 'stacks.localhost'
11
+ function isMultiProxyConfig ( options : ReverseProxyConfigs ) : options is MultiReverseProxyConfig {
12
+ return 'proxies' in options
13
+ }
14
+
15
+ function extractDomains ( options : ReverseProxyConfigs ) : string [ ] {
16
+ if ( isMultiProxyConfig ( options ) ) {
17
+ return options . proxies . map ( ( proxy ) => {
18
+ const domain = proxy . to || 'stacks.localhost'
14
19
return domain . startsWith ( 'http' ) ? new URL ( domain ) . hostname : domain
15
20
} )
16
21
}
22
+
17
23
const domain = options . to || 'stacks.localhost'
18
24
return [ domain . startsWith ( 'http' ) ? new URL ( domain ) . hostname : domain ]
19
25
}
20
26
21
27
// Generate wildcard patterns for a domain
22
28
function generateWildcardPatterns ( domain : string ) : string [ ] {
23
29
const patterns = new Set < string > ( )
24
- patterns . add ( domain ) // Add exact domain
30
+ patterns . add ( domain )
25
31
26
32
// Split domain into parts (e.g., "test.local" -> ["test", "local"])
27
33
const parts = domain . split ( '.' )
@@ -33,9 +39,26 @@ function generateWildcardPatterns(domain: string): string[] {
33
39
return Array . from ( patterns )
34
40
}
35
41
36
- function generateBaseConfig ( domains : string [ ] , verbose ?: boolean ) : TlsConfig {
42
+ function generateBaseConfig ( options : ReverseProxyConfigs , verbose ?: boolean ) : TlsConfig {
43
+ const domains = extractDomains ( options )
37
44
const sslBase = path . join ( os . homedir ( ) , '.stacks' , 'ssl' )
38
-
45
+ console . log ( 'config.https' , config . https )
46
+ const httpsConfig : Partial < CustomTlsConfig > = options . https === true
47
+ ? {
48
+ caCertPath : path . join ( sslBase , 'rpx-ca.crt' ) ,
49
+ certPath : path . join ( sslBase , 'rpx.crt' ) ,
50
+ keyPath : path . join ( sslBase , 'rpx.key' ) ,
51
+ }
52
+ : typeof config . https === 'object'
53
+ ? {
54
+ ...options . https ,
55
+ ...config . https ,
56
+ }
57
+ : { }
58
+
59
+ debugLog ( 'ssl' , `Extracted domains: ${ domains . join ( ', ' ) } ` , verbose )
60
+ debugLog ( 'ssl' , `Using SSL base path: ${ sslBase } ` , verbose )
61
+ debugLog ( 'ssl' , `Using HTTPS config: ${ JSON . stringify ( httpsConfig ) } ` , verbose )
39
62
// Generate all possible SANs, including wildcards
40
63
const allPatterns = new Set < string > ( )
41
64
domains . forEach ( ( domain ) => {
@@ -54,31 +77,29 @@ function generateBaseConfig(domains: string[], verbose?: boolean): TlsConfig {
54
77
debugLog ( 'ssl' , `Generated domain patterns: ${ uniqueDomains . join ( ', ' ) } ` , verbose )
55
78
56
79
// Create a single object that contains all the config
57
- const config : TlsConfig = {
80
+ return {
81
+ // Use the first domain for the certificate CN
58
82
domain : domains [ 0 ] ,
59
83
hostCertCN : domains [ 0 ] ,
60
- caCertPath : path . join ( sslBase , 'rpx-root-ca.crt' ) ,
61
- certPath : path . join ( sslBase , 'rpx-certificate.crt' ) ,
62
- keyPath : path . join ( sslBase , 'rpx-certificate.key' ) ,
63
- altNameIPs : [ '127.0.0.1' , '::1' ] ,
64
- // altNameURIs needs to be an empty array as we're using DNS names instead
65
- altNameURIs : [ ] ,
66
- // The real domains go in the commonName and subject alternative names
67
- commonName : domains [ 0 ] ,
68
- organizationName : 'RPX Local Development' ,
69
- countryName : 'US' ,
70
- stateName : 'California' ,
71
- localityName : 'Playa Vista' ,
72
- validityDays : 825 ,
84
+ caCertPath : httpsConfig ?. caCertPath ?? path . join ( sslBase , 'rpx-ca.crt' ) ,
85
+ certPath : httpsConfig ?. certPath ?? path . join ( sslBase , 'rpx.crt' ) ,
86
+ keyPath : httpsConfig ?. keyPath ?? path . join ( sslBase , 'rpx.key' ) ,
87
+ altNameIPs : httpsConfig ?. altNameIPs ?? [ '127.0.0.1' , '::1' ] ,
88
+ altNameURIs : httpsConfig ?. altNameURIs ?? [ ] ,
89
+ // Include all domains in the SAN
90
+ commonName : httpsConfig ?. commonName ?? domains [ 0 ] ,
91
+ organizationName : httpsConfig ?. organizationName ?? 'Local Development' ,
92
+ countryName : httpsConfig ?. countryName ?? 'US' ,
93
+ stateName : httpsConfig ?. stateName ?? 'California' ,
94
+ localityName : httpsConfig ?. localityName ?? 'Playa Vista' ,
95
+ validityDays : httpsConfig ?. validityDays ?? 825 ,
73
96
verbose : verbose ?? false ,
74
- // Add Subject Alternative Names as DNS names
97
+ // Add all domains as Subject Alternative Names
75
98
subjectAltNames : uniqueDomains . map ( domain => ( {
76
99
type : 2 , // DNS type
77
100
value : domain ,
78
101
} ) ) ,
79
- }
80
-
81
- return config
102
+ } satisfies TlsConfig
82
103
}
83
104
84
105
function generateRootCAConfig ( ) : TlsConfig {
@@ -102,21 +123,23 @@ function generateRootCAConfig(): TlsConfig {
102
123
}
103
124
}
104
125
105
- export function httpsConfig ( options : ReverseProxyOption | ReverseProxyOptions ) : TlsConfig {
106
- const domains = extractDomains ( options )
107
- const verbose = Array . isArray ( options ) ? options [ 0 ] ?. verbose : options . verbose
108
-
109
- return generateBaseConfig ( domains , verbose )
126
+ export function httpsConfig ( options : ReverseProxyConfigs ) : TlsConfig {
127
+ return generateBaseConfig ( options , options . verbose )
110
128
}
111
129
112
- export async function generateCertificate ( options : ReverseProxyOption | ReverseProxyOptions ) : Promise < void > {
130
+ export async function generateCertificate ( options : ReverseProxyConfigs ) : Promise < void > {
113
131
if ( cachedSSLConfig ) {
114
- debugLog ( 'ssl' , 'Using cached SSL configuration' , Array . isArray ( options ) ? options [ 0 ] ?. verbose : options . verbose )
132
+ const verbose = isMultiProxyConfig ( options ) ? options . verbose : options . verbose
133
+ debugLog ( 'ssl' , 'Using cached SSL configuration' , verbose )
115
134
return
116
135
}
117
136
118
- const domains = extractDomains ( options )
119
- const verbose = Array . isArray ( options ) ? options [ 0 ] ?. verbose : options . verbose
137
+ // Get all unique domains from the configuration
138
+ const domains = isMultiProxyConfig ( options )
139
+ ? [ options . proxies [ 0 ] . to , ...options . proxies . map ( proxy => proxy . to ) ] // Include the first domain from proxies array
140
+ : [ options . to ]
141
+
142
+ const verbose = isMultiProxyConfig ( options ) ? options . verbose : options . verbose
120
143
121
144
debugLog ( 'ssl' , `Generating certificate for domains: ${ domains . join ( ', ' ) } ` , verbose )
122
145
@@ -126,7 +149,7 @@ export async function generateCertificate(options: ReverseProxyOption | ReverseP
126
149
const caCert = await createRootCA ( rootCAConfig )
127
150
128
151
// Generate the host certificate with all domains
129
- const hostConfig = generateBaseConfig ( domains , verbose )
152
+ const hostConfig = generateBaseConfig ( options , verbose )
130
153
log . info ( `Generating host certificate for: ${ domains . join ( ', ' ) } ` )
131
154
132
155
const hostCert = await generateCert ( {
0 commit comments