@tutti-os/auth-bridge 0.0.205 → 0.0.206
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 +7 -0
- package/dist/browser.js +1 -1
- package/dist/{chunk-M2P3SN64.js → chunk-HC77YJ7C.js} +5 -0
- package/dist/node.d.ts +8 -1
- package/dist/node.js +72 -43
- package/package.json +1 -1
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
|
@@ -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-
|
|
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 =
|
|
340
|
+
const error = callbackErrorToError(callbackError);
|
|
283
341
|
complete(() => rejectCompletion(error));
|
|
284
342
|
sendRedirect(
|
|
285
343
|
res,
|
|
286
|
-
buildBridgeResultUrl(
|
|
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 =
|
|
397
|
+
const error = callbackErrorToError(callbackError);
|
|
336
398
|
sendJson(res, 400, {
|
|
337
399
|
ok: false,
|
|
338
|
-
error: {
|
|
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
|
};
|