Skip to content

Commit 0901380

Browse files
feat(auth): Add LinkDomain to ActionCodeSettings and deprecate DynamicLinkDomain (#475)
* feat(auth): Add LinkDomain to ActionCodeSettings This change adds support for specifying a hosting link domain in `ActionCodeSettings`. This is used for email action links. The following changes were made: - Added the `LinkDomain` property to the `ActionCodeSettings` class. - Deprecated the `DynamicLinkDomain` property in `ActionCodeSettings`. - Added the `InvalidHostingLinkDomain` error code to `AuthErrorCode` and the corresponding error handling logic in `AuthErrorHandler`. - Updated the `EmailActionLinkRequest` to use the new `LinkDomain` property and remove usage of the deprecated `DynamicLinkDomain` property. - Updated unit tests to cover the new functionality and error conditions. Removed tests for the deprecated property as per repository guidelines to avoid build failures. - Fixed a style error in the tests that was introduced. * fix(auth): Correct implementation of LinkDomain feature This commit corrects the implementation of the `LinkDomain` feature based on feedback. The previous implementation had incorrectly removed the deprecated `DynamicLinkDomain` functionality. The following corrections were made: - Restored the `DynamicLinkDomain` property and its related logic in the production code to maintain backward compatibility. - Updated all `.csproj` files to suppress the `CS0618` obsolescence warning, which is the correct way to handle this deprecation without breaking the build. - Restored the original unit tests for `DynamicLinkDomain` and added new, separate tests for the `LinkDomain` property. - Updated the code snippets to use the new `LinkDomain` property. - Fixed a style error in the test files. * fix(auth): Refactor generated tests and use pragma directives instead of permitting obsolete warnings. * fix(chore): Fixed docstring and updated `AGENTS.md` with learnings --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent 75f6b03 commit 0901380

File tree

8 files changed

+98
-5
lines changed

8 files changed

+98
-5
lines changed

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ dotnet test FirebaseAdmin/FirebaseAdmin.Tests --framework net462
7272
### Journey 2: How to Deprecate a Field/Method in an Existing API
7373

7474
1. **Add Deprecation Note**: Locate where the deprecated object is defined and add a deprecation warning with a note (e.g. [Obsolete("Use X instead")]).
75-
2. **Remove Releted Tests and Update Snippets**: Because `Obsolete` warnings result in build errors, tests and snippets where the object is used should be removed or updated not no longer used the deprecated object.
75+
2. **Suppress Obsolete Warnings**: Because `Obsolete` warnings result in build errors, tests and snippets where the object is used should be marked using `#pragma` directives to suppress warnings where the object is used.
7676

7777
## Critical Do's and Don'ts
7878

FirebaseAdmin/FirebaseAdmin.Snippets/FirebaseAuthSnippets.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@ internal static ActionCodeSettings InitActionCodeSettings()
643643
AndroidPackageName = "com.example.android",
644644
AndroidInstallApp = true,
645645
AndroidMinimumVersion = "12",
646-
DynamicLinkDomain = "coolapp.page.link",
646+
LinkDomain = "coolapp.page.link",
647647
};
648648
// [END init_action_code_settings]
649649
return actionCodeSettings;

FirebaseAdmin/FirebaseAdmin.Tests/Auth/AuthErrorHandlerTest.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ public class AuthErrorHandlerTest
6363
ErrorCode.NotFound,
6464
AuthErrorCode.EmailNotFound,
6565
},
66+
new object[]
67+
{
68+
"INVALID_HOSTING_LINK_DOMAIN",
69+
ErrorCode.InvalidArgument,
70+
AuthErrorCode.InvalidHostingLinkDomain,
71+
},
6672
};
6773

6874
[Theory]

