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