Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ActionCodeSettings.linkDomain and deprecate ActionCodeSettings.dynamicLinkDomain #8738

Merged
merged 13 commits into from
Feb 4, 2025
Merged
7 changes: 7 additions & 0 deletions .changeset/polite-lies-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'firebase': minor
'@firebase/auth-types': minor
'@firebase/auth': minor
---

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`.
3 changes: 3 additions & 0 deletions common/api-review/auth.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@ export interface ActionCodeSettings {
minimumVersion?: string;
packageName: string;
};
// @deprecated
dynamicLinkDomain?: string;
handleCodeInApp?: boolean;
iOS?: {
bundleId: string;
};
linkDomain?: string;
url: string;
}

Expand Down Expand Up @@ -236,6 +238,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
Expand Down
22 changes: 18 additions & 4 deletions docs-devsite/auth.actioncodesettings.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ 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, <code>example.page.link</code>). |
| [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 (<code>web.app</code> or <code>firebaseapp.com</code>). |
| [url](./auth.actioncodesettings.md#actioncodesettingsurl) | string | Sets the link continue/state URL. |

## ActionCodeSettings.android

Sets the Android package name.

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.
This will try to open the link in an Android app if it is installed.

<b>Signature:</b>

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

## ActionCodeSettings.dynamicLinkDomain

> Warning: This API is now obsolete.
NhienLam marked this conversation as resolved.
Show resolved Hide resolved
>
> 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)<!-- -->.
>

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`<!-- -->).

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

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

App installation is not supported for iOS.

<b>Signature:</b>

```typescript
Expand All @@ -82,11 +86,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`<!-- -->).

<b>Signature:</b>

```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.

<b>Signature:</b>

Expand Down
1 change: 1 addition & 0 deletions docs-devsite/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -1956,6 +1956,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";
}
```

Expand Down
1 change: 1 addition & 0 deletions packages/auth-types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export type ActionCodeSettings = {
iOS?: { bundleId: string };
url: string;
dynamicLinkDomain?: string;
linkDomain?: string;
};

export type AdditionalUserInfo = {
Expand Down
5 changes: 5 additions & 0 deletions packages/auth/demo/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,11 @@
<input type="text" class="form-control" id="ibi" placeholder="iOS Bundle ID"/>
</div>
</form>
<div class="group">Mobile link</div>
<div class="form-group">
<input type="text" class="form-control" id="hostingLinkDomain"
placeholder="Custom Hosting Link Domain"/>
</div>
<form class="form form-bordered no-submit">
<div class="btn-group radio-block" id="handle-in-app-selection"
data-toggle="buttons">
Expand Down
7 changes: 6 additions & 1 deletion packages/auth/demo/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ import {
signInWithCredential,
signInWithCustomToken,
signInWithEmailAndPassword,
signInWithEmailLink,
TotpMultiFactorGenerator,
TotpSecret,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be the only Totp change int his PR. Is its removal on purpose?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the removal is intentional because it's unused.

unlink,
updateEmail,
updatePassword,
Expand Down Expand Up @@ -995,6 +995,7 @@ function getActionCodeSettings() {
const installApp = $('input[name=install-app]:checked').val() === 'Yes';
const handleCodeInApp =
$('input[name=handle-in-app]:checked').val() === 'Yes';
const hostingLinkDomain = $('#hostingLinkDomain').val();
if (url || apn || ibi) {
actionCodeSettings['url'] = url;
if (apn) {
Expand All @@ -1010,6 +1011,9 @@ function getActionCodeSettings() {
};
}
actionCodeSettings['handleCodeInApp'] = handleCodeInApp;
if (hostingLinkDomain) {
actionCodeSettings['linkDomain'] = hostingLinkDomain;
}
}
return actionCodeSettings;
}
Expand All @@ -1020,6 +1024,7 @@ function onActionCodeSettingsReset() {
$('#apn').val('');
$('#amv').val('');
$('#ibi').val('');
$('#hostingLinkDomain').val('');
}

/**
Expand Down
3 changes: 2 additions & 1 deletion packages/auth/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ function getTestFiles(argv) {
return [
'test/integration/flows/totp.test.ts',
'test/integration/flows/password_policy.test.ts',
'test/integration/flows/recaptcha_enterprise.test.ts'
'test/integration/flows/recaptcha_enterprise.test.ts',
'test/integration/flows/hosting_link.test.ts'
];
}
return argv.local
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export interface GetOobCodeRequest {
dynamicLinkDomain?: string;
tenantId?: string;
targetProjectid?: string;
linkDomain?: string;
}

export interface VerifyEmailRequest extends GetOobCodeRequest {
Expand Down
3 changes: 2 additions & 1 deletion packages/auth/src/api/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}

/**
Expand Down
11 changes: 8 additions & 3 deletions packages/auth/src/core/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<AuthErrorCode> {
Expand Down Expand Up @@ -387,7 +388,10 @@ function _debugErrorMap(): ErrorMap<AuthErrorCode> {
[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`).'
};
}

Expand Down Expand Up @@ -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;
37 changes: 27 additions & 10 deletions packages/auth/src/core/strategies/action_code_settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Expand All @@ -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)');
});
Expand All @@ -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();
});
Expand All @@ -60,23 +64,36 @@ 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, {
handleCodeInApp: true,
iOS: {
bundleId: ''
},
url: 'my-url',
dynamicLinkDomain: 'fdl-domain'
url: TEST_URL,
dynamicLinkDomain: TEST_FDL_DOMAIN
})
).to.throw(FirebaseError, '(auth/missing-ios-bundle-id)');
});
Expand All @@ -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)');
});
Expand Down
7 changes: 7 additions & 0 deletions packages/auth/src/core/strategies/action_code_settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
16 changes: 12 additions & 4 deletions packages/auth/src/core/strategies/email.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,14 +162,16 @@ 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({
requestType: ActionCodeOperation.VERIFY_EMAIL,
idToken,
continueUrl: 'my-url',
dynamicLinkDomain: 'fdl-domain',
linkDomain: 'hosting-link-domain',
canHandleCodeInApp: true,
iOSBundleId: 'my-bundle'
});
Expand All @@ -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',
Expand Down Expand Up @@ -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({
Expand All @@ -279,6 +284,7 @@ describe('core/strategies/verifyBeforeUpdateEmail', () => {
newEmail,
continueUrl: 'my-url',
dynamicLinkDomain: 'fdl-domain',
linkDomain: 'hosting-link-domain',
canHandleCodeInApp: true,
iOSBundleId: 'my-bundle'
});
Expand All @@ -299,14 +305,16 @@ 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,
idToken,
newEmail,
continueUrl: 'my-url',
dynamicLinkDomain: 'fdl-domain',
linkDomain: 'hosting-link-domain',
canHandleCodeInApp: true,
androidInstallApp: false,
androidMinimumVersionCode: 'my-version',
Expand Down
Loading
Loading