FirebaseAdmin/FirebaseAdmin.Tests/Auth/Users/EmailActionRequestTest.cs

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,17 @@ public class EmailActionRequestTest
4747
new ActionCodeSettings()
4848
{
4949
Url = "https://example.dynamic.link",
50+
#pragma warning disable CS0618
5051
DynamicLinkDomain = string.Empty,
52+
#pragma warning restore CS0618
53+
},
54+
},
55+
new object[]
56+
{
57+
new ActionCodeSettings()
58+
{
59+
Url = "https://example.dynamic.link",
60+
LinkDomain = string.Empty,
5161
},
5262
},
5363
new object[]
@@ -84,7 +94,10 @@ public class EmailActionRequestTest
8494
{
8595
Url = "https://example.dynamic.link",
8696
HandleCodeInApp = true,
97+
#pragma warning disable CS0618
8798
DynamicLinkDomain = "custom.page.link",
99+
#pragma warning restore CS0618
100+
LinkDomain = "custom.page.link",
88101
IosBundleId = "com.example.ios",
89102
AndroidPackageName = "com.example.android",
90103
AndroidMinimumVersion = "6",
@@ -163,14 +176,17 @@ public async Task EmailVerificationLinkWithSettings()
163176

164177
var request = NewtonsoftJsonSerializer.Instance.Deserialize<Dictionary<string, object>>(
165178
handler.LastRequestBody);
166-
Assert.Equal(10, request.Count);
179+
Assert.Equal(11, request.Count);
167180
Assert.Equal("[email protected]", request["email"]);
168181
Assert.Equal("VERIFY_EMAIL", request["requestType"]);
169182
Assert.True((bool)request["returnOobLink"]);
170183

171184
Assert.Equal(ActionCodeSettings.Url, request["continueUrl"]);
172185
Assert.True((bool)request["canHandleCodeInApp"]);
186+
#pragma warning disable CS0618
173187
Assert.Equal(ActionCodeSettings.DynamicLinkDomain, request["dynamicLinkDomain"]);
188+
#pragma warning restore CS0618
189+
Assert.Equal(ActionCodeSettings.LinkDomain, request["linkDomain"]);
174190
Assert.Equal(ActionCodeSettings.IosBundleId, request["iOSBundleId"]);
175191
Assert.Equal(ActionCodeSettings.AndroidPackageName, request["androidPackageName"]);
176192
Assert.Equal(
@@ -229,14 +245,17 @@ public async Task PasswordResetLinkWithSettings()
229245

230246
var request = NewtonsoftJsonSerializer.Instance.Deserialize<Dictionary<string, object>>(
231247
handler.LastRequestBody);
232-
Assert.Equal(10, request.Count);
248+
Assert.Equal(11, request.Count);
233249
Assert.Equal("[email protected]", request["email"]);
234250
Assert.Equal("PASSWORD_RESET", request["requestType"]);
235251
Assert.True((bool)request["returnOobLink"]);
236252

237253
Assert.Equal(ActionCodeSettings.Url, request["continueUrl"]);
238254
Assert.True((bool)request["canHandleCodeInApp"]);
255+
#pragma warning disable CS0618
239256
Assert.Equal(ActionCodeSettings.DynamicLinkDomain, request["dynamicLinkDomain"]);
257+
#pragma warning restore CS0618
258+
Assert.Equal(ActionCodeSettings.LinkDomain, request["linkDomain"]);
240259
Assert.Equal(ActionCodeSettings.IosBundleId, request["iOSBundleId"]);
241260
Assert.Equal(ActionCodeSettings.AndroidPackageName, request["androidPackageName"]);
242261
Assert.Equal(
@@ -287,14 +306,17 @@ public async Task SignInWithEmailLink()
287306

288307
var request = NewtonsoftJsonSerializer.Instance.Deserialize<Dictionary<string, object>>(
289308
handler.LastRequestBody);
290-
Assert.Equal(10, request.Count);
309+
Assert.Equal(11, request.Count);
291310
Assert.Equal("[email protected]", request["email"]);
292311
Assert.Equal("EMAIL_SIGNIN", request["requestType"]);
293312
Assert.True((bool)request["returnOobLink"]);
294313

295314
Assert.Equal(ActionCodeSettings.Url, request["continueUrl"]);
296315
Assert.True((bool)request["canHandleCodeInApp"]);
316+
#pragma warning disable CS0618
297317
Assert.Equal(ActionCodeSettings.DynamicLinkDomain, request["dynamicLinkDomain"]);
318+
#pragma warning restore CS0618
319+
Assert.Equal(ActionCodeSettings.LinkDomain, request["linkDomain"]);
298320
Assert.Equal(ActionCodeSettings.IosBundleId, request["iOSBundleId"]);
299321
Assert.Equal(ActionCodeSettings.AndroidPackageName, request["androidPackageName"]);
300322
Assert.Equal(
@@ -351,6 +373,39 @@ public async Task InvalidDynamicLinkDomain()
351373
Assert.Null(exception.InnerException);
352374
}
353375

376+
[Fact]
377+
public async Task InvalidHostingLinkDomain()
378+
{
379+
var json = $@"{{
380+
""error"": {{
381+
""message"": ""INVALID_HOSTING_LINK_DOMAIN"",
382+
}}
383+
}}";
384+
var handler = new MockMessageHandler()
385+
{
386+
StatusCode = HttpStatusCode.InternalServerError,
387+
Response = json,
388+
};
389+
var auth = this.CreateFirebaseAuth(handler);
390+
var settings = new ActionCodeSettings()
391+
{
392+
Url = "https://example.dynamic.link",
393+
LinkDomain = "custom.page.link",
394+
};
395+
396+
var exception = await Assert.ThrowsAsync<FirebaseAuthException>(
397+
async () => await auth.GenerateSignInWithEmailLinkAsync(
398+
"[email protected]", settings));
399+
400+
Assert.Equal(ErrorCode.InvalidArgument, exception.ErrorCode);
401+
Assert.Equal(AuthErrorCode.InvalidHostingLinkDomain, exception.AuthErrorCode);
402+
Assert.Equal(
403+
"The provided hosting link domain is not configured in Firebase Hosting or is not owned by the current project (INVALID_HOSTING_LINK_DOMAIN).",
404+
exception.Message);
405+
Assert.NotNull(exception.HttpResponse);
406+
Assert.Null(exception.InnerException);
407+
}
408+
354409
private FirebaseAuth CreateFirebaseAuth(HttpMessageHandler handler)
355410
{
356411
var userManager = new FirebaseUserManager(new FirebaseUserManager.Args

FirebaseAdmin/FirebaseAdmin/Auth/ActionCodeSettings.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,21 @@ public sealed class ActionCodeSettings
4949
/// </summary>
5050
public bool HandleCodeInApp { get; set; }
5151

52+
/// <summary>
53+
/// Gets or sets the hosting link domain to use for the current link if it is to be opened
54+
/// using <c>handleCodeInApp</c>, as multiple hosting link domains can be configured per
55+
/// project. This setting provides the ability to explicitly choose one. If none is provided,
56+
/// the default hosting domain is used.
57+
/// </summary>
58+
public string LinkDomain { get; set; }
59+
5260
/// <summary>
5361
/// Gets or sets the dynamic link domain to use for the current link if it is to be opened
5462
/// using Firebase Dynamic Links, as multiple dynamic link domains can be configured per
5563
/// project. This setting provides the ability to explicitly choose one. If none is provided,
5664
/// the oldest domain is used by default.
5765
/// </summary>
66+
[System.Obsolete("DynamicLinkDomain is deprecated, use LinkDomain instead")]
5867
public string DynamicLinkDomain { get; set; }
5968

6069
/// <summary>

FirebaseAdmin/FirebaseAdmin/Auth/AuthErrorCode.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ public enum AuthErrorCode
6464
/// </summary>
6565
InvalidDynamicLinkDomain,
6666

67+
/// <summary>
68+
/// The provided hosting link domain is not configured in Firebase Hosting or is not owned by the current project.
69+
/// </summary>
70+
InvalidHostingLinkDomain,
71+
6772
/// <summary>
6873
/// The specified ID token has been revoked.
6974
/// </summary>

FirebaseAdmin/FirebaseAdmin/Auth/AuthErrorHandler.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ internal sealed class AuthErrorHandler
7777
AuthErrorCode.InvalidDynamicLinkDomain,
7878
"Dynamic link domain specified in ActionCodeSettings is not authorized")
7979
},
80+
{
81+
"INVALID_HOSTING_LINK_DOMAIN",
82+
new ErrorInfo(
83+
ErrorCode.InvalidArgument,
84+
AuthErrorCode.InvalidHostingLinkDomain,
85+
"The provided hosting link domain is not configured in Firebase Hosting or is not owned by the current project")
86+
},
8087
{
8188
"PHONE_NUMBER_EXISTS",
8289
new ErrorInfo(

FirebaseAdmin/FirebaseAdmin/Auth/Users/EmailActionLinkRequest.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@ private EmailActionLinkRequest(string type, string email, ActionCodeSettings set
4242
{
4343
this.Url = settings.Url;
4444
this.HandleCodeInApp = settings.HandleCodeInApp;
45+
#pragma warning disable CS0618
4546
this.DynamicLinkDomain = settings.DynamicLinkDomain;
47+
#pragma warning restore CS0618
48+
this.LinkDomain = settings.LinkDomain;
4649
this.IosBundleId = settings.IosBundleId;
4750
this.AndroidPackageName = settings.AndroidPackageName;
4851
this.AndroidMinimumVersion = settings.AndroidMinimumVersion;
@@ -70,6 +73,9 @@ private EmailActionLinkRequest(string type, string email, ActionCodeSettings set
7073
[JsonProperty("dynamicLinkDomain")]
7174
internal string DynamicLinkDomain { get; }
7275

76+
[JsonProperty("linkDomain")]
77+
internal string LinkDomain { get; }
78+
7379
[JsonProperty("iOSBundleId")]
7480
internal string IosBundleId { get; }
7581

@@ -125,6 +131,11 @@ private void ValidateSettings()
125131
throw new ArgumentException("DynamicLinkDomain must not be empty");
126132
}
127133

134+
if (this.LinkDomain == string.Empty)
135+
{
136+
throw new ArgumentException("LinkDomain must not be empty");
137+
}
138+
128139
if (this.IosBundleId == string.Empty)
129140
{
130141
throw new ArgumentException("IosBundleId must not be empty");

0 commit comments

Comments
 (0)