@microsoft/rayfin-auth-provider-fabric 1.34.0-beta.0 → 1.34.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.
@@ -1,14 +1,16 @@
1
1
  import type { FabricAuthOptions } from './types.js';
2
2
  /**
3
- * Constructs the broker launch URL with PKCE and state parameters.
3
+ * Constructs the broker launch URL for the chromeless SecureItemEmbed view.
4
4
  *
5
- * Preserves any existing path and query parameters on `fabricPortalUrl`,
6
- * appends the artifact deep-link path, and adds PKCE query parameters.
5
+ * Uses the SecureItemEmbed path so the popup renders without Fabric portal
6
+ * chrome (navigation, ribbon). Preserves any existing query parameters on
7
+ * `fabricPortalUrl` and adds artifact identification + extensionPath.
8
+ *
9
+ * PKCE parameters are no longer included in the URL — they are delivered to
10
+ * the extension via the postMessage challenge flow (`brokeredAuth.challenge`).
7
11
  *
8
12
  * @param options - Auth options (workspace/project IDs, portal URL).
9
- * @param codeChallenge - PKCE S256 code challenge.
10
- * @param state - CSRF/correlation state nonce.
11
13
  * @returns Fully constructed broker URL.
12
14
  */
13
- export declare function buildBrokerUrl(options: FabricAuthOptions, codeChallenge: string, state: string): string;
15
+ export declare function buildBrokerUrl(options: FabricAuthOptions): string;
14
16
  //# sourceMappingURL=brokerUrl.d.ts.map
package/dist/brokerUrl.js CHANGED
@@ -1,31 +1,30 @@
1
1
  /**
2
- * Constructs the broker launch URL with PKCE and state parameters.
2
+ * Constructs the broker launch URL for the chromeless SecureItemEmbed view.
3
3
  *
4
- * Preserves any existing path and query parameters on `fabricPortalUrl`,
5
- * appends the artifact deep-link path, and adds PKCE query parameters.
4
+ * Uses the SecureItemEmbed path so the popup renders without Fabric portal
5
+ * chrome (navigation, ribbon). Preserves any existing query parameters on
6
+ * `fabricPortalUrl` and adds artifact identification + extensionPath.
7
+ *
8
+ * PKCE parameters are no longer included in the URL — they are delivered to
9
+ * the extension via the postMessage challenge flow (`brokeredAuth.challenge`).
6
10
  *
7
11
  * @param options - Auth options (workspace/project IDs, portal URL).
8
- * @param codeChallenge - PKCE S256 code challenge.
9
- * @param state - CSRF/correlation state nonce.
10
12
  * @returns Fully constructed broker URL.
11
13
  */
