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;
}
/**