Skip to content

Commit 6c51b93

Browse files
authored
feat(auth): Added RevokeRefreshTokensAsync() API (#171)
* feat(auth): Added RevokeRefreshTokensAsync() API * Adding ConfigureAwait to async API call
1 parent f9a23c7 commit 6c51b93

File tree

4 files changed

+115
-3
lines changed

4 files changed

+115
-3
lines changed

FirebaseAdmin/FirebaseAdmin.Tests/Auth/FirebaseUserManagerTest.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
using FirebaseAdmin.Util;
2424
using Google.Apis.Auth.OAuth2;
2525
using Google.Apis.Json;
26+
using Google.Apis.Util;
2627
using Newtonsoft.Json.Linq;
2728
using Xunit;
2829

@@ -37,6 +38,8 @@ public class FirebaseUserManagerTest
3738
private static readonly GoogleCredential MockCredential =
3839
GoogleCredential.FromAccessToken("test-token");
3940

41+
private static readonly IClock MockClock = new MockClock();
42+
4043
private static readonly IList<string> ListUsersResponse = new List<string>()
4144
{
4245
@"{
@@ -1529,6 +1532,49 @@ public async Task DeleteUserNotFound()
15291532
Assert.Null(exception.InnerException);
15301533
}
15311534

1535+
[Fact]
1536+
public async Task RevokeRefreshTokens()
1537+
{
1538+
var handler = new MockMessageHandler()
1539+
{
1540+
Response = CreateUserResponse,
1541+
};
1542+
var auth = this.CreateFirebaseAuth(handler);
1543+
1544+
await auth.RevokeRefreshTokensAsync("user1");
1545+
1546+
Assert.Equal(1, handler.Requests.Count);
1547+
var request = NewtonsoftJsonSerializer.Instance.Deserialize<JObject>(handler.LastRequestBody);
1548+
Assert.Equal(2, request.Count);
1549+
Assert.Equal("user1", request["localId"]);
1550+
Assert.Equal(MockClock.UnixTimestamp(), request["validSince"]);
1551+
1552+
this.AssertClientVersion(handler.LastRequestHeaders);
1553+
}
1554+
1555+
[Fact]
1556+
public void RevokeRefreshTokensNoUid()
1557+
{
1558+
var handler = new MockMessageHandler() { Response = CreateUserResponse };
1559+
var auth = this.CreateFirebaseAuth(handler);
1560+
1561+
Assert.ThrowsAsync<ArgumentException>(
1562+
async () => await auth.RevokeRefreshTokensAsync(null));
1563+
Assert.ThrowsAsync<ArgumentException>(
1564+
async () => await auth.RevokeRefreshTokensAsync(string.Empty));
1565+
}
1566+
1567+
[Fact]
1568+
public void RevokeRefreshTokensInvalidUid()
1569+
{
1570+
var handler = new MockMessageHandler() { Response = CreateUserResponse };
1571+
var auth = this.CreateFirebaseAuth(handler);
1572+
1573+
var uid = new string('a', 129);
1574+
Assert.ThrowsAsync<ArgumentException>(
1575+
async () => await auth.RevokeRefreshTokensAsync(uid));
1576+
}
1577+
15321578
[Fact]
15331579
public async Task ServiceUnvailable()
15341580
{
@@ -1576,6 +1622,7 @@ private FirebaseAuth CreateFirebaseAuth(HttpMessageHandler handler)
15761622
ProjectId = MockProjectId,
15771623
ClientFactory = new MockHttpClientFactory(handler),
15781624
RetryOptions = RetryOptions.NoBackOff,
1625+
Clock = MockClock,
15791626
});
15801627
return new FirebaseAuth(new FirebaseAuth.FirebaseAuthArgs()
15811628
{

FirebaseAdmin/FirebaseAdmin/Auth/FirebaseAuth.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,51 @@ public async Task<UserRecord> UpdateUserAsync(
451451
.ConfigureAwait(false);
452452
}
453453

454+
/// <summary>
455+
/// Revokes all refresh tokens for the specified user.
456+
///
457+
/// Updates the user's tokensValidAfterTimestamp to the current UTC time expressed in
458+
/// seconds since the epoch and truncated to 1 second accuracy. It is important that
459+
/// the server on which this is called has its clock set correctly and synchronized.
460+
///
461+
/// While this will revoke all sessions for a specified user and disable any new ID tokens
462+
/// for existing sessions from getting minted, existing ID tokens may remain active until
463+
/// their natural expiration (one hour).
464+
/// </summary>
465+
/// <param name="uid">A user ID string.</param>
466+
/// <returns>A task that completes when the user's refresh tokens have been revoked.</returns>
467+
/// <exception cref="ArgumentException">If the user ID argument is null or empty.</exception>
468+
/// <exception cref="FirebaseAuthException">If an error occurs while revoking the tokens.</exception>
469+
public async Task RevokeRefreshTokensAsync(string uid)
470+
{
471+
await this.RevokeRefreshTokensAsync(uid, default(CancellationToken))
472+
.ConfigureAwait(false);
473+
}
474+
475+
/// <summary>
476+
/// Revokes all refresh tokens for the specified user.
477+
///
478+
/// Updates the user's tokensValidAfterTimestamp to the current UTC time expressed in
479+
/// seconds since the epoch and truncated to 1 second accuracy. It is important that
480+
/// the server on which this is called has its clock set correctly and synchronized.
481+
///
482+
/// While this will revoke all sessions for a specified user and disable any new ID tokens
483+
/// for existing sessions from getting minted, existing ID tokens may remain active until
484+
/// their natural expiration (one hour).
485+
/// </summary>
486+
/// <param name="uid">A user ID string.</param>
487+
/// <param name="cancellationToken">A cancellation token to monitor the asynchronous
488+
/// operation.</param>
489+
/// <returns>A task that completes when the user's refresh tokens have been revoked.</returns>
490+
/// <exception cref="ArgumentException">If the user ID argument is null or empty.</exception>
491+
/// <exception cref="FirebaseAuthException">If an error occurs while revoking the tokens.</exception>
492+
public async Task RevokeRefreshTokensAsync(string uid, CancellationToken cancellationToken)
493+
{
494+
var userManager = this.IfNotDeleted(() => this.userManager.Value);
495+
await userManager.RevokeRefreshTokensAsync(uid, cancellationToken)
496+
.ConfigureAwait(false);
497+
}
498+
454499
/// <summary>
455500
/// Deletes the user identified by the specified <paramref name="uid"/>.
456501
/// </summary>

FirebaseAdmin/FirebaseAdmin/Auth/FirebaseUserManager.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@
1818
using System.Threading;
1919
using System.Threading.Tasks;
2020
using FirebaseAdmin.Util;
21-
using Google.Api.Gax;
2221
using Google.Api.Gax.Rest;
2322
using Google.Apis.Auth.OAuth2;
2423
using Google.Apis.Http;
2524
using Google.Apis.Json;
2625
using Google.Apis.Util;
27-
using Newtonsoft.Json;
2826
using Newtonsoft.Json.Linq;
2927

28+
using Gax = Google.Api.Gax;
29+
3030
namespace FirebaseAdmin.Auth
3131
{
3232
/// <summary>
@@ -45,6 +45,7 @@ internal class FirebaseUserManager : IDisposable
4545

4646
private readonly ErrorHandlingHttpClient<FirebaseAuthException> httpClient;
4747
private readonly string baseUrl;
48+
private readonly IClock clock;
4849

4950
internal FirebaseUserManager(Args args)
5051
{
@@ -65,6 +66,7 @@ internal FirebaseUserManager(Args args)
6566
RetryOptions = args.RetryOptions,
6667
});
6768
this.baseUrl = string.Format(IdTooklitUrl, args.ProjectId);
69+
this.clock = args.Clock ?? SystemClock.Default;
6870
}
6971

7072
public void Dispose()
@@ -159,7 +161,7 @@ internal async Task<UserRecord> GetUserByPhoneNumberAsync(
159161
.ConfigureAwait(false);
160162
}
161163

162-
internal PagedAsyncEnumerable<ExportedUserRecords, ExportedUserRecord> ListUsers(
164+
internal Gax.PagedAsyncEnumerable<ExportedUserRecords, ExportedUserRecord> ListUsers(
163165
ListUsersOptions options)
164166
{
165167
var factory = new ListUsersRequest.Factory(this.baseUrl, this.httpClient, options);
@@ -244,6 +246,16 @@ internal async Task DeleteUserAsync(
244246
}
245247
}
246248

249+
internal async Task RevokeRefreshTokensAsync(string uid, CancellationToken cancellationToken)
250+
{
251+
var args = new UserRecordArgs()
252+
{
253+
Uid = uid,
254+
ValidSince = this.clock.UnixTimestamp(),
255+
};
256+
await this.UpdateUserAsync(args, cancellationToken).ConfigureAwait(false);
257+
}
258+
247259
internal async Task<string> GenerateEmailActionLinkAsync(
248260
EmailActionLinkRequest request,
249261
CancellationToken cancellationToken = default(CancellationToken))
@@ -313,6 +325,8 @@ internal sealed class Args
313325
internal string ProjectId { get; set; }
314326

315327
internal RetryOptions RetryOptions { get; set; }
328+
329+
internal IClock Clock { get; set; }
316330
}
317331

318332
/// <summary>

FirebaseAdmin/FirebaseAdmin/Auth/UserRecordArgs.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ public bool Disabled
9393
/// </summary>
9494
public string Password { get; set; }
9595

96+
internal long? ValidSince { get; set; }
97+
9698
internal IReadOnlyDictionary<string, object> CustomClaims
9799
{
98100
get => this.customClaims?.Value;
@@ -283,6 +285,7 @@ internal UpdateUserRequest(UserRecordArgs args)
283285
this.Email = CheckEmail(args.Email);
284286
this.EmailVerified = args.emailVerified;
285287
this.Password = CheckPassword(args.Password);
288+
this.ValidSince = args.ValidSince;
286289

287290
if (args.displayName != null)
288291
{
@@ -357,6 +360,9 @@ internal UpdateUserRequest(UserRecordArgs args)
357360
[JsonProperty("localId")]
358361
public string Uid { get; set; }
359362

363+
[JsonProperty("validSince")]
364+
public long? ValidSince { get; set; }
365+
360366
private void AddDeleteAttribute(string attribute)
361367
{
362368
if (this.DeleteAttribute == null)

0 commit comments

Comments
 (0)