wood-fired-tasks 2.0.4 → 2.0.5

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,23 @@ vulnerabilities, supply-chain pinning) are always called out under `Security`.
13
13
 
14
14
  _No changes yet._
15
15
 
16
+ ## [v2.0.5] - 2026-06-08
17
+
18
+ ### Changed
19
+ - **`tasks setup` → Remote now tells you the truth about what a server needs for
20
+ browser login.** When the server reports OIDC ready but you entered a
21
+ **plain-http, non-localhost URL**, browser/device login via Google SSO can
22
+ never complete — identity providers reject non-`https` OAuth redirect URIs
23
+ except for `localhost`. Previously setup would launch the device flow anyway
24
+ and the verification page dead-ended at the IdP callback ("unable to connect").
25
+ Setup now detects this up front and explains it: it tells you to either re-run
26
+ with an **https** URL (front the server with a TLS reverse proxy / real domain
27
+ so Google SSO completes) or **paste a personal access token**, and prints the
28
+ exact on-host command to mint one (`tasks db mint-token --user <email-or-id>`),
29
+ then drops straight into manual-PAT entry. `https` and `http://localhost`
30
+ servers are unaffected and complete browser login as before. New exported
31
+ helper `canUseBrowserSso()`.
32
+
16
33
  ## [v2.0.4] - 2026-06-08
17
34
 
18
35
  ### Fixed
@@ -334,6 +334,23 @@ export declare function probeOidcState(baseUrl: string): Promise<OidcProbeResult
334
334
  * - probe failure → manual-PAT (connectivity escape hatch).
335
335
  */
336
336
  export declare function selectRemoteOnboardingMethod(probe: OidcProbeResult): RemoteOnboardingMethod;
337
+ /**
338
+ * Whether the browser/device login (Google SSO) can actually COMPLETE against
339
+ * `baseUrl` (#835).
340
+ *
341
+ * The whole OIDC dance — the verification page AND the IdP's OAuth callback —
342
+ * happens at the server's origin, and identity providers (Google especially)
343
+ * reject non-`https` OAuth redirect URIs *except* for `localhost`/`127.0.0.1`.
344
+ * So a server reached over plain `http` at a non-localhost address can report
345
+ * `oidc: 'ready'` yet still be unable to finish browser login: the user's
346
+ * browser gets bounced to an `http://…/auth/callback` the IdP won't honor. We
347
+ * detect that up front so the interview can tell the user the truth (need https,
348
+ * or use a PAT) instead of opening a URL that dead-ends.
349
+ *
350
+ * Returns true for any `https` URL and for `http://localhost` / `127.0.0.1` /
351
+ * `[::1]`; false for plain-http non-loopback hosts and unparseable input.
352
+ */
353
+ export declare function canUseBrowserSso(baseUrl: string): boolean;
337
354
  /**
338
355
  * The minimal identity envelope `GET /api/v1/me` returns (task #809). Mirrors
339
356
  * the fields {@link writeCredentials} needs so a manually-pasted PAT lands in
@@ -604,6 +604,37 @@ export function selectRemoteOnboardingMethod(probe) {
604
604
  return 'manual-pat';
605
605
  return probe.oidc === 'ready' ? 'device-flow' : 'manual-pat';
606
606
  }
607
+ /**
608
+ * Whether the browser/device login (Google SSO) can actually COMPLETE against
609
+ * `baseUrl` (#835).
610
+ *
611
+ * The whole OIDC dance — the verification page AND the IdP's OAuth callback —
612
+ * happens at the server's origin, and identity providers (Google especially)
613
+ * reject non-`https` OAuth redirect URIs *except* for `localhost`/`127.0.0.1`.
614
+ * So a server reached over plain `http` at a non-localhost address can report
615
+ * `oidc: 'ready'` yet still be unable to finish browser login: the user's
616
+ * browser gets bounced to an `http://…/auth/callback` the IdP won't honor. We
617
+ * detect that up front so the interview can tell the user the truth (need https,
618
+ * or use a PAT) instead of opening a URL that dead-ends.
619
+ *
620
+ * Returns true for any `https` URL and for `http://localhost` / `127.0.0.1` /
621
+ * `[::1]`; false for plain-http non-loopback hosts and unparseable input.
622
+ */
623
+ export function canUseBrowserSso(baseUrl) {
624
+ let url;
625
+ try {
626
+ url = new URL(baseUrl);
627
+ }
628
+ catch {
629
+ return false;
630
+ }
631
+ if (url.protocol === 'https:')
632
+ return true;
633
+ if (url.protocol !== 'http:')
634
+ return false;
635
+ const host = url.hostname.toLowerCase();
636
+ return host === 'localhost' || host === '127.0.0.1' || host === '::1' || host === '[::1]';
637
+ }
607
638
  /**
608
639
  * Default manual-PAT persistence (task #809).
609
640
  *
@@ -811,6 +842,28 @@ export async function runRemoteOnboarding(options = {}) {
811
842
  // 3. Branch on the selected method.
812
843
  let method = selectRemoteOnboardingMethod(probeResult);
813
844
  const oidc = probeResult.ok ? probeResult.oidc : null;
845
+ // #835: even when the server reports OIDC ready, browser login can only
846
+ // COMPLETE over https (or localhost) — Google rejects non-https OAuth
847
+ // redirect URIs everywhere else. Catch a plain-http non-localhost URL here
848
+ // and tell the user plainly, then route to manual-PAT entry (with on-host
849
+ // mint instructions) rather than opening a verification URL that dead-ends at
850
+ // the IdP callback.
851
+ if (method === 'device-flow' && !canUseBrowserSso(baseUrl)) {
852
+ log('');
853
+ log(`"${baseUrl}" is plain http at a non-localhost address.`);
854
+ log('Browser login via Google SSO requires an https URL — identity providers');
855
+ log('reject non-https OAuth redirect URIs except for localhost — so the device');
856
+ log('flow cannot complete against this server. To finish setup, either:');
857
+ log(' • re-run with an https URL for this server (e.g. front it with a TLS');
858
+ log(' reverse proxy / real domain so Google SSO completes), or');
859
+ log(' • paste a personal access token now.');
860
+ log('');
861
+ log('To mint a PAT, run this ON THE SERVER HOST:');
862
+ log(' tasks db mint-token --user <your-email-or-user-id>');
863
+ log('(or create one from your account page once logged in via the browser).');
864
+ log('');
865
+ method = 'manual-pat';
866
+ }
814
867
  if (method === 'device-flow') {
815
868
  // Self-provision a PAT via the OIDC device flow (#806). runDeviceLogin owns
816
869
  // the entire RFC 8628 exchange AND persists the minted PAT via the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wood-fired-tasks",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
4
4
  "description": "Network-wide task tracking system for Wood Fired Games",
5
5
  "keywords": [
6
6
  "task-tracker",