@microsoft/rayfin-auth-provider-fabric 1.20.0 → 1.21.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.
- package/dist/PostMessageAuthTransport.d.ts +34 -0
- package/dist/PostMessageAuthTransport.js +44 -0
- package/dist/embeddedFabricLogin.d.ts +20 -0
- package/dist/embeddedFabricLogin.js +55 -0
- package/dist/ensureSignedInWithFabric.d.ts +6 -4
- package/dist/ensureSignedInWithFabric.js +34 -28
- package/dist/fabricAuthHelpers.d.ts +25 -0
- package/dist/fabricAuthHelpers.js +48 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/initEmbeddedAuth.d.ts +29 -0
- package/dist/initEmbeddedAuth.js +54 -0
- package/dist/initiateFabricLogin.js +51 -1
- package/dist/types.d.ts +8 -0
- package/package.json +4 -3
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handoff result returned by the host extension's `AuthPlugin`.
|
|
3
|
+
*/
|
|
4
|
+
export interface PostMessageHandoffResult {
|
|
5
|
+
/** Single-use handoff code to exchange for tokens. */
|
|
6
|
+
handoffCode: string;
|
|
7
|
+
/** Opaque nonce echoed back for correlation. */
|
|
8
|
+
state: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* PostMessage transport for embedded (iframe) auth.
|
|
12
|
+
*
|
|
13
|
+
* Sends a `fabric-auth` / `auth.requestHandoff` message to the parent
|
|
14
|
+
* window and waits for a correlated response carrying the handoff code.
|
|
15
|
+
*
|
|
16
|
+
* Delegates the low-level postMessage send/receive/correlate to
|
|
17
|
+
* `sendBridgeRequest` from `@microsoft/fabric-embedded-host`.
|
|
18
|
+
*
|
|
19
|
+
* @param params.callbackUrl The callback origin where the SPA is hosted.
|
|
20
|
+
* @param params.codeChallenge PKCE S256 code challenge.
|
|
21
|
+
* @param params.codeChallengeMethod Always `"S256"`.
|
|
22
|
+
* @param params.state Opaque nonce for CSRF/correlation.
|
|
23
|
+
* @param params.timeoutMs Response timeout in milliseconds (default 30 000).
|
|
24
|
+
* @returns Promise resolving with the handoff code and state.
|
|
25
|
+
* @throws {AuthError} On timeout, error response, or missing parent.
|
|
26
|
+
*/
|
|
27
|
+
export declare function requestHandoff(params: {
|
|
28
|
+
callbackUrl: string;
|
|
29
|
+
codeChallenge: string;
|
|
30
|
+
codeChallengeMethod: string;
|
|
31
|
+
state: string;
|
|
32
|
+
timeoutMs?: number;
|
|
33
|
+
}): Promise<PostMessageHandoffResult>;
|
|
34
|
+
//# sourceMappingURL=PostMessageAuthTransport.d.ts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { sendBridgeRequest, BridgeError, } from '@microsoft/fabric-embedded-host';
|
|
2
|
+
import { AuthError } from '@microsoft/rayfin-lib';
|
|
3
|
+
/**
|
|
4
|
+
* PostMessage transport for embedded (iframe) auth.
|
|
5
|
+
*
|
|
6
|
+
* Sends a `fabric-auth` / `auth.requestHandoff` message to the parent
|
|
7
|
+
* window and waits for a correlated response carrying the handoff code.
|
|
8
|
+
*
|
|
9
|
+
* Delegates the low-level postMessage send/receive/correlate to
|
|
10
|
+
* `sendBridgeRequest` from `@microsoft/fabric-embedded-host`.
|
|
11
|
+
*
|
|
12
|
+
* @param params.callbackUrl The callback origin where the SPA is hosted.
|
|
13
|
+
* @param params.codeChallenge PKCE S256 code challenge.
|
|
14
|
+
* @param params.codeChallengeMethod Always `"S256"`.
|
|
15
|
+
* @param params.state Opaque nonce for CSRF/correlation.
|
|
16
|
+
* @param params.timeoutMs Response timeout in milliseconds (default 30 000).
|
|
17
|
+
* @returns Promise resolving with the handoff code and state.
|
|
18
|
+
* @throws {AuthError} On timeout, error response, or missing parent.
|
|
19
|
+
*/
|
|
20
|
+
export function requestHandoff(params) {
|
|
21
|
+
const { callbackUrl, codeChallenge, codeChallengeMethod, state, timeoutMs } = params;
|
|
22
|
+
if (!window.parent || window.parent === window) {
|
|
23
|
+
return Promise.reject(new AuthError('No parent window — embedded auth requires an iframe host.', 'NO_PARENT_WINDOW'));
|
|
24
|
+
}
|
|
25
|
+
return sendBridgeRequest({
|
|
26
|
+
target: window.parent,
|
|
27
|
+
channel: 'fabric-auth',
|
|
28
|
+
kind: 'auth.requestHandoff',
|
|
29
|
+
payload: {
|
|
30
|
+
callbackUrl,
|
|
31
|
+
codeChallenge,
|
|
32
|
+
codeChallengeMethod,
|
|
33
|
+
state,
|
|
34
|
+
},
|
|
35
|
+
timeoutMs,
|
|
36
|
+
}).catch((err) => {
|
|
37
|
+
// Re-wrap BridgeError as AuthError for backward compatibility
|
|
38
|
+
if (err instanceof BridgeError) {
|
|
39
|
+
throw new AuthError(err.message, err.code);
|
|
40
|
+
}
|
|
41
|
+
throw err;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=PostMessageAuthTransport.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Auth } from '@microsoft/rayfin-auth';
|
|
2
|
+
import type { FabricAuthOptions } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Embedded-mode Fabric login — acquires a session via postMessage to the
|
|
5
|
+
* parent Fabric Extension Host without opening a popup.
|
|
6
|
+
*
|
|
7
|
+
* 1. Generates PKCE parameters in local variables (no `localStorage`).
|
|
8
|
+
* 2. Sends `auth.requestHandoff` to the parent via {@link requestHandoff}.
|
|
9
|
+
* 3. Receives the handoff code from the host's `AuthPlugin`.
|
|
10
|
+
* 4. Exchanges the handoff code for tokens via `exchangeVerificationCode`.
|
|
11
|
+
* 5. Creates a session via `createSessionFromTokenResponse`.
|
|
12
|
+
*
|
|
13
|
+
* The session is stored in the iframe's own `localStorage`.
|
|
14
|
+
*
|
|
15
|
+
* @param auth Auth instance for token exchange and session management.
|
|
16
|
+
* @param options Fabric auth options (`returnOrigin` required).
|
|
17
|
+
* @throws {AuthError} On missing options, transport failure, or token exchange error.
|
|
18
|
+
*/
|
|
19
|
+
export declare function embeddedFabricLogin(auth: Auth, options: FabricAuthOptions): Promise<void>;
|
|
20
|
+
//# sourceMappingURL=embeddedFabricLogin.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { generateCodeVerifier, generateCodeChallenge, generateState, } from '@microsoft/rayfin-auth';
|
|
2
|
+
import { AuthError } from '@microsoft/rayfin-lib';
|
|
3
|
+
import { requestHandoff } from './PostMessageAuthTransport';
|
|
4
|
+
/**
|
|
5
|
+
* Embedded-mode Fabric login — acquires a session via postMessage to the
|
|
6
|
+
* parent Fabric Extension Host without opening a popup.
|
|
7
|
+
*
|
|
8
|
+
* 1. Generates PKCE parameters in local variables (no `localStorage`).
|
|
9
|
+
* 2. Sends `auth.requestHandoff` to the parent via {@link requestHandoff}.
|
|
10
|
+
* 3. Receives the handoff code from the host's `AuthPlugin`.
|
|
11
|
+
* 4. Exchanges the handoff code for tokens via `exchangeVerificationCode`.
|
|
12
|
+
* 5. Creates a session via `createSessionFromTokenResponse`.
|
|
13
|
+
*
|
|
14
|
+
* The session is stored in the iframe's own `localStorage`.
|
|
15
|
+
*
|
|
16
|
+
* @param auth Auth instance for token exchange and session management.
|
|
17
|
+
* @param options Fabric auth options (`returnOrigin` required).
|
|
18
|
+
* @throws {AuthError} On missing options, transport failure, or token exchange error.
|
|
19
|
+
*/
|
|
20
|
+
export async function embeddedFabricLogin(auth, options) {
|
|
21
|
+
if (!options.returnOrigin) {
|
|
22
|
+
throw new AuthError('returnOrigin is required for embedded Fabric authentication.', 'MISSING_RETURN_ORIGIN');
|
|
23
|
+
}
|
|
24
|
+
// PKCE in local variables only — not persisted to localStorage.
|
|
25
|
+
const codeVerifier = generateCodeVerifier();
|
|
26
|
+
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
27
|
+
const state = generateState();
|
|
28
|
+
console.debug('[FabricAuth:embedded] Requesting handoff from host via postMessage');
|
|
29
|
+
// NOTE: We intentionally do NOT pass fabricPortalUrl as the targetOrigin.
|
|
30
|
+
// The SPA's window.parent is the Fabric *extension* iframe (AppViewMode),
|
|
31
|
+
// not the portal itself. The extension runs at a different origin than
|
|
32
|
+
// fabricPortalUrl, and the child iframe cannot read window.parent.origin.
|
|
33
|
+
// Security relies on UUID v4 requestId correlation + PKCE.
|
|
34
|
+
const result = await requestHandoff({
|
|
35
|
+
callbackUrl: options.returnOrigin,
|
|
36
|
+
codeChallenge,
|
|
37
|
+
codeChallengeMethod: 'S256',
|
|
38
|
+
state,
|
|
39
|
+
});
|
|
40
|
+
// Validate state to prevent replay attacks.
|
|
41
|
+
if (result.state !== state) {
|
|
42
|
+
throw new AuthError('State mismatch in embedded auth handoff response.', 'STATE_MISMATCH');
|
|
43
|
+
}
|
|
44
|
+
console.debug('[FabricAuth:embedded] Handoff received, exchanging code for tokens');
|
|
45
|
+
const redirectUri = options.returnOrigin;
|
|
46
|
+
const tokenResponse = await auth.getAuthApi().exchangeVerificationCode({
|
|
47
|
+
verificationCode: result.handoffCode,
|
|
48
|
+
codeVerifier,
|
|
49
|
+
codeType: 'fabric_handoff',
|
|
50
|
+
redirectUri,
|
|
51
|
+
});
|
|
52
|
+
auth.createSessionFromTokenResponse(tokenResponse);
|
|
53
|
+
console.debug('[FabricAuth:embedded] Session established');
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=embeddedFabricLogin.js.map
|
|
@@ -3,20 +3,22 @@ import type { FabricAuthOptions } from './types';
|
|
|
3
3
|
/**
|
|
4
4
|
* Ensures the user is signed in via Fabric brokered authentication.
|
|
5
5
|
*
|
|
6
|
-
* Implements a
|
|
6
|
+
* Implements a multi-step waterfall — the first step that succeeds short-circuits the rest:
|
|
7
7
|
*
|
|
8
8
|
* 1. **Already authenticated** — if `auth.getSession().isAuthenticated` is true,
|
|
9
9
|
* return the existing session immediately.
|
|
10
10
|
* 2. **Refresh token** — if a refresh token is available, attempt `auth.refreshSession()`.
|
|
11
11
|
* Return the refreshed session on success; continue on failure.
|
|
12
|
-
* 3. **
|
|
12
|
+
* 3. **Embedded mode** — if running inside a Fabric iframe (`fabricEmbedded=true`),
|
|
13
|
+
* use `embeddedFabricLogin()` to acquire a session via `postMessage` handoff.
|
|
14
|
+
* 4. **Open Fabric broker** — no existing auth path available. Open the Fabric Portal
|
|
13
15
|
* in a new tab via `initiateFabricLogin()` and wait for the Fabric extension to post
|
|
14
16
|
* the handoff code via `postMessage`. The function exchanges the code internally
|
|
15
17
|
* and creates the session. Once the promise resolves, return the new session.
|
|
16
18
|
*
|
|
17
|
-
* **Step
|
|
19
|
+
* **Step 4 calls `window.open()`** — to avoid popup/tab blockers, call this function
|
|
18
20
|
* from inside a synchronous user-gesture handler (e.g., a button click).
|
|
19
|
-
* Steps 1–
|
|
21
|
+
* Steps 1–3 do not open windows and are safe to call on page load.
|
|
20
22
|
*
|
|
21
23
|
* @param auth The Auth instance.
|
|
22
24
|
* @param options Fabric authentication options (workspaceId, projectId, returnOrigin).
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
import { AuthError } from '@microsoft/rayfin-lib';
|
|
2
|
+
import { embeddedFabricLogin } from './embeddedFabricLogin';
|
|
3
|
+
import { isEmbeddedMode, tryResumeSession } from './fabricAuthHelpers';
|
|
2
4
|
import { initiateFabricLogin } from './initiateFabricLogin';
|
|
3
5
|
/**
|
|
4
6
|
* Ensures the user is signed in via Fabric brokered authentication.
|
|
5
7
|
*
|
|
6
|
-
* Implements a
|
|
8
|
+
* Implements a multi-step waterfall — the first step that succeeds short-circuits the rest:
|
|
7
9
|
*
|
|
8
10
|
* 1. **Already authenticated** — if `auth.getSession().isAuthenticated` is true,
|
|
9
11
|
* return the existing session immediately.
|
|
10
12
|
* 2. **Refresh token** — if a refresh token is available, attempt `auth.refreshSession()`.
|
|
11
13
|
* Return the refreshed session on success; continue on failure.
|
|
12
|
-
* 3. **
|
|
14
|
+
* 3. **Embedded mode** — if running inside a Fabric iframe (`fabricEmbedded=true`),
|
|
15
|
+
* use `embeddedFabricLogin()` to acquire a session via `postMessage` handoff.
|
|
16
|
+
* 4. **Open Fabric broker** — no existing auth path available. Open the Fabric Portal
|
|
13
17
|
* in a new tab via `initiateFabricLogin()` and wait for the Fabric extension to post
|
|
14
18
|
* the handoff code via `postMessage`. The function exchanges the code internally
|
|
15
19
|
* and creates the session. Once the promise resolves, return the new session.
|
|
16
20
|
*
|
|
17
|
-
* **Step
|
|
21
|
+
* **Step 4 calls `window.open()`** — to avoid popup/tab blockers, call this function
|
|
18
22
|
* from inside a synchronous user-gesture handler (e.g., a button click).
|
|
19
|
-
* Steps 1–
|
|
23
|
+
* Steps 1–3 do not open windows and are safe to call on page load.
|
|
20
24
|
*
|
|
21
25
|
* @param auth The Auth instance.
|
|
22
26
|
* @param options Fabric authentication options (workspaceId, projectId, returnOrigin).
|
|
@@ -24,42 +28,44 @@ import { initiateFabricLogin } from './initiateFabricLogin';
|
|
|
24
28
|
* @throws {AuthError} If all steps fail or the broker tab is blocked.
|
|
25
29
|
*/
|
|
26
30
|
export async function ensureSignedInWithFabric(auth, options) {
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
console.debug('[FabricAuth] Step 1: existing session found, skipping remaining steps');
|
|
32
|
-
return currentSession;
|
|
31
|
+
// Steps 1-2: existing session or refresh
|
|
32
|
+
const resumed = await tryResumeSession(auth);
|
|
33
|
+
if (resumed) {
|
|
34
|
+
return resumed;
|
|
33
35
|
}
|
|
34
|
-
// Step
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
// Step 3: Embedded mode — postMessage auth via parent iframe host
|
|
37
|
+
if (isEmbeddedMode(options)) {
|
|
38
|
+
console.debug('[FabricAuth] Embedded mode detected, using postMessage auth');
|
|
37
39
|
try {
|
|
38
|
-
await auth
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
41
|
-
console.debug('[FabricAuth]
|
|
42
|
-
return
|
|
40
|
+
await embeddedFabricLogin(auth, options);
|
|
41
|
+
const embeddedSession = auth.getSession();
|
|
42
|
+
if (embeddedSession.isAuthenticated) {
|
|
43
|
+
console.debug('[FabricAuth] Embedded auth succeeded');
|
|
44
|
+
return embeddedSession;
|
|
43
45
|
}
|
|
46
|
+
throw new AuthError('Fabric embedded authentication completed but no session was established.', 'SESSION_NOT_ESTABLISHED');
|
|
44
47
|
}
|
|
45
|
-
catch {
|
|
46
|
-
|
|
48
|
+
catch (err) {
|
|
49
|
+
// If there's no parent window the embedded flag is stale (e.g.
|
|
50
|
+
// leftover sessionStorage or URL param without an actual iframe).
|
|
51
|
+
// Fall through to the popup path instead of failing outright.
|
|
52
|
+
if (err instanceof AuthError && err.code === 'NO_PARENT_WINDOW') {
|
|
53
|
+
console.debug('[FabricAuth] No parent window — falling back to popup auth');
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
47
58
|
}
|
|
48
59
|
}
|
|
49
|
-
|
|
50
|
-
console.debug('[FabricAuth] Step 2: no refresh token available, skipping to step 3');
|
|
51
|
-
}
|
|
52
|
-
// Step 3: Open Fabric broker in a new tab
|
|
60
|
+
// Step 4: Open Fabric broker in a new tab (popup flow)
|
|
53
61
|
// WARNING: This calls window.open() — must be in a user-gesture context
|
|
54
|
-
console.debug('[FabricAuth]
|
|
62
|
+
console.debug('[FabricAuth] Initiating Fabric broker login');
|
|
55
63
|
await initiateFabricLogin(auth, options);
|
|
56
|
-
// Session was created inside initiateFabricLogin via createSessionFromTokenResponse
|
|
57
64
|
const newSession = auth.getSession();
|
|
58
65
|
if (newSession.isAuthenticated) {
|
|
59
|
-
console.debug('[FabricAuth]
|
|
66
|
+
console.debug('[FabricAuth] Fabric broker login succeeded');
|
|
60
67
|
return newSession;
|
|
61
68
|
}
|
|
62
|
-
console.warn('[FabricAuth] Step 3: Fabric broker login completed but no session was established');
|
|
63
69
|
throw new AuthError('Fabric authentication completed but no session was established.', 'SESSION_NOT_ESTABLISHED');
|
|
64
70
|
}
|
|
65
71
|
//# sourceMappingURL=ensureSignedInWithFabric.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Auth, OpaqueSession } from '@microsoft/rayfin-auth';
|
|
2
|
+
import type { FabricAuthOptions } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Detects embedded mode from the `fabricEmbedded` option, the
|
|
5
|
+
* `?fabricEmbedded=true` URL query parameter, or a previously stored
|
|
6
|
+
* `sessionStorage` flag.
|
|
7
|
+
*
|
|
8
|
+
* Delegates to `@microsoft/fabric-embedded-host` for the actual
|
|
9
|
+
* detection logic. `FabricAuthOptions` satisfies the shared
|
|
10
|
+
* `EmbeddedModeOptions` interface.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isEmbeddedMode(options: FabricAuthOptions): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Attempts to resume an existing session without any user interaction.
|
|
15
|
+
*
|
|
16
|
+
* 1. Returns the current session if already authenticated.
|
|
17
|
+
* 2. If a refresh token is available, tries `auth.refreshSession()` and
|
|
18
|
+
* returns the refreshed session on success.
|
|
19
|
+
* 3. Returns `null` when neither path produces an authenticated session.
|
|
20
|
+
*
|
|
21
|
+
* This helper never opens windows, popups, or postMessage channels —
|
|
22
|
+
* it is always safe to call on page load.
|
|
23
|
+
*/
|
|
24
|
+
export declare function tryResumeSession(auth: Auth): Promise<OpaqueSession | null>;
|
|
25
|
+
//# sourceMappingURL=fabricAuthHelpers.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { isEmbeddedMode as sharedIsEmbeddedMode } from '@microsoft/fabric-embedded-host';
|
|
2
|
+
/**
|
|
3
|
+
* Detects embedded mode from the `fabricEmbedded` option, the
|
|
4
|
+
* `?fabricEmbedded=true` URL query parameter, or a previously stored
|
|
5
|
+
* `sessionStorage` flag.
|
|
6
|
+
*
|
|
7
|
+
* Delegates to `@microsoft/fabric-embedded-host` for the actual
|
|
8
|
+
* detection logic. `FabricAuthOptions` satisfies the shared
|
|
9
|
+
* `EmbeddedModeOptions` interface.
|
|
10
|
+
*/
|
|
11
|
+
export function isEmbeddedMode(options) {
|
|
12
|
+
return sharedIsEmbeddedMode(options);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Attempts to resume an existing session without any user interaction.
|
|
16
|
+
*
|
|
17
|
+
* 1. Returns the current session if already authenticated.
|
|
18
|
+
* 2. If a refresh token is available, tries `auth.refreshSession()` and
|
|
19
|
+
* returns the refreshed session on success.
|
|
20
|
+
* 3. Returns `null` when neither path produces an authenticated session.
|
|
21
|
+
*
|
|
22
|
+
* This helper never opens windows, popups, or postMessage channels —
|
|
23
|
+
* it is always safe to call on page load.
|
|
24
|
+
*/
|
|
25
|
+
export async function tryResumeSession(auth) {
|
|
26
|
+
// Step 1: Already authenticated
|
|
27
|
+
const currentSession = auth.getSession();
|
|
28
|
+
if (currentSession.isAuthenticated) {
|
|
29
|
+
console.debug('[FabricAuth] Existing session found');
|
|
30
|
+
return currentSession;
|
|
31
|
+
}
|
|
32
|
+
// Step 2: Try refresh token
|
|
33
|
+
if (auth.hasRefreshToken()) {
|
|
34
|
+
try {
|
|
35
|
+
await auth.refreshSession();
|
|
36
|
+
const refreshedSession = auth.getSession();
|
|
37
|
+
if (refreshedSession.isAuthenticated) {
|
|
38
|
+
console.debug('[FabricAuth] Session refreshed successfully');
|
|
39
|
+
return refreshedSession;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
console.warn('[FabricAuth] Session refresh failed, continuing');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=fabricAuthHelpers.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export type { FabricAuthOptions } from './types';
|
|
2
2
|
export { bridgeFabricCallback } from './bridgeFabricCallback';
|
|
3
|
+
export { embeddedFabricLogin } from './embeddedFabricLogin';
|
|
3
4
|
export { ensureSignedInWithFabric } from './ensureSignedInWithFabric';
|
|
5
|
+
export { initEmbeddedAuth } from './initEmbeddedAuth';
|
|
4
6
|
export { initiateFabricLogin } from './initiateFabricLogin';
|
|
7
|
+
export { requestHandoff } from './PostMessageAuthTransport';
|
|
8
|
+
export type { PostMessageHandoffResult } from './PostMessageAuthTransport';
|
|
5
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// Functions
|
|
2
2
|
export { bridgeFabricCallback } from './bridgeFabricCallback';
|
|
3
|
+
export { embeddedFabricLogin } from './embeddedFabricLogin';
|
|
3
4
|
export { ensureSignedInWithFabric } from './ensureSignedInWithFabric';
|
|
5
|
+
export { initEmbeddedAuth } from './initEmbeddedAuth';
|
|
4
6
|
export { initiateFabricLogin } from './initiateFabricLogin';
|
|
7
|
+
export { requestHandoff } from './PostMessageAuthTransport';
|
|
5
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Auth, OpaqueSession } from '@microsoft/rayfin-auth';
|
|
2
|
+
import type { FabricAuthOptions } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Initializes embedded Fabric authentication if running inside an iframe
|
|
5
|
+
* with `?fabricEmbedded=true`.
|
|
6
|
+
*
|
|
7
|
+
* Call this once at app startup (e.g., in a `useEffect` or initialization
|
|
8
|
+
* routine). It is safe to call on page load — it never opens a popup or
|
|
9
|
+
* new tab.
|
|
10
|
+
*
|
|
11
|
+
* **Behaviour:**
|
|
12
|
+
* - If `fabricEmbedded=true` is **not** in the URL and `options.fabricEmbedded`
|
|
13
|
+
* is not `true`, returns `null` immediately (no-op).
|
|
14
|
+
* - If embedded mode is detected, runs a 3-step waterfall:
|
|
15
|
+
* 1. Return existing session if already authenticated.
|
|
16
|
+
* 2. Attempt refresh via refresh token.
|
|
17
|
+
* 3. Request a handoff code from the parent host via `postMessage`
|
|
18
|
+
* and exchange it for a session (PKCE, no popup).
|
|
19
|
+
*
|
|
20
|
+
* Apps that also support the popup flow should continue to call
|
|
21
|
+
* {@link ensureSignedInWithFabric} from a user-gesture handler for the
|
|
22
|
+
* non-embedded case.
|
|
23
|
+
*
|
|
24
|
+
* @param auth The Auth instance.
|
|
25
|
+
* @param options Fabric authentication options.
|
|
26
|
+
* @returns The authenticated session, or `null` if not in embedded mode.
|
|
27
|
+
*/
|
|
28
|
+
export declare function initEmbeddedAuth(auth: Auth, options: FabricAuthOptions): Promise<OpaqueSession | null>;
|
|
29
|
+
//# sourceMappingURL=initEmbeddedAuth.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { embeddedFabricLogin } from './embeddedFabricLogin';
|
|
2
|
+
import { isEmbeddedMode, tryResumeSession } from './fabricAuthHelpers';
|
|
3
|
+
/**
|
|
4
|
+
* Initializes embedded Fabric authentication if running inside an iframe
|
|
5
|
+
* with `?fabricEmbedded=true`.
|
|
6
|
+
*
|
|
7
|
+
* Call this once at app startup (e.g., in a `useEffect` or initialization
|
|
8
|
+
* routine). It is safe to call on page load — it never opens a popup or
|
|
9
|
+
* new tab.
|
|
10
|
+
*
|
|
11
|
+
* **Behaviour:**
|
|
12
|
+
* - If `fabricEmbedded=true` is **not** in the URL and `options.fabricEmbedded`
|
|
13
|
+
* is not `true`, returns `null` immediately (no-op).
|
|
14
|
+
* - If embedded mode is detected, runs a 3-step waterfall:
|
|
15
|
+
* 1. Return existing session if already authenticated.
|
|
16
|
+
* 2. Attempt refresh via refresh token.
|
|
17
|
+
* 3. Request a handoff code from the parent host via `postMessage`
|
|
18
|
+
* and exchange it for a session (PKCE, no popup).
|
|
19
|
+
*
|
|
20
|
+
* Apps that also support the popup flow should continue to call
|
|
21
|
+
* {@link ensureSignedInWithFabric} from a user-gesture handler for the
|
|
22
|
+
* non-embedded case.
|
|
23
|
+
*
|
|
24
|
+
* @param auth The Auth instance.
|
|
25
|
+
* @param options Fabric authentication options.
|
|
26
|
+
* @returns The authenticated session, or `null` if not in embedded mode.
|
|
27
|
+
*/
|
|
28
|
+
export async function initEmbeddedAuth(auth, options) {
|
|
29
|
+
if (!isEmbeddedMode(options)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
// Steps 1-2: existing session or refresh
|
|
33
|
+
const resumed = await tryResumeSession(auth);
|
|
34
|
+
if (resumed) {
|
|
35
|
+
return resumed;
|
|
36
|
+
}
|
|
37
|
+
// Step 3: postMessage handoff (no popup, no window.open)
|
|
38
|
+
console.debug('[FabricAuth:initEmbedded] Starting postMessage handoff flow');
|
|
39
|
+
try {
|
|
40
|
+
await embeddedFabricLogin(auth, options);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
console.warn('[FabricAuth:initEmbedded] Embedded login failed', err);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const newSession = auth.getSession();
|
|
47
|
+
if (newSession.isAuthenticated) {
|
|
48
|
+
console.debug('[FabricAuth:initEmbedded] Session established');
|
|
49
|
+
return newSession;
|
|
50
|
+
}
|
|
51
|
+
console.warn('[FabricAuth:initEmbedded] Handoff completed but no session was established');
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=initEmbeddedAuth.js.map
|
|
@@ -41,8 +41,11 @@ export async function initiateFabricLogin(auth, options) {
|
|
|
41
41
|
let channel = null;
|
|
42
42
|
// 5-minute timeout — matches the backend's handoff code TTL.
|
|
43
43
|
const TIMEOUT_MS = 5 * 60 * 1000;
|
|
44
|
+
/** Whether the brokeredAuth.ready signal has been handled. */
|
|
45
|
+
let readyHandled = false;
|
|
44
46
|
function cleanup() {
|
|
45
47
|
window.removeEventListener('message', handleMessage);
|
|
48
|
+
window.removeEventListener('message', handleReady);
|
|
46
49
|
channel?.close();
|
|
47
50
|
channel = null;
|
|
48
51
|
clearTimeout(timeoutId);
|
|
@@ -54,7 +57,7 @@ export async function initiateFabricLogin(auth, options) {
|
|
|
54
57
|
function handleMessage(event) {
|
|
55
58
|
// Origin check is advisory — state + PKCE provide the real security.
|
|
56
59
|
if (event.origin !== expectedBrokerOrigin) {
|
|
57
|
-
console.
|
|
60
|
+
console.info(`[FabricAuth] postMessage from origin="${event.origin}" (expected portal="${expectedBrokerOrigin}") — continuing (secured by state + PKCE)`);
|
|
58
61
|
}
|
|
59
62
|
if (!event.data || typeof event.data !== 'object') {
|
|
60
63
|
console.debug('[FabricAuth] postMessage ignored: non-object payload');
|
|
@@ -101,6 +104,53 @@ export async function initiateFabricLogin(auth, options) {
|
|
|
101
104
|
cleanup();
|
|
102
105
|
reject(new AuthError('Fabric authentication timed out after 5 minutes. Please try again.', 'FABRIC_AUTH_TIMEOUT'));
|
|
103
106
|
}, TIMEOUT_MS);
|
|
107
|
+
/**
|
|
108
|
+
* Handles `brokeredAuth.ready` from the broker extension.
|
|
109
|
+
*
|
|
110
|
+
* When the broker extension sends a ready signal the SDK responds with a
|
|
111
|
+
* `brokeredAuth.challenge` containing the PKCE parameters. Only the
|
|
112
|
+
* first ready signal is handled — subsequent signals are ignored.
|
|
113
|
+
*
|
|
114
|
+
* The challenge is sent to `event.source` (the window that sent the ready
|
|
115
|
+
* signal) rather than `fabricWindow`. In Fabric the extension runs in a
|
|
116
|
+
* cross-origin iframe inside the popup — `postMessage` sent to the
|
|
117
|
+
* popup's top-level window does not propagate to child iframes.
|
|
118
|
+
*/
|
|
119
|
+
function handleReady(event) {
|
|
120
|
+
if (readyHandled)
|
|
121
|
+
return;
|
|
122
|
+
if (!event.data || typeof event.data !== 'object')
|
|
123
|
+
return;
|
|
124
|
+
if (event.data.type !== 'brokeredAuth.ready')
|
|
125
|
+
return;
|
|
126
|
+
// Origin check is advisory — the extension iframe runs at a different
|
|
127
|
+
// origin than the Fabric portal. PKCE + state provide the real security.
|
|
128
|
+
if (event.origin !== expectedBrokerOrigin) {
|
|
129
|
+
console.info(`[FabricAuth] brokeredAuth.ready from origin="${event.origin}" (expected portal="${expectedBrokerOrigin}") — continuing (secured by state + PKCE)`);
|
|
130
|
+
}
|
|
131
|
+
readyHandled = true;
|
|
132
|
+
window.removeEventListener('message', handleReady);
|
|
133
|
+
const challengePayload = {
|
|
134
|
+
type: 'brokeredAuth.challenge',
|
|
135
|
+
returnOrigin: options.returnOrigin,
|
|
136
|
+
codeChallenge,
|
|
137
|
+
codeChallengeMethod: 'S256',
|
|
138
|
+
state,
|
|
139
|
+
};
|
|
140
|
+
// Reply to the window that sent the ready signal (the extension iframe).
|
|
141
|
+
const source = event.source;
|
|
142
|
+
if (source) {
|
|
143
|
+
source.postMessage(challengePayload, event.origin || '*');
|
|
144
|
+
console.debug(`[FabricAuth] Sent brokeredAuth.challenge to event.source (origin=${event.origin || '*'})`);
|
|
145
|
+
}
|
|
146
|
+
else if (fabricWindow && !fabricWindow.closed) {
|
|
147
|
+
// Fallback: if event.source is unavailable, try the popup directly.
|
|
148
|
+
fabricWindow.postMessage(challengePayload, expectedBrokerOrigin);
|
|
149
|
+
console.debug('[FabricAuth] Sent brokeredAuth.challenge to fabricWindow (event.source unavailable)');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Listen for brokeredAuth.ready from the popup (postMessage PKCE).
|
|
153
|
+
window.addEventListener('message', handleReady);
|
|
104
154
|
// Listen for handoff via postMessage and BroadcastChannel (legacy bridge).
|
|
105
155
|
console.debug('[FabricAuth] Registering postMessage listener for broker handoff');
|
|
106
156
|
window.addEventListener('message', handleMessage);
|
package/dist/types.d.ts
CHANGED
|
@@ -34,5 +34,13 @@ export interface FabricAuthOptions {
|
|
|
34
34
|
* @deprecated Backward-compat only — will be removed once postMessage rollout is complete.
|
|
35
35
|
*/
|
|
36
36
|
callbackUrl?: string;
|
|
37
|
+
/**
|
|
38
|
+
* When `true`, use embedded (iframe) authentication via postMessage instead
|
|
39
|
+
* of the popup flow. The SDK also auto-detects embedded mode when
|
|
40
|
+
* `?fabricEmbedded=true` is present in `window.location.search`.
|
|
41
|
+
*
|
|
42
|
+
* Set explicitly to override URL-based detection.
|
|
43
|
+
*/
|
|
44
|
+
fabricEmbedded?: boolean;
|
|
37
45
|
}
|
|
38
46
|
//# sourceMappingURL=types.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@microsoft/rayfin-auth-provider-fabric",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.0",
|
|
4
4
|
"description": "Fabric brokered authentication provider for Rayfin SDK",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
],
|
|
13
13
|
"type": "module",
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@microsoft/rayfin-
|
|
16
|
-
"@microsoft/rayfin-
|
|
15
|
+
"@microsoft/rayfin-auth": "1.21.0",
|
|
16
|
+
"@microsoft/rayfin-lib": "1.21.0",
|
|
17
|
+
"@microsoft/fabric-embedded-host": "1.19.0"
|
|
17
18
|
},
|
|
18
19
|
"devDependencies": {
|
|
19
20
|
"typescript": "^5.8.3",
|