@mcp-z/oauth-microsoft 1.0.0

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 (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +98 -0
  3. package/dist/cjs/index.d.cts +16 -0
  4. package/dist/cjs/index.d.ts +16 -0
  5. package/dist/cjs/index.js +112 -0
  6. package/dist/cjs/index.js.map +1 -0
  7. package/dist/cjs/lib/dcr-router.d.cts +44 -0
  8. package/dist/cjs/lib/dcr-router.d.ts +44 -0
  9. package/dist/cjs/lib/dcr-router.js +1227 -0
  10. package/dist/cjs/lib/dcr-router.js.map +1 -0
  11. package/dist/cjs/lib/dcr-utils.d.cts +160 -0
  12. package/dist/cjs/lib/dcr-utils.d.ts +160 -0
  13. package/dist/cjs/lib/dcr-utils.js +860 -0
  14. package/dist/cjs/lib/dcr-utils.js.map +1 -0
  15. package/dist/cjs/lib/dcr-verify.d.cts +53 -0
  16. package/dist/cjs/lib/dcr-verify.d.ts +53 -0
  17. package/dist/cjs/lib/dcr-verify.js +193 -0
  18. package/dist/cjs/lib/dcr-verify.js.map +1 -0
  19. package/dist/cjs/lib/fetch-with-timeout.d.cts +14 -0
  20. package/dist/cjs/lib/fetch-with-timeout.d.ts +14 -0
  21. package/dist/cjs/lib/fetch-with-timeout.js +257 -0
  22. package/dist/cjs/lib/fetch-with-timeout.js.map +1 -0
  23. package/dist/cjs/lib/token-verifier.d.cts +44 -0
  24. package/dist/cjs/lib/token-verifier.d.ts +44 -0
  25. package/dist/cjs/lib/token-verifier.js +253 -0
  26. package/dist/cjs/lib/token-verifier.js.map +1 -0
  27. package/dist/cjs/package.json +1 -0
  28. package/dist/cjs/providers/dcr.d.cts +110 -0
  29. package/dist/cjs/providers/dcr.d.ts +110 -0
  30. package/dist/cjs/providers/dcr.js +600 -0
  31. package/dist/cjs/providers/dcr.js.map +1 -0
  32. package/dist/cjs/providers/device-code.d.cts +179 -0
  33. package/dist/cjs/providers/device-code.d.ts +179 -0
  34. package/dist/cjs/providers/device-code.js +896 -0
  35. package/dist/cjs/providers/device-code.js.map +1 -0
  36. package/dist/cjs/providers/loopback-oauth.d.cts +125 -0
  37. package/dist/cjs/providers/loopback-oauth.d.ts +125 -0
  38. package/dist/cjs/providers/loopback-oauth.js +1325 -0
  39. package/dist/cjs/providers/loopback-oauth.js.map +1 -0
  40. package/dist/cjs/schemas/index.d.cts +20 -0
  41. package/dist/cjs/schemas/index.d.ts +20 -0
  42. package/dist/cjs/schemas/index.js +37 -0
  43. package/dist/cjs/schemas/index.js.map +1 -0
  44. package/dist/cjs/setup/config.d.cts +113 -0
  45. package/dist/cjs/setup/config.d.ts +113 -0
  46. package/dist/cjs/setup/config.js +246 -0
  47. package/dist/cjs/setup/config.js.map +1 -0
  48. package/dist/cjs/types.d.cts +188 -0
  49. package/dist/cjs/types.d.ts +188 -0
  50. package/dist/cjs/types.js +18 -0
  51. package/dist/cjs/types.js.map +1 -0
  52. package/dist/esm/index.d.ts +16 -0
  53. package/dist/esm/index.js +16 -0
  54. package/dist/esm/index.js.map +1 -0
  55. package/dist/esm/lib/dcr-router.d.ts +44 -0
  56. package/dist/esm/lib/dcr-router.js +556 -0
  57. package/dist/esm/lib/dcr-router.js.map +1 -0
  58. package/dist/esm/lib/dcr-utils.d.ts +160 -0
  59. package/dist/esm/lib/dcr-utils.js +270 -0
  60. package/dist/esm/lib/dcr-utils.js.map +1 -0
  61. package/dist/esm/lib/dcr-verify.d.ts +53 -0
  62. package/dist/esm/lib/dcr-verify.js +53 -0
  63. package/dist/esm/lib/dcr-verify.js.map +1 -0
  64. package/dist/esm/lib/fetch-with-timeout.d.ts +14 -0
  65. package/dist/esm/lib/fetch-with-timeout.js +30 -0
  66. package/dist/esm/lib/fetch-with-timeout.js.map +1 -0
  67. package/dist/esm/lib/token-verifier.d.ts +44 -0
  68. package/dist/esm/lib/token-verifier.js +53 -0
  69. package/dist/esm/lib/token-verifier.js.map +1 -0
  70. package/dist/esm/package.json +1 -0
  71. package/dist/esm/providers/dcr.d.ts +110 -0
  72. package/dist/esm/providers/dcr.js +235 -0
  73. package/dist/esm/providers/dcr.js.map +1 -0
  74. package/dist/esm/providers/device-code.d.ts +179 -0
  75. package/dist/esm/providers/device-code.js +417 -0
  76. package/dist/esm/providers/device-code.js.map +1 -0
  77. package/dist/esm/providers/loopback-oauth.d.ts +125 -0
  78. package/dist/esm/providers/loopback-oauth.js +643 -0
  79. package/dist/esm/providers/loopback-oauth.js.map +1 -0
  80. package/dist/esm/schemas/index.d.ts +20 -0
  81. package/dist/esm/schemas/index.js +18 -0
  82. package/dist/esm/schemas/index.js.map +1 -0
  83. package/dist/esm/setup/config.d.ts +113 -0
  84. package/dist/esm/setup/config.js +268 -0
  85. package/dist/esm/setup/config.js.map +1 -0
  86. package/dist/esm/types.d.ts +188 -0
  87. package/dist/esm/types.js +8 -0
  88. package/dist/esm/types.js.map +1 -0
  89. package/package.json +87 -0
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Device Code OAuth Implementation for Microsoft
3
+ *
4
+ * Implements OAuth 2.0 Device Authorization Grant (RFC 8628) for headless/limited-input devices.
5
+ * Designed for scenarios where interactive browser flows are impractical (SSH sessions, CI/CD, etc.).
6
+ *
7
+ * Flow:
8
+ * 1. Request device code from Microsoft endpoint
9
+ * 2. Display user_code and verification_uri to user
10
+ * 3. Poll token endpoint until user completes authentication
11
+ * 4. Cache access token + refresh token to storage
12
+ * 5. Refresh tokens when expired
13
+ *
14
+ * Similar to service accounts in usage pattern: single static identity, minimal account management.
15
+ */
16
+ import { type OAuth2TokenStorageProvider } from '@mcp-z/oauth';
17
+ import type { Keyv } from 'keyv';
18
+ import type { Logger, MicrosoftService } from '../types.js';
19
+ /**
20
+ * Device Code Provider Configuration
21
+ */
22
+ export interface DeviceCodeConfig {
23
+ /** Microsoft service type (e.g., 'outlook') */
24
+ service: MicrosoftService;
25
+ /** Azure AD client ID */
26
+ clientId: string;
27
+ /** Azure AD tenant ID */
28
+ tenantId: string;
29
+ /** OAuth scopes to request (space-separated string or array) */
30
+ scope: string;
31
+ /** Logger instance */
32
+ logger: Logger;
33
+ /** Token storage for caching */
34
+ tokenStore: Keyv<unknown>;
35
+ /** Headless mode - print device code instead of opening browser */
36
+ headless: boolean;
37
+ }
38
+ /**
39
+ * DeviceCodeProvider implements OAuth2TokenStorageProvider using Microsoft Device Code Flow
40
+ *
41
+ * This provider:
42
+ * - Initiates device code flow with Microsoft endpoint
43
+ * - Displays user_code and verification_uri for manual authentication
44
+ * - Polls token endpoint until user completes auth
45
+ * - Stores access tokens + refresh tokens in Keyv storage
46
+ * - Refreshes tokens when expired
47
+ * - Provides single static identity (minimal account management like service accounts)
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * const provider = new DeviceCodeProvider({
52
+ * service: 'outlook',
53
+ * clientId: 'your-client-id',
54
+ * tenantId: 'common',
55
+ * scope: 'https://graph.microsoft.com/Mail.Read',
56
+ * logger: console,
57
+ * tokenStore: new Keyv(),
58
+ * headless: true,
59
+ * });
60
+ *
61
+ * // Get authenticated Microsoft Graph client
62
+ * const token = await provider.getAccessToken('default');
63
+ * ```
64
+ */
65
+ export declare class DeviceCodeProvider implements OAuth2TokenStorageProvider {
66
+ private config;
67
+ constructor(config: DeviceCodeConfig);
68
+ /**
69
+ * Start device code flow and poll for token
70
+ *
71
+ * 1. POST to /devicecode endpoint to get device_code and user_code
72
+ * 2. Display verification instructions to user
73
+ * 3. Poll /token endpoint every interval seconds
74
+ * 4. Handle authorization_pending, slow_down, expired_token errors
75
+ * 5. Return token when user completes authentication
76
+ */
77
+ private startDeviceCodeFlow;
78
+ /**
79
+ * Poll Microsoft token endpoint until user completes authentication
80
+ *
81
+ * Handles Microsoft-specific error codes:
82
+ * - authorization_pending: User hasn't completed auth yet, keep polling
83
+ * - slow_down: Increase polling interval by 5 seconds
84
+ * - authorization_declined: User denied authorization
85
+ * - expired_token: Device code expired (typically after 15 minutes)
86
+ */
87
+ private pollForToken;
88
+ /**
89
+ * Refresh expired access token using refresh token
90
+ *
91
+ * @param refreshToken - Refresh token from previous authentication
92
+ * @returns New cached token with fresh access token
93
+ */
94
+ private refreshAccessToken;
95
+ /**
96
+ * Check if token is still valid (not expired)
97
+ */
98
+ private isTokenValid;
99
+ /**
100
+ * Get access token for Microsoft Graph API
101
+ *
102
+ * Flow:
103
+ * 1. Check token storage
104
+ * 2. If valid token exists, return it
105
+ * 3. If expired but has refresh token, try refresh
106
+ * 4. Otherwise, start new device code flow
107
+ *
108
+ * @param accountId - Account identifier. Defaults to 'device-code' (fixed identifier for device code flow).
109
+ * @returns Access token for API requests
110
+ */
111
+ getAccessToken(accountId?: string): Promise<string>;
112
+ /**
113
+ * Get user email from Microsoft Graph /me endpoint (pure query)
114
+ *
115
+ * @param accountId - Account identifier
116
+ * @returns User's email address (userPrincipalName or mail field)
117
+ */
118
+ getUserEmail(accountId?: string): Promise<string>;
119
+ /**
120
+ * Create auth provider for Microsoft Graph SDK integration
121
+ *
122
+ * Device code provider ALWAYS uses fixed accountId='device-code'
123
+ * This is by design - device code is a single static identity pattern
124
+ *
125
+ * @param accountId - Account identifier (must be 'device-code' or undefined, otherwise throws error)
126
+ * @returns Auth provider with getAccessToken method
127
+ */
128
+ toAuthProvider(accountId?: string): {
129
+ getAccessToken: () => Promise<string>;
130
+ };
131
+ /**
132
+ * Create Microsoft Graph AuthenticationProvider for SDK usage
133
+ *
134
+ * @param accountId - Account identifier
135
+ * @returns AuthenticationProvider that provides access tokens
136
+ */
137
+ private createAuthProvider;
138
+ /**
139
+ * Create middleware wrapper for single-user authentication
140
+ *
141
+ * Middleware wraps tool, resource, and prompt handlers and injects authContext into extra parameter.
142
+ * Handlers receive MicrosoftAuthProvider via extra.authContext.auth for API calls.
143
+ *
144
+ * @returns Object with withToolAuth, withResourceAuth, withPromptAuth methods
145
+ *
146
+ * @example
147
+ * ```typescript
148
+ * // Server registration
149
+ * const middleware = provider.authMiddleware();
150
+ * const tools = toolFactories.map(f => f()).map(middleware.withToolAuth);
151
+ * const resources = resourceFactories.map(f => f()).map(middleware.withResourceAuth);
152
+ * const prompts = promptFactories.map(f => f()).map(middleware.withPromptAuth);
153
+ *
154
+ * // Tool handler receives auth
155
+ * async function handler({ id }: In, extra: EnrichedExtra) {
156
+ * // extra.authContext.auth is MicrosoftAuthProvider (from middleware)
157
+ * const graph = Client.initWithMiddleware({ authProvider: extra.authContext.auth });
158
+ * }
159
+ * ```
160
+ */
161
+ authMiddleware(): {
162
+ withToolAuth: <T extends {
163
+ name: string;
164
+ config: unknown;
165
+ handler: unknown;
166
+ }>(module: T) => T;
167
+ withResourceAuth: <T extends {
168
+ name: string;
169
+ template?: unknown;
170
+ config?: unknown;
171
+ handler: unknown;
172
+ }>(module: T) => T;
173
+ withPromptAuth: <T extends {
174
+ name: string;
175
+ config: unknown;
176
+ handler: unknown;
177
+ }>(module: T) => T;
178
+ };
179
+ }
@@ -0,0 +1,417 @@
1
+ /**
2
+ * Device Code OAuth Implementation for Microsoft
3
+ *
4
+ * Implements OAuth 2.0 Device Authorization Grant (RFC 8628) for headless/limited-input devices.
5
+ * Designed for scenarios where interactive browser flows are impractical (SSH sessions, CI/CD, etc.).
6
+ *
7
+ * Flow:
8
+ * 1. Request device code from Microsoft endpoint
9
+ * 2. Display user_code and verification_uri to user
10
+ * 3. Poll token endpoint until user completes authentication
11
+ * 4. Cache access token + refresh token to storage
12
+ * 5. Refresh tokens when expired
13
+ *
14
+ * Similar to service accounts in usage pattern: single static identity, minimal account management.
15
+ */ import { getToken, setToken } from '@mcp-z/oauth';
16
+ import open from 'open';
17
+ import { fetchWithTimeout } from '../lib/fetch-with-timeout.js';
18
+ /**
19
+ * DeviceCodeProvider implements OAuth2TokenStorageProvider using Microsoft Device Code Flow
20
+ *
21
+ * This provider:
22
+ * - Initiates device code flow with Microsoft endpoint
23
+ * - Displays user_code and verification_uri for manual authentication
24
+ * - Polls token endpoint until user completes auth
25
+ * - Stores access tokens + refresh tokens in Keyv storage
26
+ * - Refreshes tokens when expired
27
+ * - Provides single static identity (minimal account management like service accounts)
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const provider = new DeviceCodeProvider({
32
+ * service: 'outlook',
33
+ * clientId: 'your-client-id',
34
+ * tenantId: 'common',
35
+ * scope: 'https://graph.microsoft.com/Mail.Read',
36
+ * logger: console,
37
+ * tokenStore: new Keyv(),
38
+ * headless: true,
39
+ * });
40
+ *
41
+ * // Get authenticated Microsoft Graph client
42
+ * const token = await provider.getAccessToken('default');
43
+ * ```
44
+ */ export class DeviceCodeProvider {
45
+ /**
46
+ * Start device code flow and poll for token
47
+ *
48
+ * 1. POST to /devicecode endpoint to get device_code and user_code
49
+ * 2. Display verification instructions to user
50
+ * 3. Poll /token endpoint every interval seconds
51
+ * 4. Handle authorization_pending, slow_down, expired_token errors
52
+ * 5. Return token when user completes authentication
53
+ */ async startDeviceCodeFlow(accountId) {
54
+ const { clientId, tenantId, scope, logger, headless } = this.config;
55
+ // Step 1: Request device code
56
+ const deviceCodeEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/devicecode`;
57
+ logger.debug('Requesting device code', {
58
+ endpoint: deviceCodeEndpoint
59
+ });
60
+ const deviceCodeResponse = await fetchWithTimeout(deviceCodeEndpoint, {
61
+ method: 'POST',
62
+ headers: {
63
+ 'Content-Type': 'application/x-www-form-urlencoded'
64
+ },
65
+ body: new URLSearchParams({
66
+ client_id: clientId,
67
+ scope: scope
68
+ })
69
+ });
70
+ if (!deviceCodeResponse.ok) {
71
+ const errorText = await deviceCodeResponse.text();
72
+ throw new Error(`Device code request failed (HTTP ${deviceCodeResponse.status}): ${errorText}`);
73
+ }
74
+ const deviceCodeData = await deviceCodeResponse.json();
75
+ const { device_code, user_code, verification_uri, verification_uri_complete, expires_in, interval } = deviceCodeData;
76
+ // Step 2: Display instructions to user
77
+ logger.info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
78
+ logger.info('Device code authentication required');
79
+ logger.info('');
80
+ logger.info(`Please visit: ${verification_uri_complete || verification_uri}`);
81
+ logger.info(`And enter code: ${user_code}`);
82
+ logger.info('');
83
+ logger.info(`Code expires in ${expires_in} seconds`);
84
+ logger.info('Waiting for authentication...');
85
+ logger.info('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
86
+ // Optional: Open browser in non-headless mode
87
+ if (!headless) {
88
+ const urlToOpen = verification_uri_complete || verification_uri;
89
+ try {
90
+ await open(urlToOpen);
91
+ logger.debug('Opened browser to verification URL', {
92
+ url: urlToOpen
93
+ });
94
+ } catch (error) {
95
+ logger.debug('Failed to open browser', {
96
+ error: error instanceof Error ? error.message : String(error)
97
+ });
98
+ }
99
+ }
100
+ // Step 3: Poll token endpoint
101
+ return await this.pollForToken(device_code, interval || 5, accountId);
102
+ }
103
+ /**
104
+ * Poll Microsoft token endpoint until user completes authentication
105
+ *
106
+ * Handles Microsoft-specific error codes:
107
+ * - authorization_pending: User hasn't completed auth yet, keep polling
108
+ * - slow_down: Increase polling interval by 5 seconds
109
+ * - authorization_declined: User denied authorization
110
+ * - expired_token: Device code expired (typically after 15 minutes)
111
+ */ async pollForToken(deviceCode, intervalSeconds, accountId) {
112
+ const { clientId, tenantId, logger, service, tokenStore } = this.config;
113
+ const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
114
+ let currentInterval = intervalSeconds;
115
+ const startTime = Date.now();
116
+ while(true){
117
+ // Wait for polling interval
118
+ await new Promise((resolve)=>setTimeout(resolve, currentInterval * 1000));
119
+ logger.debug('Polling for token', {
120
+ elapsed: Math.floor((Date.now() - startTime) / 1000),
121
+ interval: currentInterval
122
+ });
123
+ const response = await fetchWithTimeout(tokenEndpoint, {
124
+ method: 'POST',
125
+ headers: {
126
+ 'Content-Type': 'application/x-www-form-urlencoded'
127
+ },
128
+ body: new URLSearchParams({
129
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
130
+ client_id: clientId,
131
+ device_code: deviceCode
132
+ })
133
+ });
134
+ const responseData = await response.json();
135
+ if (response.ok) {
136
+ // Success! Convert to CachedToken and store
137
+ const tokenData = responseData;
138
+ const token = {
139
+ accessToken: tokenData.access_token,
140
+ ...tokenData.refresh_token && {
141
+ refreshToken: tokenData.refresh_token
142
+ },
143
+ expiresAt: Date.now() + (tokenData.expires_in - 60) * 1000,
144
+ ...tokenData.scope && {
145
+ scope: tokenData.scope
146
+ }
147
+ };
148
+ // Cache token to storage
149
+ await setToken(tokenStore, {
150
+ accountId,
151
+ service
152
+ }, token);
153
+ logger.info('Device code authentication successful', {
154
+ accountId
155
+ });
156
+ return token;
157
+ }
158
+ // Handle error responses
159
+ const error = responseData.error;
160
+ const errorDescription = responseData.error_description || '';
161
+ if (error === 'authorization_pending') {
162
+ // User hasn't completed auth yet - continue polling
163
+ logger.debug('Authorization pending, waiting for user...');
164
+ continue;
165
+ }
166
+ if (error === 'slow_down') {
167
+ // Microsoft wants us to slow down polling
168
+ currentInterval += 5;
169
+ logger.debug('Received slow_down, increasing interval', {
170
+ newInterval: currentInterval
171
+ });
172
+ continue;
173
+ }
174
+ if (error === 'authorization_declined') {
175
+ throw new Error('User declined authorization. Please restart the authentication flow.');
176
+ }
177
+ if (error === 'expired_token') {
178
+ throw new Error('Device code expired. Please restart the authentication flow.');
179
+ }
180
+ // Unknown error
181
+ throw new Error(`Device code flow failed: ${error} - ${errorDescription}`);
182
+ }
183
+ }
184
+ /**
185
+ * Refresh expired access token using refresh token
186
+ *
187
+ * @param refreshToken - Refresh token from previous authentication
188
+ * @returns New cached token with fresh access token
189
+ */ async refreshAccessToken(refreshToken) {
190
+ const { clientId, tenantId, scope, logger } = this.config;
191
+ const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
192
+ logger.debug('Refreshing access token');
193
+ const response = await fetchWithTimeout(tokenEndpoint, {
194
+ method: 'POST',
195
+ headers: {
196
+ 'Content-Type': 'application/x-www-form-urlencoded'
197
+ },
198
+ body: new URLSearchParams({
199
+ grant_type: 'refresh_token',
200
+ client_id: clientId,
201
+ refresh_token: refreshToken,
202
+ scope: scope
203
+ })
204
+ });
205
+ if (!response.ok) {
206
+ const errorText = await response.text();
207
+ throw new Error(`Token refresh failed (HTTP ${response.status}): ${errorText}`);
208
+ }
209
+ const tokenData = await response.json();
210
+ return {
211
+ accessToken: tokenData.access_token,
212
+ refreshToken: tokenData.refresh_token || refreshToken,
213
+ expiresAt: Date.now() + (tokenData.expires_in - 60) * 1000,
214
+ scope: tokenData.scope || scope
215
+ };
216
+ }
217
+ /**
218
+ * Check if token is still valid (not expired)
219
+ */ isTokenValid(token) {
220
+ return token.expiresAt !== undefined && token.expiresAt > Date.now();
221
+ }
222
+ /**
223
+ * Get access token for Microsoft Graph API
224
+ *
225
+ * Flow:
226
+ * 1. Check token storage
227
+ * 2. If valid token exists, return it
228
+ * 3. If expired but has refresh token, try refresh
229
+ * 4. Otherwise, start new device code flow
230
+ *
231
+ * @param accountId - Account identifier. Defaults to 'device-code' (fixed identifier for device code flow).
232
+ * @returns Access token for API requests
233
+ */ async getAccessToken(accountId) {
234
+ const { logger, service, tokenStore } = this.config;
235
+ const effectiveAccountId = accountId !== null && accountId !== void 0 ? accountId : 'device-code';
236
+ logger.debug('Getting access token', {
237
+ service,
238
+ accountId: effectiveAccountId
239
+ });
240
+ // Check storage for cached token
241
+ const storedToken = await getToken(tokenStore, {
242
+ accountId: effectiveAccountId,
243
+ service
244
+ });
245
+ if (storedToken && this.isTokenValid(storedToken)) {
246
+ logger.debug('Using stored access token', {
247
+ accountId: effectiveAccountId
248
+ });
249
+ return storedToken.accessToken;
250
+ }
251
+ // If stored token expired but has refresh token, try refresh
252
+ if (storedToken === null || storedToken === void 0 ? void 0 : storedToken.refreshToken) {
253
+ try {
254
+ logger.info('Refreshing expired access token', {
255
+ accountId: effectiveAccountId
256
+ });
257
+ const refreshedToken = await this.refreshAccessToken(storedToken.refreshToken);
258
+ await setToken(tokenStore, {
259
+ accountId: effectiveAccountId,
260
+ service
261
+ }, refreshedToken);
262
+ return refreshedToken.accessToken;
263
+ } catch (error) {
264
+ logger.info('Token refresh failed', {
265
+ accountId: effectiveAccountId,
266
+ error: error instanceof Error ? error.message : String(error)
267
+ });
268
+ // In headless mode, cannot start interactive device code flow
269
+ if (this.config.headless) {
270
+ throw new Error(`Token refresh failed in headless mode. Cannot start interactive device code flow. Error: ${error instanceof Error ? error.message : String(error)}`);
271
+ }
272
+ // Fall through to new device code flow (interactive mode only)
273
+ }
274
+ }
275
+ // No valid token - check if we can start device code flow
276
+ if (this.config.headless) {
277
+ 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.');
278
+ }
279
+ // Interactive mode - start device code flow
280
+ logger.info('Starting device code flow', {
281
+ accountId: effectiveAccountId
282
+ });
283
+ const token = await this.startDeviceCodeFlow(effectiveAccountId);
284
+ return token.accessToken;
285
+ }
286
+ /**
287
+ * Get user email from Microsoft Graph /me endpoint (pure query)
288
+ *
289
+ * @param accountId - Account identifier
290
+ * @returns User's email address (userPrincipalName or mail field)
291
+ */ async getUserEmail(accountId) {
292
+ const { logger } = this.config;
293
+ // Device code is single-account mode
294
+ const token = await this.getAccessToken(accountId);
295
+ logger.debug('Fetching user email from Microsoft Graph');
296
+ const response = await fetchWithTimeout('https://graph.microsoft.com/v1.0/me', {
297
+ headers: {
298
+ Authorization: `Bearer ${token}`
299
+ }
300
+ });
301
+ if (!response.ok) {
302
+ const errorText = await response.text();
303
+ throw new Error(`Failed to get user email (HTTP ${response.status}): ${errorText}`);
304
+ }
305
+ const userData = await response.json();
306
+ const email = userData.userPrincipalName || userData.mail;
307
+ if (!email) {
308
+ throw new Error('User email not found in Microsoft Graph response');
309
+ }
310
+ return email;
311
+ }
312
+ /**
313
+ * Create auth provider for Microsoft Graph SDK integration
314
+ *
315
+ * Device code provider ALWAYS uses fixed accountId='device-code'
316
+ * This is by design - device code is a single static identity pattern
317
+ *
318
+ * @param accountId - Account identifier (must be 'device-code' or undefined, otherwise throws error)
319
+ * @returns Auth provider with getAccessToken method
320
+ */ toAuthProvider(accountId) {
321
+ // Device code ONLY works with 'device-code' account ID
322
+ if (accountId !== undefined && accountId !== 'device-code') {
323
+ throw new Error(`DeviceCodeProvider only supports accountId='device-code', got '${accountId}'. Device code uses a single static identity pattern.`);
324
+ }
325
+ // ALWAYS use fixed 'device-code' account ID
326
+ const getToken = ()=>this.getAccessToken('device-code');
327
+ return {
328
+ getAccessToken: getToken
329
+ };
330
+ }
331
+ /**
332
+ * Create Microsoft Graph AuthenticationProvider for SDK usage
333
+ *
334
+ * @param accountId - Account identifier
335
+ * @returns AuthenticationProvider that provides access tokens
336
+ */ createAuthProvider(accountId) {
337
+ return {
338
+ getAccessToken: async ()=>{
339
+ return await this.getAccessToken(accountId);
340
+ }
341
+ };
342
+ }
343
+ /**
344
+ * Create middleware wrapper for single-user authentication
345
+ *
346
+ * Middleware wraps tool, resource, and prompt handlers and injects authContext into extra parameter.
347
+ * Handlers receive MicrosoftAuthProvider via extra.authContext.auth for API calls.
348
+ *
349
+ * @returns Object with withToolAuth, withResourceAuth, withPromptAuth methods
350
+ *
351
+ * @example
352
+ * ```typescript
353
+ * // Server registration
354
+ * const middleware = provider.authMiddleware();
355
+ * const tools = toolFactories.map(f => f()).map(middleware.withToolAuth);
356
+ * const resources = resourceFactories.map(f => f()).map(middleware.withResourceAuth);
357
+ * const prompts = promptFactories.map(f => f()).map(middleware.withPromptAuth);
358
+ *
359
+ * // Tool handler receives auth
360
+ * async function handler({ id }: In, extra: EnrichedExtra) {
361
+ * // extra.authContext.auth is MicrosoftAuthProvider (from middleware)
362
+ * const graph = Client.initWithMiddleware({ authProvider: extra.authContext.auth });
363
+ * }
364
+ * ```
365
+ */ authMiddleware() {
366
+ // Shared wrapper logic - extracts extra parameter from specified position
367
+ // Generic T captures the actual module type; handler is cast from unknown to callable
368
+ const wrapAtPosition = (module, extraPosition)=>{
369
+ const originalHandler = module.handler;
370
+ const wrappedHandler = async (...allArgs)=>{
371
+ // Extract extra from the correct position (defensive: handle arg-less tool pattern)
372
+ // If called with fewer args than expected, use first arg as both args and extra
373
+ let extra;
374
+ if (allArgs.length <= extraPosition) {
375
+ // Arg-less tool pattern: single argument is both args and extra
376
+ extra = allArgs[0] || {};
377
+ allArgs[0] = extra;
378
+ allArgs[extraPosition] = extra;
379
+ } else {
380
+ extra = allArgs[extraPosition] || {};
381
+ allArgs[extraPosition] = extra;
382
+ }
383
+ try {
384
+ // Use fixed accountId for storage isolation (like service-account pattern)
385
+ const accountId = 'device-code';
386
+ // Create Microsoft Graph authentication provider
387
+ const auth = this.createAuthProvider(accountId);
388
+ // Inject authContext and logger into extra parameter
389
+ extra.authContext = {
390
+ auth,
391
+ accountId
392
+ };
393
+ extra.logger = this.config.logger;
394
+ // Call original handler with all args
395
+ return await originalHandler(...allArgs);
396
+ } catch (error) {
397
+ // Wrap auth errors with helpful context
398
+ throw new Error(`Device code authentication failed: ${error instanceof Error ? error.message : String(error)}`);
399
+ }
400
+ };
401
+ return {
402
+ ...module,
403
+ handler: wrappedHandler
404
+ };
405
+ };
406
+ return {
407
+ // Use structural constraints to avoid contravariance check on handler type.
408
+ // wrapAtPosition is now generic and returns T directly.
409
+ withToolAuth: (module)=>wrapAtPosition(module, 1),
410
+ withResourceAuth: (module)=>wrapAtPosition(module, 2),
411
+ withPromptAuth: (module)=>wrapAtPosition(module, 0)
412
+ };
413
+ }
414
+ constructor(config){
415
+ this.config = config;
416
+ }
417
+ }
@@ -0,0 +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":["getToken","setToken","open","fetchWithTimeout","DeviceCodeProvider","startDeviceCodeFlow","accountId","clientId","tenantId","scope","logger","headless","config","deviceCodeEndpoint","debug","endpoint","deviceCodeResponse","method","headers","body","URLSearchParams","client_id","ok","errorText","text","Error","status","deviceCodeData","json","device_code","user_code","verification_uri","verification_uri_complete","expires_in","interval","info","urlToOpen","url","error","message","String","pollForToken","deviceCode","intervalSeconds","service","tokenStore","tokenEndpoint","currentInterval","startTime","Date","now","Promise","resolve","setTimeout","elapsed","Math","floor","response","grant_type","responseData","tokenData","token","accessToken","access_token","refresh_token","refreshToken","expiresAt","errorDescription","error_description","newInterval","refreshAccessToken","isTokenValid","undefined","getAccessToken","effectiveAccountId","storedToken","refreshedToken","getUserEmail","Authorization","userData","email","userPrincipalName","mail","toAuthProvider","createAuthProvider","authMiddleware","wrapAtPosition","module","extraPosition","originalHandler","handler","wrappedHandler","allArgs","extra","length","auth","authContext","withToolAuth","withResourceAuth","withPromptAuth"],"mappings":"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"}