Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
1 change: 1 addition & 0 deletions common/api-review/auth.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export interface Auth {
setPersistence(persistence: Persistence): Promise<void>;
readonly settings: AuthSettings;
signOut(): Promise<void>;
readonly tenantConfig?: TenantConfig;
tenantId: string | null;
updateCurrentUser(user: User | null): Promise<void>;
useDeviceLanguage(): void;
Expand Down
28 changes: 27 additions & 1 deletion packages/auth/src/api/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { FirebaseError, getUA } from '@firebase/util';
import * as utils from '@firebase/util';

import { mockEndpoint } from '../../test/helpers/api/helper';
import { testAuth, TestAuth } from '../../test/helpers/mock_auth';
import { regionalTestAuth, testAuth, TestAuth } from '../../test/helpers/mock_auth';
import * as mockFetch from '../../test/helpers/mock_fetch';
import { AuthErrorCode } from '../core/errors';
import { ConfigInternal } from '../model/auth';
Expand All @@ -34,6 +34,7 @@ import {
_performApiRequest,
DEFAULT_API_TIMEOUT_MS,
Endpoint,
RegionalEndpoint,
HttpHeader,
HttpMethod,
_addTidIfNecessary
Expand All @@ -55,9 +56,11 @@ describe('api/_performApiRequest', () => {
};

let auth: TestAuth;
let regionalAuth: TestAuth;

beforeEach(async () => {
auth = await testAuth();
regionalAuth = await regionalTestAuth();
});

afterEach(() => {
Expand Down Expand Up @@ -595,4 +598,27 @@ describe('api/_performApiRequest', () => {
.and.not.have.property('tenantId');
});
});


it('should throw exception when tenantConfig is not initialized and Regional Endpoint is used', async () => {
await expect(
_performApiRequest<
typeof request,
typeof serverResponse
>(auth, HttpMethod.POST, RegionalEndpoint.EXCHANGE_TOKEN, request)).to.be.rejectedWith(
FirebaseError,
'Firebase: Operations not allowed for the auth object initialized. (auth/operation-not-allowed).'
);
});

it('should throw exception when tenantConfig is initialized and default Endpoint is used', async () => {
await expect(
_performApiRequest<
typeof request,
typeof serverResponse
>(regionalAuth, HttpMethod.POST, Endpoint.SIGN_UP, request)).to.be.rejectedWith(
FirebaseError,
'Firebase: Operations not allowed for the auth object initialized. (auth/operation-not-allowed).'
);
});
});
26 changes: 24 additions & 2 deletions packages/auth/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { AuthErrorCode, NamedErrorParams } from '../core/errors';
import {
_createError,
_errorWithCustomMessage,
_operationNotSupportedForInitializedAuthInstance,
_fail
} from '../core/util/assert';
import { Delay } from '../core/util/delay';
Expand Down Expand Up @@ -53,7 +54,7 @@ export const enum HttpHeader {
X_FIREBASE_APP_CHECK = 'X-Firebase-AppCheck'
}

export const enum Endpoint {
export enum Endpoint {
CREATE_AUTH_URI = '/v1/accounts:createAuthUri',
DELETE_ACCOUNT = '/v1/accounts:delete',
RESET_PASSWORD = '/v1/accounts:resetPassword',
Expand All @@ -80,6 +81,10 @@ export const enum Endpoint {
REVOKE_TOKEN = '/v2/accounts:revokeToken'
}

export enum RegionalEndpoint {
EXCHANGE_TOKEN = 'v2/${body.parent}:exchangeOidcToken'
}

const CookieAuthProxiedEndpoints: string[] = [
Endpoint.SIGN_IN_WITH_CUSTOM_TOKEN,
Endpoint.SIGN_IN_WITH_EMAIL_LINK,
Expand Down Expand Up @@ -139,10 +144,11 @@ export function _addTidIfNecessary<T extends { tenantId?: string }>(
export async function _performApiRequest<T, V>(
auth: Auth,
method: HttpMethod,
path: Endpoint,
path: Endpoint | RegionalEndpoint,
request?: T,
customErrorMap: Partial<ServerErrorMap<ServerError>> = {}
): Promise<V> {
_assertValidEndpointForAuth(auth, path);
return _performFetchWithErrorHandling(auth, customErrorMap, async () => {
let body = {};
let params = {};
Expand Down Expand Up @@ -322,6 +328,22 @@ export function _parseEnforcementState(
}
}

function _assertValidEndpointForAuth(
auth: Auth,
path: Endpoint | RegionalEndpoint
): void {
if (
!auth.tenantConfig &&
Object.values(RegionalEndpoint).includes(path as RegionalEndpoint)
) {
throw _operationNotSupportedForInitializedAuthInstance(auth);
}

if (auth.tenantConfig && Object.values(Endpoint).includes(path as Endpoint)) {
throw _operationNotSupportedForInitializedAuthInstance(auth);
}
}

class NetworkTimeout<T> {
// Node timers and browser timers are fundamentally incompatible, but we
// don't care about the value here
Expand Down
8 changes: 6 additions & 2 deletions packages/auth/src/core/auth/auth_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ import {
ErrorFn,
NextFn,
Unsubscribe,
PasswordValidationStatus
PasswordValidationStatus,
TenantConfig
} from '../../model/public_types';
import {
createSubscribe,
Expand Down Expand Up @@ -126,6 +127,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
| undefined = undefined;
_persistenceManagerAvailable: Promise<void>;
readonly name: string;
readonly tenantConfig?: TenantConfig;

// Tracks the last notified UID for state change listeners to prevent
// repeated calls to the callbacks. Undefined means it's never been
Expand All @@ -140,7 +142,8 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
public readonly app: FirebaseApp,
private readonly heartbeatServiceProvider: Provider<'heartbeat'>,
private readonly appCheckServiceProvider: Provider<AppCheckInternalComponentName>,
public readonly config: ConfigInternal
public readonly config: ConfigInternal,
tenantConfig?: TenantConfig
) {
this.name = app.name;
this.clientVersion = config.sdkClientVersion;
Expand All @@ -149,6 +152,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
this._persistenceManagerAvailable = new Promise<void>(
resolve => (this._resolvePersistenceManagerAvailable = resolve)
);
this.tenantConfig = tenantConfig;
}

_initializeWithPersistence(
Expand Down
3 changes: 2 additions & 1 deletion packages/auth/src/core/auth/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ export function registerAuth(clientPlatform: ClientPlatform): void {
app,
heartbeatServiceProvider,
appCheckServiceProvider,
config
config,
tenantConfig
);
_initializeAuthInstance(authInstance, deps);

Expand Down
10 changes: 10 additions & 0 deletions packages/auth/src/core/util/assert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,16 @@ export function _serverAppCurrentUserOperationNotSupportedError(
);
}

export function _operationNotSupportedForInitializedAuthInstance(
auth: Auth
): FirebaseError {
return _errorWithCustomMessage(
auth,
AuthErrorCode.OPERATION_NOT_ALLOWED,
'Operations not allowed for the auth object initialized.'
);
}

export function _assertInstanceOf(
auth: Auth,
object: object,
Expand Down
2 changes: 2 additions & 0 deletions packages/auth/src/model/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
PasswordPolicy,
PasswordValidationStatus,
PopupRedirectResolver,
TenantConfig,
User
} from './public_types';
import { ErrorFactory } from '@firebase/util';
Expand Down Expand Up @@ -100,6 +101,7 @@ export interface AuthInternal extends Auth {

readonly name: AppName;
readonly config: ConfigInternal;
readonly tenantConfig?: TenantConfig;
languageCode: string | null;
tenantId: string | null;
readonly settings: AuthSettings;
Expand Down
6 changes: 6 additions & 0 deletions packages/auth/src/model/public_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,12 @@ export interface Auth {
readonly name: string;
/** The {@link Config} used to initialize this instance. */
readonly config: Config;
/**
* The {@link TenantConfig} used to initialize a Regional Auth. This is only present
* if regional auth is initialized and {@link DefaultConfig.REGIONAL_API_HOST}
* backend endpoint is used.
*/
readonly tenantConfig?: TenantConfig;
/**
* Changes the type of persistence on the `Auth` instance.
*
Expand Down
20 changes: 20 additions & 0 deletions packages/auth/test/helpers/mock_auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,26 @@ export async function testAuth(
return auth;
}

export async function regionalTestAuth(): Promise<TestAuth> {
const tenantConfig = {'location': "us", 'tenantId': "tenant-1"};
const auth: TestAuth = new AuthImpl(
FAKE_APP,
FAKE_HEARTBEAT_CONTROLLER_PROVIDER,
FAKE_APP_CHECK_CONTROLLER_PROVIDER,
{
apiKey: TEST_KEY,
authDomain: TEST_AUTH_DOMAIN,
apiHost: TEST_HOST,
apiScheme: TEST_SCHEME,
tokenApiHost: TEST_TOKEN_HOST,
clientPlatform: ClientPlatform.BROWSER,
sdkClientVersion: 'testSDK/0.0.0'
},
tenantConfig
) as TestAuth;
return auth;
}

export function testUser(
auth: AuthInternal,
uid: string,
Expand Down
Loading