wood-fired-tasks 2.0.3 → 2.0.4
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/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,26 @@ vulnerabilities, supply-chain pinning) are always called out under `Security`.
|
|
|
13
13
|
|
|
14
14
|
_No changes yet._
|
|
15
15
|
|
|
16
|
+
## [v2.0.4] - 2026-06-08
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- **Device-flow `verification_uri` now points at the address the client actually
|
|
20
|
+
connected to, not `localhost`.** `POST /auth/device/code` built the URL the
|
|
21
|
+
user opens in a browser from a STATIC configured origin (`OIDC_REDIRECT_URI`'s
|
|
22
|
+
origin), which is typically `http://localhost:3000`. A CLI that reached the
|
|
23
|
+
server over the LAN (e.g. `http://192.168.x.x:3000`) was told to open a
|
|
24
|
+
`localhost` URL pointing at its OWN machine — a dead end. The origin is now
|
|
25
|
+
derived per-request from the `Host` header (honoring `X-Forwarded-Host` /
|
|
26
|
+
`X-Forwarded-Proto` from a trusted reverse proxy), falling back to the
|
|
27
|
+
configured origin only when no `Host` is present. This is not a
|
|
28
|
+
host-header-injection vector: the `verification_uri` is returned only to the
|
|
29
|
+
same client that made the request. (Note: a Google-backed server whose OAuth
|
|
30
|
+
callback is `http://localhost` still can't complete the *browser login* leg
|
|
31
|
+
for a remote client — Google forbids non-`localhost` `http` redirect URIs — so
|
|
32
|
+
remote clients should use `tasks setup --remote <url> --token <pat>` or an
|
|
33
|
+
HTTPS domain. This fix makes the verification URL correct for properly
|
|
34
|
+
routable servers.)
|
|
35
|
+
|
|
16
36
|
## [v2.0.3] - 2026-06-08
|
|
17
37
|
|
|
18
38
|
### Fixed
|
|
@@ -26,9 +26,15 @@
|
|
|
26
26
|
import type { FastifyPluginAsync } from 'fastify';
|
|
27
27
|
export interface DeviceCodeRouteOptions {
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
30
|
-
* 30-08 sources this from
|
|
31
|
-
* Example: `https://woodfiredbugs.local`.
|
|
29
|
+
* FALLBACK origin for the verification URIs the CLI prints, used only when
|
|
30
|
+
* the request carries no usable Host header. Plan 30-08 sources this from
|
|
31
|
+
* `new URL(env.OIDC_REDIRECT_URI).origin`. Example: `https://woodfiredbugs.local`.
|
|
32
|
+
*
|
|
33
|
+
* #834: the verification origin is now derived PER-REQUEST from the address
|
|
34
|
+
* the client actually connected to (see {@link resolveVerificationOrigin}),
|
|
35
|
+
* because this configured value is typically `http://localhost:3000` and is
|
|
36
|
+
* unroutable for any client that reached the server over the LAN / a real
|
|
37
|
+
* hostname. `origin` remains as the no-Host-header fallback.
|
|
32
38
|
*/
|
|
33
39
|
origin: string;
|
|
34
40
|
/**
|
|
@@ -37,5 +43,25 @@ export interface DeviceCodeRouteOptions {
|
|
|
37
43
|
*/
|
|
38
44
|
expectedClientId: string;
|
|
39
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolve the origin (`scheme://host[:port]`) the CLIENT used to reach this
|
|
48
|
+
* server, for building the device-flow `verification_uri` the user opens in a
|
|
49
|
+
* browser (#834).
|
|
50
|
+
*
|
|
51
|
+
* Previously this was a STATIC configured origin (`OIDC_REDIRECT_URI`'s origin),
|
|
52
|
+
* which is `http://localhost:3000` on a typical server — so a CLI that connected
|
|
53
|
+
* over the LAN (e.g. `http://192.168.x.x:3000`) was told to open a localhost URL
|
|
54
|
+
* pointing at its OWN machine. We instead use the host the request arrived on,
|
|
55
|
+
* honoring `X-Forwarded-{Host,Proto}` from a trusted reverse proxy.
|
|
56
|
+
*
|
|
57
|
+
* Security: this is NOT a host-header-injection vector. The `verification_uri`
|
|
58
|
+
* is returned ONLY to the same client that sent the request, so a spoofed Host
|
|
59
|
+
* merely misdirects the spoofer. Falls back to `fallback` (the configured
|
|
60
|
+
* origin) when no Host header is present at all.
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveVerificationOrigin(request: {
|
|
63
|
+
headers: Record<string, string | string[] | undefined>;
|
|
64
|
+
protocol?: string;
|
|
65
|
+
}, fallback: string): string;
|
|
40
66
|
declare const deviceCodeRoute: FastifyPluginAsync<DeviceCodeRouteOptions>;
|
|
41
67
|
export default deviceCodeRoute;
|
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { createSession } from '../../../services/device-flow-store.js';
|
|
3
|
+
/** First value of a possibly comma-joined / array-valued HTTP header. */
|
|
4
|
+
function firstHeaderValue(v) {
|
|
5
|
+
const raw = Array.isArray(v) ? v[0] : v;
|
|
6
|
+
if (typeof raw !== 'string')
|
|
7
|
+
return undefined;
|
|
8
|
+
const first = raw.split(',')[0]?.trim();
|
|
9
|
+
return first && first.length > 0 ? first : undefined;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Resolve the origin (`scheme://host[:port]`) the CLIENT used to reach this
|
|
13
|
+
* server, for building the device-flow `verification_uri` the user opens in a
|
|
14
|
+
* browser (#834).
|
|
15
|
+
*
|
|
16
|
+
* Previously this was a STATIC configured origin (`OIDC_REDIRECT_URI`'s origin),
|
|
17
|
+
* which is `http://localhost:3000` on a typical server — so a CLI that connected
|
|
18
|
+
* over the LAN (e.g. `http://192.168.x.x:3000`) was told to open a localhost URL
|
|
19
|
+
* pointing at its OWN machine. We instead use the host the request arrived on,
|
|
20
|
+
* honoring `X-Forwarded-{Host,Proto}` from a trusted reverse proxy.
|
|
21
|
+
*
|
|
22
|
+
* Security: this is NOT a host-header-injection vector. The `verification_uri`
|
|
23
|
+
* is returned ONLY to the same client that sent the request, so a spoofed Host
|
|
24
|
+
* merely misdirects the spoofer. Falls back to `fallback` (the configured
|
|
25
|
+
* origin) when no Host header is present at all.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveVerificationOrigin(request, fallback) {
|
|
28
|
+
const host = firstHeaderValue(request.headers['x-forwarded-host']) ??
|
|
29
|
+
firstHeaderValue(request.headers['host']);
|
|
30
|
+
if (!host)
|
|
31
|
+
return fallback;
|
|
32
|
+
const scheme = firstHeaderValue(request.headers['x-forwarded-proto']) ??
|
|
33
|
+
(request.protocol && request.protocol.length > 0 ? request.protocol : 'http');
|
|
34
|
+
return `${scheme}://${host}`;
|
|
35
|
+
}
|
|
3
36
|
/**
|
|
4
37
|
* Body schema for POST /auth/device/code (JSON only — RFC 8628 lets servers
|
|
5
38
|
* pick; the CLI always sends JSON). `scope` is accepted-and-ignored in v1.6
|
|
@@ -37,11 +70,15 @@ const deviceCodeRoute = async (fastify, opts) => {
|
|
|
37
70
|
clientId: client_id,
|
|
38
71
|
hostname: hostname ?? null,
|
|
39
72
|
}, 'device flow started');
|
|
73
|
+
// #834: build the verification URL from the address the CLIENT connected to
|
|
74
|
+
// (request Host / X-Forwarded-*), not the static configured origin, so a
|
|
75
|
+
// remote/LAN client gets a URL it can actually open instead of localhost.
|
|
76
|
+
const origin = resolveVerificationOrigin(request, opts.origin);
|
|
40
77
|
return reply.code(200).send({
|
|
41
78
|
device_code: session.deviceCode,
|
|
42
79
|
user_code: session.userCode,
|
|
43
|
-
verification_uri: `${
|
|
44
|
-
verification_uri_complete: `${
|
|
80
|
+
verification_uri: `${origin}/auth/device`,
|
|
81
|
+
verification_uri_complete: `${origin}/auth/device?user_code=${session.userCode}`,
|
|
45
82
|
expires_in: 600,
|
|
46
83
|
interval: 5,
|
|
47
84
|
});
|