@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.
@@ -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')