@tutti-os/auth-bridge 0.0.205 → 0.0.207

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/README.md CHANGED
@@ -38,6 +38,7 @@ The Node bridge starts a loopback HTTP server for desktop sign-in. New desktop O
38
38
 
39
39
  - `GET /oauth/callback?state=<base64url>&transfer_code=<code>`
40
40
  - `GET /oauth/callback?state=<base64url>&error=<providerError>`
41
+ - `GET /oauth/callback?state=<base64url>&error=user_cancelled`
41
42
 
42
43
  The callback validates the signed login state, redeems the desktop transfer code through the account service, writes `auth.json`, fetches user info, and redirects the browser back to the web result page:
43
44
 
@@ -48,4 +49,10 @@ The callback validates the signed login state, redeems the desktop transfer code
48
49
 
49
50
  `openAppUrl` is only used to focus or reopen Tutti. It is sanitized to the allowed app schemes and must not carry `transfer_code`, sessions, or tokens.
50
51
 
52
+ An explicit `user_cancelled` callback is preserved as a typed cancellation for
53
+ Node/Go consumers and returns the safe Web result code `userCancelled`.
54
+ Cancellation results intentionally omit `openAppUrl`, so the Web result page
55
+ can keep the user in the browser. Other callback errors continue to use the
56
+ safe `providerError` result code.
57
+
51
58
  `/oauth/health` and `POST /oauth/complete` remain available for compatibility with older web clients, but the current web flow redirects the browser to `/oauth/callback` instead of fetching the local bridge.
package/dist/browser.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  buildAccountUrl,
6
6
  mapUserInfo,
7
7
  readEnvelopeError
8
- } from "./chunk-M2P3SN64.js";
8
+ } from "./chunk-HC77YJ7C.js";
9
9
 
10
10
  // src/browser.ts
