Skip to content

Commit 326221d

Browse files
authored
Throw Operation Not Allowed for Invalid Auth Endpoint (#9013)
* Throw not supported exception in _performApiRequest
1 parent fe8387e commit 326221d

File tree

10 files changed

+120
-6
lines changed

10 files changed

+120
-6
lines changed

common/api-review/auth.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export interface Auth {
9595
setPersistence(persistence: Persistence): Promise<void>;
9696
readonly settings: AuthSettings;
9797
signOut(): Promise<void>;
98+
readonly tenantConfig?: TenantConfig;
9899
tenantId: string | null;
99100
updateCurrentUser(user: User | null): Promise<void>;
100101
useDeviceLanguage(): void;

docs-devsite/auth.auth.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export interface Auth
3131
| [languageCode](./auth.auth.md#authlanguagecode) | string \| null | The [Auth](./auth.auth.md#auth_interface) instance's language code. |
3232
| [name](./auth.auth.md#authname) | string | The name of the app associated with the <code>Auth</code> service instance. |
3333
| [settings](./auth.auth.md#authsettings) | [AuthSettings](./auth.authsettings.md#authsettings_interface) | The [Auth](./auth.auth.md#auth_interface) instance's settings. |
34+
| [tenantConfig](./auth.auth.md#authtenantconfig) | [TenantConfig](./auth.tenantconfig.md#tenantconfig_interface) | The [TenantConfig](./auth.tenantconfig.md#tenantconfig_interface) used to initialize a Regional Auth. This is only present if regional auth is initialized and backend endpoint is used. |
3435
| [tenantId](./auth.auth.md#authtenantid) | string \| null | The [Auth](./auth.auth.md#auth_interface) instance's tenant ID. |
3536

3637
## Methods
@@ -120,6 +121,16 @@ This is used to edit/read configuration related options such as app verification
120121
readonly settings: AuthSettings;
121122
```
122123

124+
## Auth.tenantConfig
125+
126+
The [TenantConfig](./auth.tenantconfig.md#tenantconfig_interface) used to initialize a Regional Auth. This is only present if regional auth is initialized and backend endpoint is used.
127+
128+
<b>Signature:</b>
129+
130+
```typescript
131+
readonly tenantConfig?: TenantConfig;
132+
```
133+
123134
## Auth.tenantId
124135

125136
The [Auth](./auth.auth.md#auth_interface) instance's tenant ID.

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

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ import { FirebaseError, getUA } from '@firebase/util';
2525
import * as utils from '@firebase/util';
2626

2727
import { mockEndpoint } from '../../test/helpers/api/helper';
28-
import { testAuth, TestAuth } from '../../test/helpers/mock_auth';
28+
import {
29+
regionalTestAuth,
30+
testAuth,
31+
TestAuth
32+
} from '../../test/helpers/mock_auth';
2933
import * as mockFetch from '../../test/helpers/mock_fetch';
3034
import { AuthErrorCode } from '../core/errors';
3135
import { ConfigInternal } from '../model/auth';
@@ -34,6 +38,7 @@ import {
3438
_performApiRequest,
3539
DEFAULT_API_TIMEOUT_MS,
3640
Endpoint,
41+
RegionalEndpoint,
3742
HttpHeader,
3843
HttpMethod,
3944
_addTidIfNecessary
@@ -55,9 +60,11 @@ describe('api/_performApiRequest', () => {
5560
};
5661

5762
let auth: TestAuth;
63+
let regionalAuth: TestAuth;
5864

5965
beforeEach(async () => {
6066
auth = await testAuth();
67+
regionalAuth = await regionalTestAuth();
6168
});
6269

6370
afterEach(() => {
@@ -595,4 +602,34 @@ describe('api/_performApiRequest', () => {
595602
.and.not.have.property('tenantId');
596603
});
597604
});
605+
606+
context('throws Operation not allowed exception', () => {
607+
it('when tenantConfig is not initialized and Regional Endpoint is used', async () => {
608+
await expect(
609+
_performApiRequest<typeof request, typeof serverResponse>(
610+
auth,
611+
HttpMethod.POST,
612+
RegionalEndpoint.EXCHANGE_TOKEN,
613+
request
614+
)
615+
).to.be.rejectedWith(
616+
FirebaseError,
617+
'Firebase: Operations not allowed for the auth object initialized. (auth/operation-not-allowed).'
618+
);
619+
});
620+
621+
it('when tenantConfig is initialized and default Endpoint is used', async () => {
622+
await expect(
623+
_performApiRequest<typeof request, typeof serverResponse>(
624+
regionalAuth,
625+
HttpMethod.POST,
626+
Endpoint.SIGN_UP,
627+
request
628+
)
629+
).to.be.rejectedWith(
630+
FirebaseError,
631+
'Firebase: Operations not allowed for the auth object initialized. (auth/operation-not-allowed).'
632+
);
633+
});
634+
});
598635
});

packages/auth/src/api/index.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { AuthErrorCode, NamedErrorParams } from '../core/errors';
2626
import {
2727
_createError,
2828
_errorWithCustomMessage,
29+
_operationNotSupportedForInitializedAuthInstance,
2930
_fail
3031
} from '../core/util/assert';
3132
import { Delay } from '../core/util/delay';
@@ -53,7 +54,7 @@ export const enum HttpHeader {
5354
X_FIREBASE_APP_CHECK = 'X-Firebase-AppCheck'
5455
}
5556

56-
export const enum Endpoint {
57+
export enum Endpoint {
5758
CREATE_AUTH_URI = '/v1/accounts:createAuthUri',
5859
DELETE_ACCOUNT = '/v1/accounts:delete',
5960
RESET_PASSWORD = '/v1/accounts:resetPassword',
@@ -80,6 +81,10 @@ export const enum Endpoint {
8081
REVOKE_TOKEN = '/v2/accounts:revokeToken'
8182
}
8283

84+
export enum RegionalEndpoint {
85+
EXCHANGE_TOKEN = 'v2/${body.parent}:exchangeOidcToken'
86+
}
87+
8388
const CookieAuthProxiedEndpoints: string[] = [
8489
Endpoint.SIGN_IN_WITH_CUSTOM_TOKEN,
8590
Endpoint.SIGN_IN_WITH_EMAIL_LINK,
@@ -139,10 +144,11 @@ export function _addTidIfNecessary<T extends { tenantId?: string }>(
139144
export async function _performApiRequest<T, V>(
140145
auth: Auth,
141146
method: HttpMethod,
142-
path: Endpoint,
147+
path: Endpoint | RegionalEndpoint,
143148
request?: T,
144149
customErrorMap: Partial<ServerErrorMap<ServerError>> = {}
145150
): Promise<V> {
151+
_assertValidEndpointForAuth(auth, path);
146152
return _performFetchWithErrorHandling(auth, customErrorMap, async () => {
147153
let body = {};
148154
let params = {};
@@ -322,6 +328,22 @@ export function _parseEnforcementState(
322328
}
323329
}
324330

331+
function _assertValidEndpointForAuth(
332+
auth: Auth,
333+
path: Endpoint | RegionalEndpoint
334+
): void {
335+
if (
336+
!auth.tenantConfig &&
337+
Object.values(RegionalEndpoint).includes(path as RegionalEndpoint)
338+
) {
339+
throw _operationNotSupportedForInitializedAuthInstance(auth);
340+
}
341+
342+
if (auth.tenantConfig && Object.values(Endpoint).includes(path as Endpoint)) {
343+
throw _operationNotSupportedForInitializedAuthInstance(auth);
344+
}
345+
}
346+
325347
class NetworkTimeout<T> {
326348
// Node timers and browser timers are fundamentally incompatible, but we
327349
// don't care about the value here

packages/auth/src/core/auth/auth_impl.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ import {
3636
ErrorFn,
3737
NextFn,
3838
Unsubscribe,
39-
PasswordValidationStatus
39+
PasswordValidationStatus,
40+
TenantConfig
4041
} from '../../model/public_types';
4142
import {
4243
createSubscribe,
@@ -126,6 +127,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
126127
| undefined = undefined;
127128
_persistenceManagerAvailable: Promise<void>;
128129
readonly name: string;
130+
readonly tenantConfig?: TenantConfig;
129131

130132
// Tracks the last notified UID for state change listeners to prevent
131133
// repeated calls to the callbacks. Undefined means it's never been
@@ -140,7 +142,8 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
140142
public readonly app: FirebaseApp,
141143
private readonly heartbeatServiceProvider: Provider<'heartbeat'>,
142144
private readonly appCheckServiceProvider: Provider<AppCheckInternalComponentName>,
143-
public readonly config: ConfigInternal
145+
public readonly config: ConfigInternal,
146+
tenantConfig?: TenantConfig
144147
) {
145148
this.name = app.name;
146149
this.clientVersion = config.sdkClientVersion;
@@ -149,6 +152,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
149152
this._persistenceManagerAvailable = new Promise<void>(
150153
resolve => (this._resolvePersistenceManagerAvailable = resolve)
151154
);
155+
this.tenantConfig = tenantConfig;
152156
}
153157

154158
_initializeWithPersistence(

packages/auth/src/core/auth/register.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ export function registerAuth(clientPlatform: ClientPlatform): void {
9292
app,
9393
heartbeatServiceProvider,
9494
appCheckServiceProvider,
95-
config
95+
config,
96+
tenantConfig
9697
);
9798
_initializeAuthInstance(authInstance, deps);
9899

packages/auth/src/core/util/assert.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,16 @@ export function _serverAppCurrentUserOperationNotSupportedError(
112112
);
113113
}
114114

115+
export function _operationNotSupportedForInitializedAuthInstance(
116+
auth: Auth
117+
): FirebaseError {
118+
return _errorWithCustomMessage(
119+
auth,
120+
AuthErrorCode.OPERATION_NOT_ALLOWED,
121+
'Operations not allowed for the auth object initialized.'
122+
);
123+
}
124+
115125
export function _assertInstanceOf(
116126
auth: Auth,
117127
object: object,

packages/auth/src/model/auth.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
PasswordPolicy,
2424
PasswordValidationStatus,
2525
PopupRedirectResolver,
26+
TenantConfig,
2627
User
2728
} from './public_types';
2829
import { ErrorFactory } from '@firebase/util';
@@ -100,6 +101,7 @@ export interface AuthInternal extends Auth {
100101

101102
readonly name: AppName;
102103
readonly config: ConfigInternal;
104+
readonly tenantConfig?: TenantConfig;
103105
languageCode: string | null;
104106
tenantId: string | null;
105107
readonly settings: AuthSettings;

packages/auth/src/model/public_types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@ export interface Auth {
183183
readonly name: string;
184184
/** The {@link Config} used to initialize this instance. */
185185
readonly config: Config;
186+
/**
187+
* The {@link TenantConfig} used to initialize a Regional Auth. This is only present
188+
* if regional auth is initialized and {@link DefaultConfig.REGIONAL_API_HOST}
189+
* backend endpoint is used.
190+
*/
191+
readonly tenantConfig?: TenantConfig;
186192
/**
187193
* Changes the type of persistence on the `Auth` instance.
188194
*

packages/auth/test/helpers/mock_auth.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,26 @@ export async function testAuth(
116116
return auth;
117117
}
118118

119+
export async function regionalTestAuth(): Promise<TestAuth> {
120+
const tenantConfig = { 'location': 'us', 'tenantId': 'tenant-1' };
121+
const auth: TestAuth = new AuthImpl(
122+
FAKE_APP,
123+
FAKE_HEARTBEAT_CONTROLLER_PROVIDER,
124+
FAKE_APP_CHECK_CONTROLLER_PROVIDER,
125+
{
126+
apiKey: TEST_KEY,
127+
authDomain: TEST_AUTH_DOMAIN,
128+
apiHost: TEST_HOST,
129+
apiScheme: TEST_SCHEME,
130+
tokenApiHost: TEST_TOKEN_HOST,
131+
clientPlatform: ClientPlatform.BROWSER,
132+
sdkClientVersion: 'testSDK/0.0.0'
133+
},
134+
tenantConfig
135+
) as TestAuth;
136+
return auth;
137+
}
138+
119139
export function testUser(
120140
auth: AuthInternal,
121141
uid: string,

0 commit comments

Comments
 (0)