@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.
- package/dist/cjs/providers/loopback-oauth.d.cts +78 -1
- package/dist/cjs/providers/loopback-oauth.d.ts +78 -1
- package/dist/cjs/providers/loopback-oauth.js +830 -478
- package/dist/cjs/providers/loopback-oauth.js.map +1 -1
- package/dist/cjs/schemas/index.js +1 -1
- package/dist/cjs/schemas/index.js.map +1 -1
- package/dist/cjs/setup/config.js +4 -4
- package/dist/cjs/setup/config.js.map +1 -1
- package/dist/esm/providers/loopback-oauth.d.ts +78 -1
- package/dist/esm/providers/loopback-oauth.js +413 -250
- package/dist/esm/providers/loopback-oauth.js.map +1 -1
- package/dist/esm/schemas/index.js +1 -1
- package/dist/esm/schemas/index.js.map +1 -1
- package/dist/esm/setup/config.js +4 -4
- package/dist/esm/setup/config.js.map +1 -1
- package/package.json +1 -1
|
@@ -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,
|
|
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
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
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 =
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
|
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.
|
|
476
|
+
this.startEphemeralOAuthFlow()
|
|
465
477
|
];
|
|
466
478
|
case 12:
|
|
467
|
-
|
|
468
|
-
|
|
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
|
-
|
|
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
|
|
645
|
+
var tokenResponse, cachedToken, email;
|
|
654
646
|
return _ts_generator(this, function(_state) {
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
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
|
-
|
|
697
|
-
|
|
962
|
+
return [
|
|
963
|
+
2,
|
|
964
|
+
result
|
|
965
|
+
];
|
|
698
966
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
if (
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
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
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
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
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
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
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
(
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
},
|
|
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,
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
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
|
|
1124
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1261
|
-
|
|
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
|
|
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
|
|
1293
|
-
|
|
1644
|
+
case 4:
|
|
1645
|
+
error = _state.sent();
|
|
1294
1646
|
// Token retrieval/refresh failed - return auth required
|
|
1295
|
-
if (_instanceof(
|
|
1647
|
+
if (_instanceof(error, _typests.AuthRequiredError)) {
|
|
1296
1648
|
logger.info('Authentication required', {
|
|
1297
1649
|
service: service,
|
|
1298
1650
|
tool: operation,
|
|
1299
|
-
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:
|
|
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
|
|
1328
|
-
case
|
|
1679
|
+
throw error;
|
|
1680
|
+
case 5:
|
|
1329
1681
|
return [
|
|
1330
1682
|
2
|
|
1331
1683
|
];
|