11
11
  function defaultOpenUrl(url) {
@@ -1,3 +1,7 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
1
5
  // src/shared.ts
2
6
  var DEFAULT_APP_ID = "nextop";
3
7
  var DEFAULT_ACCOUNT_BASE_URL = "https://tutti.sh/api/account";
@@ -41,6 +45,7 @@ function mapUserInfo(data) {
41
45
  }
42
46
 
43
47
  export {
48
+ __publicField,
44
49
  DEFAULT_APP_ID,
45
50
  DEFAULT_ACCOUNT_BASE_URL,
46
51
  DEFAULT_AUTH_LOGIN_URL,
package/dist/node.d.ts CHANGED
@@ -1,5 +1,12 @@
1
1
  import { T as TuttiAuthSession, a as TuttiUserInfo } from './shared-CPA5wTKA.js';
2
2
 
3
+ type TuttiAuthErrorCode = "user_cancelled";
4
+ declare class TuttiAuthError extends Error {
5
+ readonly code: TuttiAuthErrorCode;
6
+ constructor(code: TuttiAuthErrorCode, message?: "user_cancelled");
7
+ }
8
+ declare function isTuttiAuthUserCancelledError(error: unknown): error is TuttiAuthError;
9
+
3
10
  interface TuttiNodeAuthClientOptions {
4
11
  authJsonPath: string;
5
12
  appCallbackUrl: string;
@@ -29,4 +36,4 @@ declare function createTuttiNodeAuthClient(options: TuttiNodeAuthClientOptions):
29
36
  declare function readAuthJson(authJsonPath: string): Promise<TuttiAuthSession | null>;
30
37
  declare function writeAuthJson(authJsonPath: string, session: TuttiAuthSession): Promise<void>;
31
38
 
32
- export { TuttiAuthSession, type TuttiNodeAuthClient, type TuttiNodeAuthClientOptions, TuttiUserInfo, createTuttiNodeAuthClient, readAuthJson, writeAuthJson };
39
+ export { TuttiAuthError, type TuttiAuthErrorCode, TuttiAuthSession, type TuttiNodeAuthClient, type TuttiNodeAuthClientOptions, TuttiUserInfo, createTuttiNodeAuthClient, isTuttiAuthUserCancelledError, readAuthJson, writeAuthJson };
package/dist/node.js CHANGED
@@ -7,12 +7,13 @@ import {
7
7
  DEFAULT_AUTH_LOGIN_URL,
8
8
  DEFAULT_LOGIN_IDLE_TIMEOUT_MS,
9
9
  DEFAULT_LOGIN_MAX_TIMEOUT_MS,
10
+ __publicField,
10
11
  buildAccountUrl,
11
12
  buildSessionCookie,
12
13
  mapUserInfo,
13
14
  readEnvelopeError,
14
15
  trimString
15
- } from "./chunk-M2P3SN64.js";
16
+ } from "./chunk-HC77YJ7C.js";
16
17
 
17
18
  // src/node.ts
18
19
  import { spawn } from "child_process";
@@ -24,6 +25,63 @@ import {
24
25
  import { createServer as createNetServer } from "net";
25
26
  import { hostname } from "os";
26
27
  import { dirname } from "path";
28
+
29
+ // src/node-login-result.ts
30
+ var USER_CANCELLED_CALLBACK_ERROR = "user_cancelled";
31
+ var USER_CANCELLED_RESULT_ERROR = "userCancelled";
32
+ var TuttiAuthError = class extends Error {
33
+ constructor(code, message = code) {
34
+ super(message);
35
+ __publicField(this, "code");
36
+ this.name = "TuttiAuthError";
37
+ this.code = code;
38
+ }
39
+ };
40
+ function isTuttiAuthUserCancelledError(error) {
41
+ return error instanceof TuttiAuthError && error.code === USER_CANCELLED_CALLBACK_ERROR;
42
+ }
43
+ function callbackErrorToError(callbackError) {
44
+ return callbackError === USER_CANCELLED_CALLBACK_ERROR ? new TuttiAuthError(USER_CANCELLED_CALLBACK_ERROR) : new Error(callbackError);
45
+ }
46
+ function callbackErrorToSafeResultCode(callbackError) {
47
+ return callbackError === USER_CANCELLED_CALLBACK_ERROR ? USER_CANCELLED_RESULT_ERROR : "providerError";
48
+ }
49
+ function buildBridgeResultUrl(input, status, safeErrorCode) {
50
+ const url = new URL("/auth/login/callback", input.authOrigin);
51
+ url.searchParams.set("desktopBridgeStatus", status);
52
+ if (safeErrorCode) {
53
+ url.searchParams.set("desktopBridgeError", safeErrorCode);
54
+ }
55
+ const openAppUrl = safeErrorCode === USER_CANCELLED_RESULT_ERROR ? null : buildSafeOpenAppUrl(input.appCallbackUrl, status, safeErrorCode);
56
+ if (openAppUrl) {
57
+ url.searchParams.set("openAppUrl", openAppUrl);
58
+ }
59
+ return url.toString();
60
+ }
61
+ function buildSafeOpenAppUrl(rawUrl, status, safeErrorCode) {
62
+ try {
63
+ const url = new URL(rawUrl.trim());
64
+ if (!isAllowedAppCallbackProtocol(url.protocol)) {
65
+ return null;
66
+ }
67
+ url.search = "";
68
+ url.hash = "";
69
+ url.searchParams.set("desktopBridgeStatus", status);
70
+ if (safeErrorCode) {
71
+ url.searchParams.set("desktopBridgeError", safeErrorCode);
72
+ }
73
+ return url.toString();
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+ function isAllowedAppCallbackProtocol(protocol) {
79
+ const legacyProtocol = `${DEFAULT_APP_ID}:`;
80
+ const legacyDevProtocol = `${DEFAULT_APP_ID}-dev:`;
81
+ return protocol === "tutti:" || protocol === "tutti-dev:" || protocol === legacyProtocol || protocol === legacyDevProtocol;
82
+ }
83
+
84
+ // src/node.ts
27
85
  var BRIDGE_SERVER_FORCE_CLOSE_TIMEOUT_MS = 1e3;
28
86
  function createTuttiNodeAuthClient(options) {
29
87
  const authJsonPath = options.authJsonPath.trim();
@@ -279,11 +337,15 @@ async function createLoginBridgeServer(input, port) {
279
337
  return;
280
338
  }
281
339
  if (callbackError) {
282
- const error = new Error(callbackError);
340
+ const error = callbackErrorToError(callbackError);
283
341
  complete(() => rejectCompletion(error));
284
342
  sendRedirect(
285
343
  res,
286
- buildBridgeResultUrl(input, "error", "providerError")
344
+ buildBridgeResultUrl(
345
+ input,
346
+ "error",
347
+ callbackErrorToSafeResultCode(callbackError)
348
+ )
287
349
  );
288
350
  return;
289
351
  }
@@ -332,10 +394,13 @@ async function createLoginBridgeServer(input, port) {
332
394
  return;
333
395
  }
334
396
  if (callbackError) {
335
- const error = new Error(callbackError);
397
+ const error = callbackErrorToError(callbackError);
336
398
  sendJson(res, 400, {
337
399
  ok: false,
338
- error: { code: "PROVIDER_CALLBACK_ERROR", message: error.message }
400
+ error: {
401
+ code: isTuttiAuthUserCancelledError(error) ? "USER_CANCELLED" : "PROVIDER_CALLBACK_ERROR",
402
+ message: error.message
403
+ }
339
404
  });
340
405
  complete(() => rejectCompletion(error));
341
406
  return;
@@ -534,44 +599,6 @@ function sendRedirect(res, location) {
534
599
  res.writeHead(302, { Location: location, Connection: "close" });
535
600
  res.end();
536
601
  }
537
- function buildBridgeResultUrl(input, status, safeErrorCode) {
538
- const url = new URL("/auth/login/callback", input.authOrigin);
539
- url.searchParams.set("desktopBridgeStatus", status);
540
- if (safeErrorCode) {
541
- url.searchParams.set("desktopBridgeError", safeErrorCode);
542
- }
543
- const openAppUrl = buildSafeOpenAppUrl(
544
- input.appCallbackUrl,
545
- status,
546
- safeErrorCode
547
- );
548
- if (openAppUrl) {
549
- url.searchParams.set("openAppUrl", openAppUrl);
550
- }
551
- return url.toString();
552
- }
553
- function buildSafeOpenAppUrl(rawUrl, status, safeErrorCode) {
554
- try {
555
- const url = new URL(rawUrl.trim());
556
- if (!isAllowedAppCallbackProtocol(url.protocol)) {
557
- return null;
558
- }
559
- url.search = "";
560
- url.hash = "";
561
- url.searchParams.set("desktopBridgeStatus", status);
562
- if (safeErrorCode) {
563
- url.searchParams.set("desktopBridgeError", safeErrorCode);
564
- }
565
- return url.toString();
566
- } catch {
567
- return null;
568
- }
569
- }
570
- function isAllowedAppCallbackProtocol(protocol) {
571
- const legacyProtocol = `${DEFAULT_APP_ID}:`;
572
- const legacyDevProtocol = `${DEFAULT_APP_ID}-dev:`;
573
- return protocol === "tutti:" || protocol === "tutti-dev:" || protocol === legacyProtocol || protocol === legacyDevProtocol;
574
- }
575
602
  function closeServer(server) {
576
603
  return new Promise((resolve) => {
577
604
  const forceCloseTimer = setTimeout(() => {
@@ -602,7 +629,9 @@ function openUrlWithDefaultBrowser(url) {
602
629
  });
603
630
  }
604
631
  export {
632
+ TuttiAuthError,
605
633
  createTuttiNodeAuthClient,
634
+ isTuttiAuthUserCancelledError,
606
635
  readAuthJson,
607
636
  writeAuthJson
608
637
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tutti-os/auth-bridge",
3
- "version": "0.0.205",
3
+ "version": "0.0.207",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",