@mcp-z/oauth-google 1.0.1 → 1.0.2
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/dist/cjs/providers/loopback-oauth.d.cts +79 -10
- package/dist/cjs/providers/loopback-oauth.d.ts +79 -10
- package/dist/cjs/providers/loopback-oauth.js +875 -572
- package/dist/cjs/providers/loopback-oauth.js.map +1 -1
- package/dist/cjs/setup/config.js +3 -3
- package/dist/cjs/setup/config.js.map +1 -1
- package/dist/esm/providers/loopback-oauth.d.ts +79 -10
- package/dist/esm/providers/loopback-oauth.js +403 -276
- package/dist/esm/providers/loopback-oauth.js.map +1 -1
- package/dist/esm/setup/config.js +3 -3
- package/dist/esm/setup/config.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Implements OAuth 2.0 Authorization Code Flow with PKCE using loopback interface redirection.
|
|
5
5
|
* Uses ephemeral local server with OS-assigned port (RFC 8252 Section 8.3).
|
|
6
|
+
*
|
|
7
|
+
* CHANGE (2026-01-03):
|
|
8
|
+
* - Non-headless mode now opens the auth URL AND blocks (polls) until tokens are available,
|
|
9
|
+
* for BOTH redirectUri (persistent) and ephemeral (loopback) modes.
|
|
10
|
+
* - Ephemeral flow no longer calls `open()` itself. Instead it:
|
|
11
|
+
* 1) starts the loopback callback server
|
|
12
|
+
* 2) throws AuthRequiredError(auth_url)
|
|
13
|
+
* - Middleware catches AuthRequiredError(auth_url):
|
|
14
|
+
* - if not headless: open(url) once + poll pending state until callback completes (or timeout)
|
|
15
|
+
* - then retries token acquisition and injects authContext in the SAME tool call.
|
|
6
16
|
*/ "use strict";
|
|
7
17
|
Object.defineProperty(exports, "__esModule", {
|
|
8
18
|
value: true
|
|
@@ -290,10 +300,14 @@ function _ts_generator(thisArg, body) {
|
|
|
290
300
|
};
|
|
291
301
|
}
|
|
292
302
|
}
|
|
303
|
+
var OAUTH_TIMEOUT_MS = 5 * 60 * 1000;
|
|
304
|
+
var OAUTH_POLL_MS = 500;
|
|
293
305
|
var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
294
306
|
"use strict";
|
|
295
307
|
function LoopbackOAuthProvider(config) {
|
|
296
308
|
_class_call_check(this, LoopbackOAuthProvider);
|
|
309
|
+
// Track URLs we've already opened for a given state within this process (prevents tab spam).
|
|
310
|
+
this.openedStates = new Set();
|
|
297
311
|
this.config = config;
|
|
298
312
|
}
|
|
299
313
|
var _proto = LoopbackOAuthProvider.prototype;
|
|
@@ -304,7 +318,7 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
304
318
|
* @returns Access token for API requests
|
|
305
319
|
*/ _proto.getAccessToken = function getAccessToken(accountId) {
|
|
306
320
|
return _async_to_generator(function() {
|
|
307
|
-
var _this_config, logger, service, tokenStore, effectiveAccountId, _tmp, storedToken, refreshedToken, error, _this_config1, clientId, scope, redirectUri, _generatePKCE, codeVerifier, codeChallenge, stateId, authUrl,
|
|
321
|
+
var _this_config, logger, service, tokenStore, effectiveAccountId, _tmp, storedToken, refreshedToken, error, _this_config1, clientId, scope, redirectUri, _generatePKCE, codeVerifier, codeChallenge, stateId, authUrl, descriptor;
|
|
308
322
|
return _ts_generator(this, function(_state) {
|
|
309
323
|
switch(_state.label){
|
|
310
324
|
case 0:
|
|
@@ -413,92 +427,44 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
413
427
|
// Store PKCE verifier for callback (5 minute TTL)
|
|
414
428
|
return [
|
|
415
429
|
4,
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
430
|
+
this.createPendingAuth({
|
|
431
|
+
state: stateId,
|
|
432
|
+
codeVerifier: codeVerifier
|
|
433
|
+
})
|
|
420
434
|
];
|
|
421
435
|
case 10:
|
|
422
436
|
_state.sent();
|
|
423
437
|
// Build auth URL with configured redirect_uri
|
|
424
|
-
authUrl =
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
authUrl.searchParams.set('access_type', 'offline');
|
|
430
|
-
authUrl.searchParams.set('code_challenge', codeChallenge);
|
|
431
|
-
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
432
|
-
authUrl.searchParams.set('state', stateId);
|
|
433
|
-
authUrl.searchParams.set('prompt', 'consent');
|
|
438
|
+
authUrl = this.buildAuthUrl({
|
|
439
|
+
redirectUri: redirectUri,
|
|
440
|
+
codeChallenge: codeChallenge,
|
|
441
|
+
state: stateId
|
|
442
|
+
});
|
|
434
443
|
logger.info('OAuth required - persistent callback mode', {
|
|
435
444
|
service: service,
|
|
436
|
-
redirectUri: redirectUri
|
|
445
|
+
redirectUri: redirectUri,
|
|
446
|
+
clientId: clientId,
|
|
447
|
+
scope: scope
|
|
437
448
|
});
|
|
438
449
|
throw new _typests.AuthRequiredError({
|
|
439
450
|
kind: 'auth_url',
|
|
440
451
|
provider: service,
|
|
441
|
-
url: authUrl
|
|
452
|
+
url: authUrl
|
|
442
453
|
});
|
|
443
454
|
case 11:
|
|
444
455
|
// Ephemeral callback mode (local development)
|
|
456
|
+
// IMPORTANT: do NOT open here anymore; we throw auth_url and the middleware will open+poll.
|
|
445
457
|
logger.info('Starting ephemeral OAuth flow', {
|
|
446
458
|
service: service,
|
|
447
459
|
headless: this.config.headless
|
|
448
460
|
});
|
|
449
461
|
return [
|
|
450
462
|
4,
|
|
451
|
-
this.
|
|
463
|
+
this.startEphemeralOAuthFlow()
|
|
452
464
|
];
|
|
453
465
|
case 12:
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
4,
|
|
457
|
-
(0, _oauth.setToken)(tokenStore, {
|
|
458
|
-
accountId: email,
|
|
459
|
-
service: service
|
|
460
|
-
}, token)
|
|
461
|
-
];
|
|
462
|
-
case 13:
|
|
463
|
-
_state.sent();
|
|
464
|
-
return [
|
|
465
|
-
4,
|
|
466
|
-
(0, _oauth.addAccount)(tokenStore, {
|
|
467
|
-
service: service,
|
|
468
|
-
accountId: email
|
|
469
|
-
})
|
|
470
|
-
];
|
|
471
|
-
case 14:
|
|
472
|
-
_state.sent();
|
|
473
|
-
return [
|
|
474
|
-
4,
|
|
475
|
-
(0, _oauth.setActiveAccount)(tokenStore, {
|
|
476
|
-
service: service,
|
|
477
|
-
accountId: email
|
|
478
|
-
})
|
|
479
|
-
];
|
|
480
|
-
case 15:
|
|
481
|
-
_state.sent();
|
|
482
|
-
return [
|
|
483
|
-
4,
|
|
484
|
-
(0, _oauth.setAccountInfo)(tokenStore, {
|
|
485
|
-
service: service,
|
|
486
|
-
accountId: email
|
|
487
|
-
}, {
|
|
488
|
-
email: email,
|
|
489
|
-
addedAt: new Date().toISOString()
|
|
490
|
-
})
|
|
491
|
-
];
|
|
492
|
-
case 16:
|
|
493
|
-
_state.sent();
|
|
494
|
-
logger.info('OAuth flow completed', {
|
|
495
|
-
service: service,
|
|
496
|
-
accountId: email
|
|
497
|
-
});
|
|
498
|
-
return [
|
|
499
|
-
2,
|
|
500
|
-
token.accessToken
|
|
501
|
-
];
|
|
466
|
+
descriptor = _state.sent();
|
|
467
|
+
throw new _typests.AuthRequiredError(descriptor);
|
|
502
468
|
}
|
|
503
469
|
});
|
|
504
470
|
}).call(this);
|
|
@@ -666,531 +632,802 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
666
632
|
});
|
|
667
633
|
}).call(this);
|
|
668
634
|
};
|
|
669
|
-
|
|
635
|
+
// ---------------------------------------------------------------------------
|
|
636
|
+
// Shared OAuth helpers
|
|
637
|
+
// ---------------------------------------------------------------------------
|
|
638
|
+
/**
|
|
639
|
+
* Build OAuth authorization URL with the "most parameters" baseline.
|
|
640
|
+
* This is shared by BOTH persistent (redirectUri) and ephemeral (loopback) modes.
|
|
641
|
+
*/ _proto.buildAuthUrl = function buildAuthUrl(args) {
|
|
642
|
+
var _this_config = this.config, clientId = _this_config.clientId, scope = _this_config.scope;
|
|
643
|
+
var authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth');
|
|
644
|
+
authUrl.searchParams.set('client_id', clientId);
|
|
645
|
+
authUrl.searchParams.set('redirect_uri', args.redirectUri);
|
|
646
|
+
authUrl.searchParams.set('response_type', 'code');
|
|
647
|
+
authUrl.searchParams.set('scope', scope);
|
|
648
|
+
authUrl.searchParams.set('access_type', 'offline'); // always
|
|
649
|
+
authUrl.searchParams.set('prompt', 'consent'); // always
|
|
650
|
+
authUrl.searchParams.set('code_challenge', args.codeChallenge);
|
|
651
|
+
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
652
|
+
authUrl.searchParams.set('state', args.state);
|
|
653
|
+
return authUrl.toString();
|
|
654
|
+
};
|
|
655
|
+
/**
|
|
656
|
+
* Create a cached token + email from an authorization code.
|
|
657
|
+
* This is the shared callback handler for BOTH persistent and ephemeral modes.
|
|
658
|
+
*/ _proto.handleAuthorizationCode = function handleAuthorizationCode(args) {
|
|
670
659
|
return _async_to_generator(function() {
|
|
671
|
-
var
|
|
660
|
+
var tokenResponse, cachedToken, email;
|
|
672
661
|
return _ts_generator(this, function(_state) {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
// Cloud deployment: Listen on 0.0.0.0 with PORT from environment
|
|
691
|
-
// The redirectUri is the PUBLIC URL (e.g., https://example.com/oauth/callback)
|
|
692
|
-
// The server listens on 0.0.0.0:PORT and the load balancer routes to it
|
|
693
|
-
listenHost = '0.0.0.0';
|
|
694
|
-
envPort = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : undefined;
|
|
695
|
-
listenPort = envPort && Number.isFinite(envPort) ? envPort : 8080;
|
|
696
|
-
}
|
|
697
|
-
// Extract callback path from URL
|
|
698
|
-
if (parsed.pathname && parsed.pathname !== '/') {
|
|
699
|
-
callbackPath = parsed.pathname;
|
|
700
|
-
}
|
|
701
|
-
useConfiguredUri = true;
|
|
702
|
-
logger.debug('Using configured redirect URI', {
|
|
703
|
-
listenHost: listenHost,
|
|
704
|
-
listenPort: listenPort,
|
|
705
|
-
callbackPath: callbackPath,
|
|
706
|
-
redirectUri: configRedirectUri,
|
|
707
|
-
isLoopback: isLoopback
|
|
708
|
-
});
|
|
709
|
-
} catch (error) {
|
|
710
|
-
logger.warn('Failed to parse redirectUri, using ephemeral defaults', {
|
|
711
|
-
redirectUri: configRedirectUri,
|
|
712
|
-
error: _instanceof(error, Error) ? error.message : String(error)
|
|
713
|
-
});
|
|
714
|
-
// Continue with defaults (localhost, port 0, http, /callback)
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
return [
|
|
718
|
-
2,
|
|
719
|
-
new Promise(function(resolve, reject) {
|
|
720
|
-
// Generate PKCE challenge
|
|
721
|
-
var _generatePKCE = (0, _oauth.generatePKCE)(), codeVerifier = _generatePKCE.verifier, codeChallenge = _generatePKCE.challenge;
|
|
722
|
-
var server = null;
|
|
723
|
-
var serverPort;
|
|
724
|
-
var finalRedirectUri; // Will be set in server.listen callback
|
|
725
|
-
// Create ephemeral server with OS-assigned port (RFC 8252)
|
|
726
|
-
server = _http.createServer(function(req, res) {
|
|
727
|
-
return _async_to_generator(function() {
|
|
728
|
-
var url, code, _$error, tokenResponse, cachedToken, email, exchangeError;
|
|
729
|
-
return _ts_generator(this, function(_state) {
|
|
730
|
-
switch(_state.label){
|
|
731
|
-
case 0:
|
|
732
|
-
if (!req.url) {
|
|
733
|
-
res.writeHead(400, {
|
|
734
|
-
'Content-Type': 'text/html'
|
|
735
|
-
});
|
|
736
|
-
res.end((0, _oauth.getErrorTemplate)('Invalid request'));
|
|
737
|
-
server === null || server === void 0 ? void 0 : server.close();
|
|
738
|
-
reject(new Error('Invalid request: missing URL'));
|
|
739
|
-
return [
|
|
740
|
-
2
|
|
741
|
-
];
|
|
742
|
-
}
|
|
743
|
-
url = new URL(req.url, "http://127.0.0.1:".concat(serverPort));
|
|
744
|
-
if (!(url.pathname === callbackPath)) return [
|
|
745
|
-
3,
|
|
746
|
-
6
|
|
747
|
-
];
|
|
748
|
-
code = url.searchParams.get('code');
|
|
749
|
-
_$error = url.searchParams.get('error');
|
|
750
|
-
if (_$error) {
|
|
751
|
-
res.writeHead(400, {
|
|
752
|
-
'Content-Type': 'text/html'
|
|
753
|
-
});
|
|
754
|
-
res.end((0, _oauth.getErrorTemplate)(_$error));
|
|
755
|
-
server === null || server === void 0 ? void 0 : server.close();
|
|
756
|
-
reject(new Error("OAuth error: ".concat(_$error)));
|
|
757
|
-
return [
|
|
758
|
-
2
|
|
759
|
-
];
|
|
760
|
-
}
|
|
761
|
-
if (!code) {
|
|
762
|
-
res.writeHead(400, {
|
|
763
|
-
'Content-Type': 'text/html'
|
|
764
|
-
});
|
|
765
|
-
res.end((0, _oauth.getErrorTemplate)('No authorization code received'));
|
|
766
|
-
server === null || server === void 0 ? void 0 : server.close();
|
|
767
|
-
reject(new Error('No authorization code received'));
|
|
768
|
-
return [
|
|
769
|
-
2
|
|
770
|
-
];
|
|
771
|
-
}
|
|
772
|
-
_state.label = 1;
|
|
773
|
-
case 1:
|
|
774
|
-
_state.trys.push([
|
|
775
|
-
1,
|
|
776
|
-
4,
|
|
777
|
-
,
|
|
778
|
-
5
|
|
779
|
-
]);
|
|
780
|
-
return [
|
|
781
|
-
4,
|
|
782
|
-
this.exchangeCodeForToken(code, codeVerifier, finalRedirectUri)
|
|
783
|
-
];
|
|
784
|
-
case 2:
|
|
785
|
-
tokenResponse = _state.sent();
|
|
786
|
-
// Build cached token
|
|
787
|
-
cachedToken = _object_spread({
|
|
788
|
-
accessToken: tokenResponse.access_token
|
|
789
|
-
}, tokenResponse.refresh_token !== undefined && {
|
|
790
|
-
refreshToken: tokenResponse.refresh_token
|
|
791
|
-
}, tokenResponse.expires_in !== undefined && {
|
|
792
|
-
expiresAt: Date.now() + tokenResponse.expires_in * 1000
|
|
793
|
-
}, tokenResponse.scope !== undefined && {
|
|
794
|
-
scope: tokenResponse.scope
|
|
795
|
-
});
|
|
796
|
-
return [
|
|
797
|
-
4,
|
|
798
|
-
this.fetchUserEmailFromToken(tokenResponse.access_token)
|
|
799
|
-
];
|
|
800
|
-
case 3:
|
|
801
|
-
email = _state.sent();
|
|
802
|
-
res.writeHead(200, {
|
|
803
|
-
'Content-Type': 'text/html'
|
|
804
|
-
});
|
|
805
|
-
res.end((0, _oauth.getSuccessTemplate)());
|
|
806
|
-
server === null || server === void 0 ? void 0 : server.close();
|
|
807
|
-
resolve({
|
|
808
|
-
token: cachedToken,
|
|
809
|
-
email: email
|
|
810
|
-
});
|
|
811
|
-
return [
|
|
812
|
-
3,
|
|
813
|
-
5
|
|
814
|
-
];
|
|
815
|
-
case 4:
|
|
816
|
-
exchangeError = _state.sent();
|
|
817
|
-
logger.error('Token exchange failed', {
|
|
818
|
-
error: _instanceof(exchangeError, Error) ? exchangeError.message : String(exchangeError)
|
|
819
|
-
});
|
|
820
|
-
res.writeHead(500, {
|
|
821
|
-
'Content-Type': 'text/html'
|
|
822
|
-
});
|
|
823
|
-
res.end((0, _oauth.getErrorTemplate)('Token exchange failed'));
|
|
824
|
-
server === null || server === void 0 ? void 0 : server.close();
|
|
825
|
-
reject(exchangeError);
|
|
826
|
-
return [
|
|
827
|
-
3,
|
|
828
|
-
5
|
|
829
|
-
];
|
|
830
|
-
case 5:
|
|
831
|
-
return [
|
|
832
|
-
3,
|
|
833
|
-
7
|
|
834
|
-
];
|
|
835
|
-
case 6:
|
|
836
|
-
res.writeHead(404, {
|
|
837
|
-
'Content-Type': 'text/plain'
|
|
838
|
-
});
|
|
839
|
-
res.end('Not Found');
|
|
840
|
-
_state.label = 7;
|
|
841
|
-
case 7:
|
|
842
|
-
return [
|
|
843
|
-
2
|
|
844
|
-
];
|
|
845
|
-
}
|
|
846
|
-
});
|
|
847
|
-
}).call(_this);
|
|
848
|
-
});
|
|
849
|
-
// Listen on configured host/port
|
|
850
|
-
server.listen(listenPort, listenHost, function() {
|
|
851
|
-
var address = server === null || server === void 0 ? void 0 : server.address();
|
|
852
|
-
if (!address || typeof address === 'string') {
|
|
853
|
-
server === null || server === void 0 ? void 0 : server.close();
|
|
854
|
-
reject(new Error('Failed to start ephemeral server'));
|
|
855
|
-
return;
|
|
856
|
-
}
|
|
857
|
-
serverPort = address.port;
|
|
858
|
-
// Construct final redirect URI
|
|
859
|
-
if (useConfiguredUri && configRedirectUri) {
|
|
860
|
-
// Use configured redirect URI as-is (public URL for cloud, or specific local URL)
|
|
861
|
-
finalRedirectUri = configRedirectUri;
|
|
862
|
-
} else {
|
|
863
|
-
// Construct ephemeral redirect URI with actual server port (default local behavior)
|
|
864
|
-
finalRedirectUri = "http://localhost:".concat(serverPort).concat(callbackPath);
|
|
865
|
-
}
|
|
866
|
-
// Build auth URL
|
|
867
|
-
var authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth');
|
|
868
|
-
authUrl.searchParams.set('client_id', clientId);
|
|
869
|
-
authUrl.searchParams.set('redirect_uri', finalRedirectUri);
|
|
870
|
-
authUrl.searchParams.set('response_type', 'code');
|
|
871
|
-
authUrl.searchParams.set('scope', scope);
|
|
872
|
-
authUrl.searchParams.set('access_type', 'offline');
|
|
873
|
-
authUrl.searchParams.set('prompt', 'consent');
|
|
874
|
-
authUrl.searchParams.set('code_challenge', codeChallenge);
|
|
875
|
-
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
876
|
-
logger.info('Ephemeral OAuth server started', {
|
|
877
|
-
port: serverPort,
|
|
878
|
-
headless: headless
|
|
879
|
-
});
|
|
880
|
-
if (headless) {
|
|
881
|
-
// Headless mode: Print auth URL to stderr (stdout is MCP protocol)
|
|
882
|
-
console.error('\n🔐 OAuth Authorization Required');
|
|
883
|
-
console.error('📋 Please visit this URL in your browser:\n');
|
|
884
|
-
console.error(" ".concat(authUrl.toString(), "\n"));
|
|
885
|
-
console.error('⏳ Waiting for authorization...\n');
|
|
886
|
-
} else {
|
|
887
|
-
// Interactive mode: Open browser automatically
|
|
888
|
-
logger.info('Opening browser for OAuth authorization');
|
|
889
|
-
(0, _open.default)(authUrl.toString()).catch(function(error) {
|
|
890
|
-
logger.info('Failed to open browser automatically', {
|
|
891
|
-
error: error.message
|
|
892
|
-
});
|
|
893
|
-
console.error('\n🔐 OAuth Authorization Required');
|
|
894
|
-
console.error(" ".concat(authUrl.toString(), "\n"));
|
|
895
|
-
});
|
|
896
|
-
}
|
|
662
|
+
switch(_state.label){
|
|
663
|
+
case 0:
|
|
664
|
+
return [
|
|
665
|
+
4,
|
|
666
|
+
this.exchangeCodeForToken(args.code, args.codeVerifier, args.redirectUri)
|
|
667
|
+
];
|
|
668
|
+
case 1:
|
|
669
|
+
tokenResponse = _state.sent();
|
|
670
|
+
// Build cached token
|
|
671
|
+
cachedToken = _object_spread({
|
|
672
|
+
accessToken: tokenResponse.access_token
|
|
673
|
+
}, tokenResponse.refresh_token !== undefined && {
|
|
674
|
+
refreshToken: tokenResponse.refresh_token
|
|
675
|
+
}, tokenResponse.expires_in !== undefined && {
|
|
676
|
+
expiresAt: Date.now() + tokenResponse.expires_in * 1000
|
|
677
|
+
}, tokenResponse.scope !== undefined && {
|
|
678
|
+
scope: tokenResponse.scope
|
|
897
679
|
});
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
680
|
+
return [
|
|
681
|
+
4,
|
|
682
|
+
this.fetchUserEmailFromToken(tokenResponse.access_token)
|
|
683
|
+
];
|
|
684
|
+
case 2:
|
|
685
|
+
email = _state.sent();
|
|
686
|
+
return [
|
|
687
|
+
2,
|
|
688
|
+
{
|
|
689
|
+
email: email,
|
|
690
|
+
token: cachedToken
|
|
903
691
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
];
|
|
692
|
+
];
|
|
693
|
+
}
|
|
907
694
|
});
|
|
908
695
|
}).call(this);
|
|
909
696
|
};
|
|
910
|
-
|
|
697
|
+
/**
|
|
698
|
+
* Store token + account metadata. Shared by BOTH persistent and ephemeral modes.
|
|
699
|
+
*/ _proto.persistAuthResult = function persistAuthResult(args) {
|
|
911
700
|
return _async_to_generator(function() {
|
|
912
|
-
var _this_config,
|
|
701
|
+
var _this_config, tokenStore, service;
|
|
913
702
|
return _ts_generator(this, function(_state) {
|
|
914
703
|
switch(_state.label){
|
|
915
704
|
case 0:
|
|
916
|
-
_this_config = this.config,
|
|
917
|
-
tokenUrl = 'https://oauth2.googleapis.com/token';
|
|
918
|
-
params = {
|
|
919
|
-
code: code,
|
|
920
|
-
client_id: clientId,
|
|
921
|
-
redirect_uri: redirectUri,
|
|
922
|
-
grant_type: 'authorization_code',
|
|
923
|
-
code_verifier: codeVerifier
|
|
924
|
-
};
|
|
925
|
-
if (clientSecret) {
|
|
926
|
-
params.client_secret = clientSecret;
|
|
927
|
-
}
|
|
928
|
-
body = new URLSearchParams(params);
|
|
705
|
+
_this_config = this.config, tokenStore = _this_config.tokenStore, service = _this_config.service;
|
|
929
706
|
return [
|
|
930
707
|
4,
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
},
|
|
936
|
-
body: body.toString()
|
|
937
|
-
})
|
|
708
|
+
(0, _oauth.setToken)(tokenStore, {
|
|
709
|
+
accountId: args.email,
|
|
710
|
+
service: service
|
|
711
|
+
}, args.token)
|
|
938
712
|
];
|
|
939
713
|
case 1:
|
|
940
|
-
|
|
941
|
-
if (!!response.ok) return [
|
|
942
|
-
3,
|
|
943
|
-
3
|
|
944
|
-
];
|
|
714
|
+
_state.sent();
|
|
945
715
|
return [
|
|
946
716
|
4,
|
|
947
|
-
|
|
717
|
+
(0, _oauth.addAccount)(tokenStore, {
|
|
718
|
+
service: service,
|
|
719
|
+
accountId: args.email
|
|
720
|
+
})
|
|
948
721
|
];
|
|
949
722
|
case 2:
|
|
950
|
-
|
|
951
|
-
|
|
723
|
+
_state.sent();
|
|
724
|
+
return [
|
|
725
|
+
4,
|
|
726
|
+
(0, _oauth.setActiveAccount)(tokenStore, {
|
|
727
|
+
service: service,
|
|
728
|
+
accountId: args.email
|
|
729
|
+
})
|
|
730
|
+
];
|
|
952
731
|
case 3:
|
|
732
|
+
_state.sent();
|
|
953
733
|
return [
|
|
954
734
|
4,
|
|
955
|
-
|
|
735
|
+
(0, _oauth.setAccountInfo)(tokenStore, {
|
|
736
|
+
service: service,
|
|
737
|
+
accountId: args.email
|
|
738
|
+
}, {
|
|
739
|
+
email: args.email,
|
|
740
|
+
addedAt: new Date().toISOString()
|
|
741
|
+
})
|
|
956
742
|
];
|
|
957
743
|
case 4:
|
|
744
|
+
_state.sent();
|
|
958
745
|
return [
|
|
959
|
-
2
|
|
960
|
-
_state.sent()
|
|
746
|
+
2
|
|
961
747
|
];
|
|
962
748
|
}
|
|
963
749
|
});
|
|
964
750
|
}).call(this);
|
|
965
751
|
};
|
|
966
|
-
|
|
752
|
+
/**
|
|
753
|
+
* Pending auth (PKCE verifier) key format.
|
|
754
|
+
* Keep as-is (confirmed), even though it's not a public contract.
|
|
755
|
+
*/ _proto.pendingKey = function pendingKey(state) {
|
|
756
|
+
return "".concat(this.config.service, ":pending:").concat(state);
|
|
757
|
+
};
|
|
758
|
+
/**
|
|
759
|
+
* Store PKCE verifier for callback (5 minute TTL).
|
|
760
|
+
* Shared by BOTH persistent and ephemeral modes.
|
|
761
|
+
*/ _proto.createPendingAuth = function createPendingAuth(args) {
|
|
967
762
|
return _async_to_generator(function() {
|
|
968
|
-
var
|
|
763
|
+
var tokenStore, record;
|
|
969
764
|
return _ts_generator(this, function(_state) {
|
|
970
765
|
switch(_state.label){
|
|
971
766
|
case 0:
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
client_id: clientId,
|
|
977
|
-
grant_type: 'refresh_token'
|
|
767
|
+
tokenStore = this.config.tokenStore;
|
|
768
|
+
record = {
|
|
769
|
+
codeVerifier: args.codeVerifier,
|
|
770
|
+
createdAt: Date.now()
|
|
978
771
|
};
|
|
979
|
-
if (clientSecret) {
|
|
980
|
-
params.client_secret = clientSecret;
|
|
981
|
-
}
|
|
982
|
-
body = new URLSearchParams(params);
|
|
983
772
|
return [
|
|
984
773
|
4,
|
|
985
|
-
|
|
986
|
-
method: 'POST',
|
|
987
|
-
headers: {
|
|
988
|
-
'Content-Type': 'application/x-www-form-urlencoded'
|
|
989
|
-
},
|
|
990
|
-
body: body.toString()
|
|
991
|
-
})
|
|
774
|
+
tokenStore.set(this.pendingKey(args.state), record, OAUTH_TIMEOUT_MS)
|
|
992
775
|
];
|
|
993
776
|
case 1:
|
|
994
|
-
|
|
995
|
-
if (!!response.ok) return [
|
|
996
|
-
3,
|
|
997
|
-
3
|
|
998
|
-
];
|
|
999
|
-
return [
|
|
1000
|
-
4,
|
|
1001
|
-
response.text()
|
|
1002
|
-
];
|
|
1003
|
-
case 2:
|
|
1004
|
-
errorText = _state.sent();
|
|
1005
|
-
throw new Error("Token refresh failed: ".concat(response.status, " ").concat(errorText));
|
|
1006
|
-
case 3:
|
|
1007
|
-
return [
|
|
1008
|
-
4,
|
|
1009
|
-
response.json()
|
|
1010
|
-
];
|
|
1011
|
-
case 4:
|
|
1012
|
-
tokenResponse = _state.sent();
|
|
777
|
+
_state.sent();
|
|
1013
778
|
return [
|
|
1014
|
-
2
|
|
1015
|
-
_object_spread({
|
|
1016
|
-
accessToken: tokenResponse.access_token,
|
|
1017
|
-
refreshToken: refreshToken
|
|
1018
|
-
}, tokenResponse.expires_in !== undefined && {
|
|
1019
|
-
expiresAt: Date.now() + tokenResponse.expires_in * 1000
|
|
1020
|
-
}, tokenResponse.scope !== undefined && {
|
|
1021
|
-
scope: tokenResponse.scope
|
|
1022
|
-
})
|
|
779
|
+
2
|
|
1023
780
|
];
|
|
1024
781
|
}
|
|
1025
782
|
});
|
|
1026
783
|
}).call(this);
|
|
1027
784
|
};
|
|
1028
785
|
/**
|
|
1029
|
-
*
|
|
1030
|
-
*
|
|
1031
|
-
|
|
1032
|
-
* @param params - OAuth callback parameters
|
|
1033
|
-
* @returns Email and cached token
|
|
1034
|
-
*/ _proto.handleOAuthCallback = function handleOAuthCallback(params) {
|
|
786
|
+
* Load and validate pending auth state (5 minute TTL).
|
|
787
|
+
* Shared by BOTH persistent and ephemeral modes.
|
|
788
|
+
*/ _proto.readAndValidatePendingAuth = function readAndValidatePendingAuth(state) {
|
|
1035
789
|
return _async_to_generator(function() {
|
|
1036
|
-
var
|
|
790
|
+
var tokenStore, pendingAuth;
|
|
1037
791
|
return _ts_generator(this, function(_state) {
|
|
1038
792
|
switch(_state.label){
|
|
1039
793
|
case 0:
|
|
1040
|
-
|
|
1041
|
-
_this_config = this.config, logger = _this_config.logger, service = _this_config.service, tokenStore = _this_config.tokenStore, clientId = _this_config.clientId, clientSecret = _this_config.clientSecret, redirectUri = _this_config.redirectUri;
|
|
1042
|
-
if (!state) {
|
|
1043
|
-
throw new Error('Missing state parameter in OAuth callback');
|
|
1044
|
-
}
|
|
1045
|
-
if (!redirectUri) {
|
|
1046
|
-
throw new Error('handleOAuthCallback requires configured redirectUri');
|
|
1047
|
-
}
|
|
1048
|
-
// Load pending auth (includes PKCE verifier)
|
|
1049
|
-
pendingKey = "".concat(service, ":pending:").concat(state);
|
|
794
|
+
tokenStore = this.config.tokenStore;
|
|
1050
795
|
return [
|
|
1051
796
|
4,
|
|
1052
|
-
tokenStore.get(pendingKey)
|
|
797
|
+
tokenStore.get(this.pendingKey(state))
|
|
1053
798
|
];
|
|
1054
799
|
case 1:
|
|
1055
800
|
pendingAuth = _state.sent();
|
|
1056
801
|
if (!pendingAuth) {
|
|
1057
802
|
throw new Error('Invalid or expired OAuth state. Please try again.');
|
|
1058
803
|
}
|
|
1059
|
-
if (!(Date.now() - pendingAuth.createdAt >
|
|
804
|
+
if (!(Date.now() - pendingAuth.createdAt > OAUTH_TIMEOUT_MS)) return [
|
|
1060
805
|
3,
|
|
1061
806
|
3
|
|
1062
807
|
];
|
|
1063
808
|
return [
|
|
1064
809
|
4,
|
|
1065
|
-
tokenStore.delete(pendingKey)
|
|
810
|
+
tokenStore.delete(this.pendingKey(state))
|
|
1066
811
|
];
|
|
1067
812
|
case 2:
|
|
1068
813
|
_state.sent();
|
|
1069
814
|
throw new Error('OAuth state expired. Please try again.');
|
|
1070
815
|
case 3:
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
816
|
+
return [
|
|
817
|
+
2,
|
|
818
|
+
pendingAuth
|
|
819
|
+
];
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
}).call(this);
|
|
823
|
+
};
|
|
824
|
+
/**
|
|
825
|
+
* Mark pending auth as completed (used by middleware polling).
|
|
826
|
+
*/ _proto.markPendingComplete = function markPendingComplete(args) {
|
|
827
|
+
return _async_to_generator(function() {
|
|
828
|
+
var tokenStore, updated;
|
|
829
|
+
return _ts_generator(this, function(_state) {
|
|
830
|
+
switch(_state.label){
|
|
831
|
+
case 0:
|
|
832
|
+
tokenStore = this.config.tokenStore;
|
|
833
|
+
updated = _object_spread_props(_object_spread({}, args.pending), {
|
|
834
|
+
completedAt: Date.now(),
|
|
835
|
+
email: args.email
|
|
1074
836
|
});
|
|
1075
|
-
// Exchange code for token
|
|
1076
|
-
body = new URLSearchParams(_object_spread_props(_object_spread({
|
|
1077
|
-
code: code,
|
|
1078
|
-
client_id: clientId
|
|
1079
|
-
}, clientSecret && {
|
|
1080
|
-
client_secret: clientSecret
|
|
1081
|
-
}), {
|
|
1082
|
-
redirect_uri: redirectUri,
|
|
1083
|
-
grant_type: 'authorization_code',
|
|
1084
|
-
code_verifier: pendingAuth.codeVerifier
|
|
1085
|
-
}));
|
|
1086
837
|
return [
|
|
1087
838
|
4,
|
|
1088
|
-
|
|
1089
|
-
method: 'POST',
|
|
1090
|
-
headers: {
|
|
1091
|
-
'Content-Type': 'application/x-www-form-urlencoded'
|
|
1092
|
-
},
|
|
1093
|
-
body: body.toString()
|
|
1094
|
-
})
|
|
839
|
+
tokenStore.set(this.pendingKey(args.state), updated, OAUTH_TIMEOUT_MS)
|
|
1095
840
|
];
|
|
1096
|
-
case
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
6
|
|
841
|
+
case 1:
|
|
842
|
+
_state.sent();
|
|
843
|
+
return [
|
|
844
|
+
2
|
|
1101
845
|
];
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
}).call(this);
|
|
849
|
+
};
|
|
850
|
+
/**
|
|
851
|
+
* Clean up pending auth state.
|
|
852
|
+
*/ _proto.deletePendingAuth = function deletePendingAuth(state) {
|
|
853
|
+
return _async_to_generator(function() {
|
|
854
|
+
var tokenStore;
|
|
855
|
+
return _ts_generator(this, function(_state) {
|
|
856
|
+
switch(_state.label){
|
|
857
|
+
case 0:
|
|
858
|
+
tokenStore = this.config.tokenStore;
|
|
1102
859
|
return [
|
|
1103
860
|
4,
|
|
1104
|
-
|
|
861
|
+
tokenStore.delete(this.pendingKey(state))
|
|
1105
862
|
];
|
|
1106
|
-
case
|
|
1107
|
-
|
|
1108
|
-
throw new Error("Token exchange failed: ".concat(response.status, " ").concat(errorText));
|
|
1109
|
-
case 6:
|
|
863
|
+
case 1:
|
|
864
|
+
_state.sent();
|
|
1110
865
|
return [
|
|
1111
|
-
|
|
1112
|
-
|
|
866
|
+
2
|
|
867
|
+
];
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
}).call(this);
|
|
871
|
+
};
|
|
872
|
+
/**
|
|
873
|
+
* Wait until pending auth is marked completed (or timeout).
|
|
874
|
+
* Used by middleware after opening auth URL in non-headless mode.
|
|
875
|
+
*/ _proto.waitForOAuthCompletion = function waitForOAuthCompletion(state) {
|
|
876
|
+
return _async_to_generator(function() {
|
|
877
|
+
var tokenStore, key, start, pending;
|
|
878
|
+
return _ts_generator(this, function(_state) {
|
|
879
|
+
switch(_state.label){
|
|
880
|
+
case 0:
|
|
881
|
+
tokenStore = this.config.tokenStore;
|
|
882
|
+
key = this.pendingKey(state);
|
|
883
|
+
start = Date.now();
|
|
884
|
+
_state.label = 1;
|
|
885
|
+
case 1:
|
|
886
|
+
if (!(Date.now() - start < OAUTH_TIMEOUT_MS)) return [
|
|
887
|
+
3,
|
|
888
|
+
4
|
|
1113
889
|
];
|
|
1114
|
-
case 7:
|
|
1115
|
-
tokenResponse = _state.sent();
|
|
1116
890
|
return [
|
|
1117
891
|
4,
|
|
1118
|
-
|
|
892
|
+
tokenStore.get(key)
|
|
1119
893
|
];
|
|
1120
|
-
case
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
}
|
|
1130
|
-
// Store token
|
|
894
|
+
case 2:
|
|
895
|
+
pending = _state.sent();
|
|
896
|
+
if (pending === null || pending === void 0 ? void 0 : pending.completedAt) {
|
|
897
|
+
return [
|
|
898
|
+
2,
|
|
899
|
+
{
|
|
900
|
+
email: pending.email
|
|
901
|
+
}
|
|
902
|
+
];
|
|
903
|
+
}
|
|
1131
904
|
return [
|
|
1132
905
|
4,
|
|
1133
|
-
(
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
}, cachedToken)
|
|
906
|
+
new Promise(function(r) {
|
|
907
|
+
return setTimeout(r, OAUTH_POLL_MS);
|
|
908
|
+
})
|
|
1137
909
|
];
|
|
1138
|
-
case
|
|
910
|
+
case 3:
|
|
1139
911
|
_state.sent();
|
|
1140
|
-
|
|
912
|
+
return [
|
|
913
|
+
3,
|
|
914
|
+
1
|
|
915
|
+
];
|
|
916
|
+
case 4:
|
|
917
|
+
throw new Error('OAuth flow timed out after 5 minutes');
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
}).call(this);
|
|
921
|
+
};
|
|
922
|
+
/**
|
|
923
|
+
* Process an OAuth callback using shared state validation + token exchange + persistence.
|
|
924
|
+
* Used by BOTH:
|
|
925
|
+
* - ephemeral loopback server callback handler
|
|
926
|
+
* - persistent redirectUri callback handler
|
|
927
|
+
*
|
|
928
|
+
* IMPORTANT CHANGE:
|
|
929
|
+
* - We do NOT delete pending state here anymore.
|
|
930
|
+
* - We mark it completed so middleware can poll and then clean it up.
|
|
931
|
+
*/ _proto.processOAuthCallback = function processOAuthCallback(args) {
|
|
932
|
+
return _async_to_generator(function() {
|
|
933
|
+
var _this_config, logger, service, pending, result;
|
|
934
|
+
return _ts_generator(this, function(_state) {
|
|
935
|
+
switch(_state.label){
|
|
936
|
+
case 0:
|
|
937
|
+
_this_config = this.config, logger = _this_config.logger, service = _this_config.service;
|
|
1141
938
|
return [
|
|
1142
939
|
4,
|
|
1143
|
-
(
|
|
1144
|
-
|
|
1145
|
-
|
|
940
|
+
this.readAndValidatePendingAuth(args.state)
|
|
941
|
+
];
|
|
942
|
+
case 1:
|
|
943
|
+
pending = _state.sent();
|
|
944
|
+
logger.info('Processing OAuth callback', {
|
|
945
|
+
service: service,
|
|
946
|
+
state: args.state
|
|
947
|
+
});
|
|
948
|
+
return [
|
|
949
|
+
4,
|
|
950
|
+
this.handleAuthorizationCode({
|
|
951
|
+
code: args.code,
|
|
952
|
+
codeVerifier: pending.codeVerifier,
|
|
953
|
+
redirectUri: args.redirectUri
|
|
1146
954
|
})
|
|
1147
955
|
];
|
|
1148
|
-
case
|
|
956
|
+
case 2:
|
|
957
|
+
result = _state.sent();
|
|
958
|
+
return [
|
|
959
|
+
4,
|
|
960
|
+
this.persistAuthResult(result)
|
|
961
|
+
];
|
|
962
|
+
case 3:
|
|
1149
963
|
_state.sent();
|
|
1150
964
|
return [
|
|
1151
965
|
4,
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
966
|
+
this.markPendingComplete({
|
|
967
|
+
state: args.state,
|
|
968
|
+
email: result.email,
|
|
969
|
+
pending: pending
|
|
1155
970
|
})
|
|
1156
971
|
];
|
|
1157
|
-
case
|
|
972
|
+
case 4:
|
|
1158
973
|
_state.sent();
|
|
1159
|
-
|
|
974
|
+
logger.info('OAuth callback completed', {
|
|
975
|
+
service: service,
|
|
976
|
+
email: result.email
|
|
977
|
+
});
|
|
978
|
+
return [
|
|
979
|
+
2,
|
|
980
|
+
result
|
|
981
|
+
];
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
}).call(this);
|
|
985
|
+
};
|
|
986
|
+
// ---------------------------------------------------------------------------
|
|
987
|
+
// Ephemeral loopback server + flow
|
|
988
|
+
// ---------------------------------------------------------------------------
|
|
989
|
+
/**
|
|
990
|
+
* Loopback OAuth server helper (RFC 8252 Section 7.3)
|
|
991
|
+
*
|
|
992
|
+
* Implements ephemeral local server with OS-assigned port (RFC 8252 Section 8.3).
|
|
993
|
+
* Shared callback handling uses:
|
|
994
|
+
* - the same authUrl builder as redirectUri mode
|
|
995
|
+
* - the same pending PKCE verifier storage as redirectUri mode
|
|
996
|
+
* - the same callback processor as redirectUri mode
|
|
997
|
+
*/ _proto.createOAuthCallbackServer = function createOAuthCallbackServer(args) {
|
|
998
|
+
var _this = this;
|
|
999
|
+
var logger = this.config.logger;
|
|
1000
|
+
// Create ephemeral server with OS-assigned port (RFC 8252)
|
|
1001
|
+
return _http.createServer(function(req, res) {
|
|
1002
|
+
return _async_to_generator(function() {
|
|
1003
|
+
var url, code, error, state, exchangeError, outerError;
|
|
1004
|
+
return _ts_generator(this, function(_state) {
|
|
1005
|
+
switch(_state.label){
|
|
1006
|
+
case 0:
|
|
1007
|
+
_state.trys.push([
|
|
1008
|
+
0,
|
|
1009
|
+
5,
|
|
1010
|
+
,
|
|
1011
|
+
6
|
|
1012
|
+
]);
|
|
1013
|
+
if (!req.url) {
|
|
1014
|
+
res.writeHead(400, {
|
|
1015
|
+
'Content-Type': 'text/html'
|
|
1016
|
+
});
|
|
1017
|
+
res.end((0, _oauth.getErrorTemplate)('Invalid request'));
|
|
1018
|
+
args.onError(new Error('Invalid request: missing URL'));
|
|
1019
|
+
return [
|
|
1020
|
+
2
|
|
1021
|
+
];
|
|
1022
|
+
}
|
|
1023
|
+
// Use loopback base for URL parsing (port is not important for parsing path/query)
|
|
1024
|
+
url = new URL(req.url, 'http://127.0.0.1');
|
|
1025
|
+
if (url.pathname !== args.callbackPath) {
|
|
1026
|
+
res.writeHead(404, {
|
|
1027
|
+
'Content-Type': 'text/plain'
|
|
1028
|
+
});
|
|
1029
|
+
res.end('Not Found');
|
|
1030
|
+
return [
|
|
1031
|
+
2
|
|
1032
|
+
];
|
|
1033
|
+
}
|
|
1034
|
+
code = url.searchParams.get('code');
|
|
1035
|
+
error = url.searchParams.get('error');
|
|
1036
|
+
state = url.searchParams.get('state');
|
|
1037
|
+
if (error) {
|
|
1038
|
+
res.writeHead(400, {
|
|
1039
|
+
'Content-Type': 'text/html'
|
|
1040
|
+
});
|
|
1041
|
+
res.end((0, _oauth.getErrorTemplate)(error));
|
|
1042
|
+
args.onError(new Error("OAuth error: ".concat(error)));
|
|
1043
|
+
return [
|
|
1044
|
+
2
|
|
1045
|
+
];
|
|
1046
|
+
}
|
|
1047
|
+
if (!code) {
|
|
1048
|
+
res.writeHead(400, {
|
|
1049
|
+
'Content-Type': 'text/html'
|
|
1050
|
+
});
|
|
1051
|
+
res.end((0, _oauth.getErrorTemplate)('No authorization code received'));
|
|
1052
|
+
args.onError(new Error('No authorization code received'));
|
|
1053
|
+
return [
|
|
1054
|
+
2
|
|
1055
|
+
];
|
|
1056
|
+
}
|
|
1057
|
+
if (!state) {
|
|
1058
|
+
res.writeHead(400, {
|
|
1059
|
+
'Content-Type': 'text/html'
|
|
1060
|
+
});
|
|
1061
|
+
res.end((0, _oauth.getErrorTemplate)('Missing state parameter in OAuth callback'));
|
|
1062
|
+
args.onError(new Error('Missing state parameter in OAuth callback'));
|
|
1063
|
+
return [
|
|
1064
|
+
2
|
|
1065
|
+
];
|
|
1066
|
+
}
|
|
1067
|
+
_state.label = 1;
|
|
1068
|
+
case 1:
|
|
1069
|
+
_state.trys.push([
|
|
1070
|
+
1,
|
|
1071
|
+
3,
|
|
1072
|
+
,
|
|
1073
|
+
4
|
|
1074
|
+
]);
|
|
1075
|
+
return [
|
|
1076
|
+
4,
|
|
1077
|
+
this.processOAuthCallback({
|
|
1078
|
+
code: code,
|
|
1079
|
+
state: state,
|
|
1080
|
+
redirectUri: args.finalRedirectUri()
|
|
1081
|
+
})
|
|
1082
|
+
];
|
|
1083
|
+
case 2:
|
|
1084
|
+
_state.sent();
|
|
1085
|
+
res.writeHead(200, {
|
|
1086
|
+
'Content-Type': 'text/html'
|
|
1087
|
+
});
|
|
1088
|
+
res.end((0, _oauth.getSuccessTemplate)());
|
|
1089
|
+
args.onDone();
|
|
1090
|
+
return [
|
|
1091
|
+
3,
|
|
1092
|
+
4
|
|
1093
|
+
];
|
|
1094
|
+
case 3:
|
|
1095
|
+
exchangeError = _state.sent();
|
|
1096
|
+
logger.error('Token exchange failed', {
|
|
1097
|
+
error: _instanceof(exchangeError, Error) ? exchangeError.message : String(exchangeError)
|
|
1098
|
+
});
|
|
1099
|
+
res.writeHead(500, {
|
|
1100
|
+
'Content-Type': 'text/html'
|
|
1101
|
+
});
|
|
1102
|
+
res.end((0, _oauth.getErrorTemplate)('Token exchange failed'));
|
|
1103
|
+
args.onError(exchangeError);
|
|
1104
|
+
return [
|
|
1105
|
+
3,
|
|
1106
|
+
4
|
|
1107
|
+
];
|
|
1108
|
+
case 4:
|
|
1109
|
+
return [
|
|
1110
|
+
3,
|
|
1111
|
+
6
|
|
1112
|
+
];
|
|
1113
|
+
case 5:
|
|
1114
|
+
outerError = _state.sent();
|
|
1115
|
+
logger.error('OAuth callback server error', {
|
|
1116
|
+
error: _instanceof(outerError, Error) ? outerError.message : String(outerError)
|
|
1117
|
+
});
|
|
1118
|
+
res.writeHead(500, {
|
|
1119
|
+
'Content-Type': 'text/html'
|
|
1120
|
+
});
|
|
1121
|
+
res.end((0, _oauth.getErrorTemplate)('Internal server error'));
|
|
1122
|
+
args.onError(outerError);
|
|
1123
|
+
return [
|
|
1124
|
+
3,
|
|
1125
|
+
6
|
|
1126
|
+
];
|
|
1127
|
+
case 6:
|
|
1128
|
+
return [
|
|
1129
|
+
2
|
|
1130
|
+
];
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
}).call(_this);
|
|
1134
|
+
});
|
|
1135
|
+
};
|
|
1136
|
+
/**
|
|
1137
|
+
* Starts the ephemeral loopback server and returns an AuthRequiredError(auth_url).
|
|
1138
|
+
* Middleware will open+poll and then retry in the same call.
|
|
1139
|
+
*/ _proto.startEphemeralOAuthFlow = function startEphemeralOAuthFlow() {
|
|
1140
|
+
return _async_to_generator(function() {
|
|
1141
|
+
var _this, _this_config, headless, logger, configRedirectUri, service, tokenStore, listenHost, listenPort, callbackPath, useConfiguredUri, parsed, isLoopback, envPort, _generatePKCE, codeVerifier, codeChallenge, stateId, server, serverPort, finalRedirectUri, authUrl;
|
|
1142
|
+
return _ts_generator(this, function(_state) {
|
|
1143
|
+
switch(_state.label){
|
|
1144
|
+
case 0:
|
|
1145
|
+
_this = this;
|
|
1146
|
+
_this_config = this.config, headless = _this_config.headless, logger = _this_config.logger, configRedirectUri = _this_config.redirectUri, service = _this_config.service, tokenStore = _this_config.tokenStore;
|
|
1147
|
+
// Server listen configuration (where ephemeral server binds)
|
|
1148
|
+
listenHost = 'localhost'; // Default: localhost for ephemeral loopback
|
|
1149
|
+
listenPort = 0; // Default: OS-assigned ephemeral port
|
|
1150
|
+
// Redirect URI configuration (what goes in auth URL and token exchange)
|
|
1151
|
+
callbackPath = '/callback'; // Default callback path
|
|
1152
|
+
useConfiguredUri = false;
|
|
1153
|
+
if (configRedirectUri) {
|
|
1154
|
+
try {
|
|
1155
|
+
parsed = new URL(configRedirectUri);
|
|
1156
|
+
isLoopback = parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1';
|
|
1157
|
+
if (isLoopback) {
|
|
1158
|
+
// Local development: Listen on specific loopback address/port
|
|
1159
|
+
listenHost = parsed.hostname;
|
|
1160
|
+
listenPort = parsed.port ? Number.parseInt(parsed.port, 10) : 0;
|
|
1161
|
+
} else {
|
|
1162
|
+
// Cloud deployment: Listen on 0.0.0.0 with PORT from environment
|
|
1163
|
+
// The redirectUri is the PUBLIC URL (e.g., https://example.com/oauth/callback)
|
|
1164
|
+
// The server listens on 0.0.0.0:PORT and the load balancer routes to it
|
|
1165
|
+
listenHost = '0.0.0.0';
|
|
1166
|
+
envPort = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : undefined;
|
|
1167
|
+
listenPort = envPort && Number.isFinite(envPort) ? envPort : 8080;
|
|
1168
|
+
}
|
|
1169
|
+
// Extract callback path from URL
|
|
1170
|
+
if (parsed.pathname && parsed.pathname !== '/') {
|
|
1171
|
+
callbackPath = parsed.pathname;
|
|
1172
|
+
}
|
|
1173
|
+
useConfiguredUri = true;
|
|
1174
|
+
logger.debug('Using configured redirect URI', {
|
|
1175
|
+
listenHost: listenHost,
|
|
1176
|
+
listenPort: listenPort,
|
|
1177
|
+
callbackPath: callbackPath,
|
|
1178
|
+
redirectUri: configRedirectUri,
|
|
1179
|
+
isLoopback: isLoopback
|
|
1180
|
+
});
|
|
1181
|
+
} catch (error) {
|
|
1182
|
+
logger.warn('Failed to parse redirectUri, using ephemeral defaults', {
|
|
1183
|
+
redirectUri: configRedirectUri,
|
|
1184
|
+
error: _instanceof(error, Error) ? error.message : String(error)
|
|
1185
|
+
});
|
|
1186
|
+
// Continue with defaults (localhost, port 0, http, /callback)
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
// Generate PKCE challenge + state
|
|
1190
|
+
_generatePKCE = (0, _oauth.generatePKCE)(), codeVerifier = _generatePKCE.verifier, codeChallenge = _generatePKCE.challenge;
|
|
1191
|
+
stateId = (0, _crypto.randomUUID)();
|
|
1192
|
+
// Store PKCE verifier for callback (5 minute TTL)
|
|
1160
1193
|
return [
|
|
1161
1194
|
4,
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
}, {
|
|
1166
|
-
email: email,
|
|
1167
|
-
addedAt: new Date().toISOString()
|
|
1195
|
+
this.createPendingAuth({
|
|
1196
|
+
state: stateId,
|
|
1197
|
+
codeVerifier: codeVerifier
|
|
1168
1198
|
})
|
|
1169
1199
|
];
|
|
1170
|
-
case
|
|
1200
|
+
case 1:
|
|
1171
1201
|
_state.sent();
|
|
1172
|
-
|
|
1202
|
+
server = null;
|
|
1203
|
+
// Create ephemeral server with OS-assigned port (RFC 8252)
|
|
1204
|
+
server = this.createOAuthCallbackServer({
|
|
1205
|
+
callbackPath: callbackPath,
|
|
1206
|
+
finalRedirectUri: function() {
|
|
1207
|
+
return finalRedirectUri;
|
|
1208
|
+
},
|
|
1209
|
+
onDone: function() {
|
|
1210
|
+
server === null || server === void 0 ? void 0 : server.close();
|
|
1211
|
+
},
|
|
1212
|
+
onError: function(err) {
|
|
1213
|
+
logger.error('Ephemeral OAuth server error', {
|
|
1214
|
+
error: _instanceof(err, Error) ? err.message : String(err)
|
|
1215
|
+
});
|
|
1216
|
+
server === null || server === void 0 ? void 0 : server.close();
|
|
1217
|
+
}
|
|
1218
|
+
});
|
|
1219
|
+
// Start listening
|
|
1173
1220
|
return [
|
|
1174
1221
|
4,
|
|
1175
|
-
|
|
1222
|
+
new Promise(function(resolve, reject) {
|
|
1223
|
+
server === null || server === void 0 ? void 0 : server.listen(listenPort, listenHost, function() {
|
|
1224
|
+
var address = server === null || server === void 0 ? void 0 : server.address();
|
|
1225
|
+
if (!address || typeof address === 'string') {
|
|
1226
|
+
server === null || server === void 0 ? void 0 : server.close();
|
|
1227
|
+
reject(new Error('Failed to start ephemeral server'));
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
serverPort = address.port;
|
|
1231
|
+
// Construct final redirect URI
|
|
1232
|
+
if (useConfiguredUri && configRedirectUri) {
|
|
1233
|
+
finalRedirectUri = configRedirectUri;
|
|
1234
|
+
} else {
|
|
1235
|
+
finalRedirectUri = "http://localhost:".concat(serverPort).concat(callbackPath);
|
|
1236
|
+
}
|
|
1237
|
+
logger.info('Ephemeral OAuth server started', {
|
|
1238
|
+
port: serverPort,
|
|
1239
|
+
headless: headless,
|
|
1240
|
+
service: service
|
|
1241
|
+
});
|
|
1242
|
+
resolve();
|
|
1243
|
+
});
|
|
1244
|
+
})
|
|
1176
1245
|
];
|
|
1177
|
-
case
|
|
1246
|
+
case 2:
|
|
1178
1247
|
_state.sent();
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1248
|
+
// Timeout after 5 minutes (match middleware polling timeout)
|
|
1249
|
+
setTimeout(function() {
|
|
1250
|
+
if (server) {
|
|
1251
|
+
server.close();
|
|
1252
|
+
// Best-effort cleanup if user never completes flow:
|
|
1253
|
+
// delete pending so a future attempt can restart cleanly.
|
|
1254
|
+
void tokenStore.delete(_this.pendingKey(stateId));
|
|
1255
|
+
}
|
|
1256
|
+
}, OAUTH_TIMEOUT_MS);
|
|
1257
|
+
// Build auth URL - SAME helper as persistent mode
|
|
1258
|
+
authUrl = this.buildAuthUrl({
|
|
1259
|
+
redirectUri: finalRedirectUri,
|
|
1260
|
+
codeChallenge: codeChallenge,
|
|
1261
|
+
state: stateId
|
|
1182
1262
|
});
|
|
1183
1263
|
return [
|
|
1184
1264
|
2,
|
|
1185
1265
|
{
|
|
1186
|
-
|
|
1187
|
-
|
|
1266
|
+
kind: 'auth_url',
|
|
1267
|
+
provider: service,
|
|
1268
|
+
url: authUrl
|
|
1188
1269
|
}
|
|
1189
1270
|
];
|
|
1190
1271
|
}
|
|
1191
1272
|
});
|
|
1192
1273
|
}).call(this);
|
|
1193
1274
|
};
|
|
1275
|
+
_proto.exchangeCodeForToken = function exchangeCodeForToken(code, codeVerifier, redirectUri) {
|
|
1276
|
+
return _async_to_generator(function() {
|
|
1277
|
+
var _this_config, clientId, clientSecret, tokenUrl, params, body, response, errorText;
|
|
1278
|
+
return _ts_generator(this, function(_state) {
|
|
1279
|
+
switch(_state.label){
|
|
1280
|
+
case 0:
|
|
1281
|
+
_this_config = this.config, clientId = _this_config.clientId, clientSecret = _this_config.clientSecret;
|
|
1282
|
+
tokenUrl = 'https://oauth2.googleapis.com/token';
|
|
1283
|
+
params = {
|
|
1284
|
+
code: code,
|
|
1285
|
+
client_id: clientId,
|
|
1286
|
+
redirect_uri: redirectUri,
|
|
1287
|
+
grant_type: 'authorization_code',
|
|
1288
|
+
code_verifier: codeVerifier
|
|
1289
|
+
};
|
|
1290
|
+
if (clientSecret) {
|
|
1291
|
+
params.client_secret = clientSecret;
|
|
1292
|
+
}
|
|
1293
|
+
body = new URLSearchParams(params);
|
|
1294
|
+
return [
|
|
1295
|
+
4,
|
|
1296
|
+
fetch(tokenUrl, {
|
|
1297
|
+
method: 'POST',
|
|
1298
|
+
headers: {
|
|
1299
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
1300
|
+
},
|
|
1301
|
+
body: body.toString()
|
|
1302
|
+
})
|
|
1303
|
+
];
|
|
1304
|
+
case 1:
|
|
1305
|
+
response = _state.sent();
|
|
1306
|
+
if (!!response.ok) return [
|
|
1307
|
+
3,
|
|
1308
|
+
3
|
|
1309
|
+
];
|
|
1310
|
+
return [
|
|
1311
|
+
4,
|
|
1312
|
+
response.text()
|
|
1313
|
+
];
|
|
1314
|
+
case 2:
|
|
1315
|
+
errorText = _state.sent();
|
|
1316
|
+
throw new Error("Token exchange failed: ".concat(response.status, " ").concat(errorText));
|
|
1317
|
+
case 3:
|
|
1318
|
+
return [
|
|
1319
|
+
4,
|
|
1320
|
+
response.json()
|
|
1321
|
+
];
|
|
1322
|
+
case 4:
|
|
1323
|
+
return [
|
|
1324
|
+
2,
|
|
1325
|
+
_state.sent()
|
|
1326
|
+
];
|
|
1327
|
+
}
|
|
1328
|
+
});
|
|
1329
|
+
}).call(this);
|
|
1330
|
+
};
|
|
1331
|
+
_proto.refreshAccessToken = function refreshAccessToken(refreshToken) {
|
|
1332
|
+
return _async_to_generator(function() {
|
|
1333
|
+
var _this_config, clientId, clientSecret, tokenUrl, params, body, response, errorText, tokenResponse;
|
|
1334
|
+
return _ts_generator(this, function(_state) {
|
|
1335
|
+
switch(_state.label){
|
|
1336
|
+
case 0:
|
|
1337
|
+
_this_config = this.config, clientId = _this_config.clientId, clientSecret = _this_config.clientSecret;
|
|
1338
|
+
tokenUrl = 'https://oauth2.googleapis.com/token';
|
|
1339
|
+
params = {
|
|
1340
|
+
refresh_token: refreshToken,
|
|
1341
|
+
client_id: clientId,
|
|
1342
|
+
grant_type: 'refresh_token'
|
|
1343
|
+
};
|
|
1344
|
+
if (clientSecret) {
|
|
1345
|
+
params.client_secret = clientSecret;
|
|
1346
|
+
}
|
|
1347
|
+
body = new URLSearchParams(params);
|
|
1348
|
+
return [
|
|
1349
|
+
4,
|
|
1350
|
+
fetch(tokenUrl, {
|
|
1351
|
+
method: 'POST',
|
|
1352
|
+
headers: {
|
|
1353
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
1354
|
+
},
|
|
1355
|
+
body: body.toString()
|
|
1356
|
+
})
|
|
1357
|
+
];
|
|
1358
|
+
case 1:
|
|
1359
|
+
response = _state.sent();
|
|
1360
|
+
if (!!response.ok) return [
|
|
1361
|
+
3,
|
|
1362
|
+
3
|
|
1363
|
+
];
|
|
1364
|
+
return [
|
|
1365
|
+
4,
|
|
1366
|
+
response.text()
|
|
1367
|
+
];
|
|
1368
|
+
case 2:
|
|
1369
|
+
errorText = _state.sent();
|
|
1370
|
+
throw new Error("Token refresh failed: ".concat(response.status, " ").concat(errorText));
|
|
1371
|
+
case 3:
|
|
1372
|
+
return [
|
|
1373
|
+
4,
|
|
1374
|
+
response.json()
|
|
1375
|
+
];
|
|
1376
|
+
case 4:
|
|
1377
|
+
tokenResponse = _state.sent();
|
|
1378
|
+
return [
|
|
1379
|
+
2,
|
|
1380
|
+
_object_spread({
|
|
1381
|
+
accessToken: tokenResponse.access_token,
|
|
1382
|
+
refreshToken: refreshToken
|
|
1383
|
+
}, tokenResponse.expires_in !== undefined && {
|
|
1384
|
+
expiresAt: Date.now() + tokenResponse.expires_in * 1000
|
|
1385
|
+
}, tokenResponse.scope !== undefined && {
|
|
1386
|
+
scope: tokenResponse.scope
|
|
1387
|
+
})
|
|
1388
|
+
];
|
|
1389
|
+
}
|
|
1390
|
+
});
|
|
1391
|
+
}).call(this);
|
|
1392
|
+
};
|
|
1393
|
+
/**
|
|
1394
|
+
* Handle OAuth callback from persistent endpoint.
|
|
1395
|
+
* Used by HTTP servers with configured redirectUri.
|
|
1396
|
+
*
|
|
1397
|
+
* @param params - OAuth callback parameters
|
|
1398
|
+
* @returns Email and cached token
|
|
1399
|
+
*/ _proto.handleOAuthCallback = function handleOAuthCallback(params) {
|
|
1400
|
+
return _async_to_generator(function() {
|
|
1401
|
+
var code, state, redirectUri;
|
|
1402
|
+
return _ts_generator(this, function(_state) {
|
|
1403
|
+
switch(_state.label){
|
|
1404
|
+
case 0:
|
|
1405
|
+
code = params.code, state = params.state;
|
|
1406
|
+
redirectUri = this.config.redirectUri;
|
|
1407
|
+
if (!state) {
|
|
1408
|
+
throw new Error('Missing state parameter in OAuth callback');
|
|
1409
|
+
}
|
|
1410
|
+
if (!redirectUri) {
|
|
1411
|
+
throw new Error('handleOAuthCallback requires configured redirectUri');
|
|
1412
|
+
}
|
|
1413
|
+
return [
|
|
1414
|
+
4,
|
|
1415
|
+
this.processOAuthCallback({
|
|
1416
|
+
code: code,
|
|
1417
|
+
state: state,
|
|
1418
|
+
redirectUri: redirectUri
|
|
1419
|
+
})
|
|
1420
|
+
];
|
|
1421
|
+
case 1:
|
|
1422
|
+
// Shared callback processor (same code path as ephemeral)
|
|
1423
|
+
return [
|
|
1424
|
+
2,
|
|
1425
|
+
_state.sent()
|
|
1426
|
+
];
|
|
1427
|
+
}
|
|
1428
|
+
});
|
|
1429
|
+
}).call(this);
|
|
1430
|
+
};
|
|
1194
1431
|
/**
|
|
1195
1432
|
* Create authentication middleware for MCP tools, resources, and prompts
|
|
1196
1433
|
*
|
|
@@ -1205,15 +1442,6 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
1205
1442
|
* All requests use token lookups based on the active account or account override.
|
|
1206
1443
|
*
|
|
1207
1444
|
* @returns Object with withToolAuth, withResourceAuth, withPromptAuth methods
|
|
1208
|
-
*
|
|
1209
|
-
* @example
|
|
1210
|
-
* ```typescript
|
|
1211
|
-
* const loopback = new LoopbackOAuthProvider({ service: 'gmail', ... });
|
|
1212
|
-
* const authMiddleware = loopback.authMiddleware();
|
|
1213
|
-
* const tools = toolFactories.map(f => f()).map(authMiddleware.withToolAuth);
|
|
1214
|
-
* const resources = resourceFactories.map(f => f()).map(authMiddleware.withResourceAuth);
|
|
1215
|
-
* const prompts = promptFactories.map(f => f()).map(authMiddleware.withPromptAuth);
|
|
1216
|
-
* ```
|
|
1217
1445
|
*/ _proto.authMiddleware = function authMiddleware() {
|
|
1218
1446
|
var _this = this;
|
|
1219
1447
|
var _this_config = this.config, service = _this_config.service, tokenStore = _this_config.tokenStore, logger = _this_config.logger;
|
|
@@ -1228,96 +1456,173 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
1228
1456
|
allArgs[_key] = arguments[_key];
|
|
1229
1457
|
}
|
|
1230
1458
|
return _async_to_generator(function() {
|
|
1231
|
-
var
|
|
1459
|
+
var _this, extra, ensureAuthenticatedOrThrow, effectiveAccountId, auth, error, authRequiredResponse;
|
|
1232
1460
|
return _ts_generator(this, function(_state) {
|
|
1233
1461
|
switch(_state.label){
|
|
1234
1462
|
case 0:
|
|
1463
|
+
_this = this;
|
|
1235
1464
|
// Extract extra from the correct position
|
|
1236
1465
|
extra = allArgs[extraPosition];
|
|
1466
|
+
// Helper: retry once after open+poll completes
|
|
1467
|
+
ensureAuthenticatedOrThrow = function() {
|
|
1468
|
+
return _async_to_generator(function() {
|
|
1469
|
+
var accountId, _ref, _extra__meta, _tmp, error, effectiveAccountId, _tmp1, error1, authUrl, state;
|
|
1470
|
+
return _ts_generator(this, function(_state) {
|
|
1471
|
+
switch(_state.label){
|
|
1472
|
+
case 0:
|
|
1473
|
+
_state.trys.push([
|
|
1474
|
+
0,
|
|
1475
|
+
11,
|
|
1476
|
+
,
|
|
1477
|
+
16
|
|
1478
|
+
]);
|
|
1479
|
+
_state.label = 1;
|
|
1480
|
+
case 1:
|
|
1481
|
+
_state.trys.push([
|
|
1482
|
+
1,
|
|
1483
|
+
5,
|
|
1484
|
+
,
|
|
1485
|
+
6
|
|
1486
|
+
]);
|
|
1487
|
+
if (!((_ref = (_extra__meta = extra._meta) === null || _extra__meta === void 0 ? void 0 : _extra__meta.accountId) !== null && _ref !== void 0)) return [
|
|
1488
|
+
3,
|
|
1489
|
+
2
|
|
1490
|
+
];
|
|
1491
|
+
_tmp = _ref;
|
|
1492
|
+
return [
|
|
1493
|
+
3,
|
|
1494
|
+
4
|
|
1495
|
+
];
|
|
1496
|
+
case 2:
|
|
1497
|
+
return [
|
|
1498
|
+
4,
|
|
1499
|
+
(0, _oauth.getActiveAccount)(tokenStore, {
|
|
1500
|
+
service: service
|
|
1501
|
+
})
|
|
1502
|
+
];
|
|
1503
|
+
case 3:
|
|
1504
|
+
_tmp = _state.sent();
|
|
1505
|
+
_state.label = 4;
|
|
1506
|
+
case 4:
|
|
1507
|
+
accountId = _tmp;
|
|
1508
|
+
return [
|
|
1509
|
+
3,
|
|
1510
|
+
6
|
|
1511
|
+
];
|
|
1512
|
+
case 5:
|
|
1513
|
+
error = _state.sent();
|
|
1514
|
+
if (_instanceof(error, Error) && (error.code === 'REQUIRES_AUTHENTICATION' || error.name === 'AccountManagerError')) {
|
|
1515
|
+
accountId = undefined;
|
|
1516
|
+
} else {
|
|
1517
|
+
throw error;
|
|
1518
|
+
}
|
|
1519
|
+
return [
|
|
1520
|
+
3,
|
|
1521
|
+
6
|
|
1522
|
+
];
|
|
1523
|
+
case 6:
|
|
1524
|
+
return [
|
|
1525
|
+
4,
|
|
1526
|
+
this.getAccessToken(accountId)
|
|
1527
|
+
];
|
|
1528
|
+
case 7:
|
|
1529
|
+
_state.sent();
|
|
1530
|
+
if (!(accountId !== null && accountId !== void 0)) return [
|
|
1531
|
+
3,
|
|
1532
|
+
8
|
|
1533
|
+
];
|
|
1534
|
+
_tmp1 = accountId;
|
|
1535
|
+
return [
|
|
1536
|
+
3,
|
|
1537
|
+
10
|
|
1538
|
+
];
|
|
1539
|
+
case 8:
|
|
1540
|
+
return [
|
|
1541
|
+
4,
|
|
1542
|
+
(0, _oauth.getActiveAccount)(tokenStore, {
|
|
1543
|
+
service: service
|
|
1544
|
+
})
|
|
1545
|
+
];
|
|
1546
|
+
case 9:
|
|
1547
|
+
_tmp1 = _state.sent();
|
|
1548
|
+
_state.label = 10;
|
|
1549
|
+
case 10:
|
|
1550
|
+
effectiveAccountId = _tmp1;
|
|
1551
|
+
if (!effectiveAccountId) {
|
|
1552
|
+
throw new Error("No account found after OAuth flow for service ".concat(service));
|
|
1553
|
+
}
|
|
1554
|
+
return [
|
|
1555
|
+
2,
|
|
1556
|
+
effectiveAccountId
|
|
1557
|
+
];
|
|
1558
|
+
case 11:
|
|
1559
|
+
error1 = _state.sent();
|
|
1560
|
+
if (!(_instanceof(error1, _typests.AuthRequiredError) && error1.descriptor.kind === 'auth_url')) return [
|
|
1561
|
+
3,
|
|
1562
|
+
15
|
|
1563
|
+
];
|
|
1564
|
+
// Headless: don't open/poll; just propagate to outer handler to return auth_required.
|
|
1565
|
+
if (this.config.headless) throw error1;
|
|
1566
|
+
// Non-headless: open once + poll until callback completes, then retry token acquisition.
|
|
1567
|
+
authUrl = new URL(error1.descriptor.url);
|
|
1568
|
+
state = authUrl.searchParams.get('state');
|
|
1569
|
+
if (!state) throw new Error('Auth URL missing state parameter');
|
|
1570
|
+
if (!this.openedStates.has(state)) {
|
|
1571
|
+
this.openedStates.add(state);
|
|
1572
|
+
(0, _open.default)(error1.descriptor.url).catch(function(e) {
|
|
1573
|
+
logger.info('Failed to open browser automatically', {
|
|
1574
|
+
error: _instanceof(e, Error) ? e.message : String(e)
|
|
1575
|
+
});
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
// Block until callback completes (or timeout)
|
|
1579
|
+
return [
|
|
1580
|
+
4,
|
|
1581
|
+
this.waitForOAuthCompletion(state)
|
|
1582
|
+
];
|
|
1583
|
+
case 12:
|
|
1584
|
+
_state.sent();
|
|
1585
|
+
// Cleanup pending state after we observe completion
|
|
1586
|
+
return [
|
|
1587
|
+
4,
|
|
1588
|
+
this.deletePendingAuth(state)
|
|
1589
|
+
];
|
|
1590
|
+
case 13:
|
|
1591
|
+
_state.sent();
|
|
1592
|
+
return [
|
|
1593
|
+
4,
|
|
1594
|
+
ensureAuthenticatedOrThrow()
|
|
1595
|
+
];
|
|
1596
|
+
case 14:
|
|
1597
|
+
// Retry after completion
|
|
1598
|
+
return [
|
|
1599
|
+
2,
|
|
1600
|
+
_state.sent()
|
|
1601
|
+
];
|
|
1602
|
+
case 15:
|
|
1603
|
+
throw error1;
|
|
1604
|
+
case 16:
|
|
1605
|
+
return [
|
|
1606
|
+
2
|
|
1607
|
+
];
|
|
1608
|
+
}
|
|
1609
|
+
});
|
|
1610
|
+
}).call(_this);
|
|
1611
|
+
};
|
|
1237
1612
|
_state.label = 1;
|
|
1238
1613
|
case 1:
|
|
1239
1614
|
_state.trys.push([
|
|
1240
1615
|
1,
|
|
1241
|
-
|
|
1242
|
-
,
|
|
1243
|
-
14
|
|
1244
|
-
]);
|
|
1245
|
-
_state.label = 2;
|
|
1246
|
-
case 2:
|
|
1247
|
-
_state.trys.push([
|
|
1248
|
-
2,
|
|
1249
|
-
6,
|
|
1616
|
+
4,
|
|
1250
1617
|
,
|
|
1251
|
-
7
|
|
1252
|
-
]);
|
|
1253
|
-
if (!((_ref = (_extra__meta = extra._meta) === null || _extra__meta === void 0 ? void 0 : _extra__meta.accountId) !== null && _ref !== void 0)) return [
|
|
1254
|
-
3,
|
|
1255
|
-
3
|
|
1256
|
-
];
|
|
1257
|
-
_tmp = _ref;
|
|
1258
|
-
return [
|
|
1259
|
-
3,
|
|
1260
1618
|
5
|
|
1261
|
-
];
|
|
1262
|
-
case 3:
|
|
1263
|
-
return [
|
|
1264
|
-
4,
|
|
1265
|
-
(0, _oauth.getActiveAccount)(tokenStore, {
|
|
1266
|
-
service: service
|
|
1267
|
-
})
|
|
1268
|
-
];
|
|
1269
|
-
case 4:
|
|
1270
|
-
_tmp = _state.sent();
|
|
1271
|
-
_state.label = 5;
|
|
1272
|
-
case 5:
|
|
1273
|
-
accountId = _tmp;
|
|
1274
|
-
return [
|
|
1275
|
-
3,
|
|
1276
|
-
7
|
|
1277
|
-
];
|
|
1278
|
-
case 6:
|
|
1279
|
-
error = _state.sent();
|
|
1280
|
-
if (_instanceof(error, Error) && (error.code === 'REQUIRES_AUTHENTICATION' || error.name === 'AccountManagerError')) {
|
|
1281
|
-
accountId = undefined;
|
|
1282
|
-
} else {
|
|
1283
|
-
throw error;
|
|
1284
|
-
}
|
|
1285
|
-
return [
|
|
1286
|
-
3,
|
|
1287
|
-
7
|
|
1288
|
-
];
|
|
1289
|
-
case 7:
|
|
1290
|
-
// Eagerly validate token exists or trigger OAuth flow
|
|
1291
|
-
return [
|
|
1292
|
-
4,
|
|
1293
|
-
this.getAccessToken(accountId)
|
|
1294
|
-
];
|
|
1295
|
-
case 8:
|
|
1296
|
-
_state.sent();
|
|
1297
|
-
if (!(accountId !== null && accountId !== void 0)) return [
|
|
1298
|
-
3,
|
|
1299
|
-
9
|
|
1300
|
-
];
|
|
1301
|
-
_tmp1 = accountId;
|
|
1302
|
-
return [
|
|
1303
|
-
3,
|
|
1304
|
-
11
|
|
1305
|
-
];
|
|
1306
|
-
case 9:
|
|
1619
|
+
]);
|
|
1307
1620
|
return [
|
|
1308
1621
|
4,
|
|
1309
|
-
(
|
|
1310
|
-
service: service
|
|
1311
|
-
})
|
|
1622
|
+
ensureAuthenticatedOrThrow()
|
|
1312
1623
|
];
|
|
1313
|
-
case
|
|
1314
|
-
|
|
1315
|
-
_state.label = 11;
|
|
1316
|
-
case 11:
|
|
1317
|
-
effectiveAccountId = _tmp1;
|
|
1318
|
-
if (!effectiveAccountId) {
|
|
1319
|
-
throw new Error("No account found after OAuth flow for service ".concat(service));
|
|
1320
|
-
}
|
|
1624
|
+
case 2:
|
|
1625
|
+
effectiveAccountId = _state.sent();
|
|
1321
1626
|
auth = this.toAuth(effectiveAccountId);
|
|
1322
1627
|
// Inject authContext and logger into extra
|
|
1323
1628
|
extra.authContext = {
|
|
@@ -1329,27 +1634,25 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
1329
1634
|
4,
|
|
1330
1635
|
originalHandler.apply(void 0, _to_consumable_array(allArgs))
|
|
1331
1636
|
];
|
|
1332
|
-
case
|
|
1637
|
+
case 3:
|
|
1333
1638
|
// Call original handler with all args
|
|
1334
1639
|
return [
|
|
1335
1640
|
2,
|
|
1336
1641
|
_state.sent()
|
|
1337
1642
|
];
|
|
1338
|
-
case
|
|
1339
|
-
|
|
1340
|
-
if (_instanceof(
|
|
1643
|
+
case 4:
|
|
1644
|
+
error = _state.sent();
|
|
1645
|
+
if (_instanceof(error, _typests.AuthRequiredError)) {
|
|
1341
1646
|
logger.info('Authentication required', {
|
|
1342
1647
|
service: service,
|
|
1343
1648
|
tool: operation,
|
|
1344
|
-
descriptor:
|
|
1649
|
+
descriptor: error.descriptor
|
|
1345
1650
|
});
|
|
1346
|
-
// Return auth_required response wrapped in { result } to match tool outputSchema pattern
|
|
1347
|
-
// Tools define outputSchema: z.object({ result: discriminatedUnion(...) }) where auth_required is a branch
|
|
1348
1651
|
authRequiredResponse = {
|
|
1349
1652
|
type: 'auth_required',
|
|
1350
1653
|
provider: service,
|
|
1351
1654
|
message: "Authentication required for ".concat(operation, ". Please authenticate with ").concat(service, "."),
|
|
1352
|
-
url:
|
|
1655
|
+
url: error.descriptor.kind === 'auth_url' ? error.descriptor.url : undefined
|
|
1353
1656
|
};
|
|
1354
1657
|
return [
|
|
1355
1658
|
2,
|
|
@@ -1368,8 +1671,8 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
1368
1671
|
}
|
|
1369
1672
|
];
|
|
1370
1673
|
}
|
|
1371
|
-
throw
|
|
1372
|
-
case
|
|
1674
|
+
throw error;
|
|
1675
|
+
case 5:
|
|
1373
1676
|
return [
|
|
1374
1677
|
2
|
|
1375
1678
|
];
|