diff --git a/common/api-review/auth.api.md b/common/api-review/auth.api.md index 8e915daf731..18dba6f7e7e 100644 --- a/common/api-review/auth.api.md +++ b/common/api-review/auth.api.md @@ -45,6 +45,7 @@ export interface ActionCodeSettings { iOS?: { bundleId: string; }; + linkDomain?: string; url: string; } @@ -236,6 +237,7 @@ export const AuthErrorCodes: { readonly MISSING_RECAPTCHA_VERSION: "auth/missing-recaptcha-version"; readonly INVALID_RECAPTCHA_VERSION: "auth/invalid-recaptcha-version"; readonly INVALID_REQ_TYPE: "auth/invalid-req-type"; + readonly INVALID_HOSTING_LINK_DOMAIN: "auth/invalid-hosting-link-domain"; }; // @public diff --git a/docs-devsite/auth.actioncodesettings.md b/docs-devsite/auth.actioncodesettings.md index a12144adaf4..59cedb19651 100644 --- a/docs-devsite/auth.actioncodesettings.md +++ b/docs-devsite/auth.actioncodesettings.md @@ -26,6 +26,7 @@ export interface ActionCodeSettings | [dynamicLinkDomain](./auth.actioncodesettings.md#actioncodesettingsdynamiclinkdomain) | string | When multiple custom dynamic link domains are defined for a project, specify which one to use when the link is to be opened via a specified mobile app (for example, example.page.link). | | [handleCodeInApp](./auth.actioncodesettings.md#actioncodesettingshandlecodeinapp) | boolean | When set to true, the action code link will be be sent as a Universal Link or Android App Link and will be opened by the app if installed. | | [iOS](./auth.actioncodesettings.md#actioncodesettingsios) | { bundleId: string; } | Sets the iOS bundle ID. | +| [linkDomain](./auth.actioncodesettings.md#actioncodesettingslinkdomain) | string | The optional custom Firebase Hosting domain to use when the link is to be opened via a specified mobile app. The domain must be configured in Firebase Hosting and owned by the project. This cannot be a default hosting domain (web.app or firebaseapp.com). | | [url](./auth.actioncodesettings.md#actioncodesettingsurl) | string | Sets the link continue/state URL. | ## ActionCodeSettings.android @@ -82,11 +83,21 @@ iOS?: { }; ``` +## ActionCodeSettings.linkDomain + +The optional custom Firebase Hosting domain to use when the link is to be opened via a specified mobile app. The domain must be configured in Firebase Hosting and owned by the project. This cannot be a default hosting domain (web.app or firebaseapp.com). + +Signature: + +```typescript +linkDomain?: string; +``` + ## ActionCodeSettings.url Sets the link continue/state URL. -This has different meanings in different contexts: - When the link is handled in the web action widgets, this is the deep link in the `continueUrl` query parameter. - When the link is handled in the app directly, this is the `continueUrl` query parameter in the deep link of the Dynamic Link. +This has different meanings in different contexts: - When the link is handled in the web action widgets, this is the deep link in the `continueUrl` query parameter. - When the link is handled in the app directly, this is the `continueUrl` query parameter in the deep link of the Dynamic Link or Hosting Link. Signature: diff --git a/docs-devsite/auth.md b/docs-devsite/auth.md index 43d23dc8931..8e4684f1d21 100644 --- a/docs-devsite/auth.md +++ b/docs-devsite/auth.md @@ -1954,6 +1954,7 @@ AUTH_ERROR_CODES_MAP_DO_NOT_USE_INTERNALLY: { readonly MISSING_RECAPTCHA_VERSION: "auth/missing-recaptcha-version"; readonly INVALID_RECAPTCHA_VERSION: "auth/invalid-recaptcha-version"; readonly INVALID_REQ_TYPE: "auth/invalid-req-type"; + readonly INVALID_HOSTING_LINK_DOMAIN: "auth/invalid-hosting-link-domain"; } ``` diff --git a/packages/auth-types/index.d.ts b/packages/auth-types/index.d.ts index bebf4e5b18a..4b0192df925 100644 --- a/packages/auth-types/index.d.ts +++ b/packages/auth-types/index.d.ts @@ -130,6 +130,7 @@ export type ActionCodeSettings = { iOS?: { bundleId: string }; url: string; dynamicLinkDomain?: string; + linkDomain?: string; }; export type AdditionalUserInfo = { diff --git a/packages/auth/src/api/authentication/email_and_password.ts b/packages/auth/src/api/authentication/email_and_password.ts index 2f9664f72db..9012b95d213 100644 --- a/packages/auth/src/api/authentication/email_and_password.ts +++ b/packages/auth/src/api/authentication/email_and_password.ts @@ -70,6 +70,7 @@ export interface GetOobCodeRequest { dynamicLinkDomain?: string; tenantId?: string; targetProjectid?: string; + linkDomain?: string; } export interface VerifyEmailRequest extends GetOobCodeRequest { diff --git a/packages/auth/src/api/errors.ts b/packages/auth/src/api/errors.ts index d8f73f72821..0e59e3babbd 100644 --- a/packages/auth/src/api/errors.ts +++ b/packages/auth/src/api/errors.ts @@ -100,7 +100,8 @@ export const enum ServerError { MISSING_RECAPTCHA_VERSION = 'MISSING_RECAPTCHA_VERSION', INVALID_RECAPTCHA_VERSION = 'INVALID_RECAPTCHA_VERSION', INVALID_REQ_TYPE = 'INVALID_REQ_TYPE', - PASSWORD_DOES_NOT_MEET_REQUIREMENTS = 'PASSWORD_DOES_NOT_MEET_REQUIREMENTS' + PASSWORD_DOES_NOT_MEET_REQUIREMENTS = 'PASSWORD_DOES_NOT_MEET_REQUIREMENTS', + INVALID_HOSTING_LINK_DOMAIN = 'INVALID_HOSTING_LINK_DOMAIN' } /** diff --git a/packages/auth/src/core/errors.ts b/packages/auth/src/core/errors.ts index 9494658b9f0..ff012c9ee37 100644 --- a/packages/auth/src/core/errors.ts +++ b/packages/auth/src/core/errors.ts @@ -134,7 +134,8 @@ export const enum AuthErrorCode { INVALID_RECAPTCHA_VERSION = 'invalid-recaptcha-version', INVALID_REQ_TYPE = 'invalid-req-type', UNSUPPORTED_PASSWORD_POLICY_SCHEMA_VERSION = 'unsupported-password-policy-schema-version', - PASSWORD_DOES_NOT_MEET_REQUIREMENTS = 'password-does-not-meet-requirements' + PASSWORD_DOES_NOT_MEET_REQUIREMENTS = 'password-does-not-meet-requirements', + INVALID_HOSTING_LINK_DOMAIN = 'invalid-hosting-link-domain' } function _debugErrorMap(): ErrorMap { @@ -387,7 +388,10 @@ function _debugErrorMap(): ErrorMap { [AuthErrorCode.UNSUPPORTED_PASSWORD_POLICY_SCHEMA_VERSION]: 'The password policy received from the backend uses a schema version that is not supported by this version of the Firebase SDK.', [AuthErrorCode.PASSWORD_DOES_NOT_MEET_REQUIREMENTS]: - 'The password does not meet the requirements.' + 'The password does not meet the requirements.', + [AuthErrorCode.INVALID_HOSTING_LINK_DOMAIN]: + 'The provided hosting link domain is not configured in Firebase Hosting or is not owned by ' + + 'the current project. This cannot be a default hosting domain (web.app or firebaseapp.com).' }; } @@ -598,5 +602,6 @@ export const AUTH_ERROR_CODES_MAP_DO_NOT_USE_INTERNALLY = { MISSING_CLIENT_TYPE: 'auth/missing-client-type', MISSING_RECAPTCHA_VERSION: 'auth/missing-recaptcha-version', INVALID_RECAPTCHA_VERSION: 'auth/invalid-recaptcha-version', - INVALID_REQ_TYPE: 'auth/invalid-req-type' + INVALID_REQ_TYPE: 'auth/invalid-req-type', + INVALID_HOSTING_LINK_DOMAIN: 'auth/invalid-hosting-link-domain' } as const; diff --git a/packages/auth/src/core/strategies/action_code_settings.test.ts b/packages/auth/src/core/strategies/action_code_settings.test.ts index 36784151156..1a14f719e30 100644 --- a/packages/auth/src/core/strategies/action_code_settings.test.ts +++ b/packages/auth/src/core/strategies/action_code_settings.test.ts @@ -26,6 +26,10 @@ describe('core/strategies/action_code_settings', () => { let auth: TestAuth; const request: GetOobCodeRequest = {}; + const TEST_BUNDLE_ID = 'my-bundle'; + const TEST_FDL_DOMAIN = 'fdl-domain'; + const TEST_URL = 'my-url'; + beforeEach(async () => { auth = await testAuth(); }); @@ -35,10 +39,10 @@ describe('core/strategies/action_code_settings', () => { _setActionCodeSettingsOnRequest(auth, request, { handleCodeInApp: true, iOS: { - bundleId: 'my-bundle' + bundleId: TEST_BUNDLE_ID }, url: '', - dynamicLinkDomain: 'fdl-domain' + dynamicLinkDomain: TEST_FDL_DOMAIN }) ).to.throw(FirebaseError, '(auth/invalid-continue-uri)'); }); @@ -48,9 +52,9 @@ describe('core/strategies/action_code_settings', () => { _setActionCodeSettingsOnRequest(auth, request, { handleCodeInApp: true, iOS: { - bundleId: 'my-´bundle' + bundleId: TEST_BUNDLE_ID }, - url: 'my-url' + url: TEST_URL }) ).to.not.throw(); }); @@ -60,14 +64,27 @@ describe('core/strategies/action_code_settings', () => { _setActionCodeSettingsOnRequest(auth, request, { handleCodeInApp: true, iOS: { - bundleId: 'my-´bundle' + bundleId: TEST_BUNDLE_ID }, - url: 'my-url', + url: TEST_URL, dynamicLinkDomain: '' }) ).to.throw(FirebaseError, '(auth/invalid-dynamic-link-domain)'); }); + it('should require a non empty hosting link URL', () => { + expect(() => + _setActionCodeSettingsOnRequest(auth, request, { + handleCodeInApp: true, + iOS: { + bundleId: TEST_BUNDLE_ID + }, + url: TEST_URL, + linkDomain: '' + }) + ).to.throw(FirebaseError, '(auth/invalid-hosting-link-domain)'); + }); + it('should require a non-empty bundle ID', () => { expect(() => _setActionCodeSettingsOnRequest(auth, request, { @@ -75,8 +92,8 @@ describe('core/strategies/action_code_settings', () => { iOS: { bundleId: '' }, - url: 'my-url', - dynamicLinkDomain: 'fdl-domain' + url: TEST_URL, + dynamicLinkDomain: TEST_FDL_DOMAIN }) ).to.throw(FirebaseError, '(auth/missing-ios-bundle-id)'); }); @@ -88,8 +105,8 @@ describe('core/strategies/action_code_settings', () => { android: { packageName: '' }, - url: 'my-url', - dynamicLinkDomain: 'fdl-domain' + url: TEST_URL, + dynamicLinkDomain: TEST_FDL_DOMAIN }) ).to.throw(FirebaseError, '(auth/missing-android-pkg-name)'); }); diff --git a/packages/auth/src/core/strategies/action_code_settings.ts b/packages/auth/src/core/strategies/action_code_settings.ts index 4afa54f2ab3..e7e4c994838 100644 --- a/packages/auth/src/core/strategies/action_code_settings.ts +++ b/packages/auth/src/core/strategies/action_code_settings.ts @@ -37,9 +37,16 @@ export function _setActionCodeSettingsOnRequest( auth, AuthErrorCode.INVALID_DYNAMIC_LINK_DOMAIN ); + _assert( + typeof actionCodeSettings.linkDomain === 'undefined' || + actionCodeSettings.linkDomain.length > 0, + auth, + AuthErrorCode.INVALID_HOSTING_LINK_DOMAIN + ); request.continueUrl = actionCodeSettings.url; request.dynamicLinkDomain = actionCodeSettings.dynamicLinkDomain; + request.linkDomain = actionCodeSettings.linkDomain; request.canHandleCodeInApp = actionCodeSettings.handleCodeInApp; if (actionCodeSettings.iOS) { diff --git a/packages/auth/src/core/strategies/email.test.ts b/packages/auth/src/core/strategies/email.test.ts index 85493a89aef..b2edc26ebb2 100644 --- a/packages/auth/src/core/strategies/email.test.ts +++ b/packages/auth/src/core/strategies/email.test.ts @@ -162,7 +162,8 @@ describe('core/strategies/sendEmailVerification', () => { bundleId: 'my-bundle' }, url: 'my-url', - dynamicLinkDomain: 'fdl-domain' + dynamicLinkDomain: 'fdl-domain', + linkDomain: 'hosting-link-domain' }); expect(mock.calls[0].request).to.eql({ @@ -170,6 +171,7 @@ describe('core/strategies/sendEmailVerification', () => { idToken, continueUrl: 'my-url', dynamicLinkDomain: 'fdl-domain', + linkDomain: 'hosting-link-domain', canHandleCodeInApp: true, iOSBundleId: 'my-bundle' }); @@ -190,13 +192,15 @@ describe('core/strategies/sendEmailVerification', () => { packageName: 'my-package' }, url: 'my-url', - dynamicLinkDomain: 'fdl-domain' + dynamicLinkDomain: 'fdl-domain', + linkDomain: 'hosting-link-domain' }); expect(mock.calls[0].request).to.eql({ requestType: ActionCodeOperation.VERIFY_EMAIL, idToken, continueUrl: 'my-url', dynamicLinkDomain: 'fdl-domain', + linkDomain: 'hosting-link-domain', canHandleCodeInApp: true, androidInstallApp: false, androidMinimumVersionCode: 'my-version', @@ -270,7 +274,8 @@ describe('core/strategies/verifyBeforeUpdateEmail', () => { bundleId: 'my-bundle' }, url: 'my-url', - dynamicLinkDomain: 'fdl-domain' + dynamicLinkDomain: 'fdl-domain', + linkDomain: 'hosting-link-domain' }); expect(mock.calls[0].request).to.eql({ @@ -279,6 +284,7 @@ describe('core/strategies/verifyBeforeUpdateEmail', () => { newEmail, continueUrl: 'my-url', dynamicLinkDomain: 'fdl-domain', + linkDomain: 'hosting-link-domain', canHandleCodeInApp: true, iOSBundleId: 'my-bundle' }); @@ -299,7 +305,8 @@ describe('core/strategies/verifyBeforeUpdateEmail', () => { packageName: 'my-package' }, url: 'my-url', - dynamicLinkDomain: 'fdl-domain' + dynamicLinkDomain: 'fdl-domain', + linkDomain: 'hosting-link-domain' }); expect(mock.calls[0].request).to.eql({ requestType: ActionCodeOperation.VERIFY_AND_CHANGE_EMAIL, @@ -307,6 +314,7 @@ describe('core/strategies/verifyBeforeUpdateEmail', () => { newEmail, continueUrl: 'my-url', dynamicLinkDomain: 'fdl-domain', + linkDomain: 'hosting-link-domain', canHandleCodeInApp: true, androidInstallApp: false, androidMinimumVersionCode: 'my-version', diff --git a/packages/auth/src/core/strategies/email_and_password.test.ts b/packages/auth/src/core/strategies/email_and_password.test.ts index 95fe8c8c06c..811e8239a56 100644 --- a/packages/auth/src/core/strategies/email_and_password.test.ts +++ b/packages/auth/src/core/strategies/email_and_password.test.ts @@ -120,7 +120,8 @@ describe('core/strategies/sendPasswordResetEmail', () => { bundleId: 'my-bundle' }, url: 'my-url', - dynamicLinkDomain: 'fdl-domain' + dynamicLinkDomain: 'fdl-domain', + linkDomain: 'hosting-link-domain' }); expect(mock.calls[0].request).to.eql({ @@ -128,6 +129,7 @@ describe('core/strategies/sendPasswordResetEmail', () => { email, continueUrl: 'my-url', dynamicLinkDomain: 'fdl-domain', + linkDomain: 'hosting-link-domain', canHandleCodeInApp: true, iOSBundleId: 'my-bundle', clientType: 'CLIENT_TYPE_WEB' @@ -148,13 +150,15 @@ describe('core/strategies/sendPasswordResetEmail', () => { packageName: 'my-package' }, url: 'my-url', - dynamicLinkDomain: 'fdl-domain' + dynamicLinkDomain: 'fdl-domain', + linkDomain: 'hosting-link-domain' }); expect(mock.calls[0].request).to.eql({ requestType: ActionCodeOperation.PASSWORD_RESET, email, continueUrl: 'my-url', dynamicLinkDomain: 'fdl-domain', + linkDomain: 'hosting-link-domain', canHandleCodeInApp: true, androidInstallApp: false, androidMinimumVersionCode: 'my-version', diff --git a/packages/auth/src/core/strategies/email_link.test.ts b/packages/auth/src/core/strategies/email_link.test.ts index 945da88e47b..3594050416e 100644 --- a/packages/auth/src/core/strategies/email_link.test.ts +++ b/packages/auth/src/core/strategies/email_link.test.ts @@ -123,7 +123,8 @@ describe('core/strategies/sendSignInLinkToEmail', () => { bundleId: 'my-bundle' }, url: 'my-url', - dynamicLinkDomain: 'fdl-domain' + dynamicLinkDomain: 'fdl-domain', + linkDomain: 'hosting-link-domain' }); expect(mock.calls[0].request).to.eql({ @@ -131,6 +132,7 @@ describe('core/strategies/sendSignInLinkToEmail', () => { email, continueUrl: 'my-url', dynamicLinkDomain: 'fdl-domain', + linkDomain: 'hosting-link-domain', canHandleCodeInApp: true, iOSBundleId: 'my-bundle', clientType: 'CLIENT_TYPE_WEB' @@ -151,13 +153,15 @@ describe('core/strategies/sendSignInLinkToEmail', () => { packageName: 'my-package' }, url: 'my-url', - dynamicLinkDomain: 'fdl-domain' + dynamicLinkDomain: 'fdl-domain', + linkDomain: 'hosting-link-domain' }); expect(mock.calls[0].request).to.eql({ requestType: ActionCodeOperation.EMAIL_SIGNIN, email, continueUrl: 'my-url', dynamicLinkDomain: 'fdl-domain', + linkDomain: 'hosting-link-domain', canHandleCodeInApp: true, androidInstallApp: false, androidMinimumVersionCode: 'my-version', diff --git a/packages/auth/src/model/public_types.ts b/packages/auth/src/model/public_types.ts index 07e56a6b9aa..d8c89072dcc 100644 --- a/packages/auth/src/model/public_types.ts +++ b/packages/auth/src/model/public_types.ts @@ -504,7 +504,7 @@ export interface ActionCodeSettings { * - When the link is handled in the web action widgets, this is the deep link in the * `continueUrl` query parameter. * - When the link is handled in the app directly, this is the `continueUrl` query parameter in - * the deep link of the Dynamic Link. + * the deep link of the Dynamic Link or Hosting Link. */ url: string; /** @@ -515,6 +515,13 @@ export interface ActionCodeSettings { * @defaultValue The first domain is automatically selected. */ dynamicLinkDomain?: string; + /** + * The optional custom Firebase Hosting domain to use when the link is to be opened via + * a specified mobile app. The domain must be configured in Firebase Hosting and owned + * by the project. This cannot be a default hosting domain (web.app or firebaseapp.com). + * @defaultValue The default hosting domain will be used (for example, `example.firebaseapp.com`) + */ + linkDomain?: string; } /**