@riceawa/dsh-lan-gateway 0.6.1 → 0.7.0
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 +15 -12
- package/lib/client.js +106 -148
- package/lib/client.js.map +1 -1
- package/lib/index.js +52 -2
- package/package.json +21 -23
- package/src/client/index.ts +37 -26
- package/src/client/lan-gateway-card.tsx +205 -195
- package/src/gateway.ts +31 -5
- package/src/request-policy.ts +36 -0
package/src/request-policy.ts
CHANGED
|
@@ -122,6 +122,42 @@ export function isLoopbackHost(hostname: string): boolean {
|
|
|
122
122
|
)
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Whether a `Host` header names a loopback authority (127/8, `localhost`, ::1).
|
|
127
|
+
*
|
|
128
|
+
* This is the second half of the gateway's local-management exemption, and it
|
|
129
|
+
* answers a different question from {@link isLoopbackHost}'s own callers: not
|
|
130
|
+
* "is this authority loopback" but "did the browser itself use a loopback
|
|
131
|
+
* address". The two tests are combined on purpose. The socket source is what a
|
|
132
|
+
* remote client cannot forge; the Host is what a deployment cannot blur — behind
|
|
133
|
+
* a trusted TLS terminator every socket source is the terminator's loopback
|
|
134
|
+
* address, so the source test alone would readmit every remote browser, while a
|
|
135
|
+
* remote browser names the public host it dialed and stays refused.
|
|
136
|
+
*
|
|
137
|
+
* A client that can set an arbitrary Host (curl, not a browser) must still pass
|
|
138
|
+
* the socket-source test and hold a gateway session to reach anything, and the
|
|
139
|
+
* route behind the prefix is the one the native loopback listener already
|
|
140
|
+
* answers with no credential at all.
|
|
141
|
+
* @param host - the `Host` header value, or undefined.
|
|
142
|
+
* @returns true only when it parses and names a loopback authority.
|
|
143
|
+
*/
|
|
144
|
+
export function isLoopbackAuthority(host: string | undefined): boolean {
|
|
145
|
+
if (host === undefined || host === '') return false
|
|
146
|
+
let url: URL
|
|
147
|
+
try {
|
|
148
|
+
url = new URL(`http://${host}`)
|
|
149
|
+
} catch {
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
// A Host header is nothing but an authority. Anything URL parsing had to read
|
|
153
|
+
// beyond `host[:port]` — userinfo, a path, a query — means the value is not
|
|
154
|
+
// one, and `http://evil.com@127.0.0.1` must not read as loopback.
|
|
155
|
+
if (url.username !== '' || url.password !== '' || url.pathname !== '/' || url.search !== '' || url.hash !== '') {
|
|
156
|
+
return false
|
|
157
|
+
}
|
|
158
|
+
return isLoopbackHost(url.hostname)
|
|
159
|
+
}
|
|
160
|
+
|
|
125
161
|
/** Whether this source must present a gateway session (default: everyone). */
|
|
126
162
|
export function requiresLogin(source: SourceClass, lanPasswordless: boolean): boolean {
|
|
127
163
|
return !(lanPasswordless && source !== 'internet')
|