Skip to content

Commit 7d763dd

Browse files
Add admin calls for MFA (#105)
1 parent c5658ac commit 7d763dd

9 files changed

+178
-0
lines changed

Gotrue/AdminClient.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Threading.Tasks;
44
using Newtonsoft.Json;
55
using Supabase.Gotrue.Interfaces;
6+
using Supabase.Gotrue.Mfa;
67
using Supabase.Gotrue.Responses;
78

89
namespace Supabase.Gotrue
@@ -99,6 +100,7 @@ public async Task<bool> DeleteUser(string uid)
99100
{
100101
return _api.UpdateUserById(_serviceKey, userId, userData);
101102
}
103+
102104
/// <inheritdoc />
103105
public async Task<GenerateLinkResponse?> GenerateLink(GenerateLinkOptions options)
104106
{
@@ -112,6 +114,29 @@ public async Task<bool> DeleteUser(string uid)
112114
return result;
113115
}
114116

117+
/// <inheritdoc />
118+
public async Task<MfaAdminListFactorsResponse?> ListFactors(MfaAdminListFactorsParams listFactorsParams)
119+
{
120+
var response = await _api.ListFactors(_serviceKey, listFactorsParams);
121+
response.ResponseMessage?.EnsureSuccessStatusCode();
122+
123+
if (response.Content is null)
124+
return null;
125+
126+
var result = JsonConvert.DeserializeObject<List<Factor>>(response.Content);
127+
var listFactorsResponse = new MfaAdminListFactorsResponse
128+
{
129+
Factors = result
130+
};
131+
132+
return listFactorsResponse;
133+
}
134+
135+
public async Task<MfaAdminDeleteFactorResponse?> DeleteFactor(MfaAdminDeleteFactorParams deleteFactorParams)
136+
{
137+
return await _api.DeleteFactor(_serviceKey, deleteFactorParams);
138+
}
139+
115140
/// <inheritdoc />
116141
public async Task<User?> Update(UserAttributes attributes)
117142
{

Gotrue/Api.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,18 @@ public ProviderAuthState GetUriForProvider(Provider provider, SignInOptions? opt
577577
return Helpers.MakeRequest<MfaUnenrollResponse>(HttpMethod.Delete, $"{Url}/factors/{mfaUnenrollParams.FactorId}", null, CreateAuthedRequestHeaders(jwt));
578578
}
579579

580+
/// <inheritdoc />
581+
public Task<BaseResponse> ListFactors(string jwt, MfaAdminListFactorsParams listFactorsParams)
582+
{
583+
return Helpers.MakeRequest(HttpMethod.Get, $"{Url}/admin/users/{listFactorsParams.UserId}/factors", null, CreateAuthedRequestHeaders(jwt));
584+
}
585+
586+
/// <inheritdoc />
587+
public Task<MfaAdminDeleteFactorResponse?> DeleteFactor(string jwt, MfaAdminDeleteFactorParams deleteFactorParams)
588+
{
589+
return Helpers.MakeRequest<MfaAdminDeleteFactorResponse>(HttpMethod.Delete, $"{Url}/admin/users/{deleteFactorParams.UserId}/factors/{deleteFactorParams.Id}", null, CreateAuthedRequestHeaders(jwt));
590+
}
591+
580592
/// <inheritdoc />
581593
public async Task<ProviderAuthState> LinkIdentity(string token, Provider provider, SignInOptions options)
582594
{

Gotrue/Interfaces/IGotrueAdminClient.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Threading.Tasks;
22
using Supabase.Core.Attributes;
33
using Supabase.Core.Interfaces;
4+
using Supabase.Gotrue.Mfa;
45
using Supabase.Gotrue.Responses;
56

67
namespace Supabase.Gotrue.Interfaces
@@ -91,5 +92,19 @@ public interface IGotrueAdminClient<TUser> : IGettableHeaders
9192
/// <param name="options">Options for this call. `Password` is required for <see cref="GenerateLinkOptions.LinkType.SignUp"/>, `Data` is an optional parameter for <see cref="GenerateLinkOptions.LinkType.SignUp"/>.</param>
9293
/// <returns></returns>
9394
public Task<GenerateLinkResponse?> GenerateLink(GenerateLinkOptions options);
95+
96+
/// <summary>
97+
/// Lists all factors associated to a specific user.
98+
/// </summary>
99+
/// <param name="listFactorParams">A <see cref="MfaAdminListFactorsParams"/> object that contains the user id.</param>
100+
/// <returns>A list of <see cref="Factor"/> that this user has enabled.</returns>
101+
public Task<MfaAdminListFactorsResponse?> ListFactors(MfaAdminListFactorsParams listFactorsParams);
102+
103+
/// <summary>
104+
/// Deletes a factor on a user. This will log the user out of all active sessions if the deleted factor was verified.
105+
/// </summary>
106+
/// <param name="listFactorParams">A <see cref="MfaAdminListFactorsParams"/> object that contains the user id.</param>
107+
/// <returns>A <see cref="MfaAdminDeleteFactorResponse"/> containing the deleted factor id.</returns>
108+
public Task<MfaAdminDeleteFactorResponse?> DeleteFactor(MfaAdminDeleteFactorParams deleteFactorParams);
94109
}
95110
}

Gotrue/Interfaces/IGotrueApi.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ public interface IGotrueApi<TUser, TSession> : IGettableHeaders
4949
Task<MfaChallengeResponse?> Challenge(string jwt, MfaChallengeParams mfaChallengeParams);
5050
Task<MfaVerifyResponse?> Verify(string jwt, MfaVerifyParams mfaVerifyParams);
5151
Task<MfaUnenrollResponse?> Unenroll(string jwt, MfaUnenrollParams mfaVerifyParams);
52+
Task<BaseResponse> ListFactors(string jwt, MfaAdminListFactorsParams listFactorsParams);
53+
Task<MfaAdminDeleteFactorResponse?> DeleteFactor(string jwt, MfaAdminDeleteFactorParams deleteFactorParams);
5254

5355
/// <summary>
5456
/// Links an oauth identity to an existing user.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace Supabase.Gotrue.Mfa
2+
{
3+
public class MfaAdminDeleteFactorParams
4+
{
5+
// Id of the MFA factor to delete
6+
public string Id { get; set; }
7+
8+
// Id of the user whose factor is being deleted
9+
public string UserId { get; set; }
10+
}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace Supabase.Gotrue.Mfa
4+
{
5+
public class MfaAdminDeleteFactorResponse
6+
{
7+
// Id of the factor that was successfully deleted
8+
[JsonPropertyName("id")]
9+
public string Id { get; set; }
10+
}
11+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace Supabase.Gotrue.Mfa
2+
{
3+
public class MfaAdminListFactorsParams
4+
{
5+
// Id of the user
6+
public string UserId { get; set; }
7+
}
8+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Collections.Generic;
2+
using Newtonsoft.Json;
3+
4+
namespace Supabase.Gotrue.Mfa
5+
{
6+
public class MfaAdminListFactorsResponse
7+
{
8+
public List<Factor> Factors { get; set; } = new List<Factor>();
9+
}
10+
}

GotrueTests/MfaClientTests.cs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@ namespace GotrueTests;
1919
public class MfaClientTests
2020
{
2121
private IGotrueClient<User, Session> _client;
22+
private IGotrueAdminClient<User> _adminClient;
23+
24+
private readonly string _serviceKey = GenerateServiceRoleToken();
2225

2326
[TestInitialize]
2427
public void TestInitializer()
2528
{
2629
_client = new Client(new ClientOptions { AllowUnconfirmedUserSessions = true });
30+
_adminClient = new AdminClient(_serviceKey, new ClientOptions { AllowUnconfirmedUserSessions = true });
2731
}
2832

2933
[TestMethod("MFA: Complete flow")]
@@ -103,6 +107,86 @@ await _client.ChallengeAndVerify(new MfaChallengeAndVerifyParams
103107
IsTrue(factors.Totp.Count == 0);
104108
}
105109

110+
[TestMethod("MFA Admin: List factors for user")]
111+
public async Task MfaAdminListFactorsForUser()
112+
{
113+
var email = $"{RandomString(12)}@supabase.io";
114+
var session = await _client.SignUp(email, PASSWORD);
115+
VerifyGoodSession(session);
116+
117+
var mfaEnrollParams = new MfaEnrollParams
118+
{
119+
Issuer = "Supabase",
120+
FactorType = "totp",
121+
FriendlyName = "Enroll test"
122+
};
123+
124+
var enrollResponse = await _client.Enroll(mfaEnrollParams);
125+
IsNotNull(enrollResponse.Id);
126+
127+
var factors = await _adminClient.ListFactors(new MfaAdminListFactorsParams
128+
{
129+
UserId = session.User.Id
130+
});
131+
IsNotNull(factors);
132+
AreEqual(1, factors.Factors.Count);
133+
AreEqual(enrollResponse.Id, factors.Factors.FirstOrDefault().Id);
134+
AreEqual("unverified", factors.Factors.FirstOrDefault().Status);
135+
136+
var totpCode = TotpGenerator.GeneratePin(enrollResponse.Totp.Secret, 30, 6);
137+
await _client.ChallengeAndVerify(new MfaChallengeAndVerifyParams
138+
{
139+
FactorId = enrollResponse.Id,
140+
Code = totpCode
141+
});
142+
143+
factors = await _adminClient.ListFactors(new MfaAdminListFactorsParams
144+
{
145+
UserId = session.User.Id
146+
});
147+
IsNotNull(factors);
148+
AreEqual(1, factors.Factors.Count);
149+
AreEqual(enrollResponse.Id, factors.Factors.FirstOrDefault().Id);
150+
AreEqual("verified", factors.Factors.FirstOrDefault().Status);
151+
}
152+
153+
[TestMethod("MFA Admin: Delete factor for user")]
154+
public async Task MfaAdminDeleteFactorForUser()
155+
{
156+
var email = $"{RandomString(12)}@supabase.io";
157+
var session = await _client.SignUp(email, PASSWORD);
158+
VerifyGoodSession(session);
159+
160+
var mfaEnrollParams = new MfaEnrollParams
161+
{
162+
Issuer = "Supabase",
163+
FactorType = "totp",
164+
FriendlyName = "Enroll test"
165+
};
166+
167+
var enrollResponse = await _client.Enroll(mfaEnrollParams);
168+
IsNotNull(enrollResponse.Id);
169+
170+
var listFactors = await _adminClient.ListFactors(new MfaAdminListFactorsParams
171+
{
172+
UserId = session.User.Id
173+
});
174+
AreEqual(1, listFactors.Factors.Count);
175+
176+
var deleteFactorResponse = await _adminClient.DeleteFactor(new MfaAdminDeleteFactorParams
177+
{
178+
Id = enrollResponse.Id,
179+
UserId = session.User.Id
180+
});
181+
AreEqual(enrollResponse.Id, deleteFactorResponse.Id);
182+
183+
listFactors = await _adminClient.ListFactors(new MfaAdminListFactorsParams
184+
{
185+
UserId = session.User.Id
186+
});
187+
AreEqual(0, listFactors.Factors.Count);
188+
}
189+
106190
[TestMethod("MFA: Invalid TOTP")]
107191
public async Task MfaInvalidTotp()
108192
{

0 commit comments

Comments
 (0)