Skip to content

Commit ca3f264

Browse files
authored
Merge 2646d94 into ea1f913
2 parents ea1f913 + 2646d94 commit ca3f264

File tree

28 files changed

+314
-44
lines changed

28 files changed

+314
-44
lines changed

.changeset/nine-pugs-crash.md

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@firebase/app-check": patch
3+
"@firebase/auth": patch
4+
"@firebase/data-connect": patch
5+
"@firebase/database-compat": patch
6+
"@firebase/database": patch
7+
"@firebase/firestore": patch
8+
"@firebase/storage": patch
9+
"@firebase/util": patch
10+
---
11+
12+
Fix Auth Redirects on Firebase Studio

common/api-review/storage.api.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export class _FirebaseStorageImpl implements FirebaseStorage {
5858
constructor(
5959
app: FirebaseApp, _authProvider: Provider<FirebaseAuthInternalName>,
6060
_appCheckProvider: Provider<AppCheckInternalComponentName>,
61-
_url?: string | undefined, _firebaseVersion?: string | undefined);
61+
_url?: string | undefined, _firebaseVersion?: string | undefined, _isUsingEmulator?: boolean);
6262
readonly app: FirebaseApp;
6363
// (undocumented)
6464
readonly _appCheckProvider: Provider<AppCheckInternalComponentName>;
@@ -77,6 +77,8 @@ export class _FirebaseStorageImpl implements FirebaseStorage {
7777
_getAuthToken(): Promise<string | null>;
7878
get host(): string;
7979
set host(host: string);
80+
// (undocumented)
81+
_isUsingEmulator: boolean;
8082
// Warning: (ae-forgotten-export) The symbol "ConnectionType" needs to be exported by the entry point index.d.ts
8183
// Warning: (ae-forgotten-export) The symbol "RequestInfo" needs to be exported by the entry point index.d.ts
8284
// Warning: (ae-forgotten-export) The symbol "Connection" needs to be exported by the entry point index.d.ts

packages/auth/src/api/index.test.ts

+24
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ describe('api/_performApiRequest', () => {
6060
auth = await testAuth();
6161
});
6262

63+
afterEach(() => {
64+
sinon.restore();
65+
});
66+
6367
context('with regular requests', () => {
6468
beforeEach(mockFetch.setUp);
6569
afterEach(mockFetch.tearDown);
@@ -80,6 +84,26 @@ describe('api/_performApiRequest', () => {
8084
expect(mock.calls[0].headers!.get(HttpHeader.X_CLIENT_VERSION)).to.eq(
8185
'testSDK/0.0.0'
8286
);
87+
expect(mock.calls[0].fullRequest?.credentials).to.be.undefined;
88+
});
89+
90+
it('should set credentials to "include" when using IDX and emulator', async () => {
91+
const mock = mockEndpoint(Endpoint.SIGN_UP, serverResponse);
92+
auth.emulatorConfig = {
93+
host: 'https://something.cloudworkstations.dev',
94+
protocol: '',
95+
port: 8,
96+
options: {
97+
disableWarnings: false
98+
}
99+
};
100+
await _performApiRequest<typeof request, typeof serverResponse>(
101+
auth,
102+
HttpMethod.POST,
103+
Endpoint.SIGN_UP,
104+
request
105+
);
106+
expect(mock.calls[0].fullRequest?.credentials).to.eq('include');
83107
});
84108

85109
it('should set the device language if available', async () => {

packages/auth/src/api/index.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { FirebaseError, isCloudflareWorker, querystring } from '@firebase/util';
18+
import {
19+
FirebaseError,
20+
isCloudflareWorker,
21+
isCloudWorkstation,
22+
querystring
23+
} from '@firebase/util';
1924

2025
import { AuthErrorCode, NamedErrorParams } from '../core/errors';
2126
import {
@@ -177,6 +182,10 @@ export async function _performApiRequest<T, V>(
177182
fetchArgs.referrerPolicy = 'no-referrer';
178183
}
179184

185+
if (auth.emulatorConfig && isCloudWorkstation(auth.emulatorConfig.host)) {
186+
fetchArgs.credentials = 'include';
187+
}
188+
180189
return FetchProvider.fetch()(
181190
await _getFinalTarget(auth, auth.config.apiHost, path, query),
182191
fetchArgs

packages/auth/test/helpers/mock_fetch.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export interface Call {
2222
request?: object | string;
2323
method?: string;
2424
headers: Headers;
25+
fullRequest?: RequestInit;
2526
}
2627

2728
export interface Route {
@@ -59,7 +60,8 @@ const fakeFetch: typeof fetch = (
5960
calls.push({
6061
request: requestBody,
6162
method: request?.method,
62-
headers
63+
headers,
64+
fullRequest: request
6365
});
6466

6567
return Promise.resolve(

packages/data-connect/src/network/fetch.ts

+12-6
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@
1515
* limitations under the License.
1616
*/
1717

18+
import { isCloudWorkstation } from '@firebase/util';
19+
1820
import {
1921
Code,
2022
DataConnectError,
2123
DataConnectOperationError,
2224
DataConnectOperationFailureResponse
2325
} from '../core/error';
2426
import { SDK_VERSION } from '../core/version';
25-
import { logDebug, logError } from '../logger';
27+
import { logError } from '../logger';
2628

2729
import { CallerSdkType, CallerSdkTypeEnum } from './transport';
2830

@@ -58,7 +60,8 @@ export function dcFetch<T, U>(
5860
accessToken: string | null,
5961
appCheckToken: string | null,
6062
_isUsingGen: boolean,
61-
_callerSdkType: CallerSdkType
63+
_callerSdkType: CallerSdkType,
64+
_isUsingEmulator: boolean
6265
): Promise<{ data: T; errors: Error[] }> {
6366
if (!connectFetch) {
6467
throw new DataConnectError(Code.OTHER, 'No Fetch Implementation detected!');
@@ -77,14 +80,17 @@ export function dcFetch<T, U>(
7780
headers['X-Firebase-AppCheck'] = appCheckToken;
7881
}
7982
const bodyStr = JSON.stringify(body);
80-
logDebug(`Making request out to ${url} with body: ${bodyStr}`);
81-
82-
return connectFetch(url, {
83+
const fetchOptions: RequestInit = {
8384
body: bodyStr,
8485
method: 'POST',
8586
headers,
8687
signal
87-
})
88+
};
89+
if (isCloudWorkstation(url) && _isUsingEmulator) {
90+
fetchOptions.credentials = 'include';
91+
}
92+
93+
return connectFetch(url, fetchOptions)
8894
.catch(err => {
8995
throw new DataConnectError(
9096
Code.OTHER,

packages/data-connect/src/network/transport/rest.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export class RESTTransport implements DataConnectTransport {
3636
private _accessToken: string | null = null;
3737
private _appCheckToken: string | null = null;
3838
private _lastToken: string | null = null;
39+
private _isUsingEmulator = false;
3940
constructor(
4041
options: DataConnectOptions,
4142
private apiKey?: string | undefined,
@@ -93,6 +94,7 @@ export class RESTTransport implements DataConnectTransport {
9394
}
9495
useEmulator(host: string, port?: number, isSecure?: boolean): void {
9596
this._host = host;
97+
this._isUsingEmulator = true;
9698
if (typeof port === 'number') {
9799
this._port = port;
98100
}
@@ -182,7 +184,8 @@ export class RESTTransport implements DataConnectTransport {
182184
this._accessToken,
183185
this._appCheckToken,
184186
this._isUsingGen,
185-
this._callerSdkType
187+
this._callerSdkType,
188+
this._isUsingEmulator
186189
)
187190
);
188191
return withAuth;
@@ -208,7 +211,8 @@ export class RESTTransport implements DataConnectTransport {
208211
this._accessToken,
209212
this._appCheckToken,
210213
this._isUsingGen,
211-
this._callerSdkType
214+
this._callerSdkType,
215+
this._isUsingEmulator
212216
);
213217
});
214218
return taskResult;

packages/data-connect/test/unit/fetch.test.ts

+38-5
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818
import { expect, use } from 'chai';
1919
import chaiAsPromised from 'chai-as-promised';
2020
import * as sinon from 'sinon';
21+
import sinonChai from 'sinon-chai';
2122

2223
import { dcFetch, initializeFetch } from '../../src/network/fetch';
2324
import { CallerSdkType, CallerSdkTypeEnum } from '../../src/network/transport';
2425
use(chaiAsPromised);
26+
use(sinonChai);
2527
function mockFetch(json: object, reject: boolean): sinon.SinonStub {
2628
const fakeFetchImpl = sinon.stub().returns(
2729
Promise.resolve({
@@ -57,7 +59,8 @@ describe('fetch', () => {
5759
null,
5860
null,
5961
false,
60-
CallerSdkTypeEnum.Base
62+
CallerSdkTypeEnum.Base,
63+
false
6164
)
6265
).to.eventually.be.rejectedWith(message);
6366
});
@@ -81,7 +84,8 @@ describe('fetch', () => {
8184
null,
8285
null,
8386
false,
84-
CallerSdkTypeEnum.Base
87+
CallerSdkTypeEnum.Base,
88+
false
8589
)
8690
).to.eventually.be.rejectedWith(JSON.stringify(json));
8791
});
@@ -112,7 +116,8 @@ describe('fetch', () => {
112116
null,
113117
null,
114118
false,
115-
CallerSdkTypeEnum.Base
119+
CallerSdkTypeEnum.Base,
120+
false
116121
)
117122
).to.eventually.be.rejected.then(error => {
118123
expect(error.response.data).to.eq(json.data);
@@ -143,7 +148,8 @@ describe('fetch', () => {
143148
null,
144149
null,
145150
false, // _isUsingGen is false
146-
callerSdkType as CallerSdkType
151+
callerSdkType as CallerSdkType,
152+
false
147153
);
148154

149155
let expectedHeaderRegex: RegExp;
@@ -191,7 +197,8 @@ describe('fetch', () => {
191197
null,
192198
null,
193199
true, // _isUsingGen is true
194-
callerSdkType as CallerSdkType
200+
callerSdkType as CallerSdkType,
201+
false
195202
);
196203

197204
let expectedHeaderRegex: RegExp;
@@ -215,4 +222,30 @@ describe('fetch', () => {
215222
}
216223
}
217224
});
225+
it('should call credentials include if using emulator on cloud workstation', async () => {
226+
const json = {
227+
code: 200,
228+
message1: 'success'
229+
};
230+
const fakeFetchImpl = mockFetch(json, false);
231+
await dcFetch(
232+
'https://abc.cloudworkstations.dev',
233+
{
234+
name: 'n',
235+
operationName: 'n',
236+
variables: {}
237+
},
238+
{} as AbortController,
239+
null,
240+
null,
241+
null,
242+
true, // _isUsingGen is true
243+
CallerSdkTypeEnum.Base,
244+
true
245+
);
246+
expect(fakeFetchImpl).to.have.been.calledWithMatch(
247+
'https://abc.cloudworkstations.dev',
248+
{ credentials: 'include' }
249+
);
250+
});
218251
});

packages/firestore/externs.json

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"packages/util/dist/src/defaults.d.ts",
3131
"packages/util/dist/src/emulator.d.ts",
3232
"packages/util/dist/src/environment.d.ts",
33+
"packages/util/dist/src/url.d.ts",
3334
"packages/util/dist/src/compat.d.ts",
3435
"packages/util/dist/src/global.d.ts",
3536
"packages/util/dist/src/obj.d.ts",

packages/firestore/src/core/database_info.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ export class DatabaseInfo {
4848
readonly forceLongPolling: boolean,
4949
readonly autoDetectLongPolling: boolean,
5050
readonly longPollingOptions: ExperimentalLongPollingOptions,
51-
readonly useFetchStreams: boolean
51+
readonly useFetchStreams: boolean,
52+
readonly isUsingEmulator: boolean
5253
) {}
5354
}
5455

packages/firestore/src/lite-api/components.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ export function makeDatabaseInfo(
119119
settings.experimentalForceLongPolling,
120120
settings.experimentalAutoDetectLongPolling,
121121
cloneLongPollingOptions(settings.experimentalLongPollingOptions),
122-
settings.useFetchStreams
122+
settings.useFetchStreams,
123+
settings.isUsingEmulator
123124
);
124125
}

packages/firestore/src/lite-api/settings.ts

+3
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ export class FirestoreSettingsImpl {
112112
readonly useFetchStreams: boolean;
113113
readonly localCache?: FirestoreLocalCache;
114114

115+
readonly isUsingEmulator: boolean;
116+
115117
// Can be a google-auth-library or gapi client.
116118
// eslint-disable-next-line @typescript-eslint/no-explicit-any
117119
credentials?: any;
@@ -130,6 +132,7 @@ export class FirestoreSettingsImpl {
130132
this.host = settings.host;
131133
this.ssl = settings.ssl ?? DEFAULT_SSL;
132134
}
135+
this.isUsingEmulator = settings.emulatorOptions !== undefined;
133136

134137
this.credentials = settings.credentials;
135138
this.ignoreUndefinedProperties = !!settings.ignoreUndefinedProperties;

packages/firestore/src/platform/browser/webchannel_connection.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ export class WebChannelConnection extends RestConnection {
7171
rpcName: string,
7272
url: string,
7373
headers: StringMap,
74-
body: Req
74+
body: Req,
75+
_isUsingEmulator: boolean
7576
): Promise<Resp> {
7677
const streamId = generateUniqueDebugId();
7778
return new Promise((resolve: Resolver<Resp>, reject: Rejecter) => {

packages/firestore/src/platform/browser_lite/fetch_connection.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
* limitations under the License.
1616
*/
1717

18+
import { isCloudWorkstation } from '@firebase/util';
19+
1820
import { Token } from '../../api/credentials';
1921
import { Stream } from '../../remote/connection';
2022
import { RestConnection } from '../../remote/rest_connection';
@@ -38,17 +40,22 @@ export class FetchConnection extends RestConnection {
3840
rpcName: string,
3941
url: string,
4042
headers: StringMap,
41-
body: Req
43+
body: Req,
44+
isUsingEmulator: boolean
4245
): Promise<Resp> {
4346
const requestJson = JSON.stringify(body);
4447
let response: Response;
4548

4649
try {
47-
response = await fetch(url, {
50+
const fetchArgs: RequestInit = {
4851
method: 'POST',
4952
headers,
5053
body: requestJson
51-
});
54+
};
55+
if (isCloudWorkstation(new URL(url).host) && isUsingEmulator) {
56+
fetchArgs.credentials = 'include';
57+
}
58+
response = await fetch(url, fetchArgs);
5259
} catch (e) {
5360
const err = e as { status: number | undefined; statusText: string };
5461
throw new FirestoreError(

0 commit comments

Comments
 (0)