mikser-io 11.10.5 → 11.11.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/auth.js +31 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mikser-io",
3
- "version": "11.10.5",
3
+ "version": "11.11.0",
4
4
  "files": [
5
5
  "app.js",
6
6
  "index.js",
package/src/auth.js CHANGED
@@ -123,6 +123,37 @@ export function reachabilityOf({ auth, token, allowRemote } = {}) {
123
123
  // token-gated endpoint stayed open to localhost. It exists so the api and
124
124
  // mcp plugins can keep their documented semantics for a plain `token:`
125
125
  // while a real verifier gets the stricter default.
126
+ // The exact bytes of a Basic challenge, in one place.
127
+ //
128
+ // It was written out at five call sites across two packages, which is how the
129
+ // `charset` parameter came to be a hardcoded decision nobody could change
130
+ // without a reverse-proxy rewrite.
131
+ //
132
+ // `charset="UTF-8"` is RFC 7617 §2.1, and the RFC is explicit about its
133
+ // weight: "This information is purely advisory." It tells a client to encode
134
+ // the user-pass as UTF-8 NFC; a client that ignores it is unaffected for
135
+ // ASCII credentials, which is nearly all of them. It buys exactly one thing —
136
+ // a non-ASCII password from a client that would otherwise send latin-1, which
137
+ // this server cannot match because it decodes the header as UTF-8.
138
+ //
139
+ // OFF by default, because the measured cost is larger than that benefit. The
140
+ // Windows WebDAV mini-redirector appears not to parse the parameter, and a
141
+ // challenge carrying it leaves a mapped drive unable to reconnect from stored
142
+ // credentials at all: before/after on a live deployment, with the parameter
143
+ // stripped at the proxy as the only variable, the client went from never
144
+ // sending an Authorization header to offering Basic unprompted. Interactive
145
+ // entry worked either way — that path sends Basic blind and never reads the
146
+ // challenge, which is the asymmetry that identified it.
147
+ //
148
+ // Turn it on where non-ASCII passwords matter more than Explorer does.
149
+ export function basicChallenge({ realm = 'mikser', charset = false } = {}) {
150
+ // A realm is a quoted-string, so a quote or a backslash inside one would
151
+ // end the field early and hand the rest to the parser as garbage. Cheap
152
+ // to prevent here, and impossible to prevent at five call sites.
153
+ const quoted = String(realm).replace(/[\\"]/g, '\\$&')
154
+ return charset ? `Basic realm="${quoted}", charset="UTF-8"` : `Basic realm="${quoted}"`
155
+ }
156
+
126
157
  export async function authorize(req, verifier, { allowRemote = false, trustLoopback = false } = {}) {
127
158
  const local = isLoopback(req.ip)
128
159