@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.
Files changed (51) hide show
  1. package/README.md +8 -0
  2. package/dist/cjs/index.d.cts +2 -1
  3. package/dist/cjs/index.d.ts +2 -1
  4. package/dist/cjs/index.js +4 -0
  5. package/dist/cjs/index.js.map +1 -1
  6. package/dist/cjs/lib/dcr-router.js.map +1 -1
  7. package/dist/cjs/lib/dcr-utils.js.map +1 -1
  8. package/dist/cjs/lib/dcr-verify.js.map +1 -1
  9. package/dist/cjs/lib/fetch-with-timeout.js.map +1 -1
  10. package/dist/cjs/lib/loopback-router.d.cts +8 -0
  11. package/dist/cjs/lib/loopback-router.d.ts +8 -0
  12. package/dist/cjs/lib/loopback-router.js +219 -0
  13. package/dist/cjs/lib/loopback-router.js.map +1 -0
  14. package/dist/cjs/lib/token-verifier.js.map +1 -1
  15. package/dist/cjs/providers/dcr.js.map +1 -1
  16. package/dist/cjs/providers/device-code.js.map +1 -1
  17. package/dist/cjs/providers/loopback-oauth.d.cts +93 -18
  18. package/dist/cjs/providers/loopback-oauth.d.ts +93 -18
  19. package/dist/cjs/providers/loopback-oauth.js +877 -491
  20. package/dist/cjs/providers/loopback-oauth.js.map +1 -1
  21. package/dist/cjs/schemas/index.js +1 -1
  22. package/dist/cjs/schemas/index.js.map +1 -1
  23. package/dist/cjs/setup/config.d.cts +4 -1
  24. package/dist/cjs/setup/config.d.ts +4 -1
  25. package/dist/cjs/setup/config.js +7 -4
  26. package/dist/cjs/setup/config.js.map +1 -1
  27. package/dist/cjs/types.js.map +1 -1
  28. package/dist/esm/index.d.ts +2 -1
  29. package/dist/esm/index.js +1 -0
  30. package/dist/esm/index.js.map +1 -1
  31. package/dist/esm/lib/dcr-router.js.map +1 -1
  32. package/dist/esm/lib/dcr-utils.js.map +1 -1
  33. package/dist/esm/lib/dcr-verify.js.map +1 -1
  34. package/dist/esm/lib/fetch-with-timeout.js.map +1 -1
  35. package/dist/esm/lib/loopback-router.d.ts +8 -0
  36. package/dist/esm/lib/loopback-router.js +32 -0
  37. package/dist/esm/lib/loopback-router.js.map +1 -0
  38. package/dist/esm/lib/token-verifier.js.map +1 -1
  39. package/dist/esm/providers/dcr.js.map +1 -1
  40. package/dist/esm/providers/device-code.js +2 -2
  41. package/dist/esm/providers/device-code.js.map +1 -1
  42. package/dist/esm/providers/loopback-oauth.d.ts +93 -18
  43. package/dist/esm/providers/loopback-oauth.js +470 -289
  44. package/dist/esm/providers/loopback-oauth.js.map +1 -1
  45. package/dist/esm/schemas/index.js +1 -1
  46. package/dist/esm/schemas/index.js.map +1 -1
  47. package/dist/esm/setup/config.d.ts +4 -1
  48. package/dist/esm/setup/config.js +7 -4
  49. package/dist/esm/setup/config.js.map +1 -1
  50. package/dist/esm/types.js.map +1 -1
  51. package/package.json +1 -1
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Loopback OAuth callback router
3
+ *
4
+ * Handles GET /oauth/callback for persistent redirectUri deployments.
5
+ */ "use strict";
6
+ Object.defineProperty(exports, "__esModule", {
7
+ value: true
8
+ });
9
+ Object.defineProperty(exports, "createLoopbackCallbackRouter", {
10
+ enumerable: true,
11
+ get: function() {
12
+ return createLoopbackCallbackRouter;
13
+ }
14
+ });
15
+ var _oauth = require("@mcp-z/oauth");
16
+ var _express = /*#__PURE__*/ _interop_require_default(require("express"));
17
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
18
+ try {
19
+ var info = gen[key](arg);
20
+ var value = info.value;
21
+ } catch (error) {
22
+ reject(error);
23
+ return;
24
+ }
25
+ if (info.done) {
26
+ resolve(value);
27
+ } else {
28
+ Promise.resolve(value).then(_next, _throw);
29
+ }
30
+ }
31
+ function _async_to_generator(fn) {
32
+ return function() {
33
+ var self = this, args = arguments;
34
+ return new Promise(function(resolve, reject) {
35
+ var gen = fn.apply(self, args);
36
+ function _next(value) {
37
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
38
+ }
39
+ function _throw(err) {
40
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
41
+ }
42
+ _next(undefined);
43
+ });
44
+ };
45
+ }
46
+ function _instanceof(left, right) {
47
+ if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
48
+ return !!right[Symbol.hasInstance](left);
49
+ } else {
50
+ return left instanceof right;
51
+ }
52
+ }
53
+ function _interop_require_default(obj) {
54
+ return obj && obj.__esModule ? obj : {
55
+ default: obj
56
+ };
57
+ }
58
+ function _ts_generator(thisArg, body) {
59
+ var f, y, t, _ = {
60
+ label: 0,
61
+ sent: function() {
62
+ if (t[0] & 1) throw t[1];
63
+ return t[1];
64
+ },
65
+ trys: [],
66
+ ops: []
67
+ }, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype), d = Object.defineProperty;
68
+ return d(g, "next", {
69
+ value: verb(0)
70
+ }), d(g, "throw", {
71
+ value: verb(1)
72
+ }), d(g, "return", {
73
+ value: verb(2)
74
+ }), typeof Symbol === "function" && d(g, Symbol.iterator, {
75
+ value: function() {
76
+ return this;
77
+ }
78
+ }), g;
79
+ function verb(n) {
80
+ return function(v) {
81
+ return step([
82
+ n,
83
+ v
84
+ ]);
85
+ };
86
+ }
87
+ function step(op) {
88
+ if (f) throw new TypeError("Generator is already executing.");
89
+ while(g && (g = 0, op[0] && (_ = 0)), _)try {
90
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
91
+ if (y = 0, t) op = [
92
+ op[0] & 2,
93
+ t.value
94
+ ];
95
+ switch(op[0]){
96
+ case 0:
97
+ case 1:
98
+ t = op;
99
+ break;
100
+ case 4:
101
+ _.label++;
102
+ return {
103
+ value: op[1],
104
+ done: false
105
+ };
106
+ case 5:
107
+ _.label++;
108
+ y = op[1];
109
+ op = [
110
+ 0
111
+ ];
112
+ continue;
113
+ case 7:
114
+ op = _.ops.pop();
115
+ _.trys.pop();
116
+ continue;
117
+ default:
118
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
119
+ _ = 0;
120
+ continue;
121
+ }
122
+ if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
123
+ _.label = op[1];
124
+ break;
125
+ }
126
+ if (op[0] === 6 && _.label < t[1]) {
127
+ _.label = t[1];
128
+ t = op;
129
+ break;
130
+ }
131
+ if (t && _.label < t[2]) {
132
+ _.label = t[2];
133
+ _.ops.push(op);
134
+ break;
135
+ }
136
+ if (t[2]) _.ops.pop();
137
+ _.trys.pop();
138
+ continue;
139
+ }
140
+ op = body.call(thisArg, _);
141
+ } catch (e) {
142
+ op = [
143
+ 6,
144
+ e
145
+ ];
146
+ y = 0;
147
+ } finally{
148
+ f = t = 0;
149
+ }
150
+ if (op[0] & 5) throw op[1];
151
+ return {
152
+ value: op[0] ? op[1] : void 0,
153
+ done: true
154
+ };
155
+ }
156
+ }
157
+ function createLoopbackCallbackRouter(provider) {
158
+ var router = _express.default.Router();
159
+ router.get('/oauth/callback', function(req, res) {
160
+ return _async_to_generator(function() {
161
+ var code, state, error, callbackError;
162
+ return _ts_generator(this, function(_state) {
163
+ switch(_state.label){
164
+ case 0:
165
+ code = typeof req.query.code === 'string' ? req.query.code : undefined;
166
+ state = typeof req.query.state === 'string' ? req.query.state : undefined;
167
+ error = typeof req.query.error === 'string' ? req.query.error : undefined;
168
+ if (error) {
169
+ res.status(400).send((0, _oauth.getErrorTemplate)(error));
170
+ return [
171
+ 2
172
+ ];
173
+ }
174
+ if (!code) {
175
+ res.status(400).send((0, _oauth.getErrorTemplate)('No authorization code received'));
176
+ return [
177
+ 2
178
+ ];
179
+ }
180
+ _state.label = 1;
181
+ case 1:
182
+ _state.trys.push([
183
+ 1,
184
+ 3,
185
+ ,
186
+ 4
187
+ ]);
188
+ return [
189
+ 4,
190
+ provider.handleOAuthCallback({
191
+ code: code,
192
+ state: state
193
+ })
194
+ ];
195
+ case 2:
196
+ _state.sent();
197
+ res.status(200).send((0, _oauth.getSuccessTemplate)());
198
+ return [
199
+ 3,
200
+ 4
201
+ ];
202
+ case 3:
203
+ callbackError = _state.sent();
204
+ res.status(500).send((0, _oauth.getErrorTemplate)(_instanceof(callbackError, Error) ? callbackError.message : 'Token exchange failed'));
205
+ return [
206
+ 3,
207
+ 4
208
+ ];
209
+ case 4:
210
+ return [
211
+ 2
212
+ ];
213
+ }
214
+ });
215
+ })();
216
+ });
217
+ return router;
218
+ }
219
+ /* CJS INTEROP */ if (exports.__esModule && exports.default) { try { Object.defineProperty(exports.default, '__esModule', { value: true }); for (var key in exports) { exports.default[key] = exports[key]; } } catch (_) {}; module.exports = exports.default; }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth-microsoft/src/lib/loopback-router.ts"],"sourcesContent":["/**\n * Loopback OAuth callback router\n *\n * Handles GET /oauth/callback for persistent redirectUri deployments.\n */\n\nimport { getErrorTemplate, getSuccessTemplate } from '@mcp-z/oauth';\nimport type { Request, Response } from 'express';\nimport express from 'express';\nimport type { LoopbackOAuthProvider } from '../providers/loopback-oauth.ts';\n\nexport function createLoopbackCallbackRouter(provider: LoopbackOAuthProvider): express.Router {\n const router = express.Router();\n\n router.get('/oauth/callback', async (req: Request, res: Response) => {\n const code = typeof req.query.code === 'string' ? req.query.code : undefined;\n const state = typeof req.query.state === 'string' ? req.query.state : undefined;\n const error = typeof req.query.error === 'string' ? req.query.error : undefined;\n\n if (error) {\n res.status(400).send(getErrorTemplate(error));\n return;\n }\n\n if (!code) {\n res.status(400).send(getErrorTemplate('No authorization code received'));\n return;\n }\n\n try {\n await provider.handleOAuthCallback({ code, state });\n res.status(200).send(getSuccessTemplate());\n } catch (callbackError) {\n res.status(500).send(getErrorTemplate(callbackError instanceof Error ? callbackError.message : 'Token exchange failed'));\n }\n });\n\n return router;\n}\n"],"names":["createLoopbackCallbackRouter","provider","router","express","Router","get","req","res","code","state","error","callbackError","query","undefined","status","send","getErrorTemplate","handleOAuthCallback","getSuccessTemplate","Error","message"],"mappings":"AAAA;;;;CAIC;;;;+BAOeA;;;eAAAA;;;qBALqC;8DAEjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGb,SAASA,6BAA6BC,QAA+B;IAC1E,IAAMC,SAASC,gBAAO,CAACC,MAAM;IAE7BF,OAAOG,GAAG,CAAC,mBAAmB,SAAOC,KAAcC;;gBAC3CC,MACAC,OACAC,OAeGC;;;;wBAjBHH,OAAO,OAAOF,IAAIM,KAAK,CAACJ,IAAI,KAAK,WAAWF,IAAIM,KAAK,CAACJ,IAAI,GAAGK;wBAC7DJ,QAAQ,OAAOH,IAAIM,KAAK,CAACH,KAAK,KAAK,WAAWH,IAAIM,KAAK,CAACH,KAAK,GAAGI;wBAChEH,QAAQ,OAAOJ,IAAIM,KAAK,CAACF,KAAK,KAAK,WAAWJ,IAAIM,KAAK,CAACF,KAAK,GAAGG;wBAEtE,IAAIH,OAAO;4BACTH,IAAIO,MAAM,CAAC,KAAKC,IAAI,CAACC,IAAAA,uBAAgB,EAACN;4BACtC;;;wBACF;wBAEA,IAAI,CAACF,MAAM;4BACTD,IAAIO,MAAM,CAAC,KAAKC,IAAI,CAACC,IAAAA,uBAAgB,EAAC;4BACtC;;;wBACF;;;;;;;;;wBAGE;;4BAAMf,SAASgB,mBAAmB,CAAC;gCAAET,MAAAA;gCAAMC,OAAAA;4BAAM;;;wBAAjD;wBACAF,IAAIO,MAAM,CAAC,KAAKC,IAAI,CAACG,IAAAA,yBAAkB;;;;;;wBAChCP;wBACPJ,IAAIO,MAAM,CAAC,KAAKC,IAAI,CAACC,IAAAA,uBAAgB,EAACL,AAAa,YAAbA,eAAyBQ,SAAQR,cAAcS,OAAO,GAAG;;;;;;;;;;;QAEnG;;IAEA,OAAOlB;AACT"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth-microsoft/src/lib/token-verifier.ts"],"sourcesContent":["/**\n * DCR Token Verifier\n *\n * Validates bearer tokens by calling Authorization Server's verification endpoint.\n * Implements proper AS/RS separation - Resource Server doesn't access token storage.\n */\n\nimport type { ProviderTokens } from '@mcp-z/oauth';\n\n/**\n * Authentication information from token verification\n */\nexport interface AuthInfo {\n /** Bearer access token */\n token: string;\n\n /** Client ID that owns the token */\n clientId: string;\n\n /** Granted scopes */\n scopes: string[];\n\n /** Token expiration timestamp (milliseconds since epoch) */\n expiresAt: number;\n\n /** Microsoft provider tokens (if available) */\n providerTokens?: ProviderTokens;\n}\n\n/**\n * DCR Token Verifier validates access tokens via Authorization Server\n *\n * This implements proper OAuth 2.0 architecture where the Resource Server\n * (MCP server) validates tokens by calling the Authorization Server's\n * verification endpoint rather than accessing token storage directly.\n */\nexport class DcrTokenVerifier {\n private verifyUrl: string;\n\n /**\n * @param verifyUrl - Authorization Server's /oauth/verify endpoint URL\n */\n constructor(verifyUrl: string) {\n this.verifyUrl = verifyUrl;\n }\n\n /**\n * Verify an access token by calling the Authorization Server\n *\n * @param token - Bearer access token to validate\n * @returns AuthInfo with token metadata and provider tokens\n * @throws Error if token is invalid or verification fails\n */\n async verifyAccessToken(token: string): Promise<AuthInfo> {\n try {\n const response = await fetch(this.verifyUrl, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n\n if (!response.ok) {\n let errorMessage = 'Unknown error';\n try {\n const error = await response.json();\n errorMessage = (error as { error_description?: string; error?: string }).error_description ?? (error as { error?: string }).error ?? errorMessage;\n } catch {\n // Failed to parse error JSON, use status text\n errorMessage = response.statusText;\n }\n throw new Error(`Token verification failed: ${errorMessage}`);\n }\n\n const authInfo = (await response.json()) as AuthInfo;\n return authInfo;\n } catch (error) {\n if (error instanceof Error) {\n throw error;\n }\n throw new Error(`Token verification failed: ${String(error)}`);\n }\n }\n}\n"],"names":["DcrTokenVerifier","verifyUrl","verifyAccessToken","token","response","errorMessage","error","authInfo","fetch","method","headers","Authorization","ok","json","error_description","statusText","Error","String"],"mappings":"AAAA;;;;;CAKC;;;;+BA+BYA;;;eAAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAN,IAAA,AAAMA,iCAAN;;aAAMA,iBAMCC,SAAiB;gCANlBD;QAOT,IAAI,CAACC,SAAS,GAAGA;;iBAPRD;IAUX;;;;;;GAMC,GACD,OAAME,iBA6BL,GA7BD,SAAMA,kBAAkBC,KAAa;;gBAE3BC,UAQAC,cAGa,MAAA,0BADTC,iBASJC,UAECD;;;;;;;;;;wBArBU;;4BAAME,MAAM,IAAI,CAACP,SAAS,EAAE;gCAC3CQ,QAAQ;gCACRC,SAAS;oCACPC,eAAe,AAAC,UAAe,OAANR;gCAC3B;4BACF;;;wBALMC,WAAW;6BAOb,CAACA,SAASQ,EAAE,EAAZ;;;;wBACEP,eAAe;;;;;;;;;wBAEH;;4BAAMD,SAASS,IAAI;;;wBAA3BP,UAAQ;wBACdD,gBAAe,QAAA,2BAAA,AAACC,QAAyDQ,iBAAiB,cAA3E,sCAAA,2BAA+E,AAACR,QAA6BA,KAAK,cAAlH,kBAAA,OAAsHD;;;;;;;wBAErI,8CAA8C;wBAC9CA,eAAeD,SAASW,UAAU;;;;;;wBAEpC,MAAM,IAAIC,MAAM,AAAC,8BAA0C,OAAbX;;wBAG9B;;4BAAMD,SAASS,IAAI;;;wBAA/BN,WAAY;wBAClB;;4BAAOA;;;wBACAD;wBACP,IAAIA,AAAK,YAALA,OAAiBU,QAAO;4BAC1B,MAAMV;wBACR;wBACA,MAAM,IAAIU,MAAM,AAAC,8BAA2C,OAAdC,OAAOX;;;;;;;QAEzD;;WA9CWN"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth-microsoft/src/lib/token-verifier.ts"],"sourcesContent":["/**\n * DCR Token Verifier\n *\n * Validates bearer tokens by calling Authorization Server's verification endpoint.\n * Implements proper AS/RS separation - Resource Server doesn't access token storage.\n */\n\nimport type { ProviderTokens } from '@mcp-z/oauth';\n\n/**\n * Authentication information from token verification\n */\nexport interface AuthInfo {\n /** Bearer access token */\n token: string;\n\n /** Client ID that owns the token */\n clientId: string;\n\n /** Granted scopes */\n scopes: string[];\n\n /** Token expiration timestamp (milliseconds since epoch) */\n expiresAt: number;\n\n /** Microsoft provider tokens (if available) */\n providerTokens?: ProviderTokens;\n}\n\n/**\n * DCR Token Verifier validates access tokens via Authorization Server\n *\n * This implements proper OAuth 2.0 architecture where the Resource Server\n * (MCP server) validates tokens by calling the Authorization Server's\n * verification endpoint rather than accessing token storage directly.\n */\nexport class DcrTokenVerifier {\n private verifyUrl: string;\n\n /**\n * @param verifyUrl - Authorization Server's /oauth/verify endpoint URL\n */\n constructor(verifyUrl: string) {\n this.verifyUrl = verifyUrl;\n }\n\n /**\n * Verify an access token by calling the Authorization Server\n *\n * @param token - Bearer access token to validate\n * @returns AuthInfo with token metadata and provider tokens\n * @throws Error if token is invalid or verification fails\n */\n async verifyAccessToken(token: string): Promise<AuthInfo> {\n try {\n const response = await fetch(this.verifyUrl, {\n method: 'GET',\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n\n if (!response.ok) {\n let errorMessage = 'Unknown error';\n try {\n const error = await response.json();\n errorMessage = (error as { error_description?: string; error?: string }).error_description ?? (error as { error?: string }).error ?? errorMessage;\n } catch {\n // Failed to parse error JSON, use status text\n errorMessage = response.statusText;\n }\n throw new Error(`Token verification failed: ${errorMessage}`);\n }\n\n const authInfo = (await response.json()) as AuthInfo;\n return authInfo;\n } catch (error) {\n if (error instanceof Error) {\n throw error;\n }\n throw new Error(`Token verification failed: ${String(error)}`);\n }\n }\n}\n"],"names":["DcrTokenVerifier","verifyUrl","verifyAccessToken","token","response","errorMessage","error","authInfo","fetch","method","headers","Authorization","ok","json","error_description","statusText","Error","String"],"mappings":"AAAA;;;;;CAKC;;;;+BA+BYA;;;eAAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAN,IAAA,AAAMA,iCAAN;;aAAMA,iBAMCC,SAAiB;gCANlBD;QAOT,IAAI,CAACC,SAAS,GAAGA;;iBAPRD;IAUX;;;;;;GAMC,GACD,OAAME,iBA6BL,GA7BD,SAAMA,kBAAkBC,KAAa;;gBAE3BC,UAQAC,cAGa,MAAA,0BADTC,iBASJC,UAECD;;;;;;;;;;wBArBU;;4BAAME,MAAM,IAAI,CAACP,SAAS,EAAE;gCAC3CQ,QAAQ;gCACRC,SAAS;oCACPC,eAAe,AAAC,UAAe,OAANR;gCAC3B;4BACF;;;wBALMC,WAAW;6BAOb,CAACA,SAASQ,EAAE,EAAZ;;;;wBACEP,eAAe;;;;;;;;;wBAEH;;4BAAMD,SAASS,IAAI;;;wBAA3BP,UAAQ;wBACdD,gBAAe,QAAA,2BAAA,AAACC,QAAyDQ,iBAAiB,cAA3E,sCAAA,2BAA+E,AAACR,QAA6BA,KAAK,cAAlH,kBAAA,OAAsHD;;;;;;;wBAErI,8CAA8C;wBAC9CA,eAAeD,SAASW,UAAU;;;;;;wBAEpC,MAAM,IAAIC,MAAM,AAAC,8BAA0C,OAAbX;;wBAG9B;;4BAAMD,SAASS,IAAI;;;wBAA/BN,WAAY;wBAClB;;4BAAOA;;;wBACAD;wBACP,IAAIA,AAAK,YAALA,OAAiBU,QAAO;4BAC1B,MAAMV;wBACR;wBACA,MAAM,IAAIU,MAAM,AAAC,8BAA2C,OAAdC,OAAOX;;;;;;;QAEzD;;WA9CWN"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth-microsoft/src/providers/dcr.ts"],"sourcesContent":["/**\n * DCR Provider - Stateless Dynamic Client Registration Provider\n *\n * Implements stateless provider pattern where provider tokens are received from\n * token verification context rather than managed by the provider itself.\n *\n * Use case: MCP HTTP servers with DCR authentication where client manages tokens\n * and provider only handles Microsoft Graph API calls with provided credentials.\n */\n\nimport type { ProviderTokens } from '@mcp-z/oauth';\nimport { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';\nimport { fetchWithTimeout } from '../lib/fetch-with-timeout.ts';\nimport type { AuthContext, EnrichedExtra, Logger, MicrosoftAuthProvider } from '../types.ts';\n\n/**\n * DCR Provider configuration\n */\nexport interface DcrOAuthProviderConfig {\n /** Microsoft application client ID */\n clientId: string;\n\n /** Microsoft application client secret (optional for public clients) */\n clientSecret?: string;\n\n /** Azure AD tenant ID */\n tenantId: string;\n\n /** OAuth scopes */\n scope: string;\n\n /** Custom token endpoint URL (for testing, defaults to Microsoft OAuth endpoint) */\n tokenUrl?: string;\n\n /** DCR token verification endpoint URL (e.g., http://localhost:3000/oauth/verify) */\n verifyEndpoint: string;\n\n /** Logger for auth operations */\n logger: Logger;\n}\n\n/**\n * Microsoft Graph TokenResponse\n */\ninterface TokenResponse {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n token_type?: string;\n}\n\n/**\n * DCR Provider - Stateless OAuth provider for Dynamic Client Registration\n *\n * Unlike LoopbackOAuthProvider which manages token storage, DcrOAuthProvider is stateless:\n * - Receives provider tokens from verification context (HTTP bearer auth)\n * - Creates auth providers on-demand from tokens\n * - Handles token refresh using Microsoft OAuth\n * - No token storage dependency\n *\n * Pattern:\n * ```typescript\n * const provider = new DcrOAuthProvider(config);\n * const auth = provider.toAuthProvider(providerTokens);\n * const accessToken = await auth.getAccessToken();\n * ```\n */\nexport class DcrOAuthProvider {\n private config: DcrOAuthProviderConfig;\n private emailCache = new Map<string, { email: string; expiresAt: number }>();\n\n constructor(config: DcrOAuthProviderConfig) {\n this.config = config;\n }\n\n /**\n * Create Microsoft Graph auth provider from provider tokens\n *\n * This is the core stateless pattern - provider receives tokens from context\n * (token verification, HTTP request) and creates auth provider on-demand.\n *\n * @param tokens - Provider tokens (Microsoft access/refresh tokens)\n * @returns Microsoft Graph-compatible auth provider\n */\n toAuthProvider(tokens: ProviderTokens): MicrosoftAuthProvider {\n // Capture tokens in closure for auth provider\n let currentTokens = { ...tokens };\n\n return {\n getAccessToken: async (): Promise<string> => {\n // Check if token is still valid\n if (this.isTokenValid(currentTokens)) {\n return currentTokens.accessToken;\n }\n\n // Token expired - try refresh if available\n if (currentTokens.refreshToken) {\n try {\n const refreshedTokens = await this.refreshAccessToken(currentTokens.refreshToken);\n currentTokens = refreshedTokens;\n return currentTokens.accessToken;\n } catch (error) {\n throw new Error(`Token refresh failed: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n // No refresh token - token expired and cannot refresh\n throw new Error('Access token expired and no refresh token available');\n },\n };\n }\n\n /**\n * Check if token is still valid (with 1 minute buffer)\n */\n private isTokenValid(tokens: ProviderTokens): boolean {\n if (!tokens.expiresAt) return true; // No expiry = assume valid\n return Date.now() < tokens.expiresAt - 60000; // 1 minute buffer\n }\n\n /**\n * Refresh Microsoft access token using refresh token\n *\n * @param refreshToken - Microsoft refresh token\n * @returns New provider tokens\n */\n async refreshAccessToken(refreshToken: string): Promise<ProviderTokens> {\n const { clientId, clientSecret, tenantId, scope, tokenUrl: customTokenUrl } = this.config;\n\n const tokenUrl = customTokenUrl ?? `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;\n const params: Record<string, string> = {\n refresh_token: refreshToken,\n client_id: clientId,\n grant_type: 'refresh_token',\n scope,\n };\n\n // Only include client_secret for confidential clients\n if (clientSecret) {\n params.client_secret = clientSecret;\n }\n\n const body = new URLSearchParams(params);\n\n const response = await fetchWithTimeout(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token refresh failed: ${response.status} ${errorText}`);\n }\n\n const tokenResponse = (await response.json()) as TokenResponse;\n\n return {\n accessToken: tokenResponse.access_token,\n refreshToken: refreshToken, // Keep original refresh token\n ...(tokenResponse.expires_in !== undefined && { expiresAt: Date.now() + tokenResponse.expires_in * 1000 }),\n ...(tokenResponse.scope !== undefined && { scope: tokenResponse.scope }),\n };\n }\n\n /**\n * Get user email from Microsoft Graph API (with caching)\n *\n * @param tokens - Provider tokens to use for API call\n * @returns User's email address\n */\n async getUserEmail(tokens: ProviderTokens): Promise<string> {\n const cacheKey = tokens.accessToken;\n const cached = this.emailCache.get(cacheKey);\n\n // Check cache (with same expiry as access token)\n if (cached && Date.now() < cached.expiresAt) {\n return cached.email;\n }\n\n const auth = this.toAuthProvider(tokens);\n const accessToken = await auth.getAccessToken();\n\n const response = await fetchWithTimeout('https://graph.microsoft.com/v1.0/me', {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to get user info: ${response.status} ${await response.text()}`);\n }\n\n const userInfo = (await response.json()) as { mail?: string; userPrincipalName: string };\n const email = userInfo.mail ?? userInfo.userPrincipalName;\n\n // Cache with token expiration (default 1 hour if not specified)\n this.emailCache.set(cacheKey, {\n email,\n expiresAt: tokens.expiresAt ?? Date.now() + 3600000,\n });\n\n return email;\n }\n\n /**\n * Auth middleware for HTTP servers with DCR bearer auth\n * Validates bearer tokens and enriches extra with provider tokens\n *\n * Pattern:\n * ```typescript\n * const provider = new DcrOAuthProvider({ ..., verifyEndpoint: 'http://localhost:3000/oauth/verify' });\n * const middleware = provider.authMiddleware();\n * const tools = toolFactories.map(f => f()).map(middleware.withToolAuth);\n * const resources = resourceFactories.map(f => f()).map(middleware.withResourceAuth);\n * const prompts = promptFactories.map(f => f()).map(middleware.withPromptAuth);\n * ```\n */\n authMiddleware() {\n // Shared wrapper logic - extracts extra parameter from specified position\n // Generic T captures the actual module type; handler is cast from unknown to callable\n const wrapAtPosition = <T extends { name: string; handler: unknown; [key: string]: unknown }>(module: T, extraPosition: number): T => {\n const originalHandler = module.handler as (...args: unknown[]) => Promise<unknown>;\n\n const wrappedHandler = async (...allArgs: unknown[]) => {\n // Extract extra from the correct position\n const extra = allArgs[extraPosition] as EnrichedExtra;\n\n // Extract DCR bearer token from SDK's authInfo (if present) or request headers\n let bearerToken: string | undefined;\n\n // Option 1: Token already verified by SDK's bearerAuth middleware\n if (extra.authInfo && typeof extra.authInfo === 'object') {\n // authInfo contains the validated token - extract it\n // The SDK's bearerAuth middleware already validated it, but we need the raw token for /oauth/verify\n // Check if authInfo has the token directly, otherwise extract from headers\n const authInfo = extra.authInfo as unknown as Record<string, unknown>;\n bearerToken = (typeof authInfo.accessToken === 'string' ? authInfo.accessToken : undefined) ?? (typeof authInfo.token === 'string' ? authInfo.token : undefined);\n }\n\n // Option 2: Extract from Authorization header\n if (!bearerToken && extra.requestInfo?.headers) {\n const authHeader = extra.requestInfo.headers.authorization || extra.requestInfo.headers.Authorization;\n if (authHeader) {\n // Handle both string and string[] types\n const headerValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;\n if (headerValue) {\n const match = /^Bearer\\s+(.+)$/i.exec(headerValue);\n if (match) {\n bearerToken = match[1];\n }\n }\n }\n }\n\n if (!bearerToken) {\n throw new McpError(ErrorCode.InvalidRequest, 'Missing Authorization header. DCR mode requires bearer token.');\n }\n\n // Call /oauth/verify to validate DCR token and get provider tokens\n const verifyResponse = await fetchWithTimeout(this.config.verifyEndpoint, {\n headers: { Authorization: `Bearer ${bearerToken}` },\n });\n\n if (!verifyResponse.ok) {\n throw new McpError(ErrorCode.InvalidRequest, `Token verification failed: ${verifyResponse.status}`);\n }\n\n const verifyData = (await verifyResponse.json()) as {\n providerTokens: ProviderTokens;\n };\n\n // Fetch user email to use as accountId (with caching)\n let accountId: string;\n try {\n accountId = await this.getUserEmail(verifyData.providerTokens);\n } catch (error) {\n throw new McpError(ErrorCode.InternalError, `Failed to get user email for DCR authentication: ${error instanceof Error ? error.message : String(error)}`);\n }\n\n // Create auth provider from provider tokens\n const auth = this.toAuthProvider(verifyData.providerTokens);\n\n // Inject authContext and logger into extra\n (extra as { authContext?: AuthContext }).authContext = {\n auth,\n accountId, // User's email address\n };\n (extra as { logger?: unknown }).logger = this.config.logger;\n\n // Call original handler with all args\n return await originalHandler(...allArgs);\n };\n\n return {\n ...module,\n handler: wrappedHandler,\n } as T;\n };\n\n return {\n // Use structural constraints to avoid contravariance check on handler type.\n // wrapAtPosition is now generic and returns T directly.\n withToolAuth: <T extends { name: string; config: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 1),\n withResourceAuth: <T extends { name: string; template?: unknown; config?: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 2),\n withPromptAuth: <T extends { name: string; config: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 0),\n };\n }\n}\n"],"names":["DcrOAuthProvider","config","emailCache","Map","toAuthProvider","tokens","currentTokens","getAccessToken","refreshedTokens","error","isTokenValid","accessToken","refreshToken","refreshAccessToken","Error","message","String","expiresAt","Date","now","clientId","clientSecret","tenantId","scope","customTokenUrl","tokenUrl","params","body","response","errorText","tokenResponse","refresh_token","client_id","grant_type","client_secret","URLSearchParams","fetchWithTimeout","method","headers","toString","ok","text","status","json","access_token","expires_in","undefined","getUserEmail","userInfo","cacheKey","cached","auth","email","get","Authorization","mail","userPrincipalName","set","authMiddleware","wrapAtPosition","module","extraPosition","originalHandler","handler","wrappedHandler","allArgs","extra","bearerToken","authInfo","authHeader","headerValue","match","verifyResponse","verifyData","accountId","token","requestInfo","authorization","Array","isArray","exec","McpError","ErrorCode","InvalidRequest","verifyEndpoint","providerTokens","InternalError","authContext","logger","withToolAuth","withResourceAuth","withPromptAuth"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth-microsoft/src/providers/dcr.ts"],"sourcesContent":["/**\n * DCR Provider - Stateless Dynamic Client Registration Provider\n *\n * Implements stateless provider pattern where provider tokens are received from\n * token verification context rather than managed by the provider itself.\n *\n * Use case: MCP HTTP servers with DCR authentication where client manages tokens\n * and provider only handles Microsoft Graph API calls with provided credentials.\n */\n\nimport type { ProviderTokens } from '@mcp-z/oauth';\nimport { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';\nimport { fetchWithTimeout } from '../lib/fetch-with-timeout.ts';\nimport type { AuthContext, EnrichedExtra, Logger, MicrosoftAuthProvider } from '../types.ts';\n\n/**\n * DCR Provider configuration\n */\nexport interface DcrOAuthProviderConfig {\n /** Microsoft application client ID */\n clientId: string;\n\n /** Microsoft application client secret (optional for public clients) */\n clientSecret?: string;\n\n /** Azure AD tenant ID */\n tenantId: string;\n\n /** OAuth scopes */\n scope: string;\n\n /** Custom token endpoint URL (for testing, defaults to Microsoft OAuth endpoint) */\n tokenUrl?: string;\n\n /** DCR token verification endpoint URL (e.g., http://localhost:3000/oauth/verify) */\n verifyEndpoint: string;\n\n /** Logger for auth operations */\n logger: Logger;\n}\n\n/**\n * Microsoft Graph TokenResponse\n */\ninterface TokenResponse {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n token_type?: string;\n}\n\n/**\n * DCR Provider - Stateless OAuth provider for Dynamic Client Registration\n *\n * Unlike LoopbackOAuthProvider which manages token storage, DcrOAuthProvider is stateless:\n * - Receives provider tokens from verification context (HTTP bearer auth)\n * - Creates auth providers on-demand from tokens\n * - Handles token refresh using Microsoft OAuth\n * - No token storage dependency\n *\n * Pattern:\n * ```typescript\n * const provider = new DcrOAuthProvider(config);\n * const auth = provider.toAuthProvider(providerTokens);\n * const accessToken = await auth.getAccessToken();\n * ```\n */\nexport class DcrOAuthProvider {\n private config: DcrOAuthProviderConfig;\n private emailCache = new Map<string, { email: string; expiresAt: number }>();\n\n constructor(config: DcrOAuthProviderConfig) {\n this.config = config;\n }\n\n /**\n * Create Microsoft Graph auth provider from provider tokens\n *\n * This is the core stateless pattern - provider receives tokens from context\n * (token verification, HTTP request) and creates auth provider on-demand.\n *\n * @param tokens - Provider tokens (Microsoft access/refresh tokens)\n * @returns Microsoft Graph-compatible auth provider\n */\n toAuthProvider(tokens: ProviderTokens): MicrosoftAuthProvider {\n // Capture tokens in closure for auth provider\n let currentTokens = { ...tokens };\n\n return {\n getAccessToken: async (): Promise<string> => {\n // Check if token is still valid\n if (this.isTokenValid(currentTokens)) {\n return currentTokens.accessToken;\n }\n\n // Token expired - try refresh if available\n if (currentTokens.refreshToken) {\n try {\n const refreshedTokens = await this.refreshAccessToken(currentTokens.refreshToken);\n currentTokens = refreshedTokens;\n return currentTokens.accessToken;\n } catch (error) {\n throw new Error(`Token refresh failed: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n // No refresh token - token expired and cannot refresh\n throw new Error('Access token expired and no refresh token available');\n },\n };\n }\n\n /**\n * Check if token is still valid (with 1 minute buffer)\n */\n private isTokenValid(tokens: ProviderTokens): boolean {\n if (!tokens.expiresAt) return true; // No expiry = assume valid\n return Date.now() < tokens.expiresAt - 60000; // 1 minute buffer\n }\n\n /**\n * Refresh Microsoft access token using refresh token\n *\n * @param refreshToken - Microsoft refresh token\n * @returns New provider tokens\n */\n async refreshAccessToken(refreshToken: string): Promise<ProviderTokens> {\n const { clientId, clientSecret, tenantId, scope, tokenUrl: customTokenUrl } = this.config;\n\n const tokenUrl = customTokenUrl ?? `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;\n const params: Record<string, string> = {\n refresh_token: refreshToken,\n client_id: clientId,\n grant_type: 'refresh_token',\n scope,\n };\n\n // Only include client_secret for confidential clients\n if (clientSecret) {\n params.client_secret = clientSecret;\n }\n\n const body = new URLSearchParams(params);\n\n const response = await fetchWithTimeout(tokenUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token refresh failed: ${response.status} ${errorText}`);\n }\n\n const tokenResponse = (await response.json()) as TokenResponse;\n\n return {\n accessToken: tokenResponse.access_token,\n refreshToken: refreshToken, // Keep original refresh token\n ...(tokenResponse.expires_in !== undefined && { expiresAt: Date.now() + tokenResponse.expires_in * 1000 }),\n ...(tokenResponse.scope !== undefined && { scope: tokenResponse.scope }),\n };\n }\n\n /**\n * Get user email from Microsoft Graph API (with caching)\n *\n * @param tokens - Provider tokens to use for API call\n * @returns User's email address\n */\n async getUserEmail(tokens: ProviderTokens): Promise<string> {\n const cacheKey = tokens.accessToken;\n const cached = this.emailCache.get(cacheKey);\n\n // Check cache (with same expiry as access token)\n if (cached && Date.now() < cached.expiresAt) {\n return cached.email;\n }\n\n const auth = this.toAuthProvider(tokens);\n const accessToken = await auth.getAccessToken();\n\n const response = await fetchWithTimeout('https://graph.microsoft.com/v1.0/me', {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to get user info: ${response.status} ${await response.text()}`);\n }\n\n const userInfo = (await response.json()) as { mail?: string; userPrincipalName: string };\n const email = userInfo.mail ?? userInfo.userPrincipalName;\n\n // Cache with token expiration (default 1 hour if not specified)\n this.emailCache.set(cacheKey, {\n email,\n expiresAt: tokens.expiresAt ?? Date.now() + 3600000,\n });\n\n return email;\n }\n\n /**\n * Auth middleware for HTTP servers with DCR bearer auth\n * Validates bearer tokens and enriches extra with provider tokens\n *\n * Pattern:\n * ```typescript\n * const provider = new DcrOAuthProvider({ ..., verifyEndpoint: 'http://localhost:3000/oauth/verify' });\n * const middleware = provider.authMiddleware();\n * const tools = toolFactories.map(f => f()).map(middleware.withToolAuth);\n * const resources = resourceFactories.map(f => f()).map(middleware.withResourceAuth);\n * const prompts = promptFactories.map(f => f()).map(middleware.withPromptAuth);\n * ```\n */\n authMiddleware() {\n // Shared wrapper logic - extracts extra parameter from specified position\n // Generic T captures the actual module type; handler is cast from unknown to callable\n const wrapAtPosition = <T extends { name: string; handler: unknown; [key: string]: unknown }>(module: T, extraPosition: number): T => {\n const originalHandler = module.handler as (...args: unknown[]) => Promise<unknown>;\n\n const wrappedHandler = async (...allArgs: unknown[]) => {\n // Extract extra from the correct position\n const extra = allArgs[extraPosition] as EnrichedExtra;\n\n // Extract DCR bearer token from SDK's authInfo (if present) or request headers\n let bearerToken: string | undefined;\n\n // Option 1: Token already verified by SDK's bearerAuth middleware\n if (extra.authInfo && typeof extra.authInfo === 'object') {\n // authInfo contains the validated token - extract it\n // The SDK's bearerAuth middleware already validated it, but we need the raw token for /oauth/verify\n // Check if authInfo has the token directly, otherwise extract from headers\n const authInfo = extra.authInfo as unknown as Record<string, unknown>;\n bearerToken = (typeof authInfo.accessToken === 'string' ? authInfo.accessToken : undefined) ?? (typeof authInfo.token === 'string' ? authInfo.token : undefined);\n }\n\n // Option 2: Extract from Authorization header\n if (!bearerToken && extra.requestInfo?.headers) {\n const authHeader = extra.requestInfo.headers.authorization || extra.requestInfo.headers.Authorization;\n if (authHeader) {\n // Handle both string and string[] types\n const headerValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;\n if (headerValue) {\n const match = /^Bearer\\s+(.+)$/i.exec(headerValue);\n if (match) {\n bearerToken = match[1];\n }\n }\n }\n }\n\n if (!bearerToken) {\n throw new McpError(ErrorCode.InvalidRequest, 'Missing Authorization header. DCR mode requires bearer token.');\n }\n\n // Call /oauth/verify to validate DCR token and get provider tokens\n const verifyResponse = await fetchWithTimeout(this.config.verifyEndpoint, {\n headers: { Authorization: `Bearer ${bearerToken}` },\n });\n\n if (!verifyResponse.ok) {\n throw new McpError(ErrorCode.InvalidRequest, `Token verification failed: ${verifyResponse.status}`);\n }\n\n const verifyData = (await verifyResponse.json()) as {\n providerTokens: ProviderTokens;\n };\n\n // Fetch user email to use as accountId (with caching)\n let accountId: string;\n try {\n accountId = await this.getUserEmail(verifyData.providerTokens);\n } catch (error) {\n throw new McpError(ErrorCode.InternalError, `Failed to get user email for DCR authentication: ${error instanceof Error ? error.message : String(error)}`);\n }\n\n // Create auth provider from provider tokens\n const auth = this.toAuthProvider(verifyData.providerTokens);\n\n // Inject authContext and logger into extra\n (extra as { authContext?: AuthContext }).authContext = {\n auth,\n accountId, // User's email address\n };\n (extra as { logger?: unknown }).logger = this.config.logger;\n\n // Call original handler with all args\n return await originalHandler(...allArgs);\n };\n\n return {\n ...module,\n handler: wrappedHandler,\n } as T;\n };\n\n return {\n // Use structural constraints to avoid contravariance check on handler type.\n // wrapAtPosition is now generic and returns T directly.\n withToolAuth: <T extends { name: string; config: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 1),\n withResourceAuth: <T extends { name: string; template?: unknown; config?: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 2),\n withPromptAuth: <T extends { name: string; config: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 0),\n };\n }\n}\n"],"names":["DcrOAuthProvider","config","emailCache","Map","toAuthProvider","tokens","currentTokens","getAccessToken","refreshedTokens","error","isTokenValid","accessToken","refreshToken","refreshAccessToken","Error","message","String","expiresAt","Date","now","clientId","clientSecret","tenantId","scope","customTokenUrl","tokenUrl","params","body","response","errorText","tokenResponse","refresh_token","client_id","grant_type","client_secret","URLSearchParams","fetchWithTimeout","method","headers","toString","ok","text","status","json","access_token","expires_in","undefined","getUserEmail","userInfo","cacheKey","cached","auth","email","get","Authorization","mail","userPrincipalName","set","authMiddleware","wrapAtPosition","module","extraPosition","originalHandler","handler","wrappedHandler","allArgs","extra","bearerToken","authInfo","authHeader","headerValue","match","verifyResponse","verifyData","accountId","token","requestInfo","authorization","Array","isArray","exec","McpError","ErrorCode","InvalidRequest","verifyEndpoint","providerTokens","InternalError","authContext","logger","withToolAuth","withResourceAuth","withPromptAuth"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/oauth/oauth-microsoft/src/providers/device-code.ts"],"sourcesContent":["/**\n * Device Code OAuth Implementation for Microsoft\n *\n * Implements OAuth 2.0 Device Authorization Grant (RFC 8628) for headless/limited-input devices.\n * Designed for scenarios where interactive browser flows are impractical (SSH sessions, CI/CD, etc.).\n *\n * Flow:\n * 1. Request device code from Microsoft endpoint\n * 2. Display user_code and verification_uri to user\n * 3. Poll token endpoint until user completes authentication\n * 4. Cache access token + refresh token to storage\n * 5. Refresh tokens when expired\n *\n * Similar to service accounts in usage pattern: single static identity, minimal account management.\n */\n\nimport { getToken, type OAuth2TokenStorageProvider, setToken } from '@mcp-z/oauth';\nimport type { Keyv } from 'keyv';\nimport open from 'open';\nimport { fetchWithTimeout } from '../lib/fetch-with-timeout.ts';\nimport type { AuthContext, CachedToken, EnrichedExtra, Logger, MicrosoftAuthProvider, MicrosoftService } from '../types.ts';\n\n/**\n * Device Code Flow Response\n * Response from Microsoft device authorization endpoint\n */\ninterface DeviceCodeResponse {\n device_code: string;\n user_code: string;\n verification_uri: string;\n verification_uri_complete?: string;\n expires_in: number;\n interval: number;\n message?: string;\n}\n\n/**\n * Token Response from Microsoft OAuth endpoint\n */\ninterface TokenResponse {\n access_token: string;\n refresh_token?: string;\n expires_in: number;\n scope?: string;\n token_type?: string;\n}\n\n/**\n * Device Code Provider Configuration\n */\nexport interface DeviceCodeConfig {\n /** Microsoft service type (e.g., 'outlook') */\n service: MicrosoftService;\n /** Azure AD client ID */\n clientId: string;\n /** Azure AD tenant ID */\n tenantId: string;\n /** OAuth scopes to request (space-separated string or array) */\n scope: string;\n /** Logger instance */\n logger: Logger;\n /** Token storage for caching */\n tokenStore: Keyv<unknown>;\n /** Headless mode - print device code instead of opening browser */\n headless: boolean;\n}\n\n/**\n * DeviceCodeProvider implements OAuth2TokenStorageProvider using Microsoft Device Code Flow\n *\n * This provider:\n * - Initiates device code flow with Microsoft endpoint\n * - Displays user_code and verification_uri for manual authentication\n * - Polls token endpoint until user completes auth\n * - Stores access tokens + refresh tokens in Keyv storage\n * - Refreshes tokens when expired\n * - Provides single static identity (minimal account management like service accounts)\n *\n * @example\n * ```typescript\n * const provider = new DeviceCodeProvider({\n * service: 'outlook',\n * clientId: 'your-client-id',\n * tenantId: 'common',\n * scope: 'https://graph.microsoft.com/Mail.Read',\n * logger: console,\n * tokenStore: new Keyv(),\n * headless: true,\n * });\n *\n * // Get authenticated Microsoft Graph client\n * const token = await provider.getAccessToken('default');\n * ```\n */\nexport class DeviceCodeProvider implements OAuth2TokenStorageProvider {\n private config: DeviceCodeConfig;\n\n constructor(config: DeviceCodeConfig) {\n this.config = config;\n }\n\n /**\n * Start device code flow and poll for token\n *\n * 1. POST to /devicecode endpoint to get device_code and user_code\n * 2. Display verification instructions to user\n * 3. Poll /token endpoint every interval seconds\n * 4. Handle authorization_pending, slow_down, expired_token errors\n * 5. Return token when user completes authentication\n */\n private async startDeviceCodeFlow(accountId: string): Promise<CachedToken> {\n const { clientId, tenantId, scope, logger, headless } = this.config;\n\n // Step 1: Request device code\n const deviceCodeEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/devicecode`;\n logger.debug('Requesting device code', { endpoint: deviceCodeEndpoint });\n\n const deviceCodeResponse = await fetchWithTimeout(deviceCodeEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n client_id: clientId,\n scope: scope,\n }),\n });\n\n if (!deviceCodeResponse.ok) {\n const errorText = await deviceCodeResponse.text();\n throw new Error(`Device code request failed (HTTP ${deviceCodeResponse.status}): ${errorText}`);\n }\n\n const deviceCodeData = (await deviceCodeResponse.json()) as DeviceCodeResponse;\n const { device_code, user_code, verification_uri, verification_uri_complete, expires_in, interval } = deviceCodeData;\n\n // Step 2: Display instructions to user\n logger.info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');\n logger.info('Device code authentication required');\n logger.info('');\n logger.info(`Please visit: ${verification_uri_complete || verification_uri}`);\n logger.info(`And enter code: ${user_code}`);\n logger.info('');\n logger.info(`Code expires in ${expires_in} seconds`);\n logger.info('Waiting for authentication...');\n logger.info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');\n\n // Optional: Open browser in non-headless mode\n if (!headless) {\n const urlToOpen = verification_uri_complete || verification_uri;\n try {\n await open(urlToOpen);\n logger.debug('Opened browser to verification URL', { url: urlToOpen });\n } catch (error) {\n logger.debug('Failed to open browser', { error: error instanceof Error ? error.message : String(error) });\n }\n }\n\n // Step 3: Poll token endpoint\n return await this.pollForToken(device_code, interval || 5, accountId);\n }\n\n /**\n * Poll Microsoft token endpoint until user completes authentication\n *\n * Handles Microsoft-specific error codes:\n * - authorization_pending: User hasn't completed auth yet, keep polling\n * - slow_down: Increase polling interval by 5 seconds\n * - authorization_declined: User denied authorization\n * - expired_token: Device code expired (typically after 15 minutes)\n */\n private async pollForToken(deviceCode: string, intervalSeconds: number, accountId: string): Promise<CachedToken> {\n const { clientId, tenantId, logger, service, tokenStore } = this.config;\n const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;\n\n let currentInterval = intervalSeconds;\n const startTime = Date.now();\n\n while (true) {\n // Wait for polling interval\n await new Promise((resolve) => setTimeout(resolve, currentInterval * 1000));\n\n logger.debug('Polling for token', { elapsed: Math.floor((Date.now() - startTime) / 1000), interval: currentInterval });\n\n const response = await fetchWithTimeout(tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n client_id: clientId,\n device_code: deviceCode,\n }),\n });\n\n const responseData = (await response.json()) as TokenResponse & { error?: string; error_description?: string };\n\n if (response.ok) {\n // Success! Convert to CachedToken and store\n const tokenData = responseData as TokenResponse;\n const token: CachedToken = {\n accessToken: tokenData.access_token,\n ...(tokenData.refresh_token && { refreshToken: tokenData.refresh_token }),\n expiresAt: Date.now() + (tokenData.expires_in - 60) * 1000, // 60s safety margin\n ...(tokenData.scope && { scope: tokenData.scope }),\n };\n\n // Cache token to storage\n await setToken(tokenStore, { accountId, service }, token);\n logger.info('Device code authentication successful', { accountId });\n\n return token;\n }\n\n // Handle error responses\n const error = responseData.error;\n const errorDescription = responseData.error_description || '';\n\n if (error === 'authorization_pending') {\n // User hasn't completed auth yet - continue polling\n logger.debug('Authorization pending, waiting for user...');\n continue;\n }\n\n if (error === 'slow_down') {\n // Microsoft wants us to slow down polling\n currentInterval += 5;\n logger.debug('Received slow_down, increasing interval', { newInterval: currentInterval });\n continue;\n }\n\n if (error === 'authorization_declined') {\n throw new Error('User declined authorization. Please restart the authentication flow.');\n }\n\n if (error === 'expired_token') {\n throw new Error('Device code expired. Please restart the authentication flow.');\n }\n\n // Unknown error\n throw new Error(`Device code flow failed: ${error} - ${errorDescription}`);\n }\n }\n\n /**\n * Refresh expired access token using refresh token\n *\n * @param refreshToken - Refresh token from previous authentication\n * @returns New cached token with fresh access token\n */\n private async refreshAccessToken(refreshToken: string): Promise<CachedToken> {\n const { clientId, tenantId, scope, logger } = this.config;\n const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;\n\n logger.debug('Refreshing access token');\n\n const response = await fetchWithTimeout(tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: clientId,\n refresh_token: refreshToken,\n scope: scope,\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token refresh failed (HTTP ${response.status}): ${errorText}`);\n }\n\n const tokenData = (await response.json()) as TokenResponse;\n\n return {\n accessToken: tokenData.access_token,\n refreshToken: tokenData.refresh_token || refreshToken, // Some responses may not include new refresh token\n expiresAt: Date.now() + (tokenData.expires_in - 60) * 1000, // 60s safety margin\n scope: tokenData.scope || scope,\n };\n }\n\n /**\n * Check if token is still valid (not expired)\n */\n private isTokenValid(token: CachedToken): boolean {\n return token.expiresAt !== undefined && token.expiresAt > Date.now();\n }\n\n /**\n * Get access token for Microsoft Graph API\n *\n * Flow:\n * 1. Check token storage\n * 2. If valid token exists, return it\n * 3. If expired but has refresh token, try refresh\n * 4. Otherwise, start new device code flow\n *\n * @param accountId - Account identifier. Defaults to 'device-code' (fixed identifier for device code flow).\n * @returns Access token for API requests\n */\n async getAccessToken(accountId?: string): Promise<string> {\n const { logger, service, tokenStore } = this.config;\n const effectiveAccountId = accountId ?? 'device-code';\n\n logger.debug('Getting access token', { service, accountId: effectiveAccountId });\n\n // Check storage for cached token\n const storedToken = await getToken<CachedToken>(tokenStore, { accountId: effectiveAccountId, service });\n\n if (storedToken && this.isTokenValid(storedToken)) {\n logger.debug('Using stored access token', { accountId: effectiveAccountId });\n return storedToken.accessToken;\n }\n\n // If stored token expired but has refresh token, try refresh\n if (storedToken?.refreshToken) {\n try {\n logger.info('Refreshing expired access token', { accountId: effectiveAccountId });\n const refreshedToken = await this.refreshAccessToken(storedToken.refreshToken);\n await setToken(tokenStore, { accountId: effectiveAccountId, service }, refreshedToken);\n return refreshedToken.accessToken;\n } catch (error) {\n logger.info('Token refresh failed', {\n accountId: effectiveAccountId,\n error: error instanceof Error ? error.message : String(error),\n });\n // In headless mode, cannot start interactive device code flow\n if (this.config.headless) {\n throw new Error(`Token refresh failed in headless mode. Cannot start interactive device code flow. Error: ${error instanceof Error ? error.message : String(error)}`);\n }\n // Fall through to new device code flow (interactive mode only)\n }\n }\n\n // No valid token - check if we can start device code flow\n if (this.config.headless) {\n throw new Error('No valid token available in headless mode. Device code flow requires user interaction. ' + 'Please run authentication flow interactively first or provide valid tokens.');\n }\n\n // Interactive mode - start device code flow\n logger.info('Starting device code flow', { accountId: effectiveAccountId });\n const token = await this.startDeviceCodeFlow(effectiveAccountId);\n return token.accessToken;\n }\n\n /**\n * Get user email from Microsoft Graph /me endpoint (pure query)\n *\n * @param accountId - Account identifier\n * @returns User's email address (userPrincipalName or mail field)\n */\n async getUserEmail(accountId?: string): Promise<string> {\n const { logger } = this.config;\n // Device code is single-account mode\n const token = await this.getAccessToken(accountId);\n\n logger.debug('Fetching user email from Microsoft Graph');\n\n const response = await fetchWithTimeout('https://graph.microsoft.com/v1.0/me', {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to get user email (HTTP ${response.status}): ${errorText}`);\n }\n\n const userData = (await response.json()) as { userPrincipalName?: string; mail?: string };\n const email = userData.userPrincipalName || userData.mail;\n\n if (!email) {\n throw new Error('User email not found in Microsoft Graph response');\n }\n\n return email;\n }\n\n /**\n * Create auth provider for Microsoft Graph SDK integration\n *\n * Device code provider ALWAYS uses fixed accountId='device-code'\n * This is by design - device code is a single static identity pattern\n *\n * @param accountId - Account identifier (must be 'device-code' or undefined, otherwise throws error)\n * @returns Auth provider with getAccessToken method\n */\n toAuthProvider(accountId?: string): { getAccessToken: () => Promise<string> } {\n // Device code ONLY works with 'device-code' account ID\n if (accountId !== undefined && accountId !== 'device-code') {\n throw new Error(`DeviceCodeProvider only supports accountId='device-code', got '${accountId}'. Device code uses a single static identity pattern.`);\n }\n\n // ALWAYS use fixed 'device-code' account ID\n const getToken = () => this.getAccessToken('device-code');\n\n return {\n getAccessToken: getToken,\n };\n }\n\n /**\n * Create Microsoft Graph AuthenticationProvider for SDK usage\n *\n * @param accountId - Account identifier\n * @returns AuthenticationProvider that provides access tokens\n */\n private createAuthProvider(accountId?: string): MicrosoftAuthProvider {\n return {\n getAccessToken: async () => {\n return await this.getAccessToken(accountId);\n },\n };\n }\n\n /**\n * Create middleware wrapper for single-user authentication\n *\n * Middleware wraps tool, resource, and prompt handlers and injects authContext into extra parameter.\n * Handlers receive MicrosoftAuthProvider via extra.authContext.auth for API calls.\n *\n * @returns Object with withToolAuth, withResourceAuth, withPromptAuth methods\n *\n * @example\n * ```typescript\n * // Server registration\n * const middleware = provider.authMiddleware();\n * const tools = toolFactories.map(f => f()).map(middleware.withToolAuth);\n * const resources = resourceFactories.map(f => f()).map(middleware.withResourceAuth);\n * const prompts = promptFactories.map(f => f()).map(middleware.withPromptAuth);\n *\n * // Tool handler receives auth\n * async function handler({ id }: In, extra: EnrichedExtra) {\n * // extra.authContext.auth is MicrosoftAuthProvider (from middleware)\n * const graph = Client.initWithMiddleware({ authProvider: extra.authContext.auth });\n * }\n * ```\n */\n authMiddleware() {\n // Shared wrapper logic - extracts extra parameter from specified position\n // Generic T captures the actual module type; handler is cast from unknown to callable\n const wrapAtPosition = <T extends { name: string; handler: unknown; [key: string]: unknown }>(module: T, extraPosition: number): T => {\n const originalHandler = module.handler as (...args: unknown[]) => Promise<unknown>;\n\n const wrappedHandler = async (...allArgs: unknown[]) => {\n // Extract extra from the correct position (defensive: handle arg-less tool pattern)\n // If called with fewer args than expected, use first arg as both args and extra\n let extra: EnrichedExtra;\n if (allArgs.length <= extraPosition) {\n // Arg-less tool pattern: single argument is both args and extra\n extra = (allArgs[0] || {}) as EnrichedExtra;\n allArgs[0] = extra;\n allArgs[extraPosition] = extra;\n } else {\n extra = (allArgs[extraPosition] || {}) as EnrichedExtra;\n allArgs[extraPosition] = extra;\n }\n\n try {\n // Use fixed accountId for storage isolation (like service-account pattern)\n const accountId = 'device-code';\n\n // Create Microsoft Graph authentication provider\n const auth = this.createAuthProvider(accountId);\n\n // Inject authContext and logger into extra parameter\n (extra as { authContext?: AuthContext }).authContext = {\n auth, // MicrosoftAuthProvider for Graph SDK\n accountId, // Account identifier\n };\n (extra as { logger?: unknown }).logger = this.config.logger;\n\n // Call original handler with all args\n return await originalHandler(...allArgs);\n } catch (error) {\n // Wrap auth errors with helpful context\n throw new Error(`Device code authentication failed: ${error instanceof Error ? error.message : String(error)}`);\n }\n };\n\n return {\n ...module,\n handler: wrappedHandler,\n } as T;\n };\n\n return {\n // Use structural constraints to avoid contravariance check on handler type.\n // wrapAtPosition is now generic and returns T directly.\n withToolAuth: <T extends { name: string; config: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 1),\n withResourceAuth: <T extends { name: string; template?: unknown; config?: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 2),\n withPromptAuth: <T extends { name: string; config: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 0),\n };\n }\n}\n"],"names":["DeviceCodeProvider","config","startDeviceCodeFlow","accountId","clientId","tenantId","scope","logger","headless","deviceCodeEndpoint","deviceCodeResponse","errorText","deviceCodeData","device_code","user_code","verification_uri","verification_uri_complete","expires_in","interval","urlToOpen","error","debug","endpoint","fetchWithTimeout","method","headers","body","URLSearchParams","client_id","ok","text","Error","status","json","info","open","url","message","String","pollForToken","deviceCode","intervalSeconds","service","tokenStore","tokenEndpoint","currentInterval","startTime","response","responseData","tokenData","token","errorDescription","Date","now","Promise","resolve","setTimeout","elapsed","Math","floor","grant_type","accessToken","access_token","refresh_token","refreshToken","expiresAt","setToken","error_description","newInterval","refreshAccessToken","isTokenValid","undefined","getAccessToken","effectiveAccountId","storedToken","refreshedToken","getToken","getUserEmail","userData","email","Authorization","userPrincipalName","mail","toAuthProvider","createAuthProvider","authMiddleware","wrapAtPosition","module","extraPosition","originalHandler","handler","wrappedHandler","allArgs","extra","auth","length","authContext","withToolAuth","withResourceAuth","withPromptAuth"],"mappings":"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"}
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/mcp-z/oauth-microsoft/src/providers/device-code.ts"],"sourcesContent":["/**\n * Device Code OAuth Implementation for Microsoft\n *\n * Implements OAuth 2.0 Device Authorization Grant (RFC 8628) for headless/limited-input devices.\n * Designed for scenarios where interactive browser flows are impractical (SSH sessions, CI/CD, etc.).\n *\n * Flow:\n * 1. Request device code from Microsoft endpoint\n * 2. Display user_code and verification_uri to user\n * 3. Poll token endpoint until user completes authentication\n * 4. Cache access token + refresh token to storage\n * 5. Refresh tokens when expired\n *\n * Similar to service accounts in usage pattern: single static identity, minimal account management.\n */\n\nimport { getToken, type OAuth2TokenStorageProvider, setToken } from '@mcp-z/oauth';\nimport type { Keyv } from 'keyv';\nimport open from 'open';\nimport { fetchWithTimeout } from '../lib/fetch-with-timeout.ts';\nimport type { AuthContext, CachedToken, EnrichedExtra, Logger, MicrosoftAuthProvider, MicrosoftService } from '../types.ts';\n\n/**\n * Device Code Flow Response\n * Response from Microsoft device authorization endpoint\n */\ninterface DeviceCodeResponse {\n device_code: string;\n user_code: string;\n verification_uri: string;\n verification_uri_complete?: string;\n expires_in: number;\n interval: number;\n message?: string;\n}\n\n/**\n * Token Response from Microsoft OAuth endpoint\n */\ninterface TokenResponse {\n access_token: string;\n refresh_token?: string;\n expires_in: number;\n scope?: string;\n token_type?: string;\n}\n\n/**\n * Device Code Provider Configuration\n */\nexport interface DeviceCodeConfig {\n /** Microsoft service type (e.g., 'outlook') */\n service: MicrosoftService;\n /** Azure AD client ID */\n clientId: string;\n /** Azure AD tenant ID */\n tenantId: string;\n /** OAuth scopes to request (space-separated string or array) */\n scope: string;\n /** Logger instance */\n logger: Logger;\n /** Token storage for caching */\n tokenStore: Keyv<unknown>;\n /** Headless mode - print device code instead of opening browser */\n headless: boolean;\n}\n\n/**\n * DeviceCodeProvider implements OAuth2TokenStorageProvider using Microsoft Device Code Flow\n *\n * This provider:\n * - Initiates device code flow with Microsoft endpoint\n * - Displays user_code and verification_uri for manual authentication\n * - Polls token endpoint until user completes auth\n * - Stores access tokens + refresh tokens in Keyv storage\n * - Refreshes tokens when expired\n * - Provides single static identity (minimal account management like service accounts)\n *\n * @example\n * ```typescript\n * const provider = new DeviceCodeProvider({\n * service: 'outlook',\n * clientId: 'your-client-id',\n * tenantId: 'common',\n * scope: 'https://graph.microsoft.com/Mail.Read',\n * logger: console,\n * tokenStore: new Keyv(),\n * headless: true,\n * });\n *\n * // Get authenticated Microsoft Graph client\n * const token = await provider.getAccessToken('default');\n * ```\n */\nexport class DeviceCodeProvider implements OAuth2TokenStorageProvider {\n private config: DeviceCodeConfig;\n\n constructor(config: DeviceCodeConfig) {\n this.config = config;\n }\n\n /**\n * Start device code flow and poll for token\n *\n * 1. POST to /devicecode endpoint to get device_code and user_code\n * 2. Display verification instructions to user\n * 3. Poll /token endpoint every interval seconds\n * 4. Handle authorization_pending, slow_down, expired_token errors\n * 5. Return token when user completes authentication\n */\n private async startDeviceCodeFlow(accountId: string): Promise<CachedToken> {\n const { clientId, tenantId, scope, logger, headless } = this.config;\n\n // Step 1: Request device code\n const deviceCodeEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/devicecode`;\n logger.debug('Requesting device code', { endpoint: deviceCodeEndpoint });\n\n const deviceCodeResponse = await fetchWithTimeout(deviceCodeEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n client_id: clientId,\n scope,\n }),\n });\n\n if (!deviceCodeResponse.ok) {\n const errorText = await deviceCodeResponse.text();\n throw new Error(`Device code request failed (HTTP ${deviceCodeResponse.status}): ${errorText}`);\n }\n\n const deviceCodeData = (await deviceCodeResponse.json()) as DeviceCodeResponse;\n const { device_code, user_code, verification_uri, verification_uri_complete, expires_in, interval } = deviceCodeData;\n\n // Step 2: Display instructions to user\n logger.info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');\n logger.info('Device code authentication required');\n logger.info('');\n logger.info(`Please visit: ${verification_uri_complete || verification_uri}`);\n logger.info(`And enter code: ${user_code}`);\n logger.info('');\n logger.info(`Code expires in ${expires_in} seconds`);\n logger.info('Waiting for authentication...');\n logger.info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');\n\n // Optional: Open browser in non-headless mode\n if (!headless) {\n const urlToOpen = verification_uri_complete || verification_uri;\n try {\n await open(urlToOpen);\n logger.debug('Opened browser to verification URL', { url: urlToOpen });\n } catch (error) {\n logger.debug('Failed to open browser', { error: error instanceof Error ? error.message : String(error) });\n }\n }\n\n // Step 3: Poll token endpoint\n return await this.pollForToken(device_code, interval || 5, accountId);\n }\n\n /**\n * Poll Microsoft token endpoint until user completes authentication\n *\n * Handles Microsoft-specific error codes:\n * - authorization_pending: User hasn't completed auth yet, keep polling\n * - slow_down: Increase polling interval by 5 seconds\n * - authorization_declined: User denied authorization\n * - expired_token: Device code expired (typically after 15 minutes)\n */\n private async pollForToken(deviceCode: string, intervalSeconds: number, accountId: string): Promise<CachedToken> {\n const { clientId, tenantId, logger, service, tokenStore } = this.config;\n const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;\n\n let currentInterval = intervalSeconds;\n const startTime = Date.now();\n\n while (true) {\n // Wait for polling interval\n await new Promise((resolve) => setTimeout(resolve, currentInterval * 1000));\n\n logger.debug('Polling for token', { elapsed: Math.floor((Date.now() - startTime) / 1000), interval: currentInterval });\n\n const response = await fetchWithTimeout(tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n client_id: clientId,\n device_code: deviceCode,\n }),\n });\n\n const responseData = (await response.json()) as TokenResponse & { error?: string; error_description?: string };\n\n if (response.ok) {\n // Success! Convert to CachedToken and store\n const tokenData = responseData as TokenResponse;\n const token: CachedToken = {\n accessToken: tokenData.access_token,\n ...(tokenData.refresh_token && { refreshToken: tokenData.refresh_token }),\n expiresAt: Date.now() + (tokenData.expires_in - 60) * 1000, // 60s safety margin\n ...(tokenData.scope && { scope: tokenData.scope }),\n };\n\n // Cache token to storage\n await setToken(tokenStore, { accountId, service }, token);\n logger.info('Device code authentication successful', { accountId });\n\n return token;\n }\n\n // Handle error responses\n const error = responseData.error;\n const errorDescription = responseData.error_description || '';\n\n if (error === 'authorization_pending') {\n // User hasn't completed auth yet - continue polling\n logger.debug('Authorization pending, waiting for user...');\n continue;\n }\n\n if (error === 'slow_down') {\n // Microsoft wants us to slow down polling\n currentInterval += 5;\n logger.debug('Received slow_down, increasing interval', { newInterval: currentInterval });\n continue;\n }\n\n if (error === 'authorization_declined') {\n throw new Error('User declined authorization. Please restart the authentication flow.');\n }\n\n if (error === 'expired_token') {\n throw new Error('Device code expired. Please restart the authentication flow.');\n }\n\n // Unknown error\n throw new Error(`Device code flow failed: ${error} - ${errorDescription}`);\n }\n }\n\n /**\n * Refresh expired access token using refresh token\n *\n * @param refreshToken - Refresh token from previous authentication\n * @returns New cached token with fresh access token\n */\n private async refreshAccessToken(refreshToken: string): Promise<CachedToken> {\n const { clientId, tenantId, scope, logger } = this.config;\n const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;\n\n logger.debug('Refreshing access token');\n\n const response = await fetchWithTimeout(tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: clientId,\n refresh_token: refreshToken,\n scope,\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token refresh failed (HTTP ${response.status}): ${errorText}`);\n }\n\n const tokenData = (await response.json()) as TokenResponse;\n\n return {\n accessToken: tokenData.access_token,\n refreshToken: tokenData.refresh_token || refreshToken, // Some responses may not include new refresh token\n expiresAt: Date.now() + (tokenData.expires_in - 60) * 1000, // 60s safety margin\n scope: tokenData.scope || scope,\n };\n }\n\n /**\n * Check if token is still valid (not expired)\n */\n private isTokenValid(token: CachedToken): boolean {\n return token.expiresAt !== undefined && token.expiresAt > Date.now();\n }\n\n /**\n * Get access token for Microsoft Graph API\n *\n * Flow:\n * 1. Check token storage\n * 2. If valid token exists, return it\n * 3. If expired but has refresh token, try refresh\n * 4. Otherwise, start new device code flow\n *\n * @param accountId - Account identifier. Defaults to 'device-code' (fixed identifier for device code flow).\n * @returns Access token for API requests\n */\n async getAccessToken(accountId?: string): Promise<string> {\n const { logger, service, tokenStore } = this.config;\n const effectiveAccountId = accountId ?? 'device-code';\n\n logger.debug('Getting access token', { service, accountId: effectiveAccountId });\n\n // Check storage for cached token\n const storedToken = await getToken<CachedToken>(tokenStore, { accountId: effectiveAccountId, service });\n\n if (storedToken && this.isTokenValid(storedToken)) {\n logger.debug('Using stored access token', { accountId: effectiveAccountId });\n return storedToken.accessToken;\n }\n\n // If stored token expired but has refresh token, try refresh\n if (storedToken?.refreshToken) {\n try {\n logger.info('Refreshing expired access token', { accountId: effectiveAccountId });\n const refreshedToken = await this.refreshAccessToken(storedToken.refreshToken);\n await setToken(tokenStore, { accountId: effectiveAccountId, service }, refreshedToken);\n return refreshedToken.accessToken;\n } catch (error) {\n logger.info('Token refresh failed', {\n accountId: effectiveAccountId,\n error: error instanceof Error ? error.message : String(error),\n });\n // In headless mode, cannot start interactive device code flow\n if (this.config.headless) {\n throw new Error(`Token refresh failed in headless mode. Cannot start interactive device code flow. Error: ${error instanceof Error ? error.message : String(error)}`);\n }\n // Fall through to new device code flow (interactive mode only)\n }\n }\n\n // No valid token - check if we can start device code flow\n if (this.config.headless) {\n throw new Error('No valid token available in headless mode. Device code flow requires user interaction. ' + 'Please run authentication flow interactively first or provide valid tokens.');\n }\n\n // Interactive mode - start device code flow\n logger.info('Starting device code flow', { accountId: effectiveAccountId });\n const token = await this.startDeviceCodeFlow(effectiveAccountId);\n return token.accessToken;\n }\n\n /**\n * Get user email from Microsoft Graph /me endpoint (pure query)\n *\n * @param accountId - Account identifier\n * @returns User's email address (userPrincipalName or mail field)\n */\n async getUserEmail(accountId?: string): Promise<string> {\n const { logger } = this.config;\n // Device code is single-account mode\n const token = await this.getAccessToken(accountId);\n\n logger.debug('Fetching user email from Microsoft Graph');\n\n const response = await fetchWithTimeout('https://graph.microsoft.com/v1.0/me', {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to get user email (HTTP ${response.status}): ${errorText}`);\n }\n\n const userData = (await response.json()) as { userPrincipalName?: string; mail?: string };\n const email = userData.userPrincipalName || userData.mail;\n\n if (!email) {\n throw new Error('User email not found in Microsoft Graph response');\n }\n\n return email;\n }\n\n /**\n * Create auth provider for Microsoft Graph SDK integration\n *\n * Device code provider ALWAYS uses fixed accountId='device-code'\n * This is by design - device code is a single static identity pattern\n *\n * @param accountId - Account identifier (must be 'device-code' or undefined, otherwise throws error)\n * @returns Auth provider with getAccessToken method\n */\n toAuthProvider(accountId?: string): { getAccessToken: () => Promise<string> } {\n // Device code ONLY works with 'device-code' account ID\n if (accountId !== undefined && accountId !== 'device-code') {\n throw new Error(`DeviceCodeProvider only supports accountId='device-code', got '${accountId}'. Device code uses a single static identity pattern.`);\n }\n\n // ALWAYS use fixed 'device-code' account ID\n const getToken = () => this.getAccessToken('device-code');\n\n return {\n getAccessToken: getToken,\n };\n }\n\n /**\n * Create Microsoft Graph AuthenticationProvider for SDK usage\n *\n * @param accountId - Account identifier\n * @returns AuthenticationProvider that provides access tokens\n */\n private createAuthProvider(accountId?: string): MicrosoftAuthProvider {\n return {\n getAccessToken: async () => {\n return await this.getAccessToken(accountId);\n },\n };\n }\n\n /**\n * Create middleware wrapper for single-user authentication\n *\n * Middleware wraps tool, resource, and prompt handlers and injects authContext into extra parameter.\n * Handlers receive MicrosoftAuthProvider via extra.authContext.auth for API calls.\n *\n * @returns Object with withToolAuth, withResourceAuth, withPromptAuth methods\n *\n * @example\n * ```typescript\n * // Server registration\n * const middleware = provider.authMiddleware();\n * const tools = toolFactories.map(f => f()).map(middleware.withToolAuth);\n * const resources = resourceFactories.map(f => f()).map(middleware.withResourceAuth);\n * const prompts = promptFactories.map(f => f()).map(middleware.withPromptAuth);\n *\n * // Tool handler receives auth\n * async function handler({ id }: In, extra: EnrichedExtra) {\n * // extra.authContext.auth is MicrosoftAuthProvider (from middleware)\n * const graph = Client.initWithMiddleware({ authProvider: extra.authContext.auth });\n * }\n * ```\n */\n authMiddleware() {\n // Shared wrapper logic - extracts extra parameter from specified position\n // Generic T captures the actual module type; handler is cast from unknown to callable\n const wrapAtPosition = <T extends { name: string; handler: unknown; [key: string]: unknown }>(module: T, extraPosition: number): T => {\n const originalHandler = module.handler as (...args: unknown[]) => Promise<unknown>;\n\n const wrappedHandler = async (...allArgs: unknown[]) => {\n // Extract extra from the correct position (defensive: handle arg-less tool pattern)\n // If called with fewer args than expected, use first arg as both args and extra\n let extra: EnrichedExtra;\n if (allArgs.length <= extraPosition) {\n // Arg-less tool pattern: single argument is both args and extra\n extra = (allArgs[0] || {}) as EnrichedExtra;\n allArgs[0] = extra;\n allArgs[extraPosition] = extra;\n } else {\n extra = (allArgs[extraPosition] || {}) as EnrichedExtra;\n allArgs[extraPosition] = extra;\n }\n\n try {\n // Use fixed accountId for storage isolation (like service-account pattern)\n const accountId = 'device-code';\n\n // Create Microsoft Graph authentication provider\n const auth = this.createAuthProvider(accountId);\n\n // Inject authContext and logger into extra parameter\n (extra as { authContext?: AuthContext }).authContext = {\n auth, // MicrosoftAuthProvider for Graph SDK\n accountId, // Account identifier\n };\n (extra as { logger?: unknown }).logger = this.config.logger;\n\n // Call original handler with all args\n return await originalHandler(...allArgs);\n } catch (error) {\n // Wrap auth errors with helpful context\n throw new Error(`Device code authentication failed: ${error instanceof Error ? error.message : String(error)}`);\n }\n };\n\n return {\n ...module,\n handler: wrappedHandler,\n } as T;\n };\n\n return {\n // Use structural constraints to avoid contravariance check on handler type.\n // wrapAtPosition is now generic and returns T directly.\n withToolAuth: <T extends { name: string; config: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 1),\n withResourceAuth: <T extends { name: string; template?: unknown; config?: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 2),\n withPromptAuth: <T extends { name: string; config: unknown; handler: unknown }>(module: T) => wrapAtPosition(module, 0),\n };\n }\n}\n"],"names":["DeviceCodeProvider","config","startDeviceCodeFlow","accountId","clientId","tenantId","scope","logger","headless","deviceCodeEndpoint","deviceCodeResponse","errorText","deviceCodeData","device_code","user_code","verification_uri","verification_uri_complete","expires_in","interval","urlToOpen","error","debug","endpoint","fetchWithTimeout","method","headers","body","URLSearchParams","client_id","ok","text","Error","status","json","info","open","url","message","String","pollForToken","deviceCode","intervalSeconds","service","tokenStore","tokenEndpoint","currentInterval","startTime","response","responseData","tokenData","token","errorDescription","Date","now","Promise","resolve","setTimeout","elapsed","Math","floor","grant_type","accessToken","access_token","refresh_token","refreshToken","expiresAt","setToken","error_description","newInterval","refreshAccessToken","isTokenValid","undefined","getAccessToken","effectiveAccountId","storedToken","refreshedToken","getToken","getUserEmail","userData","email","Authorization","userPrincipalName","mail","toAuthProvider","createAuthProvider","authMiddleware","wrapAtPosition","module","extraPosition","originalHandler","handler","wrappedHandler","allArgs","extra","auth","length","authContext","withToolAuth","withResourceAuth","withPromptAuth"],"mappings":"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"}
@@ -13,9 +13,19 @@
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
  */
17
27
  import { type OAuth2TokenStorageProvider } from '@mcp-z/oauth';
18
- import { type LoopbackOAuthConfig } from '../types.js';
28
+ import { type CachedToken, type LoopbackOAuthConfig } from '../types.js';
19
29
  /**
20
30
  * Loopback OAuth Client (RFC 8252 Section 7.3)
21
31
  *
@@ -27,6 +37,7 @@ import { type LoopbackOAuthConfig } from '../types.js';
27
37
  */
28
38
  export declare class LoopbackOAuthProvider implements OAuth2TokenStorageProvider {
29
39
  private config;
40
+ private openedStates;
30
41
  constructor(config: LoopbackOAuthConfig);
31
42
  /**
32
43
  * Get access token from Keyv using compound key
@@ -44,14 +55,6 @@ export declare class LoopbackOAuthProvider implements OAuth2TokenStorageProvider
44
55
  toAuthProvider(accountId?: string): {
45
56
  getAccessToken: () => Promise<string>;
46
57
  };
47
- /**
48
- * Authenticate new account with OAuth flow
49
- * Triggers account selection, stores token, registers account
50
- *
51
- * @returns Email address of newly authenticated account
52
- * @throws Error in headless mode (cannot open browser for OAuth)
53
- */
54
- authenticateNewAccount(): Promise<string>;
55
58
  /**
56
59
  * Get user email from Microsoft Graph API (pure query)
57
60
  * Used to query email for existing authenticated account
@@ -60,14 +63,6 @@ export declare class LoopbackOAuthProvider implements OAuth2TokenStorageProvider
60
63
  * @returns User's email address
61
64
  */
62
65
  getUserEmail(accountId?: string): Promise<string>;
63
- /**
64
- * Check for existing accounts in token storage (incremental OAuth detection)
65
- *
66
- * Uses key-utils helper for forward compatibility with key format changes.
67
- *
68
- * @returns Array of account IDs that have tokens for this service
69
- */
70
- private getExistingAccounts;
71
66
  private isTokenValid;
72
67
  /**
73
68
  * Fetch user email from Microsoft Graph using access token
@@ -77,9 +72,89 @@ export declare class LoopbackOAuthProvider implements OAuth2TokenStorageProvider
77
72
  * @returns User's email address (mail field or userPrincipalName fallback)
78
73
  */
79
74
  private fetchUserEmailFromToken;
80
- private performEphemeralOAuthFlow;
75
+ /**
76
+ * Build Microsoft OAuth authorization URL with the "most parameters" baseline.
77
+ * This is shared by BOTH persistent (redirectUri) and ephemeral (loopback) modes.
78
+ */
79
+ private buildAuthUrl;
80
+ /**
81
+ * Create a cached token + email from an authorization code.
82
+ * This is the shared callback handler for BOTH persistent and ephemeral modes.
83
+ */
84
+ private handleAuthorizationCode;
85
+ /**
86
+ * Store token + account metadata. Shared by BOTH persistent and ephemeral modes.
87
+ */
88
+ private persistAuthResult;
89
+ /**
90
+ * Pending auth (PKCE verifier) key format.
91
+ */
92
+ private pendingKey;
93
+ /**
94
+ * Store PKCE verifier for callback (5 minute TTL).
95
+ * Shared by BOTH persistent and ephemeral modes.
96
+ */
97
+ private createPendingAuth;
98
+ /**
99
+ * Load and validate pending auth state (5 minute TTL).
100
+ * Shared by BOTH persistent and ephemeral modes.
101
+ */
102
+ private readAndValidatePendingAuth;
103
+ /**
104
+ * Mark pending auth as completed (used by middleware polling).
105
+ */
106
+ private markPendingComplete;
107
+ /**
108
+ * Clean up pending auth state.
109
+ */
110
+ private deletePendingAuth;
111
+ /**
112
+ * Wait until pending auth is marked completed (or timeout).
113
+ * Used by middleware after opening auth URL in non-headless mode.
114
+ */
115
+ private waitForOAuthCompletion;
116
+ /**
117
+ * Process an OAuth callback using shared state validation + token exchange + persistence.
118
+ * Used by BOTH:
119
+ * - ephemeral loopback server callback handler
120
+ * - persistent redirectUri callback handler
121
+ *
122
+ * IMPORTANT CHANGE:
123
+ * - We do NOT delete pending state here anymore.
124
+ * - We mark it completed so middleware can poll and then clean it up.
125
+ */
126
+ private processOAuthCallback;
127
+ /**
128
+ * Loopback OAuth server helper (RFC 8252 Section 7.3)
129
+ *
130
+ * Implements ephemeral local server with OS-assigned port (RFC 8252 Section 8.3).
131
+ * Shared callback handling uses:
132
+ * - the same authUrl builder as redirectUri mode
133
+ * - the same pending PKCE verifier storage as redirectUri mode
134
+ * - the same callback processor as redirectUri mode
135
+ */
136
+ private createOAuthCallbackServer;
137
+ /**
138
+ * Starts the ephemeral loopback server and returns an AuthRequiredError(auth_url).
139
+ * Middleware will open+poll and then retry in the same call.
140
+ */
141
+ private startEphemeralOAuthFlow;
81
142
  private exchangeCodeForToken;
82
143
  private refreshAccessToken;
144
+ /**
145
+ * Handle OAuth callback from persistent endpoint.
146
+ * Used by HTTP servers with configured redirectUri.
147
+ *
148
+ * @param params - OAuth callback parameters
149
+ * @returns Email and cached token
150
+ */
151
+ handleOAuthCallback(params: {
152
+ code: string;
153
+ state?: string;
154
+ }): Promise<{
155
+ email: string;
156
+ token: CachedToken;
157
+ }>;
83
158
  /**
84
159
  * Create auth middleware for single-user context (single active account per service)
85
160
  *