wood-fired-tasks 2.0.2 → 2.0.3
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 +25 -0
- package/dist/api/server.js +7 -2
- package/dist/cli/commands/setup.js +41 -20
- package/dist/config/env.d.ts +1 -0
- package/dist/config/env.js +10 -0
- package/docs/SETUP.md +8 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,31 @@ vulnerabilities, supply-chain pinning) are always called out under `Security`.
|
|
|
13
13
|
|
|
14
14
|
_No changes yet._
|
|
15
15
|
|
|
16
|
+
## [v2.0.3] - 2026-06-08
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- **`tasks setup` device-flow login no longer fails with `invalid_client` on
|
|
20
|
+
servers backed by a real IdP.** The RFC 8628 device-flow `client_id` was
|
|
21
|
+
validated against `OIDC_CLIENT_ID` — the *IdP's* OAuth client id used for the
|
|
22
|
+
browser SSO leg — while the CLI sends a logical `'wft-cli'`. On any server
|
|
23
|
+
using e.g. Google, those never matched, so `POST /auth/device/code` returned
|
|
24
|
+
`400 invalid_client` and setup crashed. The device-flow client id is now a
|
|
25
|
+
**separate** setting, `OIDC_DEVICE_CLIENT_ID` (defaults to `'wft-cli'` on both
|
|
26
|
+
server and CLI), decoupled from `OIDC_CLIENT_ID` and not part of the
|
|
27
|
+
all-or-nothing OIDC group — so the stock CLI authenticates against a stock
|
|
28
|
+
server with no configuration. Operators who customize it set the same value
|
|
29
|
+
on both sides.
|
|
30
|
+
- **Remote onboarding degrades gracefully when the device flow can't start.**
|
|
31
|
+
A failed/throwing `POST /auth/device/code` (e.g. `invalid_client`, or a
|
|
32
|
+
network error) previously aborted `tasks setup` with a raw stack-trace-style
|
|
33
|
+
error. It now logs the reason and falls back to manual personal-access-token
|
|
34
|
+
entry, so onboarding can still complete.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
- New optional env var **`OIDC_DEVICE_CLIENT_ID`** (default `'wft-cli'`),
|
|
38
|
+
documented in `docs/SETUP.md`. No action required for existing deployments
|
|
39
|
+
using the default CLI.
|
|
40
|
+
|
|
16
41
|
## [v2.0.2] - 2026-06-08
|
|
17
42
|
|
|
18
43
|
### Fixed
|
package/dist/api/server.js
CHANGED
|
@@ -362,6 +362,11 @@ export async function createServer(options) {
|
|
|
362
362
|
}, 'device-flow origin resolved to localhost — CLI verification_uri will be unroutable for remote clients');
|
|
363
363
|
}
|
|
364
364
|
const clientId = config.OIDC_CLIENT_ID;
|
|
365
|
+
// #833: the device-flow `client_id` is a SEPARATE logical identifier the
|
|
366
|
+
// CLI sends (default `'wft-cli'`), NOT the IdP's OAuth client id. Using
|
|
367
|
+
// OIDC_CLIENT_ID here rejected the stock CLI with `invalid_client` on any
|
|
368
|
+
// real-IdP server. Always defaulted, so device flow works out of the box.
|
|
369
|
+
const deviceClientId = config.OIDC_DEVICE_CLIENT_ID;
|
|
365
370
|
await server.register(authRoutes, {
|
|
366
371
|
prefix: '/auth',
|
|
367
372
|
oidcConfig: app.oidcConfig,
|
|
@@ -413,9 +418,9 @@ export async function createServer(options) {
|
|
|
413
418
|
await scope.register(authPlugin);
|
|
414
419
|
await scope.register(deviceCodeRoute, {
|
|
415
420
|
origin,
|
|
416
|
-
expectedClientId:
|
|
421
|
+
expectedClientId: deviceClientId,
|
|
417
422
|
});
|
|
418
|
-
await scope.register(deviceTokenRoute, { expectedClientId:
|
|
423
|
+
await scope.register(deviceTokenRoute, { expectedClientId: deviceClientId });
|
|
419
424
|
await scope.register(deviceHtmlRoute, { origin });
|
|
420
425
|
});
|
|
421
426
|
}
|
|
@@ -809,32 +809,53 @@ export async function runRemoteOnboarding(options = {}) {
|
|
|
809
809
|
log('This server supports browser login (OIDC ready); using the device flow.');
|
|
810
810
|
}
|
|
811
811
|
// 3. Branch on the selected method.
|
|
812
|
-
|
|
812
|
+
let method = selectRemoteOnboardingMethod(probeResult);
|
|
813
813
|
const oidc = probeResult.ok ? probeResult.oidc : null;
|
|
814
814
|
if (method === 'device-flow') {
|
|
815
815
|
// Self-provision a PAT via the OIDC device flow (#806). runDeviceLogin owns
|
|
816
816
|
// the entire RFC 8628 exchange AND persists the minted PAT via the
|
|
817
817
|
// credentials writer (writeCredentials) — there is NO host token-mint path
|
|
818
|
-
// here.
|
|
819
|
-
//
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
818
|
+
// here.
|
|
819
|
+
//
|
|
820
|
+
// client_id (#833): send the DEVICE-flow client id (`OIDC_DEVICE_CLIENT_ID`,
|
|
821
|
+
// default `'wft-cli'`), NOT the IdP's `OIDC_CLIENT_ID`. The server validates
|
|
822
|
+
// it against its own `OIDC_DEVICE_CLIENT_ID` (also defaulting to `'wft-cli'`),
|
|
823
|
+
// so the stock CLI matches a stock server out of the box.
|
|
824
|
+
let deviceOk = false;
|
|
825
|
+
try {
|
|
826
|
+
const result = await deviceLogin({
|
|
827
|
+
baseUrl,
|
|
828
|
+
clientId: process.env['OIDC_DEVICE_CLIENT_ID'] ?? 'wft-cli',
|
|
829
|
+
hostname: os.hostname(),
|
|
830
|
+
openBrowser: true,
|
|
831
|
+
isJson: false,
|
|
832
|
+
});
|
|
833
|
+
deviceOk = result.ok;
|
|
834
|
+
if (!deviceOk) {
|
|
835
|
+
log('Browser/device login did not complete; falling back to manual PAT entry.');
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
catch (err) {
|
|
839
|
+
// #833: device/code can hard-reject (e.g. a client_id mismatch →
|
|
840
|
+
// `invalid_client`) or the network can fail. Don't crash setup — degrade
|
|
841
|
+
// to manual personal-access-token entry so onboarding can still finish.
|
|
842
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
843
|
+
log(`Browser/device login could not start (${message}).`);
|
|
844
|
+
log('Falling back to manual personal-access-token entry.');
|
|
845
|
+
}
|
|
846
|
+
if (deviceOk) {
|
|
847
|
+
// Device login succeeded and the PAT is already persisted in the
|
|
848
|
+
// credentials file. Now write the URL-only remote MCP entry (#810) into
|
|
849
|
+
// ~/.claude.json. The entry carries ONLY WFT_API_URL — the bridge resolves
|
|
850
|
+
// its bearer token at runtime from the credentials file the device flow
|
|
851
|
+
// just wrote, so NO token is ever embedded in claude.json and NO PAT is
|
|
852
|
+
// double-cached here.
|
|
853
|
+
const setup = writeRemoteMcpEntryOnly({ ...options, remote: baseUrl });
|
|
854
|
+
return { mode: 'remote', oidc, method, ok: true, setup };
|
|
829
855
|
}
|
|
830
|
-
// Device
|
|
831
|
-
//
|
|
832
|
-
|
|
833
|
-
// its bearer token at runtime from the credentials file the device flow
|
|
834
|
-
// just wrote, so NO token is ever embedded in claude.json and NO PAT is
|
|
835
|
-
// double-cached here.
|
|
836
|
-
const setup = writeRemoteMcpEntryOnly({ ...options, remote: baseUrl });
|
|
837
|
-
return { mode: 'remote', oidc, method, ok: true, setup };
|
|
856
|
+
// Device flow unavailable/aborted → fall through to the manual-PAT path
|
|
857
|
+
// below (records the method actually used so the result reflects reality).
|
|
858
|
+
method = 'manual-pat';
|
|
838
859
|
}
|
|
839
860
|
// method === 'manual-pat' (task #809): obtain a PAT, validate it, and persist
|
|
840
861
|
// it through the SAME credentials writer the device flow uses
|
package/dist/config/env.d.ts
CHANGED
|
@@ -79,6 +79,7 @@ export declare const configSchema: z.ZodObject<{
|
|
|
79
79
|
OIDC_CLIENT_ID: z.ZodOptional<z.ZodString>;
|
|
80
80
|
OIDC_CLIENT_SECRET: z.ZodOptional<z.ZodString>;
|
|
81
81
|
OIDC_REDIRECT_URI: z.ZodOptional<z.ZodString>;
|
|
82
|
+
OIDC_DEVICE_CLIENT_ID: z.ZodDefault<z.ZodString>;
|
|
82
83
|
OIDC_POST_LOGOUT_REDIRECT_URI: z.ZodOptional<z.ZodString>;
|
|
83
84
|
OIDC_SCOPES: z.ZodDefault<z.ZodString>;
|
|
84
85
|
OIDC_DISCOVERY_MAX_ATTEMPTS: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<number, string>>;
|
package/dist/config/env.js
CHANGED
|
@@ -108,6 +108,16 @@ export const configSchema = z
|
|
|
108
108
|
OIDC_CLIENT_ID: z.string().min(1).optional(),
|
|
109
109
|
OIDC_CLIENT_SECRET: z.string().min(1).optional(),
|
|
110
110
|
OIDC_REDIRECT_URI: z.string().url().optional(),
|
|
111
|
+
// RFC 8628 device-flow client_id (#833). DISTINCT from OIDC_CLIENT_ID: the
|
|
112
|
+
// latter is the IdP's OAuth client id used for the BROWSER SSO leg
|
|
113
|
+
// (`/auth/login` → Google), which is opaque to the CLI. The device-flow
|
|
114
|
+
// `client_id` is a logical identifier the CLI and server agree on out of
|
|
115
|
+
// band; the `tasks` CLI sends `'wft-cli'` by default. Conflating the two
|
|
116
|
+
// (the original Plan-30-08 wiring used OIDC_CLIENT_ID for both) made the
|
|
117
|
+
// device flow reject the CLI's default `client_id` with `invalid_client`
|
|
118
|
+
// on any server backed by a real IdP. Defaulted so it works with the
|
|
119
|
+
// stock CLI and is NOT part of the all-or-nothing OIDC group.
|
|
120
|
+
OIDC_DEVICE_CLIENT_ID: z.string().min(1).default('wft-cli'),
|
|
111
121
|
// WR-03 fix — `post_logout_redirect_uri` for RP-initiated logout.
|
|
112
122
|
// Optional: when absent, the wiring at src/api/server.ts derives a
|
|
113
123
|
// default from OIDC_REDIRECT_URI's origin (+ `/auth/login`). Sourcing
|
package/docs/SETUP.md
CHANGED
|
@@ -332,6 +332,14 @@ OIDC_REDIRECT_URI=http://localhost:3000/auth/callback
|
|
|
332
332
|
# Optional — defaults to "openid email profile". The server requires at
|
|
333
333
|
# minimum "openid email" to map the OIDC subject to a local user row.
|
|
334
334
|
OIDC_SCOPES=openid email profile
|
|
335
|
+
|
|
336
|
+
# Optional — defaults to "wft-cli". The RFC 8628 device-flow client_id the
|
|
337
|
+
# `tasks` CLI sends during `tasks setup` → Remote. DISTINCT from OIDC_CLIENT_ID
|
|
338
|
+
# (the IdP's OAuth client id for the browser SSO leg): the device flow uses a
|
|
339
|
+
# logical client id the CLI and server agree on. Leave unset on both sides to
|
|
340
|
+
# use the default — the stock CLI then authenticates out of the box. Override
|
|
341
|
+
# only if you also set OIDC_DEVICE_CLIENT_ID to a matching value on the client.
|
|
342
|
+
OIDC_DEVICE_CLIENT_ID=wft-cli
|
|
335
343
|
```
|
|
336
344
|
|
|
337
345
|
### 3. Generate the session cookie secret
|