Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/auth/src/api/authentication/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export async function requestStsToken(
'refresh_token': refreshToken
}).slice(1);
const { tokenApiHost, apiKey } = auth.config;
const url = _getFinalTarget(
const url = await _getFinalTarget(
auth,
tokenApiHost,
Endpoint.TOKEN,
Expand Down
8 changes: 4 additions & 4 deletions packages/auth/src/api/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,17 +509,17 @@ describe('api/_performApiRequest', () => {
});

context('_getFinalTarget', () => {
it('works properly with a non-emulated environment', () => {
expect(_getFinalTarget(auth, 'host', '/path', 'query=test')).to.eq(
it('works properly with a non-emulated environment', async () => {
expect(await _getFinalTarget(auth, 'host', '/path', 'query=test')).to.eq(
'mock://host/path?query=test'
);
});

it('works properly with an emulated environment', () => {
it('works properly with an emulated environment', async () => {
(auth.config as ConfigInternal).emulator = {
url: 'http://localhost:5000/'
};
expect(_getFinalTarget(auth, 'host', '/path', 'query=test')).to.eq(
expect(await _getFinalTarget(auth, 'host', '/path', 'query=test')).to.eq(
'http://localhost:5000/host/path?query=test'
);
});
Expand Down
22 changes: 12 additions & 10 deletions packages/auth/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export async function _performApiRequest<T, V>(
}

return FetchProvider.fetch()(
_getFinalTarget(auth, auth.config.apiHost, path, query),
await _getFinalTarget(auth, auth.config.apiHost, path, query),
fetchArgs
);
});
Expand Down Expand Up @@ -268,12 +268,12 @@ export async function _performSignInRequest<T, V extends IdTokenResponse>(
return serverResponse as V;
}

export function _getFinalTarget(
export async function _getFinalTarget(
auth: Auth,
host: string,
path: string,
query: string
): string {
): Promise<string> {
const base = `${host}${path}?${query}`;

const authInternal = auth as AuthInternal;
Expand All @@ -284,13 +284,15 @@ export function _getFinalTarget(
// Cookie auth works by MiTMing the signIn and token endpoints from the developer's backend,
// saving the idToken and refreshToken into cookies, and then redacting the refreshToken
// from the response
if (
authInternal._getPersistenceType() === PersistenceType.COOKIE &&
CookieAuthProxiedEndpoints.includes(path)
) {
const cookiePersistence =
authInternal._getPersistence() as CookiePersistence;
return cookiePersistence._getFinalTarget(finalTarget).toString();
if (CookieAuthProxiedEndpoints.includes(path)) {
// Persistence manager is async, we need to await it. We can't just wait for auth initialized
// here since auth initialization calls this function.
await authInternal._persistenceManagerAvailable;
if (authInternal._getPersistenceType() === PersistenceType.COOKIE) {
const cookiePersistence =
authInternal._getPersistence() as CookiePersistence;
return cookiePersistence._getFinalTarget(finalTarget).toString();
}
}

return finalTarget;
Expand Down
10 changes: 10 additions & 0 deletions packages/auth/src/core/auth/auth_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
_tenantRecaptchaConfigs: Record<string, RecaptchaConfig> = {};
_projectPasswordPolicy: PasswordPolicyInternal | null = null;
_tenantPasswordPolicies: Record<string, PasswordPolicyInternal> = {};
_resolvePersistenceManagerAvailable:
| ((value: void | PromiseLike<void>) => void)
| undefined = undefined;
_persistenceManagerAvailable: Promise<void>;
readonly name: string;

// Tracks the last notified UID for state change listeners to prevent
Expand All @@ -139,6 +143,11 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
) {
this.name = app.name;
this.clientVersion = config.sdkClientVersion;
// TODO(jamesdaniels) explore less hacky way to do this, cookie authentication needs
// persistenceMananger to be available. see _getFinalTarget for more context
this._persistenceManagerAvailable = new Promise<void>(
resolve => (this._resolvePersistenceManagerAvailable = resolve)
);
Copy link
Member Author

@jamesdaniels jamesdaniels Mar 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can i haz Promise.withResolvers yet? 🤣

}

_initializeWithPersistence(
Expand All @@ -160,6 +169,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
this,
persistenceHierarchy
);
this._resolvePersistenceManagerAvailable?.();

if (this._deleted) {
return;
Expand Down
1 change: 1 addition & 0 deletions packages/auth/src/model/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export interface AuthInternal extends Auth {
_canInitEmulator: boolean;
_isInitialized: boolean;
_initializationPromise: Promise<void> | null;
_persistenceManagerAvailable: Promise<void>;
_updateCurrentUser(user: UserInternal | null): Promise<void>;

_onStorageEvent(): void;
Expand Down
Loading