Skip to content

Commit 431fc85

Browse files
authored
feat(auth): Added APIs for generating email verification links (#164)
* feat(auth): Added GenerateEmailVerificationLinkAsync() API * Updated API docs
1 parent d51d33c commit 431fc85

File tree

3 files changed

+132
-0
lines changed

3 files changed

+132
-0
lines changed

FirebaseAdmin/FirebaseAdmin.Tests/Auth/EmailActionRequestTest.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ public void NoEmail()
9696
var handler = new MockMessageHandler() { Response = GenerateEmailLinkResponse };
9797
var auth = this.CreateFirebaseAuth(handler);
9898

99+
Assert.ThrowsAsync<ArgumentException>(
100+
async () => await auth.GenerateEmailVerificationLinkAsync(null));
101+
Assert.ThrowsAsync<ArgumentException>(
102+
async () => await auth.GenerateEmailVerificationLinkAsync(string.Empty));
103+
99104
Assert.ThrowsAsync<ArgumentException>(
100105
async () => await auth.GeneratePasswordResetLinkAsync(null));
101106
Assert.ThrowsAsync<ArgumentException>(
@@ -110,10 +115,79 @@ public void InvalidActionCodeSettings(ActionCodeSettings settings)
110115
var auth = this.CreateFirebaseAuth(handler);
111116
var email = "[email protected]";
112117

118+
Assert.ThrowsAsync<ArgumentException>(
119+
async () => await auth.GenerateEmailVerificationLinkAsync(email, settings));
120+
113121
Assert.ThrowsAsync<ArgumentException>(
114122
async () => await auth.GeneratePasswordResetLinkAsync(email, settings));
115123
}
116124

125+
[Fact]
126+
public async Task EmailVerificationLink()
127+
{
128+
var handler = new MockMessageHandler() { Response = GenerateEmailLinkResponse };
129+
var auth = this.CreateFirebaseAuth(handler);
130+
131+
var link = await auth.GenerateEmailVerificationLinkAsync("[email protected]");
132+
133+
Assert.Equal("https://mock-oob-link.for.auth.tests", link);
134+
135+
var request = NewtonsoftJsonSerializer.Instance.Deserialize<Dictionary<string, object>>(
136+
handler.LastRequestBody);
137+
Assert.Equal(3, request.Count);
138+
Assert.Equal("[email protected]", request["email"]);
139+
Assert.Equal("VERIFY_EMAIL", request["requestType"]);
140+
Assert.True((bool)request["returnOobLink"]);
141+
this.AssertRequest(handler.Requests[0]);
142+
}
143+
144+
[Fact]
145+
public async Task EmailVerificationLinkWithSettings()
146+
{
147+
var handler = new MockMessageHandler() { Response = GenerateEmailLinkResponse };
148+
var auth = this.CreateFirebaseAuth(handler);
149+
150+
var link = await auth.GenerateEmailVerificationLinkAsync(
151+
"[email protected]", ActionCodeSettings);
152+
153+
Assert.Equal("https://mock-oob-link.for.auth.tests", link);
154+
155+
var request = NewtonsoftJsonSerializer.Instance.Deserialize<Dictionary<string, object>>(
156+
handler.LastRequestBody);
157+
Assert.Equal(10, request.Count);
158+
Assert.Equal("[email protected]", request["email"]);
159+
Assert.Equal("VERIFY_EMAIL", request["requestType"]);
160+
Assert.True((bool)request["returnOobLink"]);
161+
162+
Assert.Equal(ActionCodeSettings.Url, request["continueUrl"]);
163+
Assert.True((bool)request["canHandleCodeInApp"]);
164+
Assert.Equal(ActionCodeSettings.DynamicLinkDomain, request["dynamicLinkDomain"]);
165+
Assert.Equal(ActionCodeSettings.IosBundleId, request["iOSBundleId"]);
166+
Assert.Equal(ActionCodeSettings.AndroidPackageName, request["androidPackageName"]);
167+
Assert.Equal(
168+
ActionCodeSettings.AndroidMinimumVersion, request["androidMinimumVersion"]);
169+
Assert.True((bool)request["androidInstallApp"]);
170+
this.AssertRequest(handler.Requests[0]);
171+
}
172+
173+
[Fact]
174+
public async Task EmailVerificationLinkUnexpectedResponse()
175+
{
176+
var handler = new MockMessageHandler() { Response = "{}" };
177+
var auth = this.CreateFirebaseAuth(handler);
178+
179+
var exception = await Assert.ThrowsAsync<FirebaseAuthException>(
180+
async () => await auth.GenerateEmailVerificationLinkAsync("[email protected]"));
181+
182+
Assert.Equal(ErrorCode.Unknown, exception.ErrorCode);
183+
Assert.Equal(AuthErrorCode.UnexpectedResponse, exception.AuthErrorCode);
184+
Assert.Equal(
185+
$"Failed to generate email action link for: [email protected]",
186+
exception.Message);
187+
Assert.NotNull(exception.HttpResponse);
188+
Assert.Null(exception.InnerException);
189+
}
190+
117191
[Fact]
118192
public async Task PasswordResetLink()
119193
{

FirebaseAdmin/FirebaseAdmin/Auth/EmailActionLinkRequest.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ namespace FirebaseAdmin.Auth
1919
{
2020
internal sealed class EmailActionLinkRequest
2121
{
22+
private const string VerifyEmail = "VERIFY_EMAIL";
2223
private const string PasswordReset = "PASSWORD_RESET";
2324

2425
private EmailActionLinkRequest(string type, string email, ActionCodeSettings settings)
@@ -74,6 +75,12 @@ private EmailActionLinkRequest(string type, string email, ActionCodeSettings set
7475
[JsonProperty("androidInstallApp")]
7576
internal bool? AndroidInstallApp { get; }
7677

78+
internal static EmailActionLinkRequest EmailVerificationLinkRequest(
79+
string email, ActionCodeSettings settings)
80+
{
81+
return new EmailActionLinkRequest(VerifyEmail, email, settings);
82+
}
83+
7784
internal static EmailActionLinkRequest PasswordResetLinkRequest(
7885
string email, ActionCodeSettings settings)
7986
{

FirebaseAdmin/FirebaseAdmin/Auth/FirebaseAuth.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,57 @@ public PagedAsyncEnumerable<ExportedUserRecords, ExportedUserRecord> ListUsersAs
552552
return userManager.ListUsers(options);
553553
}
554554

555+
/// <summary>
556+
/// Generates the out-of-band email action link for email verification flows for the specified
557+
/// email address.
558+
/// </summary>
559+
/// <exception cref="FirebaseAuthException">If an error occurs while generating the link.</exception>
560+
/// <param name="email">The email of the user to be verified.</param>
561+
/// <returns>A task that completes with the email verification link.</returns>
562+
public async Task<string> GenerateEmailVerificationLinkAsync(string email)
563+
{
564+
return await this.GenerateEmailVerificationLinkAsync(email, null)
565+
.ConfigureAwait(false);
566+
}
567+
568+
/// <summary>
569+
/// Generates the out-of-band email action link for email verification flows for the specified
570+
/// email address.
571+
/// </summary>
572+
/// <exception cref="FirebaseAuthException">If an error occurs while generating the link.</exception>
573+
/// <param name="email">The email of the user to be verifed.</param>
574+
/// <param name="settings">The action code settings object that defines whether
575+
/// the link is to be handled by a mobile app and the additional state information to be
576+
/// passed in the deep link.</param>
577+
/// <returns>A task that completes with the email verification link.</returns>
578+
public async Task<string> GenerateEmailVerificationLinkAsync(
579+
string email, ActionCodeSettings settings)
580+
{
581+
return await this.GenerateEmailVerificationLinkAsync(email, settings, default(CancellationToken))
582+
.ConfigureAwait(false);
583+
}
584+
585+
/// <summary>
586+
/// Generates the out-of-band email action link for email verification flows for the specified
587+
/// email address.
588+
/// </summary>
589+
/// <exception cref="FirebaseAuthException">If an error occurs while generating the link.</exception>
590+
/// <param name="email">The email of the user to be verified.</param>
591+
/// <param name="settings">The action code settings object that defines whether
592+
/// the link is to be handled by a mobile app and the additional state information to be
593+
/// passed in the deep link.</param>
594+
/// <param name="cancellationToken">A cancellation token to monitor the asynchronous
595+
/// operation.</param>
596+
/// <returns>A task that completes with the email verification reset link.</returns>
597+
public async Task<string> GenerateEmailVerificationLinkAsync(
598+
string email, ActionCodeSettings settings, CancellationToken cancellationToken)
599+
{
600+
var userManager = this.IfNotDeleted(() => this.userManager.Value);
601+
var request = EmailActionLinkRequest.EmailVerificationLinkRequest(email, settings);
602+
return await userManager.GenerateEmailActionLinkAsync(request, cancellationToken)
603+
.ConfigureAwait(false);
604+
}
605+
555606
/// <summary>
556607
/// Generates the out-of-band email action link for password reset flows for the specified
557608
/// email address.

0 commit comments

Comments
 (0)