Skip to content

Commit 9d88e3a

Browse files
Add ActionCodeSettings.linkDomain and deprecate ActionCodeSettings.dynamicLinkDomain (#8738)
* Add linkDomain field to ActionCodeSettings (#8428) * Add linkDomain field to ActionCodeSettings * Update API reports * Update error message for ERROR_INVALID_HOSTING_LINK_DOMAIN to include that default hosting domains cannot be used. * Use constants for test values --------- Co-authored-by: NhienLam <[email protected]> * FDL Deprecation & Hosting link Integration Test (#8603) * Implement Integration test for passwordless email sign-in via firebase-hosting links. * Add license to new test file created' did not match any files * Remove unwanted _ * Add support for setting custom hosting link domain in test app (#8614) * Add afterEach method and apply formatting to the hosting links integration test (#8615) * Mark ActionCodeSettings.dynamicLinkDomain as deprecated * Add changeset * Update API reports * Address review comments * Fix changeset * Update refdocs and address review comments * Update deprecated comment * Add FDL deprecation FAQ link * Fix linkDomain link in refdocs --------- Co-authored-by: NhienLam <[email protected]> Co-authored-by: mansisampat <[email protected]>
1 parent 2f92a74 commit 9d88e3a

File tree

18 files changed

+232
-37
lines changed

18 files changed

+232
-37
lines changed

.changeset/polite-lies-vanish.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'firebase': minor
3+
'@firebase/auth-types': minor
4+
'@firebase/auth': minor
5+
---
6+
7+
Added `ActionCodeSettings.linkDomain` to customize the Firebase Hosting link domain that is used in mobile out-of-band email action flows. Also, deprecated `ActionCodeSettings.dynamicLinkDomain`.

common/api-review/auth.api.md

+3
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,13 @@ export interface ActionCodeSettings {
4040
minimumVersion?: string;
4141
packageName: string;
4242
};
43+
// @deprecated
4344
dynamicLinkDomain?: string;
4445
handleCodeInApp?: boolean;
4546
iOS?: {
4647
bundleId: string;
4748
};
49+
linkDomain?: string;
4850
url: string;
4951
}
5052

@@ -236,6 +238,7 @@ export const AuthErrorCodes: {
236238
readonly MISSING_RECAPTCHA_VERSION: "auth/missing-recaptcha-version";
237239
readonly INVALID_RECAPTCHA_VERSION: "auth/invalid-recaptcha-version";
238240
readonly INVALID_REQ_TYPE: "auth/invalid-req-type";
241+
readonly INVALID_HOSTING_LINK_DOMAIN: "auth/invalid-hosting-link-domain";
239242
};
240243

241244
// @public

docs-devsite/auth.actioncodesettings.md

+18-4
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,14 @@ export interface ActionCodeSettings
2626
| [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, <code>example.page.link</code>). |
2727
| [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. |
2828
| [iOS](./auth.actioncodesettings.md#actioncodesettingsios) | { bundleId: string; } | Sets the iOS bundle ID. |
29+
| [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 (<code>web.app</code> or <code>firebaseapp.com</code>). |
2930
| [url](./auth.actioncodesettings.md#actioncodesettingsurl) | string | Sets the link continue/state URL. |
3031

3132
## ActionCodeSettings.android
3233

3334
Sets the Android package name.
3435

35-
This will try to open the link in an android app if it is installed. If `installApp` is passed, it specifies whether to install the Android app if the device supports it and the app is not already installed. If this field is provided without a `packageName`<!-- -->, an error is thrown explaining that the `packageName` must be provided in conjunction with this field. If `minimumVersion` is specified, and an older version of the app is installed, the user is taken to the Play Store to upgrade the app.
36+
This will try to open the link in an Android app if it is installed.
3637

3738
<b>Signature:</b>
3839

@@ -46,6 +47,11 @@ android?: {
4647

4748
## ActionCodeSettings.dynamicLinkDomain
4849

50+
> Warning: This API is now obsolete.
51+
>
52+
> Firebase Dynamic Links is deprecated and will be shut down as early as August 2025. Instead, use [ActionCodeSettings.linkDomain](./auth.actioncodesettings.md#actioncodesettingslinkdomain) to set a custom domain for mobile links. Learn more in the [Dynamic Links deprecation FAQ](https://firebase.google.com/support/dynamic-links-faq)<!-- -->.
53+
>
54+
4955
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`<!-- -->).
5056

5157
<b>Signature:</b>
@@ -72,8 +78,6 @@ Sets the iOS bundle ID.
7278

7379
This will try to open the link in an iOS app if it is installed.
7480

75-
App installation is not supported for iOS.
76-
7781
<b>Signature:</b>
7882

7983
```typescript
@@ -82,11 +86,21 @@ iOS?: {
8286
};
8387
```
8488

89+
## ActionCodeSettings.linkDomain
90+
91+
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`<!-- -->).
92+
93+
<b>Signature:</b>
94+
95+
```typescript
96+
linkDomain?: string;
97+
```
98+
8599
## ActionCodeSettings.url
86100

87101
Sets the link continue/state URL.
88102

89-
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.
103+
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.
90104

91105
<b>Signature:</b>
92106

docs-devsite/auth.md

+1
Original file line numberDiff line numberDiff line change
@@ -1956,6 +1956,7 @@ AUTH_ERROR_CODES_MAP_DO_NOT_USE_INTERNALLY: {
19561956
readonly MISSING_RECAPTCHA_VERSION: "auth/missing-recaptcha-version";
19571957
readonly INVALID_RECAPTCHA_VERSION: "auth/invalid-recaptcha-version";
19581958
readonly INVALID_REQ_TYPE: "auth/invalid-req-type";
1959+
readonly INVALID_HOSTING_LINK_DOMAIN: "auth/invalid-hosting-link-domain";
19591960
}
19601961
```
19611962

packages/auth-types/index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ export type ActionCodeSettings = {
130130
iOS?: { bundleId: string };
131131
url: string;
132132
dynamicLinkDomain?: string;
133+
linkDomain?: string;
133134
};
134135

135136
export type AdditionalUserInfo = {

packages/auth/demo/public/index.html

+5
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,11 @@
823823
<input type="text" class="form-control" id="ibi" placeholder="iOS Bundle ID"/>
824824
</div>
825825
</form>
826+
<div class="group">Mobile link</div>
827+
<div class="form-group">
828+
<input type="text" class="form-control" id="hostingLinkDomain"
829+
placeholder="Custom Hosting Link Domain"/>
830+
</div>
826831
<form class="form form-bordered no-submit">
827832
<div class="btn-group radio-block" id="handle-in-app-selection"
828833
data-toggle="buttons">

packages/auth/demo/src/index.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ import {
4949
signInWithCredential,
5050
signInWithCustomToken,
5151
signInWithEmailAndPassword,
52+
signInWithEmailLink,
5253
TotpMultiFactorGenerator,
53-
TotpSecret,
5454
unlink,
5555
updateEmail,
5656
updatePassword,
@@ -995,6 +995,7 @@ function getActionCodeSettings() {
995995
const installApp = $('input[name=install-app]:checked').val() === 'Yes';
996996
const handleCodeInApp =
997997
$('input[name=handle-in-app]:checked').val() === 'Yes';
998+
const hostingLinkDomain = $('#hostingLinkDomain').val();
998999
if (url || apn || ibi) {
9991000
actionCodeSettings['url'] = url;
10001001
if (apn) {
@@ -1010,6 +1011,9 @@ function getActionCodeSettings() {
10101011
};
10111012
}
10121013
actionCodeSettings['handleCodeInApp'] = handleCodeInApp;
1014+
if (hostingLinkDomain) {
1015+
actionCodeSettings['linkDomain'] = hostingLinkDomain;
1016+
}
10131017
}
10141018
return actionCodeSettings;
10151019
}
@@ -1020,6 +1024,7 @@ function onActionCodeSettingsReset() {
10201024
$('#apn').val('');
10211025
$('#amv').val('');
10221026
$('#ibi').val('');
1027+
$('#hostingLinkDomain').val('');
10231028
}
10241029

10251030
/**

packages/auth/karma.conf.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ function getTestFiles(argv) {
4141
return [
4242
'test/integration/flows/totp.test.ts',
4343
'test/integration/flows/password_policy.test.ts',
44-
'test/integration/flows/recaptcha_enterprise.test.ts'
44+
'test/integration/flows/recaptcha_enterprise.test.ts',
45+
'test/integration/flows/hosting_link.test.ts'
4546
];
4647
}
4748
return argv.local

packages/auth/src/api/authentication/email_and_password.ts

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export interface GetOobCodeRequest {
7070
dynamicLinkDomain?: string;
7171
tenantId?: string;
7272
targetProjectid?: string;
73+
linkDomain?: string;
7374
}
7475

7576
export interface VerifyEmailRequest extends GetOobCodeRequest {

packages/auth/src/api/errors.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ export const enum ServerError {
100100
MISSING_RECAPTCHA_VERSION = 'MISSING_RECAPTCHA_VERSION',
101101
INVALID_RECAPTCHA_VERSION = 'INVALID_RECAPTCHA_VERSION',
102102
INVALID_REQ_TYPE = 'INVALID_REQ_TYPE',
103-
PASSWORD_DOES_NOT_MEET_REQUIREMENTS = 'PASSWORD_DOES_NOT_MEET_REQUIREMENTS'
103+
PASSWORD_DOES_NOT_MEET_REQUIREMENTS = 'PASSWORD_DOES_NOT_MEET_REQUIREMENTS',
104+
INVALID_HOSTING_LINK_DOMAIN = 'INVALID_HOSTING_LINK_DOMAIN'
104105
}
105106

106107
/**

packages/auth/src/core/errors.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,8 @@ export const enum AuthErrorCode {
134134
INVALID_RECAPTCHA_VERSION = 'invalid-recaptcha-version',
135135
INVALID_REQ_TYPE = 'invalid-req-type',
136136
UNSUPPORTED_PASSWORD_POLICY_SCHEMA_VERSION = 'unsupported-password-policy-schema-version',
137-
PASSWORD_DOES_NOT_MEET_REQUIREMENTS = 'password-does-not-meet-requirements'
137+
PASSWORD_DOES_NOT_MEET_REQUIREMENTS = 'password-does-not-meet-requirements',
138+
INVALID_HOSTING_LINK_DOMAIN = 'invalid-hosting-link-domain'
138139
}
139140

140141
function _debugErrorMap(): ErrorMap<AuthErrorCode> {
@@ -387,7 +388,10 @@ function _debugErrorMap(): ErrorMap<AuthErrorCode> {
387388
[AuthErrorCode.UNSUPPORTED_PASSWORD_POLICY_SCHEMA_VERSION]:
388389
'The password policy received from the backend uses a schema version that is not supported by this version of the Firebase SDK.',
389390
[AuthErrorCode.PASSWORD_DOES_NOT_MEET_REQUIREMENTS]:
390-
'The password does not meet the requirements.'
391+
'The password does not meet the requirements.',
392+
[AuthErrorCode.INVALID_HOSTING_LINK_DOMAIN]:
393+
'The provided Hosting link domain is not configured in Firebase Hosting or is not owned by ' +
394+
'the current project. This cannot be a default Hosting domain (`web.app` or `firebaseapp.com`).'
391395
};
392396
}
393397

@@ -598,5 +602,6 @@ export const AUTH_ERROR_CODES_MAP_DO_NOT_USE_INTERNALLY = {
598602
MISSING_CLIENT_TYPE: 'auth/missing-client-type',
599603
MISSING_RECAPTCHA_VERSION: 'auth/missing-recaptcha-version',
600604
INVALID_RECAPTCHA_VERSION: 'auth/invalid-recaptcha-version',
601-
INVALID_REQ_TYPE: 'auth/invalid-req-type'
605+
INVALID_REQ_TYPE: 'auth/invalid-req-type',
606+
INVALID_HOSTING_LINK_DOMAIN: 'auth/invalid-hosting-link-domain'
602607
} as const;

packages/auth/src/core/strategies/action_code_settings.test.ts

+27-10
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ describe('core/strategies/action_code_settings', () => {
2626
let auth: TestAuth;
2727
const request: GetOobCodeRequest = {};
2828

29+
const TEST_BUNDLE_ID = 'my-bundle';
30+
const TEST_FDL_DOMAIN = 'fdl-domain';
31+
const TEST_URL = 'my-url';
32+
2933
beforeEach(async () => {
3034
auth = await testAuth();
3135
});
@@ -35,10 +39,10 @@ describe('core/strategies/action_code_settings', () => {
3539
_setActionCodeSettingsOnRequest(auth, request, {
3640
handleCodeInApp: true,
3741
iOS: {
38-
bundleId: 'my-bundle'
42+
bundleId: TEST_BUNDLE_ID
3943
},
4044
url: '',
41-
dynamicLinkDomain: 'fdl-domain'
45+
dynamicLinkDomain: TEST_FDL_DOMAIN
4246
})
4347
).to.throw(FirebaseError, '(auth/invalid-continue-uri)');
4448
});
@@ -48,9 +52,9 @@ describe('core/strategies/action_code_settings', () => {
4852
_setActionCodeSettingsOnRequest(auth, request, {
4953
handleCodeInApp: true,
5054
iOS: {
51-
bundleId: 'my-´bundle'
55+
bundleId: TEST_BUNDLE_ID
5256
},
53-
url: 'my-url'
57+
url: TEST_URL
5458
})
5559
).to.not.throw();
5660
});
@@ -60,23 +64,36 @@ describe('core/strategies/action_code_settings', () => {
6064
_setActionCodeSettingsOnRequest(auth, request, {
6165
handleCodeInApp: true,
6266
iOS: {
63-
bundleId: 'my-´bundle'
67+
bundleId: TEST_BUNDLE_ID
6468
},
65-
url: 'my-url',
69+
url: TEST_URL,
6670
dynamicLinkDomain: ''
6771
})
6872
).to.throw(FirebaseError, '(auth/invalid-dynamic-link-domain)');
6973
});
7074

75+
it('should require a non empty Hosting link URL', () => {
76+
expect(() =>
77+
_setActionCodeSettingsOnRequest(auth, request, {
78+
handleCodeInApp: true,
79+
iOS: {
80+
bundleId: TEST_BUNDLE_ID
81+
},
82+
url: TEST_URL,
83+
linkDomain: ''
84+
})
85+
).to.throw(FirebaseError, '(auth/invalid-hosting-link-domain)');
86+
});
87+
7188
it('should require a non-empty bundle ID', () => {
7289
expect(() =>
7390
_setActionCodeSettingsOnRequest(auth, request, {
7491
handleCodeInApp: true,
7592
iOS: {
7693
bundleId: ''
7794
},
78-
url: 'my-url',
79-
dynamicLinkDomain: 'fdl-domain'
95+
url: TEST_URL,
96+
dynamicLinkDomain: TEST_FDL_DOMAIN
8097
})
8198
).to.throw(FirebaseError, '(auth/missing-ios-bundle-id)');
8299
});
@@ -88,8 +105,8 @@ describe('core/strategies/action_code_settings', () => {
88105
android: {
89106
packageName: ''
90107
},
91-
url: 'my-url',
92-
dynamicLinkDomain: 'fdl-domain'
108+
url: TEST_URL,
109+
dynamicLinkDomain: TEST_FDL_DOMAIN
93110
})
94111
).to.throw(FirebaseError, '(auth/missing-android-pkg-name)');
95112
});

packages/auth/src/core/strategies/action_code_settings.ts

+7
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,16 @@ export function _setActionCodeSettingsOnRequest(
3737
auth,
3838
AuthErrorCode.INVALID_DYNAMIC_LINK_DOMAIN
3939
);
40+
_assert(
41+
typeof actionCodeSettings.linkDomain === 'undefined' ||
42+
actionCodeSettings.linkDomain.length > 0,
43+
auth,
44+
AuthErrorCode.INVALID_HOSTING_LINK_DOMAIN
45+
);
4046

4147
request.continueUrl = actionCodeSettings.url;
4248
request.dynamicLinkDomain = actionCodeSettings.dynamicLinkDomain;
49+
request.linkDomain = actionCodeSettings.linkDomain;
4350
request.canHandleCodeInApp = actionCodeSettings.handleCodeInApp;
4451

4552
if (actionCodeSettings.iOS) {

packages/auth/src/core/strategies/email.test.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -162,14 +162,16 @@ describe('core/strategies/sendEmailVerification', () => {
162162
bundleId: 'my-bundle'
163163
},
164164
url: 'my-url',
165-
dynamicLinkDomain: 'fdl-domain'
165+
dynamicLinkDomain: 'fdl-domain',
166+
linkDomain: 'hosting-link-domain'
166167
});
167168

168169
expect(mock.calls[0].request).to.eql({
169170
requestType: ActionCodeOperation.VERIFY_EMAIL,
170171
idToken,
171172
continueUrl: 'my-url',
172173
dynamicLinkDomain: 'fdl-domain',
174+
linkDomain: 'hosting-link-domain',
173175
canHandleCodeInApp: true,
174176
iOSBundleId: 'my-bundle'
175177
});
@@ -190,13 +192,15 @@ describe('core/strategies/sendEmailVerification', () => {
190192
packageName: 'my-package'
191193
},
192194
url: 'my-url',
193-
dynamicLinkDomain: 'fdl-domain'
195+
dynamicLinkDomain: 'fdl-domain',
196+
linkDomain: 'hosting-link-domain'
194197
});
195198
expect(mock.calls[0].request).to.eql({
196199
requestType: ActionCodeOperation.VERIFY_EMAIL,
197200
idToken,
198201
continueUrl: 'my-url',
199202
dynamicLinkDomain: 'fdl-domain',
203+
linkDomain: 'hosting-link-domain',
200204
canHandleCodeInApp: true,
201205
androidInstallApp: false,
202206
androidMinimumVersionCode: 'my-version',
@@ -270,7 +274,8 @@ describe('core/strategies/verifyBeforeUpdateEmail', () => {
270274
bundleId: 'my-bundle'
271275
},
272276
url: 'my-url',
273-
dynamicLinkDomain: 'fdl-domain'
277+
dynamicLinkDomain: 'fdl-domain',
278+
linkDomain: 'hosting-link-domain'
274279
});
275280

276281
expect(mock.calls[0].request).to.eql({
@@ -279,6 +284,7 @@ describe('core/strategies/verifyBeforeUpdateEmail', () => {
279284
newEmail,
280285
continueUrl: 'my-url',
281286
dynamicLinkDomain: 'fdl-domain',
287+
linkDomain: 'hosting-link-domain',
282288
canHandleCodeInApp: true,
283289
iOSBundleId: 'my-bundle'
284290
});
@@ -299,14 +305,16 @@ describe('core/strategies/verifyBeforeUpdateEmail', () => {
299305
packageName: 'my-package'
300306
},
301307
url: 'my-url',
302-
dynamicLinkDomain: 'fdl-domain'
308+
dynamicLinkDomain: 'fdl-domain',
309+
linkDomain: 'hosting-link-domain'
303310
});
304311
expect(mock.calls[0].request).to.eql({
305312
requestType: ActionCodeOperation.VERIFY_AND_CHANGE_EMAIL,
306313
idToken,
307314
newEmail,
308315
continueUrl: 'my-url',
309316
dynamicLinkDomain: 'fdl-domain',
317+
linkDomain: 'hosting-link-domain',
310318
canHandleCodeInApp: true,
311319
androidInstallApp: false,
312320
androidMinimumVersionCode: 'my-version',

0 commit comments

Comments
 (0)