diff --git a/packages/Gotrue/Gotrue.Tests/Authentication/ResendParametersTests.cs b/packages/Gotrue/Gotrue.Tests/Authentication/ResendParametersTests.cs new file mode 100644 index 00000000..d315c3cd --- /dev/null +++ b/packages/Gotrue/Gotrue.Tests/Authentication/ResendParametersTests.cs @@ -0,0 +1,54 @@ +using System.Net; +using System.Threading.Tasks; +using Gotrue.Tests.Support; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Supabase.Gotrue.Resend; +using static Gotrue.Tests.TestUtils; + +namespace Gotrue.Tests.Authentication; + +[TestClass] +[TestCategory("E2E")] +public class ResendParametersTests : AuthClientFixture +{ + [TestMethod] + public async Task Resend_ShouldRequestConfirmationCode_GivenSignUpType() + { + var email = RandomEmail(); + var resend = new ResendParameters(ResendType.SignUp) { Email = email }; + var response = await this.Client.Resend(resend); + + Assert.IsNotNull(response); + Assert.AreEqual(HttpStatusCode.OK, response?.ResponseMessage?.StatusCode); + } + + [TestMethod] + public async Task Resend_ShouldRequestConfirmationCode_GivenSmsType() + { + var resend = new ResendParameters(ResendType.Sms) { Phone = "5544989899898" }; + var response = await this.Client.Resend(resend); + + Assert.IsNotNull(response); + Assert.AreEqual(HttpStatusCode.OK, response?.ResponseMessage?.StatusCode); + } + + [TestMethod] + public async Task Resend_ShouldRequestConfirmationCode_GivenPhoneChangeType() + { + var resend = new ResendParameters(ResendType.PhoneChange) { Phone = "5544989899898" }; + var response = await this.Client.Resend(resend); + + Assert.IsNotNull(response); + Assert.AreEqual(HttpStatusCode.OK, response?.ResponseMessage?.StatusCode); + } + + [TestMethod] + public async Task Resend_ShouldRequestConfirmationCode_GivenEmailChangeType() + { + var resend = new ResendParameters(ResendType.EmailChange) { Email = RandomEmail() }; + var response = await this.Client.Resend(resend); + + Assert.IsNotNull(response); + Assert.AreEqual(HttpStatusCode.OK, response?.ResponseMessage?.StatusCode); + } +} diff --git a/packages/Gotrue/Gotrue/Api.cs b/packages/Gotrue/Gotrue/Api.cs index 4116a62d..39b92095 100644 --- a/packages/Gotrue/Gotrue/Api.cs +++ b/packages/Gotrue/Gotrue/Api.cs @@ -11,6 +11,7 @@ using Supabase.Gotrue.Exceptions; using Supabase.Gotrue.Interfaces; using Supabase.Gotrue.Mfa; +using Supabase.Gotrue.Resend; using Supabase.Gotrue.Responses; using static Supabase.Gotrue.Constants; @@ -843,4 +844,16 @@ public Task GenerateLink(string jwt, GenerateLinkOptions options) return this.MakeRequestAsync(HttpMethod.Post, $"{this.Url}/token?grant_type=refresh_token", data, this.Headers.MergeLeft(headers)); } + + /// + /// Resends a confirmation code to a user's email or phone. + /// + /// + /// BaseResponse + public Task Resend(ResendParameters resendParameters) => this.MakeRequestAsync( + HttpMethod.Post, + $"{this.Url}/resend", + resendParameters, + this.Headers + ); } diff --git a/packages/Gotrue/Gotrue/Client.cs b/packages/Gotrue/Gotrue/Client.cs index 39269d58..a93ea20a 100644 --- a/packages/Gotrue/Gotrue/Client.cs +++ b/packages/Gotrue/Gotrue/Client.cs @@ -13,6 +13,8 @@ using Supabase.Gotrue.Exceptions; using Supabase.Gotrue.Interfaces; using Supabase.Gotrue.Mfa; +using Supabase.Gotrue.Resend; +using Supabase.Gotrue.Responses; using static Supabase.Gotrue.Constants; using static Supabase.Gotrue.Constants.AuthState; using static Supabase.Gotrue.Exceptions.FailureHint.Reason; @@ -947,6 +949,9 @@ private void ApplyLoadedSession(Session? before, Session? loaded) /// public void Shutdown() => this.NotifyAuthStateChange(AuthState.Shutdown); + /// + public Task Resend(ResendParameters resendParameters) => this.api.Resend(resendParameters); + /// public async Task Enroll(MfaEnrollParams mfaEnrollParams) { diff --git a/packages/Gotrue/Gotrue/Constants.cs b/packages/Gotrue/Gotrue/Constants.cs index fac3e04b..5b534b36 100644 --- a/packages/Gotrue/Gotrue/Constants.cs +++ b/packages/Gotrue/Gotrue/Constants.cs @@ -1,167 +1,197 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Supabase.Core.Attributes; #pragma warning disable CS1591 -namespace Supabase.Gotrue +namespace Supabase.Gotrue; + +/// +/// Constants used throughout the Gotrue Client +/// +public static class Constants { - /// - /// Constants used throughout the Gotrue Client - /// - public static class Constants - { - /// - /// URL for the Gotrue server. Defaults to localhost:9999. - /// - public const string GOTRUE_URL = "http://localhost:9999"; - public const string AUDIENCE = ""; - public const int EXPIRY_MARGIN = 60 * 1000; - public const string STORAGE_KEY = "supabase.auth.token"; - public static readonly Dictionary CookieOptions = new Dictionary - { - { "name", "sb:token" }, - { "lifetime", 60 * 60 * 8 }, - { "domain", "" }, - { "path", "/" }, - { "sameSite", "lax" } - }; - - public enum SortOrder - { - [MapTo("asc")] - Ascending, - [MapTo("desc")] - Descending - } - - public enum MobileOtpType - { - [MapTo("sms")] - SMS, - [MapTo("phone_change")] - PhoneChange - } - - public enum EmailOtpType - { - [MapTo("signup")] - Signup, - [MapTo("invite")] - Invite, - [MapTo("magiclink")] - MagicLink, - [MapTo("recovery")] - Recovery, - [MapTo("email_change")] - EmailChange, - [MapTo("email")] - Email - } - - /// - /// Providers available to Supabase - /// Ref: https://supabase.github.io/gotrue-js/modules.html#Provider - /// - public enum Provider - { - [MapTo("anonymous_users")] - AnonymousUsers, - [MapTo("apple")] - Apple, - [MapTo("azure")] - Azure, - [MapTo("bitbucket")] - Bitbucket, - [MapTo("discord")] - Discord, - [MapTo("facebook")] - Facebook, - [MapTo("figma")] - Figma, - [MapTo("fly")] - Fly, - [MapTo("github")] - Github, - [MapTo("gitlab")] - Gitlab, - [MapTo("google")] - Google, - [MapTo("kakao")] - Kakao, - [MapTo("keycloak")] - KeyCloak, - [MapTo("linkedin")] - LinkedIn, - [MapTo("linkedin_oidc")] - LinkedInOIDC, - [MapTo("notion")] - Notion, - [MapTo("slack")] - Slack, - [MapTo("spotify")] - Spotify, - [MapTo("twitch")] - Twitch, - [MapTo("twitter")] - Twitter, - [MapTo("workos")] - WorkOS, - [MapTo("zoom")] - Zoom - } - - /// - /// States that the Auth Client will raise events for. - /// - public enum AuthState - { - SignedIn, - SignedOut, - UserUpdated, - PasswordRecovery, - TokenRefreshed, - Shutdown, - MfaChallengeVerified - } - - /// - /// Specifies the functionality expected from the `SignIn` method - /// - public enum SignInType - { - Email, - Phone, - RefreshToken - } - - /// - /// Represents an OAuth Flow type - /// - public enum OAuthFlowType - { - [MapTo("implicit")] - Implicit, - [MapTo("pkce")] - PKCE - } - - /// - /// Specifies the functionality expected from the `SignUp` method - /// - public enum SignUpType - { - Email, - Phone - } - - public enum SignOutScope - { - [MapTo("global")] - Global, - [MapTo("local")] - Local, - [MapTo("others")] - Others - } + /// + /// URL for the Gotrue server. Defaults to localhost:9999. + /// + public const string GOTRUE_URL = "http://localhost:9999"; + public const string AUDIENCE = ""; + public const int EXPIRY_MARGIN = 60 * 1000; + public const string STORAGE_KEY = "supabase.auth.token"; + public static readonly Dictionary CookieOptions = new Dictionary + { + { "name", "sb:token" }, + { "lifetime", 60 * 60 * 8 }, + { "domain", "" }, + { "path", "/" }, + { "sameSite", "lax" }, + }; + + public enum SortOrder + { + [MapTo("asc")] + Ascending, + + [MapTo("desc")] + Descending, + } + + public enum MobileOtpType + { + [MapTo("sms")] + SMS, + + [MapTo("phone_change")] + PhoneChange, + } + + public enum EmailOtpType + { + [MapTo("signup")] + Signup, + + [MapTo("invite")] + Invite, + + [MapTo("magiclink")] + MagicLink, + + [MapTo("recovery")] + Recovery, + + [MapTo("email_change")] + EmailChange, + + [MapTo("email")] + Email, + } + + /// + /// Providers available to Supabase + /// Ref: https://supabase.github.io/gotrue-js/modules.html#Provider + /// + public enum Provider + { + [MapTo("anonymous_users")] + AnonymousUsers, + + [MapTo("apple")] + Apple, + + [MapTo("azure")] + Azure, + + [MapTo("bitbucket")] + Bitbucket, + + [MapTo("discord")] + Discord, + + [MapTo("facebook")] + Facebook, + + [MapTo("figma")] + Figma, + + [MapTo("fly")] + Fly, + + [MapTo("github")] + Github, + + [MapTo("gitlab")] + Gitlab, + + [MapTo("google")] + Google, + + [MapTo("kakao")] + Kakao, + + [MapTo("keycloak")] + KeyCloak, + + [MapTo("linkedin")] + LinkedIn, + + [MapTo("linkedin_oidc")] + LinkedInOIDC, + + [MapTo("notion")] + Notion, + + [MapTo("slack")] + Slack, + + [MapTo("spotify")] + Spotify, + + [MapTo("twitch")] + Twitch, + + [MapTo("twitter")] + Twitter, + + [MapTo("workos")] + WorkOS, + + [MapTo("zoom")] + Zoom, + } + + /// + /// States that the Auth Client will raise events for. + /// + public enum AuthState + { + SignedIn, + SignedOut, + UserUpdated, + PasswordRecovery, + TokenRefreshed, + Shutdown, + MfaChallengeVerified, + } + + /// + /// Specifies the functionality expected from the `SignIn` method + /// + public enum SignInType + { + Email, + Phone, + RefreshToken, + } + + /// + /// Represents an OAuth Flow type + /// + public enum OAuthFlowType + { + [MapTo("implicit")] + Implicit, + + [MapTo("pkce")] + PKCE, + } + + /// + /// Specifies the functionality expected from the `SignUp` method + /// + public enum SignUpType + { + Email, + Phone, + } + + public enum SignOutScope + { + [MapTo("global")] + Global, + + [MapTo("local")] + Local, + + [MapTo("others")] + Others, } } diff --git a/packages/Gotrue/Gotrue/Interfaces/IGotrueApi.cs b/packages/Gotrue/Gotrue/Interfaces/IGotrueApi.cs index 2ed2d1af..717cee8e 100644 --- a/packages/Gotrue/Gotrue/Interfaces/IGotrueApi.cs +++ b/packages/Gotrue/Gotrue/Interfaces/IGotrueApi.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Supabase.Core.Interfaces; using Supabase.Gotrue.Mfa; +using Supabase.Gotrue.Resend; using Supabase.Gotrue.Responses; using static Supabase.Gotrue.Constants; @@ -81,4 +82,11 @@ public interface IGotrueApi : IGettableHeaders /// Identity to be unlinked /// Task UnlinkIdentity(string token, UserIdentity userIdentity); + + /// + /// Resends a confirmation code to a user's email or phone. + /// + /// + /// + Task Resend(ResendParameters resendParameters); } diff --git a/packages/Gotrue/Gotrue/Interfaces/IGotrueClient.cs b/packages/Gotrue/Gotrue/Interfaces/IGotrueClient.cs index 9f6034dd..c26029a9 100644 --- a/packages/Gotrue/Gotrue/Interfaces/IGotrueClient.cs +++ b/packages/Gotrue/Gotrue/Interfaces/IGotrueClient.cs @@ -6,6 +6,8 @@ using Supabase.Core.Interfaces; using Supabase.Gotrue.Exceptions; using Supabase.Gotrue.Mfa; +using Supabase.Gotrue.Resend; +using Supabase.Gotrue.Responses; using static Supabase.Gotrue.Constants; #endregion @@ -532,6 +534,13 @@ public interface IGotrueClient : IGettableHeaders /// The refresh token to exchange for a new session. Task RefreshToken(string accessToken, string refreshToken); + /// + /// Resends a confirmation code to a user's email or phone. + /// + /// + /// + Task Resend(ResendParameters resendParameters); + #region MFA /// diff --git a/packages/Gotrue/Gotrue/PublicAPI.Unshipped.txt b/packages/Gotrue/Gotrue/PublicAPI.Unshipped.txt index 7dc5c581..52aac80c 100644 --- a/packages/Gotrue/Gotrue/PublicAPI.Unshipped.txt +++ b/packages/Gotrue/Gotrue/PublicAPI.Unshipped.txt @@ -1 +1,26 @@ #nullable enable +Supabase.Gotrue.Api.Resend(Supabase.Gotrue.Resend.ResendParameters! resendParameters) -> System.Threading.Tasks.Task! +Supabase.Gotrue.Client.Resend(Supabase.Gotrue.Resend.ResendParameters! resendParameters) -> System.Threading.Tasks.Task! +Supabase.Gotrue.Interfaces.IGotrueApi.Resend(Supabase.Gotrue.Resend.ResendParameters! resendParameters) -> System.Threading.Tasks.Task! +Supabase.Gotrue.Interfaces.IGotrueClient.Resend(Supabase.Gotrue.Resend.ResendParameters! resendParameters) -> System.Threading.Tasks.Task! +Supabase.Gotrue.Resend.ResendOptions +Supabase.Gotrue.Resend.ResendOptions.CaptchaToken.get -> string? +Supabase.Gotrue.Resend.ResendOptions.CaptchaToken.set -> void +Supabase.Gotrue.Resend.ResendOptions.EmailRedirectTo.get -> string? +Supabase.Gotrue.Resend.ResendOptions.EmailRedirectTo.set -> void +Supabase.Gotrue.Resend.ResendOptions.ResendOptions() -> void +Supabase.Gotrue.Resend.ResendParameters +Supabase.Gotrue.Resend.ResendParameters.Email.get -> string? +Supabase.Gotrue.Resend.ResendParameters.Email.set -> void +Supabase.Gotrue.Resend.ResendParameters.Options.get -> Supabase.Gotrue.Resend.ResendOptions? +Supabase.Gotrue.Resend.ResendParameters.Options.set -> void +Supabase.Gotrue.Resend.ResendParameters.Phone.get -> string? +Supabase.Gotrue.Resend.ResendParameters.Phone.set -> void +Supabase.Gotrue.Resend.ResendParameters.ResendParameters(Supabase.Gotrue.Resend.ResendType type) -> void +Supabase.Gotrue.Resend.ResendParameters.Type.get -> string? +Supabase.Gotrue.Resend.ResendParameters.Type.set -> void +Supabase.Gotrue.Resend.ResendType +Supabase.Gotrue.Resend.ResendType.EmailChange = 3 -> Supabase.Gotrue.Resend.ResendType +Supabase.Gotrue.Resend.ResendType.PhoneChange = 2 -> Supabase.Gotrue.Resend.ResendType +Supabase.Gotrue.Resend.ResendType.SignUp = 0 -> Supabase.Gotrue.Resend.ResendType +Supabase.Gotrue.Resend.ResendType.Sms = 1 -> Supabase.Gotrue.Resend.ResendType diff --git a/packages/Gotrue/Gotrue/Resend/ResendOptions.cs b/packages/Gotrue/Gotrue/Resend/ResendOptions.cs new file mode 100644 index 00000000..012e0ea6 --- /dev/null +++ b/packages/Gotrue/Gotrue/Resend/ResendOptions.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; + +namespace Supabase.Gotrue.Resend; + +/// +/// Represents optional parameters used in the Resend API. These parameters provide +/// additional customization for the resend operation, such as including a CAPTCHA +/// token or specifying a redirection URL after email confirmation. +/// +public class ResendOptions +{ + /// + /// Verification token received when the user completes the captcha on the site. + /// + [JsonPropertyName("captchaToken")] + public string? CaptchaToken { get; set; } + + /// + /// A URL or mobile address to send the user to after they are confirmed. + /// + [JsonPropertyName("emailRedirectTo")] + public string? EmailRedirectTo { get; set; } +} diff --git a/packages/Gotrue/Gotrue/Resend/ResendParameters.cs b/packages/Gotrue/Gotrue/Resend/ResendParameters.cs new file mode 100644 index 00000000..a87087b0 --- /dev/null +++ b/packages/Gotrue/Gotrue/Resend/ResendParameters.cs @@ -0,0 +1,42 @@ +using System.Text.Json.Serialization; + +namespace Supabase.Gotrue.Resend; + +/// +/// Parameters for the Resend API. +/// +public class ResendParameters +{ + /// + /// The email address to resend to. + /// + [JsonPropertyName("email")] + public string? Email { get; set; } + + /// + /// The phone number to resend to. + /// + [JsonPropertyName("phone")] + public string? Phone { get; set; } + + /// + /// The type of resend. + /// + [JsonPropertyName("type")] + public string? Type { get; set; } + + /// + /// Additional options to customize the behavior of the Resend API. + /// Provides support for features such as CAPTCHA tokens and email redirection. + /// + [JsonPropertyName("options")] + public ResendOptions? Options { get; set; } + + /// + /// Represents the parameters used for the Resend API, allowing for resending + /// confirmation codes or links based on the provided inputs. This includes + /// support for email, phone, and custom data. + /// + public ResendParameters(ResendType type) => + this.Type = Core.Helpers.GetMappedToAttr(type).Mapping; +} diff --git a/packages/Gotrue/Gotrue/Resend/ResendType.cs b/packages/Gotrue/Gotrue/Resend/ResendType.cs new file mode 100644 index 00000000..b19c36f7 --- /dev/null +++ b/packages/Gotrue/Gotrue/Resend/ResendType.cs @@ -0,0 +1,30 @@ +using System.Runtime.Serialization; +using Supabase.Core.Attributes; + +namespace Supabase.Gotrue.Resend; + +/// +/// The type of resend. +/// +public enum ResendType +{ + /// Signup resend. + [MapTo("signup")] + [EnumMember(Value = "signup")] + SignUp, + + /// SMS resend. + [MapTo("sms")] + [EnumMember(Value = "sms")] + Sms, + + /// Phone change resend. + [MapTo("phone_change")] + [EnumMember(Value = "phone_change")] + PhoneChange, + + /// Email change resend. + [MapTo("email_change")] + [EnumMember(Value = "email_change")] + EmailChange, +}