@mcp-z/oauth-microsoft 1.0.0 → 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/README.md +8 -0
- package/dist/cjs/index.d.cts +2 -1
- package/dist/cjs/index.d.ts +2 -1
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/lib/dcr-router.js.map +1 -1
- package/dist/cjs/lib/dcr-utils.js.map +1 -1
- package/dist/cjs/lib/dcr-verify.js.map +1 -1
- package/dist/cjs/lib/fetch-with-timeout.js.map +1 -1
- package/dist/cjs/lib/loopback-router.d.cts +8 -0
- package/dist/cjs/lib/loopback-router.d.ts +8 -0
- package/dist/cjs/lib/loopback-router.js +219 -0
- package/dist/cjs/lib/loopback-router.js.map +1 -0
- package/dist/cjs/lib/token-verifier.js.map +1 -1
- package/dist/cjs/providers/dcr.js.map +1 -1
- package/dist/cjs/providers/device-code.js.map +1 -1
- package/dist/cjs/providers/loopback-oauth.d.cts +93 -18
- package/dist/cjs/providers/loopback-oauth.d.ts +93 -18
- package/dist/cjs/providers/loopback-oauth.js +877 -491
- 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.d.cts +4 -1
- package/dist/cjs/setup/config.d.ts +4 -1
- package/dist/cjs/setup/config.js +7 -4
- package/dist/cjs/setup/config.js.map +1 -1
- package/dist/cjs/types.js.map +1 -1
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/dcr-router.js.map +1 -1
- package/dist/esm/lib/dcr-utils.js.map +1 -1
- package/dist/esm/lib/dcr-verify.js.map +1 -1
- package/dist/esm/lib/fetch-with-timeout.js.map +1 -1
- package/dist/esm/lib/loopback-router.d.ts +8 -0
- package/dist/esm/lib/loopback-router.js +32 -0
- package/dist/esm/lib/loopback-router.js.map +1 -0
- package/dist/esm/lib/token-verifier.js.map +1 -1
- package/dist/esm/providers/dcr.js.map +1 -1
- package/dist/esm/providers/device-code.js +2 -2
- package/dist/esm/providers/device-code.js.map +1 -1
- package/dist/esm/providers/loopback-oauth.d.ts +93 -18
- package/dist/esm/providers/loopback-oauth.js +470 -289
- 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.d.ts +4 -1
- package/dist/esm/setup/config.js +7 -4
- package/dist/esm/setup/config.js.map +1 -1
- package/dist/esm/types.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
|
|
@@ -32,6 +42,7 @@ _export(exports, {
|
|
|
32
42
|
}
|
|
33
43
|
});
|
|
34
44
|
var _oauth = require("@mcp-z/oauth");
|
|
45
|
+
var _crypto = require("crypto");
|
|
35
46
|
var _http = /*#__PURE__*/ _interop_require_wildcard(require("http"));
|
|
36
47
|
var _open = /*#__PURE__*/ _interop_require_default(require("open"));
|
|
37
48
|
var _fetchwithtimeoutts = require("../lib/fetch-with-timeout.js");
|
|
@@ -303,10 +314,14 @@ function _ts_generator(thisArg, body) {
|
|
|
303
314
|
};
|
|
304
315
|
}
|
|
305
316
|
}
|
|
317
|
+
var OAUTH_TIMEOUT_MS = 5 * 60 * 1000;
|
|
318
|
+
var OAUTH_POLL_MS = 500;
|
|
306
319
|
var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
307
320
|
"use strict";
|
|
308
321
|
function LoopbackOAuthProvider(config) {
|
|
309
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();
|
|
310
325
|
this.config = config;
|
|
311
326
|
}
|
|
312
327
|
var _proto = LoopbackOAuthProvider.prototype;
|
|
@@ -317,7 +332,7 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
317
332
|
* @returns Access token for API requests
|
|
318
333
|
*/ _proto.getAccessToken = function getAccessToken(accountId) {
|
|
319
334
|
return _async_to_generator(function() {
|
|
320
|
-
var _this_config, logger, service, tokenStore, effectiveAccountId, _tmp, storedToken, refreshedToken, error,
|
|
335
|
+
var _this_config, logger, service, tokenStore, effectiveAccountId, _tmp, storedToken, refreshedToken, error, _this_config1, clientId, tenantId, scope, redirectUri, _generatePKCE, codeVerifier, codeChallenge, stateId, authUrl, descriptor;
|
|
321
336
|
return _ts_generator(this, function(_state) {
|
|
322
337
|
switch(_state.label){
|
|
323
338
|
case 0:
|
|
@@ -414,110 +429,55 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
414
429
|
9
|
|
415
430
|
];
|
|
416
431
|
case 9:
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
if (!headless) return [
|
|
432
|
+
_this_config1 = this.config, clientId = _this_config1.clientId, tenantId = _this_config1.tenantId, scope = _this_config1.scope, redirectUri = _this_config1.redirectUri;
|
|
433
|
+
if (!redirectUri) return [
|
|
420
434
|
3,
|
|
421
435
|
11
|
|
422
436
|
];
|
|
423
|
-
//
|
|
424
|
-
|
|
425
|
-
|
|
437
|
+
// Persistent callback mode (cloud deployment with configured redirect_uri)
|
|
438
|
+
_generatePKCE = (0, _oauth.generatePKCE)(), codeVerifier = _generatePKCE.verifier, codeChallenge = _generatePKCE.challenge;
|
|
439
|
+
stateId = (0, _crypto.randomUUID)();
|
|
440
|
+
// Store PKCE verifier for callback (5 minute TTL)
|
|
426
441
|
return [
|
|
427
442
|
4,
|
|
428
|
-
this.
|
|
443
|
+
this.createPendingAuth({
|
|
444
|
+
state: stateId,
|
|
445
|
+
codeVerifier: codeVerifier
|
|
446
|
+
})
|
|
429
447
|
];
|
|
430
448
|
case 10:
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
hint = 'Use account-add to authenticate interactively';
|
|
447
|
-
}
|
|
448
|
-
baseDescriptor = {
|
|
449
|
+
_state.sent();
|
|
450
|
+
// Build auth URL with configured redirect_uri
|
|
451
|
+
authUrl = this.buildAuthUrl({
|
|
452
|
+
tenantId: tenantId,
|
|
453
|
+
clientId: clientId,
|
|
454
|
+
redirectUri: redirectUri,
|
|
455
|
+
scope: scope,
|
|
456
|
+
codeChallenge: codeChallenge,
|
|
457
|
+
state: stateId
|
|
458
|
+
});
|
|
459
|
+
logger.info('OAuth required - persistent callback mode', {
|
|
460
|
+
service: service,
|
|
461
|
+
redirectUri: redirectUri
|
|
462
|
+
});
|
|
463
|
+
throw new _typests.AuthRequiredError({
|
|
449
464
|
kind: 'auth_url',
|
|
450
|
-
provider:
|
|
451
|
-
url: authUrl
|
|
452
|
-
|
|
453
|
-
};
|
|
454
|
-
descriptor = effectiveAccountId ? _object_spread_props(_object_spread({}, baseDescriptor), {
|
|
455
|
-
accountId: effectiveAccountId
|
|
456
|
-
}) : baseDescriptor;
|
|
457
|
-
throw new _typests.AuthRequiredError(descriptor);
|
|
465
|
+
provider: service,
|
|
466
|
+
url: authUrl
|
|
467
|
+
});
|
|
458
468
|
case 11:
|
|
459
|
-
//
|
|
469
|
+
// Ephemeral callback mode (local development)
|
|
460
470
|
logger.info('Starting ephemeral OAuth flow', {
|
|
461
471
|
service: service,
|
|
462
|
-
headless: headless
|
|
472
|
+
headless: this.config.headless
|
|
463
473
|
});
|
|
464
474
|
return [
|
|
465
475
|
4,
|
|
466
|
-
this.
|
|
476
|
+
this.startEphemeralOAuthFlow()
|
|
467
477
|
];
|
|
468
478
|
case 12:
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
return [
|
|
472
|
-
4,
|
|
473
|
-
(0, _oauth.setToken)(tokenStore, {
|
|
474
|
-
accountId: email,
|
|
475
|
-
service: service
|
|
476
|
-
}, token)
|
|
477
|
-
];
|
|
478
|
-
case 13:
|
|
479
|
-
_state.sent();
|
|
480
|
-
// Register account in account management system
|
|
481
|
-
return [
|
|
482
|
-
4,
|
|
483
|
-
(0, _oauth.addAccount)(tokenStore, {
|
|
484
|
-
service: service,
|
|
485
|
-
accountId: email
|
|
486
|
-
})
|
|
487
|
-
];
|
|
488
|
-
case 14:
|
|
489
|
-
_state.sent();
|
|
490
|
-
// Set as active account so subsequent getAccessToken() calls find it
|
|
491
|
-
return [
|
|
492
|
-
4,
|
|
493
|
-
(0, _oauth.setActiveAccount)(tokenStore, {
|
|
494
|
-
service: service,
|
|
495
|
-
accountId: email
|
|
496
|
-
})
|
|
497
|
-
];
|
|
498
|
-
case 15:
|
|
499
|
-
_state.sent();
|
|
500
|
-
// Store account metadata (email, added timestamp)
|
|
501
|
-
return [
|
|
502
|
-
4,
|
|
503
|
-
(0, _oauth.setAccountInfo)(tokenStore, {
|
|
504
|
-
service: service,
|
|
505
|
-
accountId: email
|
|
506
|
-
}, {
|
|
507
|
-
email: email,
|
|
508
|
-
addedAt: new Date().toISOString()
|
|
509
|
-
})
|
|
510
|
-
];
|
|
511
|
-
case 16:
|
|
512
|
-
_state.sent();
|
|
513
|
-
logger.info('OAuth flow completed', {
|
|
514
|
-
service: service,
|
|
515
|
-
accountId: email
|
|
516
|
-
});
|
|
517
|
-
return [
|
|
518
|
-
2,
|
|
519
|
-
token.accessToken
|
|
520
|
-
];
|
|
479
|
+
descriptor = _state.sent();
|
|
480
|
+
throw new _typests.AuthRequiredError(descriptor);
|
|
521
481
|
}
|
|
522
482
|
});
|
|
523
483
|
}).call(this);
|
|
@@ -538,86 +498,6 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
538
498
|
};
|
|
539
499
|
};
|
|
540
500
|
/**
|
|
541
|
-
* Authenticate new account with OAuth flow
|
|
542
|
-
* Triggers account selection, stores token, registers account
|
|
543
|
-
*
|
|
544
|
-
* @returns Email address of newly authenticated account
|
|
545
|
-
* @throws Error in headless mode (cannot open browser for OAuth)
|
|
546
|
-
*/ _proto.authenticateNewAccount = function authenticateNewAccount() {
|
|
547
|
-
return _async_to_generator(function() {
|
|
548
|
-
var _this_config, logger, headless, service, tokenStore, _ref, token, email;
|
|
549
|
-
return _ts_generator(this, function(_state) {
|
|
550
|
-
switch(_state.label){
|
|
551
|
-
case 0:
|
|
552
|
-
_this_config = this.config, logger = _this_config.logger, headless = _this_config.headless, service = _this_config.service, tokenStore = _this_config.tokenStore;
|
|
553
|
-
if (headless) {
|
|
554
|
-
throw new Error('Cannot authenticate new account in headless mode - interactive OAuth required');
|
|
555
|
-
}
|
|
556
|
-
logger.info('Starting new account authentication', {
|
|
557
|
-
service: service
|
|
558
|
-
});
|
|
559
|
-
return [
|
|
560
|
-
4,
|
|
561
|
-
this.performEphemeralOAuthFlow()
|
|
562
|
-
];
|
|
563
|
-
case 1:
|
|
564
|
-
_ref = _state.sent(), token = _ref.token, email = _ref.email;
|
|
565
|
-
// Store token
|
|
566
|
-
return [
|
|
567
|
-
4,
|
|
568
|
-
(0, _oauth.setToken)(tokenStore, {
|
|
569
|
-
accountId: email,
|
|
570
|
-
service: service
|
|
571
|
-
}, token)
|
|
572
|
-
];
|
|
573
|
-
case 2:
|
|
574
|
-
_state.sent();
|
|
575
|
-
// Register account
|
|
576
|
-
return [
|
|
577
|
-
4,
|
|
578
|
-
(0, _oauth.addAccount)(tokenStore, {
|
|
579
|
-
service: service,
|
|
580
|
-
accountId: email
|
|
581
|
-
})
|
|
582
|
-
];
|
|
583
|
-
case 3:
|
|
584
|
-
_state.sent();
|
|
585
|
-
// Set as active account
|
|
586
|
-
return [
|
|
587
|
-
4,
|
|
588
|
-
(0, _oauth.setActiveAccount)(tokenStore, {
|
|
589
|
-
service: service,
|
|
590
|
-
accountId: email
|
|
591
|
-
})
|
|
592
|
-
];
|
|
593
|
-
case 4:
|
|
594
|
-
_state.sent();
|
|
595
|
-
// Store account metadata
|
|
596
|
-
return [
|
|
597
|
-
4,
|
|
598
|
-
(0, _oauth.setAccountInfo)(tokenStore, {
|
|
599
|
-
service: service,
|
|
600
|
-
accountId: email
|
|
601
|
-
}, {
|
|
602
|
-
email: email,
|
|
603
|
-
addedAt: new Date().toISOString()
|
|
604
|
-
})
|
|
605
|
-
];
|
|
606
|
-
case 5:
|
|
607
|
-
_state.sent();
|
|
608
|
-
logger.info('New account authenticated', {
|
|
609
|
-
service: service,
|
|
610
|
-
email: email
|
|
611
|
-
});
|
|
612
|
-
return [
|
|
613
|
-
2,
|
|
614
|
-
email
|
|
615
|
-
];
|
|
616
|
-
}
|
|
617
|
-
});
|
|
618
|
-
}).call(this);
|
|
619
|
-
};
|
|
620
|
-
/**
|
|
621
501
|
* Get user email from Microsoft Graph API (pure query)
|
|
622
502
|
* Used to query email for existing authenticated account
|
|
623
503
|
*
|
|
@@ -677,24 +557,6 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
677
557
|
});
|
|
678
558
|
}).call(this);
|
|
679
559
|
};
|
|
680
|
-
/**
|
|
681
|
-
* Check for existing accounts in token storage (incremental OAuth detection)
|
|
682
|
-
*
|
|
683
|
-
* Uses key-utils helper for forward compatibility with key format changes.
|
|
684
|
-
*
|
|
685
|
-
* @returns Array of account IDs that have tokens for this service
|
|
686
|
-
*/ _proto.getExistingAccounts = function getExistingAccounts() {
|
|
687
|
-
return _async_to_generator(function() {
|
|
688
|
-
var _this_config, service, tokenStore;
|
|
689
|
-
return _ts_generator(this, function(_state) {
|
|
690
|
-
_this_config = this.config, service = _this_config.service, tokenStore = _this_config.tokenStore;
|
|
691
|
-
return [
|
|
692
|
-
2,
|
|
693
|
-
(0, _oauth.listAccountIds)(tokenStore, service)
|
|
694
|
-
];
|
|
695
|
-
});
|
|
696
|
-
}).call(this);
|
|
697
|
-
};
|
|
698
560
|
_proto.isTokenValid = function isTokenValid(token) {
|
|
699
561
|
if (!token.expiresAt) return true; // No expiry = assume valid
|
|
700
562
|
return Date.now() < token.expiresAt - 60000; // 1 minute buffer
|
|
@@ -752,240 +614,648 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
752
614
|
});
|
|
753
615
|
}).call(this);
|
|
754
616
|
};
|
|
755
|
-
|
|
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) {
|
|
756
644
|
return _async_to_generator(function() {
|
|
757
|
-
var
|
|
645
|
+
var tokenResponse, cachedToken, email;
|
|
758
646
|
return _ts_generator(this, function(_state) {
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
}
|
|
777
|
-
|
|
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.');
|
|
778
787
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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
|
+
];
|
|
782
887
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
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
|
|
790
931
|
});
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
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
|
|
795
961
|
});
|
|
796
|
-
|
|
797
|
-
|
|
962
|
+
return [
|
|
963
|
+
2,
|
|
964
|
+
result
|
|
965
|
+
];
|
|
798
966
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
if (_$error) {
|
|
833
|
-
res.writeHead(400, {
|
|
834
|
-
'Content-Type': 'text/html'
|
|
835
|
-
});
|
|
836
|
-
res.end((0, _oauth.getErrorTemplate)(_$error));
|
|
837
|
-
server === null || server === void 0 ? void 0 : server.close();
|
|
838
|
-
reject(new Error("OAuth error: ".concat(_$error)));
|
|
839
|
-
return [
|
|
840
|
-
2
|
|
841
|
-
];
|
|
842
|
-
}
|
|
843
|
-
if (!code) {
|
|
844
|
-
res.writeHead(400, {
|
|
845
|
-
'Content-Type': 'text/html'
|
|
846
|
-
});
|
|
847
|
-
res.end((0, _oauth.getErrorTemplate)('No authorization code received'));
|
|
848
|
-
server === null || server === void 0 ? void 0 : server.close();
|
|
849
|
-
reject(new Error('No authorization code received'));
|
|
850
|
-
return [
|
|
851
|
-
2
|
|
852
|
-
];
|
|
853
|
-
}
|
|
854
|
-
_state.label = 1;
|
|
855
|
-
case 1:
|
|
856
|
-
_state.trys.push([
|
|
857
|
-
1,
|
|
858
|
-
4,
|
|
859
|
-
,
|
|
860
|
-
5
|
|
861
|
-
]);
|
|
862
|
-
return [
|
|
863
|
-
4,
|
|
864
|
-
this.exchangeCodeForToken(code, codeVerifier, finalRedirectUri)
|
|
865
|
-
];
|
|
866
|
-
case 2:
|
|
867
|
-
tokenResponse = _state.sent();
|
|
868
|
-
// Build cached token
|
|
869
|
-
cachedToken = _object_spread({
|
|
870
|
-
accessToken: tokenResponse.access_token
|
|
871
|
-
}, tokenResponse.refresh_token !== undefined && {
|
|
872
|
-
refreshToken: tokenResponse.refresh_token
|
|
873
|
-
}, tokenResponse.expires_in !== undefined && {
|
|
874
|
-
expiresAt: Date.now() + tokenResponse.expires_in * 1000
|
|
875
|
-
}, tokenResponse.scope !== undefined && {
|
|
876
|
-
scope: tokenResponse.scope
|
|
877
|
-
});
|
|
878
|
-
return [
|
|
879
|
-
4,
|
|
880
|
-
this.fetchUserEmailFromToken(tokenResponse.access_token)
|
|
881
|
-
];
|
|
882
|
-
case 3:
|
|
883
|
-
email = _state.sent();
|
|
884
|
-
res.writeHead(200, {
|
|
885
|
-
'Content-Type': 'text/html'
|
|
886
|
-
});
|
|
887
|
-
res.end((0, _oauth.getSuccessTemplate)());
|
|
888
|
-
server === null || server === void 0 ? void 0 : server.close();
|
|
889
|
-
resolve({
|
|
890
|
-
token: cachedToken,
|
|
891
|
-
email: email
|
|
892
|
-
});
|
|
893
|
-
return [
|
|
894
|
-
3,
|
|
895
|
-
5
|
|
896
|
-
];
|
|
897
|
-
case 4:
|
|
898
|
-
exchangeError = _state.sent();
|
|
899
|
-
logger.error('Token exchange failed', {
|
|
900
|
-
error: _instanceof(exchangeError, Error) ? exchangeError.message : String(exchangeError)
|
|
901
|
-
});
|
|
902
|
-
res.writeHead(500, {
|
|
903
|
-
'Content-Type': 'text/html'
|
|
904
|
-
});
|
|
905
|
-
res.end((0, _oauth.getErrorTemplate)('Token exchange failed'));
|
|
906
|
-
server === null || server === void 0 ? void 0 : server.close();
|
|
907
|
-
reject(exchangeError);
|
|
908
|
-
return [
|
|
909
|
-
3,
|
|
910
|
-
5
|
|
911
|
-
];
|
|
912
|
-
case 5:
|
|
913
|
-
return [
|
|
914
|
-
3,
|
|
915
|
-
7
|
|
916
|
-
];
|
|
917
|
-
case 6:
|
|
918
|
-
res.writeHead(404, {
|
|
919
|
-
'Content-Type': 'text/plain'
|
|
920
|
-
});
|
|
921
|
-
res.end('Not Found');
|
|
922
|
-
_state.label = 7;
|
|
923
|
-
case 7:
|
|
924
|
-
return [
|
|
925
|
-
2
|
|
926
|
-
];
|
|
927
|
-
}
|
|
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'
|
|
928
1000
|
});
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
if (!address || typeof address === 'string') {
|
|
935
|
-
server === null || server === void 0 ? void 0 : server.close();
|
|
936
|
-
reject(new Error('Failed to start ephemeral server'));
|
|
937
|
-
return;
|
|
1001
|
+
res.end((0, _oauth.getErrorTemplate)('Invalid request'));
|
|
1002
|
+
args.onError(new Error('Invalid request: missing URL'));
|
|
1003
|
+
return [
|
|
1004
|
+
2
|
|
1005
|
+
];
|
|
938
1006
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
if (
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
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
|
+
];
|
|
947
1017
|
}
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
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
|
+
];
|
|
1030
|
+
}
|
|
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
|
+
];
|
|
1040
|
+
}
|
|
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'
|
|
961
1071
|
});
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
(
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
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)
|
|
977
1199
|
});
|
|
1200
|
+
server === null || server === void 0 ? void 0 : server.close();
|
|
978
1201
|
}
|
|
979
1202
|
});
|
|
980
|
-
//
|
|
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)
|
|
981
1233
|
setTimeout(function() {
|
|
982
1234
|
if (server) {
|
|
983
1235
|
server.close();
|
|
984
|
-
|
|
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));
|
|
985
1239
|
}
|
|
986
|
-
},
|
|
987
|
-
|
|
988
|
-
|
|
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
|
+
}
|
|
989
1259
|
});
|
|
990
1260
|
}).call(this);
|
|
991
1261
|
};
|
|
@@ -1048,22 +1318,22 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
1048
1318
|
};
|
|
1049
1319
|
_proto.refreshAccessToken = function refreshAccessToken(refreshToken) {
|
|
1050
1320
|
return _async_to_generator(function() {
|
|
1051
|
-
var _this_config, clientId, clientSecret, tenantId,
|
|
1321
|
+
var _this_config, clientId, clientSecret, tenantId, tokenUrl, params, body, response, errorText, tokenResponse;
|
|
1052
1322
|
return _ts_generator(this, function(_state) {
|
|
1053
1323
|
switch(_state.label){
|
|
1054
1324
|
case 0:
|
|
1055
|
-
_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;
|
|
1056
1326
|
tokenUrl = "https://login.microsoftonline.com/".concat(tenantId, "/oauth2/v2.0/token");
|
|
1057
1327
|
params = {
|
|
1058
1328
|
refresh_token: refreshToken,
|
|
1059
1329
|
client_id: clientId,
|
|
1060
|
-
grant_type: 'refresh_token'
|
|
1061
|
-
scope: scope
|
|
1330
|
+
grant_type: 'refresh_token'
|
|
1062
1331
|
};
|
|
1063
1332
|
// Only include client_secret for confidential clients
|
|
1064
1333
|
if (clientSecret) {
|
|
1065
1334
|
params.client_secret = clientSecret;
|
|
1066
1335
|
}
|
|
1336
|
+
// NOTE: We intentionally do NOT include "scope" in refresh requests.
|
|
1067
1337
|
body = new URLSearchParams(params);
|
|
1068
1338
|
return [
|
|
1069
1339
|
4,
|
|
@@ -1111,6 +1381,44 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
1111
1381
|
}).call(this);
|
|
1112
1382
|
};
|
|
1113
1383
|
/**
|
|
1384
|
+
* Handle OAuth callback from persistent endpoint.
|
|
1385
|
+
* Used by HTTP servers with configured redirectUri.
|
|
1386
|
+
*
|
|
1387
|
+
* @param params - OAuth callback parameters
|
|
1388
|
+
* @returns Email and cached token
|
|
1389
|
+
*/ _proto.handleOAuthCallback = function handleOAuthCallback(params) {
|
|
1390
|
+
return _async_to_generator(function() {
|
|
1391
|
+
var code, state, redirectUri;
|
|
1392
|
+
return _ts_generator(this, function(_state) {
|
|
1393
|
+
switch(_state.label){
|
|
1394
|
+
case 0:
|
|
1395
|
+
code = params.code, state = params.state;
|
|
1396
|
+
redirectUri = this.config.redirectUri;
|
|
1397
|
+
if (!state) {
|
|
1398
|
+
throw new Error('Missing state parameter in OAuth callback');
|
|
1399
|
+
}
|
|
1400
|
+
if (!redirectUri) {
|
|
1401
|
+
throw new Error('handleOAuthCallback requires configured redirectUri');
|
|
1402
|
+
}
|
|
1403
|
+
return [
|
|
1404
|
+
4,
|
|
1405
|
+
this.processOAuthCallback({
|
|
1406
|
+
code: code,
|
|
1407
|
+
state: state,
|
|
1408
|
+
redirectUri: redirectUri
|
|
1409
|
+
})
|
|
1410
|
+
];
|
|
1411
|
+
case 1:
|
|
1412
|
+
// Shared callback processor (same code path as ephemeral)
|
|
1413
|
+
return [
|
|
1414
|
+
2,
|
|
1415
|
+
_state.sent()
|
|
1416
|
+
];
|
|
1417
|
+
}
|
|
1418
|
+
});
|
|
1419
|
+
}).call(this);
|
|
1420
|
+
};
|
|
1421
|
+
/**
|
|
1114
1422
|
* Create auth middleware for single-user context (single active account per service)
|
|
1115
1423
|
*
|
|
1116
1424
|
* Single-user mode:
|
|
@@ -1142,10 +1450,11 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
1142
1450
|
allArgs[_key] = arguments[_key];
|
|
1143
1451
|
}
|
|
1144
1452
|
return _async_to_generator(function() {
|
|
1145
|
-
var
|
|
1453
|
+
var _this, extra, ensureAuthenticatedOrThrow, effectiveAccountId, auth, error, authRequiredResponse;
|
|
1146
1454
|
return _ts_generator(this, function(_state) {
|
|
1147
1455
|
switch(_state.label){
|
|
1148
1456
|
case 0:
|
|
1457
|
+
_this = this;
|
|
1149
1458
|
if (allArgs.length <= extraPosition) {
|
|
1150
1459
|
// Arg-less tool pattern: keep args as-is, create separate extra object
|
|
1151
1460
|
extra = allArgs[0] && _type_of(allArgs[0]) === 'object' ? {} : {};
|
|
@@ -1154,90 +1463,167 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
1154
1463
|
extra = allArgs[extraPosition] || {};
|
|
1155
1464
|
allArgs[extraPosition] = extra;
|
|
1156
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
|
+
};
|
|
1157
1613
|
_state.label = 1;
|
|
1158
1614
|
case 1:
|
|
1159
1615
|
_state.trys.push([
|
|
1160
1616
|
1,
|
|
1161
|
-
|
|
1162
|
-
,
|
|
1163
|
-
14
|
|
1164
|
-
]);
|
|
1165
|
-
_state.label = 2;
|
|
1166
|
-
case 2:
|
|
1167
|
-
_state.trys.push([
|
|
1168
|
-
2,
|
|
1169
|
-
6,
|
|
1617
|
+
4,
|
|
1170
1618
|
,
|
|
1171
|
-
7
|
|
1172
|
-
]);
|
|
1173
|
-
if (!((_ref = (_extra__meta = extra._meta) === null || _extra__meta === void 0 ? void 0 : _extra__meta.accountId) !== null && _ref !== void 0)) return [
|
|
1174
|
-
3,
|
|
1175
|
-
3
|
|
1176
|
-
];
|
|
1177
|
-
_tmp = _ref;
|
|
1178
|
-
return [
|
|
1179
|
-
3,
|
|
1180
1619
|
5
|
|
1181
|
-
];
|
|
1182
|
-
case 3:
|
|
1183
|
-
return [
|
|
1184
|
-
4,
|
|
1185
|
-
(0, _oauth.getActiveAccount)(tokenStore, {
|
|
1186
|
-
service: service
|
|
1187
|
-
})
|
|
1188
|
-
];
|
|
1189
|
-
case 4:
|
|
1190
|
-
_tmp = _state.sent();
|
|
1191
|
-
_state.label = 5;
|
|
1192
|
-
case 5:
|
|
1193
|
-
accountId = _tmp;
|
|
1194
|
-
return [
|
|
1195
|
-
3,
|
|
1196
|
-
7
|
|
1197
|
-
];
|
|
1198
|
-
case 6:
|
|
1199
|
-
error = _state.sent();
|
|
1200
|
-
if (_instanceof(error, Error) && (error.code === 'REQUIRES_AUTHENTICATION' || error.name === 'AccountManagerError')) {
|
|
1201
|
-
accountId = undefined;
|
|
1202
|
-
} else {
|
|
1203
|
-
throw error;
|
|
1204
|
-
}
|
|
1205
|
-
return [
|
|
1206
|
-
3,
|
|
1207
|
-
7
|
|
1208
|
-
];
|
|
1209
|
-
case 7:
|
|
1210
|
-
// Eagerly validate token exists or trigger OAuth flow
|
|
1211
|
-
return [
|
|
1212
|
-
4,
|
|
1213
|
-
this.getAccessToken(accountId)
|
|
1214
|
-
];
|
|
1215
|
-
case 8:
|
|
1216
|
-
_state.sent();
|
|
1217
|
-
if (!(accountId !== null && accountId !== void 0)) return [
|
|
1218
|
-
3,
|
|
1219
|
-
9
|
|
1220
|
-
];
|
|
1221
|
-
_tmp1 = accountId;
|
|
1222
|
-
return [
|
|
1223
|
-
3,
|
|
1224
|
-
11
|
|
1225
|
-
];
|
|
1226
|
-
case 9:
|
|
1620
|
+
]);
|
|
1227
1621
|
return [
|
|
1228
1622
|
4,
|
|
1229
|
-
(
|
|
1230
|
-
service: service
|
|
1231
|
-
})
|
|
1623
|
+
ensureAuthenticatedOrThrow()
|
|
1232
1624
|
];
|
|
1233
|
-
case
|
|
1234
|
-
|
|
1235
|
-
_state.label = 11;
|
|
1236
|
-
case 11:
|
|
1237
|
-
effectiveAccountId = _tmp1;
|
|
1238
|
-
if (!effectiveAccountId) {
|
|
1239
|
-
throw new Error("No account found after OAuth flow for service ".concat(service));
|
|
1240
|
-
}
|
|
1625
|
+
case 2:
|
|
1626
|
+
effectiveAccountId = _state.sent();
|
|
1241
1627
|
auth = this.toAuthProvider(effectiveAccountId);
|
|
1242
1628
|
// Inject authContext and logger into extra
|
|
1243
1629
|
extra.authContext = {
|
|
@@ -1249,20 +1635,20 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
1249
1635
|
4,
|
|
1250
1636
|
originalHandler.apply(void 0, _to_consumable_array(allArgs))
|
|
1251
1637
|
];
|
|
1252
|
-
case
|
|
1638
|
+
case 3:
|
|
1253
1639
|
// Call original handler with all args
|
|
1254
1640
|
return [
|
|
1255
1641
|
2,
|
|
1256
1642
|
_state.sent()
|
|
1257
1643
|
];
|
|
1258
|
-
case
|
|
1259
|
-
|
|
1644
|
+
case 4:
|
|
1645
|
+
error = _state.sent();
|
|
1260
1646
|
// Token retrieval/refresh failed - return auth required
|
|
1261
|
-
if (_instanceof(
|
|
1647
|
+
if (_instanceof(error, _typests.AuthRequiredError)) {
|
|
1262
1648
|
logger.info('Authentication required', {
|
|
1263
1649
|
service: service,
|
|
1264
1650
|
tool: operation,
|
|
1265
|
-
descriptor:
|
|
1651
|
+
descriptor: error.descriptor
|
|
1266
1652
|
});
|
|
1267
1653
|
// Return auth_required response wrapped in { result } to match tool outputSchema pattern
|
|
1268
1654
|
// Tools define outputSchema: z.object({ result: discriminatedUnion(...) }) where auth_required is a branch
|
|
@@ -1270,7 +1656,7 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
1270
1656
|
type: 'auth_required',
|
|
1271
1657
|
provider: service,
|
|
1272
1658
|
message: "Authentication required for ".concat(operation, ". Please authenticate with ").concat(service, "."),
|
|
1273
|
-
url:
|
|
1659
|
+
url: error.descriptor.kind === 'auth_url' ? error.descriptor.url : undefined
|
|
1274
1660
|
};
|
|
1275
1661
|
return [
|
|
1276
1662
|
2,
|
|
@@ -1290,8 +1676,8 @@ var LoopbackOAuthProvider = /*#__PURE__*/ function() {
|
|
|
1290
1676
|
];
|
|
1291
1677
|
}
|
|
1292
1678
|
// Other errors - propagate
|
|
1293
|
-
throw
|
|
1294
|
-
case
|
|
1679
|
+
throw error;
|
|
1680
|
+
case 5:
|
|
1295
1681
|
return [
|
|
1296
1682
|
2
|
|
1297
1683
|
];
|