12
- export function buildBrokerUrl(options, codeChallenge, state) {
14
+ export function buildBrokerUrl(options) {
13
15
  const portalUrl = new URL(options.fabricPortalUrl);
14
- // Append artifact deep-link path with /BrokeredAuth route segment.
15
- // Query params are preserved for backward compatibility with older
16
- // AppBackend versions that detect broker mode via parseBrokerParams.
16
+ // Use the chromeless SecureItemEmbed path instead of the full portal
17
+ // deep-link so the popup renders without Fabric navigation chrome.
17
18
  const basePath = portalUrl.pathname.replace(/\/$/, '');
18
- const artifactPath = `/groups/${encodeURIComponent(options.workspaceId)}/appbackends/${encodeURIComponent(options.projectId)}/brokeredauth`;
19
- portalUrl.pathname = `${basePath}${artifactPath}`;
20
- // `returnOrigin` must be a bare origin (scheme + host + port) — used as
21
- // the postMessage targetOrigin and the redirect target for legacy brokers.
22
- portalUrl.searchParams.set('returnOrigin', options.returnOrigin);
23
- // Fallback redirect URL for legacy brokers that don't use postMessage.
24
- const callbackUrl = options.callbackUrl || `${options.returnOrigin}/auth/callback`;
25
- portalUrl.searchParams.set('callbackUrl', callbackUrl);
26
- portalUrl.searchParams.set('code_challenge', codeChallenge);
27
- portalUrl.searchParams.set('code_challenge_method', 'S256');
28
- portalUrl.searchParams.set('state', state);
19
+ portalUrl.pathname = `${basePath}/secureItemEmbed`;
20
+ // Artifact identification for SecureItemEmbed
21
+ portalUrl.searchParams.set('workspaceId', options.workspaceId);
22
+ portalUrl.searchParams.set('itemType', 'AppBackend');
23
+ portalUrl.searchParams.set('itemId', options.projectId);
24
+ // Route the extension to the dedicated BrokeredAuthPage.
25
+ // PKCE parameters are delivered via postMessage (brokeredAuth.challenge),
26
+ // not through the URL, so only the path is needed here.
27
+ portalUrl.searchParams.set('extensionPath', '/brokeredauth');
29
28
  return portalUrl.toString();
30
29
  }
31
30
  //# sourceMappingURL=brokerUrl.js.map
@@ -5,14 +5,17 @@ import type { FabricAuthOptions } from './types.js';
5
5
  *
6
6
  * **Must be called inside a user click handler** to avoid popup blockers.
7
7
  *
8
- * Generates PKCE params, opens the broker URL, and listens for a
9
- * `brokeredAuth.handoff` postMessage. On handoff, exchanges the code for
10
- * tokens and creates a session. The `code_verifier` is kept in closure only.
8
+ * Opens the popup synchronously in the gesture (Safari/iOS block `window.open`
9
+ * after an `await`) and delivers the PKCE challenge via the
10
+ * `brokeredAuth.challenge` postMessage. On `brokeredAuth.handoff`, exchanges
11
+ * the code for tokens and creates a session. `code_verifier` stays in closure.
11
12
  *
12
13
  * @param auth - Auth instance for token exchange.
13
14
  * @param options - Broker auth options (`returnOrigin` required).
14
15
  * @returns Resolves when the session is established.
15
- * @throws `AuthError` - On missing options, blocked popup, or auth failure.
16
+ * @throws `AuthError` - On missing options, blocked popup, PKCE preparation or
17
+ * delivery failure (`PKCE_CHALLENGE_FAILED`, `CHALLENGE_DELIVERY_FAILED`), or
18
+ * auth failure.
16
19
  */
17
20
  export declare function initiateFabricLogin(auth: Auth, options: FabricAuthOptions): Promise<void>;
18
21
  //# sourceMappingURL=initiateFabricLogin.d.ts.map
@@ -8,14 +8,17 @@ import { buildBrokerUrl } from './brokerUrl.js';
8
8
  *
9
9
  * **Must be called inside a user click handler** to avoid popup blockers.
10
10
  *
11
- * Generates PKCE params, opens the broker URL, and listens for a
12
- * `brokeredAuth.handoff` postMessage. On handoff, exchanges the code for
13
- * tokens and creates a session. The `code_verifier` is kept in closure only.
11
+ * Opens the popup synchronously in the gesture (Safari/iOS block `window.open`
12
+ * after an `await`) and delivers the PKCE challenge via the
13
+ * `brokeredAuth.challenge` postMessage. On `brokeredAuth.handoff`, exchanges
14
+ * the code for tokens and creates a session. `code_verifier` stays in closure.
14
15
  *
15
16
  * @param auth - Auth instance for token exchange.
16
17
  * @param options - Broker auth options (`returnOrigin` required).
17
18
  * @returns Resolves when the session is established.
18
- * @throws `AuthError` - On missing options, blocked popup, or auth failure.
19
+ * @throws `AuthError` - On missing options, blocked popup, PKCE preparation or
20
+ * delivery failure (`PKCE_CHALLENGE_FAILED`, `CHALLENGE_DELIVERY_FAILED`), or
21
+ * auth failure.
19
22
  */
20
23
  export async function initiateFabricLogin(auth, options) {
21
24
  assertBrowser('initiateFabricLogin');
@@ -32,12 +35,17 @@ export async function initiateFabricLogin(auth, options) {
32
35
  if (!options.fabricPortalUrl) {
33
36
  throw new AuthError('fabricPortalUrl is required for Fabric authentication.', 'MISSING_FABRIC_PORTAL_URL');
34
37
  }
35
- // PKCE + state
38
+ // Generate verifier + state synchronously so the popup can open in the user
39
+ // gesture below. The S256 challenge is computed async (off the click path)
40
+ // and delivered later via the `brokeredAuth.challenge` postMessage —
41
+ // awaiting before `window.open` would trip Safari/iOS popup blocking.
36
42
  const codeVerifier = generateCodeVerifier();
37
- const codeChallenge = await generateCodeChallenge(codeVerifier);
38
43
  const state = generateState();
44
+ const codeChallengePromise = generateCodeChallenge(codeVerifier);
45
+ // Mark rejection handled for paths that skip handleReady; real handling is there.
46
+ codeChallengePromise.catch(() => { });
39
47
  const expectedBrokerOrigin = new URL(options.fabricPortalUrl).origin;
40
- const brokerUrl = buildBrokerUrl(options, codeChallenge, state);
48
+ const brokerUrl = buildBrokerUrl(options);
41
49
  return new Promise((resolve, reject) => {
42
50
  let fabricWindow = null;
43
51
  let channel = null;
@@ -118,7 +126,7 @@ export async function initiateFabricLogin(auth, options) {
118
126
  * cross-origin iframe inside the popup — `postMessage` sent to the
119
127
  * popup's top-level window does not propagate to child iframes.
120
128
  */
121
- function handleReady(event) {
129
+ async function handleReady(event) {
122
130
  if (readyHandled)
123
131
  return;
124
132
  if (!event.data || typeof event.data !== 'object')
@@ -132,6 +140,18 @@ export async function initiateFabricLogin(auth, options) {
132
140
  }
133
141
  readyHandled = true;
134
142
  window.removeEventListener('message', handleReady);
143
+ // Challenge computed off the click path; await it here (almost always
144
+ // already resolved by the time the broker signals ready).
145
+ let codeChallenge;
146
+ try {
147
+ codeChallenge = await codeChallengePromise;
148
+ }
149
+ catch (err) {
150
+ console.warn('[FabricAuth] Failed to compute PKCE code challenge', err);
151
+ cleanup();
152
+ reject(new AuthError('Failed to prepare Fabric authentication.', 'PKCE_CHALLENGE_FAILED'));
153
+ return;
154
+ }
135
155
  const challengePayload = {
136
156
  type: 'brokeredAuth.challenge',
137
157
  returnOrigin: options.returnOrigin,
@@ -139,16 +159,29 @@ export async function initiateFabricLogin(auth, options) {
139
159
  codeChallengeMethod: 'S256',
140
160
  state,
141
161
  };
142
- // Reply to the window that sent the ready signal (the extension iframe).
143
- const source = event.source;
144
- if (source) {
145
- source.postMessage(challengePayload, event.origin || '*');
146
- console.debug(`[FabricAuth] Sent brokeredAuth.challenge to event.source (origin=${event.origin || '*'})`);
162
+ // Deliver the challenge. Guard postMessage: an opaque/sandboxed sender
163
+ // reports event.origin === 'null' (an invalid targetOrigin that throws),
164
+ // and this listener is async so a throw is swallowed by the DOM rather
165
+ // than rejecting the outer promise — the flow would hang until timeout.
166
+ try {
167
+ // Normalize 'null'/empty origin to '*'; valid origins pass through.
168
+ const targetOrigin = event.origin && event.origin !== 'null' ? event.origin : '*';
169
+ // Reply to the window that sent the ready signal (the extension iframe).
170
+ const source = event.source;
171
+ if (source) {
172
+ source.postMessage(challengePayload, targetOrigin);
173
+ console.debug(`[FabricAuth] Sent brokeredAuth.challenge to event.source (origin=${targetOrigin})`);
174
+ }
175
+ else if (fabricWindow && !fabricWindow.closed) {
176
+ // Fallback when event.source is unavailable: post to the popup.
177
+ fabricWindow.postMessage(challengePayload, expectedBrokerOrigin);
178
+ console.debug('[FabricAuth] Sent brokeredAuth.challenge to fabricWindow (event.source unavailable)');
179
+ }
147
180
  }
148
- else if (fabricWindow && !fabricWindow.closed) {
149
- // Fallback: if event.source is unavailable, try the popup directly.
150
- fabricWindow.postMessage(challengePayload, expectedBrokerOrigin);
151
- console.debug('[FabricAuth] Sent brokeredAuth.challenge to fabricWindow (event.source unavailable)');
181
+ catch (err) {
182
+ console.warn('[FabricAuth] Failed to deliver PKCE challenge', err);
183
+ cleanup();
184
+ reject(new AuthError('Failed to deliver Fabric authentication challenge.', 'CHALLENGE_DELIVERY_FAILED'));
152
185
  }
153
186
  }
154
187
  // Listen for brokeredAuth.ready from the popup (postMessage PKCE).
@@ -164,7 +197,7 @@ export async function initiateFabricLogin(auth, options) {
164
197
  catch {
165
198
  console.debug('[FabricAuth] BroadcastChannel not available — relying on postMessage only');
166
199
  }
167
- // Open broker portal in a centered popup
200
+ // Open the chromeless SecureItemEmbed broker URL in a centered popup.
168
201
  const popupWidth = 600;
169
202
  const popupHeight = 700;
170
203
  const left = Math.round(window.screenX + (window.outerWidth - popupWidth) / 2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/rayfin-auth-provider-fabric",
3
- "version": "1.34.0-beta.0",
3
+ "version": "1.34.0",
4
4
  "description": "Fabric brokered authentication provider for Rayfin SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -13,9 +13,9 @@
13
13
  ],
14
14
  "type": "module",
15
15
  "dependencies": {
16
- "@microsoft/rayfin-auth": "1.34.0-beta.0",
17
- "@microsoft/fabric-embedded-host": "1.34.0-beta.0",
18
- "@microsoft/rayfin-lib": "1.34.0-beta.0"
16
+ "@microsoft/rayfin-auth": "1.34.0",
17
+ "@microsoft/fabric-embedded-host": "1.34.0",
18
+ "@microsoft/rayfin-lib": "1.34.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "typescript": "^5.8.3",