@microsoft/rayfin-auth-provider-fabric 1.35.0-alpha.1412 → 1.35.0-beta.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/embeddedFabricLogin.d.ts +9 -4
- package/dist/embeddedFabricLogin.js +20 -20
- package/dist/ensureSignedInWithFabric.d.ts +4 -6
- package/dist/ensureSignedInWithFabric.js +19 -17
- package/dist/fabricAuthHelpers.d.ts +4 -8
- package/dist/fabricAuthHelpers.js +12 -21
- package/dist/fabricUserHint.d.ts +53 -0
- package/dist/fabricUserHint.js +77 -0
- package/dist/initEmbeddedAuth.d.ts +5 -7
- package/dist/initEmbeddedAuth.js +11 -15
- package/package.json +4 -4
|
@@ -4,17 +4,22 @@ import type { FabricAuthOptions } from './types.js';
|
|
|
4
4
|
* Embedded-mode Fabric login — acquires a session via postMessage to the
|
|
5
5
|
* parent Fabric Extension Host without opening a popup.
|
|
6
6
|
*
|
|
7
|
-
* 1.
|
|
8
|
-
* so a stale session from a previous Fabric user cannot be reused.
|
|
7
|
+
* 1. Discards any prior session when the host stamps no `?_fu=` hint (see below).
|
|
9
8
|
* 2. Generates PKCE parameters in local variables (no `localStorage`).
|
|
10
9
|
* 3. Sends `auth.requestHandoff` to the parent via `requestHandoff`.
|
|
11
10
|
* 4. Receives the handoff code from the host's `AuthPlugin`.
|
|
12
11
|
* 5. Exchanges the handoff code for tokens via `exchangeVerificationCode`.
|
|
13
|
-
* 6. Creates a session via `createSessionFromTokenResponse
|
|
14
|
-
* embedded handoff as completed for this page load.
|
|
12
|
+
* 6. Creates a session via `createSessionFromTokenResponse`.
|
|
15
13
|
*
|
|
16
14
|
* The session is stored in the iframe's own `localStorage`.
|
|
17
15
|
*
|
|
16
|
+
* **Sign-out is conditional.** This function used to sign out unconditionally, as the only defence
|
|
17
|
+
* against reusing a previous Fabric user's session — at the cost of destroying a valid session (and
|
|
18
|
+
* the serve cookie sealed to it) on every single embedded load. A host that stamps `?_fu=` gives the
|
|
19
|
+
* workload gate an identity to compare, so that defence is redundant and the sign-out is skipped. A
|
|
20
|
+
* host that does not is indistinguishable from the pre-feature world, so the original behaviour is
|
|
21
|
+
* kept for it rather than silently dropping the protection during version skew.
|
|
22
|
+
*
|
|
18
23
|
* @param auth - Auth instance for token exchange and session management.
|
|
19
24
|
* @param options - Fabric auth options (`returnOrigin` required).
|
|
20
25
|
* @throws `AuthError` - On missing options, transport failure, or token exchange error.
|
|
@@ -3,21 +3,27 @@ import { createSessionFromTokenResponse } from '@microsoft/rayfin-auth/_internal
|
|
|
3
3
|
import { AuthError, assertBrowser } from '@microsoft/rayfin-lib';
|
|
4
4
|
import { requestHandoff } from './PostMessageAuthTransport.js';
|
|
5
5
|
import { markEmbeddedHandoffCompleted } from './fabricAuthHelpers.js';
|
|
6
|
+
import { hasFabricUserHint } from './fabricUserHint.js';
|
|
6
7
|
/**
|
|
7
8
|
* Embedded-mode Fabric login — acquires a session via postMessage to the
|
|
8
9
|
* parent Fabric Extension Host without opening a popup.
|
|
9
10
|
*
|
|
10
|
-
* 1.
|
|
11
|
-
* so a stale session from a previous Fabric user cannot be reused.
|
|
11
|
+
* 1. Discards any prior session when the host stamps no `?_fu=` hint (see below).
|
|
12
12
|
* 2. Generates PKCE parameters in local variables (no `localStorage`).
|
|
13
13
|
* 3. Sends `auth.requestHandoff` to the parent via `requestHandoff`.
|
|
14
14
|
* 4. Receives the handoff code from the host's `AuthPlugin`.
|
|
15
15
|
* 5. Exchanges the handoff code for tokens via `exchangeVerificationCode`.
|
|
16
|
-
* 6. Creates a session via `createSessionFromTokenResponse
|
|
17
|
-
* embedded handoff as completed for this page load.
|
|
16
|
+
* 6. Creates a session via `createSessionFromTokenResponse`.
|
|
18
17
|
*
|
|
19
18
|
* The session is stored in the iframe's own `localStorage`.
|
|
20
19
|
*
|
|
20
|
+
* **Sign-out is conditional.** This function used to sign out unconditionally, as the only defence
|
|
21
|
+
* against reusing a previous Fabric user's session — at the cost of destroying a valid session (and
|
|
22
|
+
* the serve cookie sealed to it) on every single embedded load. A host that stamps `?_fu=` gives the
|
|
23
|
+
* workload gate an identity to compare, so that defence is redundant and the sign-out is skipped. A
|
|
24
|
+
* host that does not is indistinguishable from the pre-feature world, so the original behaviour is
|
|
25
|
+
* kept for it rather than silently dropping the protection during version skew.
|
|
26
|
+
*
|
|
21
27
|
* @param auth - Auth instance for token exchange and session management.
|
|
22
28
|
* @param options - Fabric auth options (`returnOrigin` required).
|
|
23
29
|
* @throws `AuthError` - On missing options, transport failure, or token exchange error.
|
|
@@ -27,22 +33,16 @@ export async function embeddedFabricLogin(auth, options) {
|
|
|
27
33
|
if (!options.returnOrigin) {
|
|
28
34
|
throw new AuthError('returnOrigin is required for embedded Fabric authentication.', 'MISSING_RETURN_ORIGIN');
|
|
29
35
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// its own catch block) is what actually matters here.
|
|
41
|
-
try {
|
|
42
|
-
await auth.signOut();
|
|
43
|
-
}
|
|
44
|
-
catch (signOutError) {
|
|
45
|
-
console.debug('[FabricAuth:embedded] Pre-handoff signOut failed (non-fatal); local session has been cleared', signOutError);
|
|
36
|
+
if (!hasFabricUserHint()) {
|
|
37
|
+
// Legacy host: nothing server-side is comparing identities, so fall back to discarding any prior
|
|
38
|
+
// session before the handoff. Errors are swallowed — the stale token may already be invalid
|
|
39
|
+
// server-side, and the local clear that signOut performs in its own catch is what matters.
|
|
40
|
+
try {
|
|
41
|
+
await auth.signOut();
|
|
42
|
+
}
|
|
43
|
+
catch (signOutError) {
|
|
44
|
+
console.debug('[FabricAuth:embedded] Pre-handoff signOut failed (non-fatal); local session has been cleared', signOutError);
|
|
45
|
+
}
|
|
46
46
|
}
|
|
47
47
|
// PKCE in local variables only — not persisted to localStorage.
|
|
48
48
|
const codeVerifier = generateCodeVerifier();
|
|
@@ -6,16 +6,14 @@ import type { FabricAuthOptions } from './types.js';
|
|
|
6
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
|
-
* return the existing session immediately.
|
|
10
|
-
*
|
|
11
|
-
* session belonging to a previously signed-in Fabric user.
|
|
9
|
+
* return the existing session immediately. On a legacy embedded host (one that
|
|
10
|
+
* stamps no `?_fu=` hint) this is skipped on the first call per page load, so a
|
|
11
|
+
* session belonging to a previously signed-in Fabric user cannot be reused.
|
|
12
12
|
* 2. **Refresh token** — if a refresh token is available, attempt `auth.refreshSession()`.
|
|
13
13
|
* Return the refreshed session on success; continue on failure.
|
|
14
|
-
*
|
|
14
|
+
* Subject to the same skip.
|
|
15
15
|
* 3. **Embedded mode** — if running inside a Fabric iframe (`fabricEmbedded=true`),
|
|
16
16
|
* use `embeddedFabricLogin()` to acquire a session via `postMessage` handoff.
|
|
17
|
-
* `embeddedFabricLogin` performs a hard `auth.signOut()` before the
|
|
18
|
-
* handoff so the new session always reflects the current Fabric user.
|
|
19
17
|
* 4. **Open Fabric broker** — no existing auth path available. Open the Fabric Portal
|
|
20
18
|
* in a new tab via `initiateFabricLogin()` and wait for the Fabric extension to post
|
|
21
19
|
* the handoff code via `postMessage`. The function exchanges the code internally
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AuthError, assertBrowser } from '@microsoft/rayfin-lib';
|
|
2
2
|
import { embeddedFabricLogin } from './embeddedFabricLogin.js';
|
|
3
3
|
import { hasEmbeddedHandoffCompleted, isEmbeddedMode, tryResumeSession, } from './fabricAuthHelpers.js';
|
|
4
|
+
import { hasFabricUserHint } from './fabricUserHint.js';
|
|
4
5
|
import { initiateFabricLogin } from './initiateFabricLogin.js';
|
|
5
6
|
/**
|
|
6
7
|
* Ensures the user is signed in via Fabric brokered authentication.
|
|
@@ -8,16 +9,14 @@ import { initiateFabricLogin } from './initiateFabricLogin.js';
|
|
|
8
9
|
* Implements a multi-step waterfall — the first step that succeeds short-circuits the rest:
|
|
9
10
|
*
|
|
10
11
|
* 1. **Already authenticated** — if `auth.getSession().isAuthenticated` is true,
|
|
11
|
-
* return the existing session immediately.
|
|
12
|
-
*
|
|
13
|
-
* session belonging to a previously signed-in Fabric user.
|
|
12
|
+
* return the existing session immediately. On a legacy embedded host (one that
|
|
13
|
+
* stamps no `?_fu=` hint) this is skipped on the first call per page load, so a
|
|
14
|
+
* session belonging to a previously signed-in Fabric user cannot be reused.
|
|
14
15
|
* 2. **Refresh token** — if a refresh token is available, attempt `auth.refreshSession()`.
|
|
15
16
|
* Return the refreshed session on success; continue on failure.
|
|
16
|
-
*
|
|
17
|
+
* Subject to the same skip.
|
|
17
18
|
* 3. **Embedded mode** — if running inside a Fabric iframe (`fabricEmbedded=true`),
|
|
18
19
|
* use `embeddedFabricLogin()` to acquire a session via `postMessage` handoff.
|
|
19
|
-
* `embeddedFabricLogin` performs a hard `auth.signOut()` before the
|
|
20
|
-
* handoff so the new session always reflects the current Fabric user.
|
|
21
20
|
* 4. **Open Fabric broker** — no existing auth path available. Open the Fabric Portal
|
|
22
21
|
* in a new tab via `initiateFabricLogin()` and wait for the Fabric extension to post
|
|
23
22
|
* the handoff code via `postMessage`. The function exchanges the code internally
|
|
@@ -66,23 +65,26 @@ import { initiateFabricLogin } from './initiateFabricLogin.js';
|
|
|
66
65
|
*/
|
|
67
66
|
export async function ensureSignedInWithFabric(auth, options) {
|
|
68
67
|
assertBrowser('ensureSignedInWithFabric');
|
|
69
|
-
// Steps 1-2: existing session or refresh.
|
|
70
|
-
// In embedded mode we MUST skip this on the first call per page load —
|
|
71
|
-
// the session in iframe localStorage may belong to a previously
|
|
72
|
-
// signed-in Fabric user. After the first successful handoff this load
|
|
73
|
-
// (`hasEmbeddedHandoffCompleted()` is true) the cached session is the
|
|
74
|
-
// current user's, so resuming is safe and avoids redundant handoffs.
|
|
75
68
|
const inEmbeddedMode = isEmbeddedMode(options);
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
// Steps 1-2: existing session or refresh.
|
|
70
|
+
//
|
|
71
|
+
// A host that stamps `?_fu=` has already had its cookie identity-checked by the workload gate
|
|
72
|
+
// before this code ran — a cross-user cookie is cleared and a fresh sign-in bootstrap is served,
|
|
73
|
+
// overwriting this origin's persisted session — so resuming is safe from the first call.
|
|
74
|
+
//
|
|
75
|
+
// A host that stamps no hint is indistinguishable from the pre-feature world: nothing compared the
|
|
76
|
+
// identities, so the persisted session may belong to a previously signed-in Fabric user. There we
|
|
77
|
+
// keep the original behaviour and skip resume until a handoff has run this page load.
|
|
78
|
+
const skipResume = inEmbeddedMode && !hasFabricUserHint() && !hasEmbeddedHandoffCompleted();
|
|
79
|
+
if (skipResume) {
|
|
80
|
+
console.debug('[FabricAuth] Embedded host stamped no user hint — skipping session resume to avoid a stale cross-user session');
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
78
83
|
const resumed = await tryResumeSession(auth);
|
|
79
84
|
if (resumed) {
|
|
80
85
|
return resumed;
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
|
-
else {
|
|
84
|
-
console.debug('[FabricAuth] Embedded mode first load — skipping session resume to avoid stale cross-user session');
|
|
85
|
-
}
|
|
86
88
|
// Step 3: Embedded mode — postMessage auth via parent iframe host
|
|
87
89
|
if (inEmbeddedMode) {
|
|
88
90
|
console.debug('[FabricAuth] Embedded mode detected, using postMessage auth');
|
|
@@ -11,21 +11,17 @@ import type { FabricAuthOptions } from './types.js';
|
|
|
11
11
|
*/
|
|
12
12
|
export declare function isEmbeddedMode(options: FabricAuthOptions): boolean;
|
|
13
13
|
/**
|
|
14
|
-
* Returns whether an embedded postMessage handoff has already completed
|
|
15
|
-
* during the current page load.
|
|
14
|
+
* Returns whether an embedded postMessage handoff has already completed during this page load.
|
|
16
15
|
*/
|
|
17
16
|
export declare function hasEmbeddedHandoffCompleted(): boolean;
|
|
18
17
|
/**
|
|
19
|
-
* Marks that an embedded postMessage handoff has completed for this page
|
|
20
|
-
* load. Subsequent calls to {@link tryResumeSession} will be allowed
|
|
21
|
-
* (the established session is the current user's session).
|
|
18
|
+
* Marks that an embedded postMessage handoff has completed for this page load.
|
|
22
19
|
*/
|
|
23
20
|
export declare function markEmbeddedHandoffCompleted(): void;
|
|
24
21
|
/**
|
|
25
|
-
*
|
|
26
|
-
* Not exported from the package barrel.
|
|
22
|
+
* Clears the embedded-handoff completion flag.
|
|
27
23
|
*
|
|
28
|
-
* @internal
|
|
24
|
+
* @internal Test helper. Not exported from the package barrel.
|
|
29
25
|
*/
|
|
30
26
|
export declare function resetEmbeddedHandoffStateForTests(): void;
|
|
31
27
|
/**
|
|
@@ -12,43 +12,34 @@ export function isEmbeddedMode(options) {
|
|
|
12
12
|
return sharedIsEmbeddedMode(options);
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
* already completed during the current page load.
|
|
15
|
+
* Tracks whether an embedded postMessage handoff has completed during the current page load.
|
|
17
16
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* the
|
|
21
|
-
*
|
|
22
|
-
*
|
|
17
|
+
* Only consulted on the **legacy** embedded path — a host that stamps no `?_fu=` hint, and therefore
|
|
18
|
+
* gives the workload gate nothing to compare. There, a persisted session cannot be trusted to belong
|
|
19
|
+
* to the current Fabric user, so the first auth call per page load skips resume and forces a full
|
|
20
|
+
* handoff. Once that handoff has run, the stored session is known to be the current user's and later
|
|
21
|
+
* calls in the same load may resume it without thrashing the postMessage channel.
|
|
23
22
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* inside {@link embeddedFabricLogin}. Once that completes we set this
|
|
27
|
-
* flag so subsequent calls (e.g. additional components asking for the
|
|
28
|
-
* session) can reuse the freshly established session via
|
|
29
|
-
* {@link tryResumeSession} without thrashing the postMessage channel.
|
|
23
|
+
* Hosts that do stamp a hint never reach this flag: the gate rejects a cross-user cookie before the
|
|
24
|
+
* app boots, so resume is safe from the first call.
|
|
30
25
|
*/
|
|
31
26
|
let embeddedHandoffCompletedThisLoad = false;
|
|
32
27
|
/**
|
|
33
|
-
* Returns whether an embedded postMessage handoff has already completed
|
|
34
|
-
* during the current page load.
|
|
28
|
+
* Returns whether an embedded postMessage handoff has already completed during this page load.
|
|
35
29
|
*/
|
|
36
30
|
export function hasEmbeddedHandoffCompleted() {
|
|
37
31
|
return embeddedHandoffCompletedThisLoad;
|
|
38
32
|
}
|
|
39
33
|
/**
|
|
40
|
-
* Marks that an embedded postMessage handoff has completed for this page
|
|
41
|
-
* load. Subsequent calls to {@link tryResumeSession} will be allowed
|
|
42
|
-
* (the established session is the current user's session).
|
|
34
|
+
* Marks that an embedded postMessage handoff has completed for this page load.
|
|
43
35
|
*/
|
|
44
36
|
export function markEmbeddedHandoffCompleted() {
|
|
45
37
|
embeddedHandoffCompletedThisLoad = true;
|
|
46
38
|
}
|
|
47
39
|
/**
|
|
48
|
-
*
|
|
49
|
-
* Not exported from the package barrel.
|
|
40
|
+
* Clears the embedded-handoff completion flag.
|
|
50
41
|
*
|
|
51
|
-
* @internal
|
|
42
|
+
* @internal Test helper. Not exported from the package barrel.
|
|
52
43
|
*/
|
|
53
44
|
export function resetEmbeddedHandoffStateForTests() {
|
|
54
45
|
embeddedHandoffCompletedThisLoad = false;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fabric user-hint detection.
|
|
3
|
+
*
|
|
4
|
+
* The AppBackend host stamps an opaque `?_fu=` hint onto the embedded iframe URL, and the workload's
|
|
5
|
+
* static-hosting gate compares it against the hint sealed into the serve cookie. Its presence is the
|
|
6
|
+
* SDK's signal that **the host is new enough for cross-user protection to exist server-side**, and
|
|
7
|
+
* therefore that a persisted session may safely be resumed.
|
|
8
|
+
*
|
|
9
|
+
* When it is absent — an older AppBackend that does not stamp it, or a host that could not resolve
|
|
10
|
+
* the signed-in user — no server-side comparison is happening, so the SDK falls back to the original
|
|
11
|
+
* behaviour: discard any persisted session and run a full handoff on every embedded load. That is
|
|
12
|
+
* slower, but it is the only thing standing between a previous Fabric user's session and the current
|
|
13
|
+
* one on a host that cannot help.
|
|
14
|
+
*
|
|
15
|
+
* The URL value is captured eagerly at module load, so a client-side navigation that strips the query
|
|
16
|
+
* string does not make the SDK think the host got older mid-session and start signing the user out.
|
|
17
|
+
*
|
|
18
|
+
* That capture is deliberately held in module state rather than `sessionStorage`. Storage outlives a
|
|
19
|
+
* document, so a hint from an earlier load would still answer for a load the host never stamped — for
|
|
20
|
+
* example after a user switch where identity resolution failed — and the SDK would resume a persisted
|
|
21
|
+
* session that no server-side comparison ever vetted. Module state dies with the document, which is
|
|
22
|
+
* exactly the lifetime this signal is allowed to have.
|
|
23
|
+
*
|
|
24
|
+
* @internal Not exported from the package barrel.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Captures the `?_fu=` value from the current URL.
|
|
28
|
+
*
|
|
29
|
+
* Called once at module load to snapshot the entry URL. Also the in-package injection point for
|
|
30
|
+
* tests; it is not exported from the barrel, so app code cannot use it to fake the signal.
|
|
31
|
+
*
|
|
32
|
+
* @returns `true` when the URL carried a hint, whether or not it had already been captured.
|
|
33
|
+
*/
|
|
34
|
+
export declare function persistFabricUserHintFromUrl(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Whether the host stamped a Fabric user hint for this app.
|
|
37
|
+
*
|
|
38
|
+
* Reports the entry URL captured at module load and nothing else. It deliberately does not re-read
|
|
39
|
+
* the live URL: on the client the hint's *presence* suppresses the cross-user sign-out, so a value
|
|
40
|
+
* added after load - by app routing, a query-preserving link, or anything else the gate never saw -
|
|
41
|
+
* would let the page vouch for itself. The server-side comparison has the opposite polarity, where a
|
|
42
|
+
* forged hint can only force a re-authentication, so this direction is the one that has to be sealed.
|
|
43
|
+
*
|
|
44
|
+
* @returns `true` when server-side identity gating is in play and a persisted session may be reused.
|
|
45
|
+
*/
|
|
46
|
+
export declare function hasFabricUserHint(): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Clears the captured hint.
|
|
49
|
+
*
|
|
50
|
+
* @internal Test helper.
|
|
51
|
+
*/
|
|
52
|
+
export declare function clearFabricUserHint(): void;
|
|
53
|
+
//# sourceMappingURL=fabricUserHint.d.ts.map
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fabric user-hint detection.
|
|
3
|
+
*
|
|
4
|
+
* The AppBackend host stamps an opaque `?_fu=` hint onto the embedded iframe URL, and the workload's
|
|
5
|
+
* static-hosting gate compares it against the hint sealed into the serve cookie. Its presence is the
|
|
6
|
+
* SDK's signal that **the host is new enough for cross-user protection to exist server-side**, and
|
|
7
|
+
* therefore that a persisted session may safely be resumed.
|
|
8
|
+
*
|
|
9
|
+
* When it is absent — an older AppBackend that does not stamp it, or a host that could not resolve
|
|
10
|
+
* the signed-in user — no server-side comparison is happening, so the SDK falls back to the original
|
|
11
|
+
* behaviour: discard any persisted session and run a full handoff on every embedded load. That is
|
|
12
|
+
* slower, but it is the only thing standing between a previous Fabric user's session and the current
|
|
13
|
+
* one on a host that cannot help.
|
|
14
|
+
*
|
|
15
|
+
* The URL value is captured eagerly at module load, so a client-side navigation that strips the query
|
|
16
|
+
* string does not make the SDK think the host got older mid-session and start signing the user out.
|
|
17
|
+
*
|
|
18
|
+
* That capture is deliberately held in module state rather than `sessionStorage`. Storage outlives a
|
|
19
|
+
* document, so a hint from an earlier load would still answer for a load the host never stamped — for
|
|
20
|
+
* example after a user switch where identity resolution failed — and the SDK would resume a persisted
|
|
21
|
+
* session that no server-side comparison ever vetted. Module state dies with the document, which is
|
|
22
|
+
* exactly the lifetime this signal is allowed to have.
|
|
23
|
+
*
|
|
24
|
+
* @internal Not exported from the package barrel.
|
|
25
|
+
*/
|
|
26
|
+
const USER_HINT_QUERY_PARAM = '_fu';
|
|
27
|
+
// Scoped to this document. See the note above on why this is not persisted.
|
|
28
|
+
let hintSeenThisDocument = false;
|
|
29
|
+
/**
|
|
30
|
+
* Captures the `?_fu=` value from the current URL.
|
|
31
|
+
*
|
|
32
|
+
* Called once at module load to snapshot the entry URL. Also the in-package injection point for
|
|
33
|
+
* tests; it is not exported from the barrel, so app code cannot use it to fake the signal.
|
|
34
|
+
*
|
|
35
|
+
* @returns `true` when the URL carried a hint, whether or not it had already been captured.
|
|
36
|
+
*/
|
|
37
|
+
export function persistFabricUserHintFromUrl() {
|
|
38
|
+
if (typeof window === 'undefined') {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const hint = new URLSearchParams(window.location.search).get(USER_HINT_QUERY_PARAM);
|
|
43
|
+
if (hint) {
|
|
44
|
+
hintSeenThisDocument = true;
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// window.location may be unavailable in some non-browser hosts.
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
// Captured at module load so a session resumed before any auth call still sees the hint.
|
|
54
|
+
persistFabricUserHintFromUrl();
|
|
55
|
+
/**
|
|
56
|
+
* Whether the host stamped a Fabric user hint for this app.
|
|
57
|
+
*
|
|
58
|
+
* Reports the entry URL captured at module load and nothing else. It deliberately does not re-read
|
|
59
|
+
* the live URL: on the client the hint's *presence* suppresses the cross-user sign-out, so a value
|
|
60
|
+
* added after load - by app routing, a query-preserving link, or anything else the gate never saw -
|
|
61
|
+
* would let the page vouch for itself. The server-side comparison has the opposite polarity, where a
|
|
62
|
+
* forged hint can only force a re-authentication, so this direction is the one that has to be sealed.
|
|
63
|
+
*
|
|
64
|
+
* @returns `true` when server-side identity gating is in play and a persisted session may be reused.
|
|
65
|
+
*/
|
|
66
|
+
export function hasFabricUserHint() {
|
|
67
|
+
return hintSeenThisDocument;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Clears the captured hint.
|
|
71
|
+
*
|
|
72
|
+
* @internal Test helper.
|
|
73
|
+
*/
|
|
74
|
+
export function clearFabricUserHint() {
|
|
75
|
+
hintSeenThisDocument = false;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=fabricUserHint.js.map
|
|
@@ -11,13 +11,11 @@ import type { FabricAuthOptions } from './types.js';
|
|
|
11
11
|
* **Behaviour:**
|
|
12
12
|
* - If `fabricEmbedded=true` is **not** in the URL and `options.fabricEmbedded`
|
|
13
13
|
* is not `true`, returns `null` immediately (no-op).
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* - On subsequent calls within the same page load, runs the standard
|
|
20
|
-
* resume waterfall (existing session → refresh token → handoff).
|
|
14
|
+
* - When the host stamps a `?_fu=` hint, runs the standard resume waterfall
|
|
15
|
+
* (existing session → refresh token → handoff).
|
|
16
|
+
* - When it does not, skips resume on the first call per page load and goes
|
|
17
|
+
* straight to the handoff, preserving the pre-hint protection against reusing
|
|
18
|
+
* a previously signed-in Fabric user's session.
|
|
21
19
|
*
|
|
22
20
|
* Apps that also support the popup flow should continue to call
|
|
23
21
|
* {@link ensureSignedInWithFabric} from a user-gesture handler for the
|
package/dist/initEmbeddedAuth.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { embeddedFabricLogin } from './embeddedFabricLogin.js';
|
|
2
2
|
import { hasEmbeddedHandoffCompleted, isEmbeddedMode, tryResumeSession, } from './fabricAuthHelpers.js';
|
|
3
|
+
import { hasFabricUserHint } from './fabricUserHint.js';
|
|
3
4
|
/**
|
|
4
5
|
* Initializes embedded Fabric authentication if running inside an iframe
|
|
5
6
|
* with `?fabricEmbedded=true`.
|
|
@@ -11,13 +12,11 @@ import { hasEmbeddedHandoffCompleted, isEmbeddedMode, tryResumeSession, } from '
|
|
|
11
12
|
* **Behaviour:**
|
|
12
13
|
* - If `fabricEmbedded=true` is **not** in the URL and `options.fabricEmbedded`
|
|
13
14
|
* is not `true`, returns `null` immediately (no-op).
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* - On subsequent calls within the same page load, runs the standard
|
|
20
|
-
* resume waterfall (existing session → refresh token → handoff).
|
|
15
|
+
* - When the host stamps a `?_fu=` hint, runs the standard resume waterfall
|
|
16
|
+
* (existing session → refresh token → handoff).
|
|
17
|
+
* - When it does not, skips resume on the first call per page load and goes
|
|
18
|
+
* straight to the handoff, preserving the pre-hint protection against reusing
|
|
19
|
+
* a previously signed-in Fabric user's session.
|
|
21
20
|
*
|
|
22
21
|
* Apps that also support the popup flow should continue to call
|
|
23
22
|
* {@link ensureSignedInWithFabric} from a user-gesture handler for the
|
|
@@ -31,20 +30,17 @@ export async function initEmbeddedAuth(auth, options) {
|
|
|
31
30
|
if (!isEmbeddedMode(options)) {
|
|
32
31
|
return null;
|
|
33
32
|
}
|
|
34
|
-
// Steps 1-2: existing session or refresh.
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
// `embeddedFabricLogin` has succeeded for this load, the cached
|
|
39
|
-
// session is the current user's and resuming is safe.
|
|
40
|
-
if (hasEmbeddedHandoffCompleted()) {
|
|
33
|
+
// Steps 1-2: existing session or refresh. Skipped on a legacy host (no `?_fu=` hint) until a
|
|
34
|
+
// handoff has run this page load, because nothing server-side compared the session's identity
|
|
35
|
+
// against the current Fabric user. See ensureSignedInWithFabric for the full reasoning.
|
|
36
|
+
if (hasFabricUserHint() || hasEmbeddedHandoffCompleted()) {
|
|
41
37
|
const resumed = await tryResumeSession(auth);
|
|
42
38
|
if (resumed) {
|
|
43
39
|
return resumed;
|
|
44
40
|
}
|
|
45
41
|
}
|
|
46
42
|
else {
|
|
47
|
-
console.debug('[FabricAuth:initEmbedded]
|
|
43
|
+
console.debug('[FabricAuth:initEmbedded] Embedded host stamped no user hint — skipping session resume to avoid a stale cross-user session');
|
|
48
44
|
}
|
|
49
45
|
// Step 3: postMessage handoff (no popup, no window.open)
|
|
50
46
|
console.debug('[FabricAuth:initEmbedded] Starting postMessage handoff flow');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@microsoft/rayfin-auth-provider-fabric",
|
|
3
|
-
"version": "1.35.0-
|
|
3
|
+
"version": "1.35.0-beta.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.35.0-
|
|
17
|
-
"@microsoft/
|
|
18
|
-
"@microsoft/
|
|
16
|
+
"@microsoft/rayfin-auth": "1.35.0-beta.0",
|
|
17
|
+
"@microsoft/fabric-embedded-host": "1.35.0-beta.0",
|
|
18
|
+
"@microsoft/rayfin-lib": "1.35.0-beta.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"typescript": "^5.8.3",
|