diff --git a/.editorconfig b/.editorconfig index f6e8274..b783571 100644 --- a/.editorconfig +++ b/.editorconfig @@ -18,7 +18,7 @@ indent_size = 4 tab_width = 4 # New line preferences -end_of_line = crlf +end_of_line = lf insert_final_newline = false #### .NET Coding Conventions #### @@ -256,31 +256,31 @@ dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase dotnet_naming_symbols.interfaces.applicable_kinds = interface dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.interfaces.required_modifiers = +dotnet_naming_symbols.interfaces.required_modifiers = dotnet_naming_symbols.enums.applicable_kinds = enum dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.enums.required_modifiers = +dotnet_naming_symbols.enums.required_modifiers = dotnet_naming_symbols.events.applicable_kinds = event dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.events.required_modifiers = +dotnet_naming_symbols.events.required_modifiers = dotnet_naming_symbols.methods.applicable_kinds = method dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.methods.required_modifiers = +dotnet_naming_symbols.methods.required_modifiers = dotnet_naming_symbols.properties.applicable_kinds = property dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.properties.required_modifiers = +dotnet_naming_symbols.properties.required_modifiers = dotnet_naming_symbols.public_fields.applicable_kinds = field dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal -dotnet_naming_symbols.public_fields.required_modifiers = +dotnet_naming_symbols.public_fields.required_modifiers = dotnet_naming_symbols.private_fields.applicable_kinds = field dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected -dotnet_naming_symbols.private_fields.required_modifiers = +dotnet_naming_symbols.private_fields.required_modifiers = dotnet_naming_symbols.private_static_fields.applicable_kinds = field dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected @@ -288,15 +288,15 @@ dotnet_naming_symbols.private_static_fields.required_modifiers = static dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.types_and_namespaces.required_modifiers = +dotnet_naming_symbols.types_and_namespaces.required_modifiers = dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.non_field_members.required_modifiers = +dotnet_naming_symbols.non_field_members.required_modifiers = dotnet_naming_symbols.type_parameters.applicable_kinds = namespace dotnet_naming_symbols.type_parameters.applicable_accessibilities = * -dotnet_naming_symbols.type_parameters.required_modifiers = +dotnet_naming_symbols.type_parameters.required_modifiers = dotnet_naming_symbols.private_constant_fields.applicable_kinds = field dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected @@ -304,7 +304,7 @@ dotnet_naming_symbols.private_constant_fields.required_modifiers = const dotnet_naming_symbols.local_variables.applicable_kinds = local dotnet_naming_symbols.local_variables.applicable_accessibilities = local -dotnet_naming_symbols.local_variables.required_modifiers = +dotnet_naming_symbols.local_variables.required_modifiers = dotnet_naming_symbols.local_constants.applicable_kinds = local dotnet_naming_symbols.local_constants.applicable_accessibilities = local @@ -312,7 +312,7 @@ dotnet_naming_symbols.local_constants.required_modifiers = const dotnet_naming_symbols.parameters.applicable_kinds = parameter dotnet_naming_symbols.parameters.applicable_accessibilities = * -dotnet_naming_symbols.parameters.required_modifiers = +dotnet_naming_symbols.parameters.required_modifiers = dotnet_naming_symbols.public_constant_fields.applicable_kinds = field dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal @@ -328,36 +328,36 @@ dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readon dotnet_naming_symbols.local_functions.applicable_kinds = local_function dotnet_naming_symbols.local_functions.applicable_accessibilities = * -dotnet_naming_symbols.local_functions.required_modifiers = +dotnet_naming_symbols.local_functions.required_modifiers = # Naming styles -dotnet_naming_style.pascalcase.required_prefix = -dotnet_naming_style.pascalcase.required_suffix = -dotnet_naming_style.pascalcase.word_separator = +dotnet_naming_style.pascalcase.required_prefix = +dotnet_naming_style.pascalcase.required_suffix = +dotnet_naming_style.pascalcase.word_separator = dotnet_naming_style.pascalcase.capitalization = pascal_case dotnet_naming_style.ipascalcase.required_prefix = I -dotnet_naming_style.ipascalcase.required_suffix = -dotnet_naming_style.ipascalcase.word_separator = +dotnet_naming_style.ipascalcase.required_suffix = +dotnet_naming_style.ipascalcase.word_separator = dotnet_naming_style.ipascalcase.capitalization = pascal_case dotnet_naming_style.tpascalcase.required_prefix = T -dotnet_naming_style.tpascalcase.required_suffix = -dotnet_naming_style.tpascalcase.word_separator = +dotnet_naming_style.tpascalcase.required_suffix = +dotnet_naming_style.tpascalcase.word_separator = dotnet_naming_style.tpascalcase.capitalization = pascal_case dotnet_naming_style._camelcase.required_prefix = _ -dotnet_naming_style._camelcase.required_suffix = -dotnet_naming_style._camelcase.word_separator = +dotnet_naming_style._camelcase.required_suffix = +dotnet_naming_style._camelcase.word_separator = dotnet_naming_style._camelcase.capitalization = camel_case -dotnet_naming_style.camelcase.required_prefix = -dotnet_naming_style.camelcase.required_suffix = -dotnet_naming_style.camelcase.word_separator = +dotnet_naming_style.camelcase.required_prefix = +dotnet_naming_style.camelcase.required_suffix = +dotnet_naming_style.camelcase.word_separator = dotnet_naming_style.camelcase.capitalization = camel_case dotnet_naming_style.s_camelcase.required_prefix = s_ -dotnet_naming_style.s_camelcase.required_suffix = -dotnet_naming_style.s_camelcase.word_separator = +dotnet_naming_style.s_camelcase.required_suffix = +dotnet_naming_style.s_camelcase.word_separator = dotnet_naming_style.s_camelcase.capitalization = camel_case diff --git a/Passwordless-dotnet.sln b/Passwordless-dotnet.sln index 61913ec..1ddbe38 100644 --- a/Passwordless-dotnet.sln +++ b/Passwordless-dotnet.sln @@ -3,15 +3,17 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sdk.Tests", "tests\Sdk.Tests\Sdk.Tests.csproj", "{F64C850E-9923-43F1-BC84-432AFBBA4425}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Passwordless.Tests", "tests\Passwordless.Tests\Passwordless.Tests.csproj", "{F64C850E-9923-43F1-BC84-432AFBBA4425}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sdk", "src\Sdk\Sdk.csproj", "{A01503A8-6AB9-43A7-AC5A-4EAE091B07B6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Passwordless", "src\Passwordless\Passwordless.csproj", "{A01503A8-6AB9-43A7-AC5A-4EAE091B07B6}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F239A76C-408E-4919-AB70-4499425E6F29}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .gitignore = .gitignore Directory.Build.props = Directory.Build.props + README.md = README.md + LICENSE = LICENSE EndProjectSection EndProject Global diff --git a/src/Sdk/Base64Url.cs b/src/Passwordless/Base64Url.cs similarity index 95% rename from src/Sdk/Base64Url.cs rename to src/Passwordless/Base64Url.cs index dfc7b9a..32f92ef 100644 --- a/src/Sdk/Base64Url.cs +++ b/src/Passwordless/Base64Url.cs @@ -1,136 +1,136 @@ -using System.Buffers; -using System.Buffers.Text; - -namespace Passwordless.Net; - -internal static class Base64Url -{ - /// - /// Converts arg data to a Base64Url encoded string. - /// - public static string Encode(ReadOnlySpan arg) - { - int minimumLength = (int)(((long)arg.Length + 2L) / 3 * 4); - char[] array = ArrayPool.Shared.Rent(minimumLength); - -#if NET5_0_OR_GREATER - Convert.TryToBase64Chars(arg, array, out var charsWritten); -#elif NET462 || NETSTANDARD2_0 - var charsWritten = Convert.ToBase64CharArray(arg.ToArray(), 0, minimumLength, array, 0); -#endif - Span span = array.AsSpan(0, charsWritten); - - - for (int i = 0; i < span.Length; i++) - { - ref char reference = ref span[i]; - switch (reference) - { - case '+': - reference = '-'; - break; - case '/': - reference = '_'; - break; - } - } - int num = span.IndexOf('='); - if (num > -1) - { - span = span.Slice(0, num); - } - -#if NET5_0_OR_GREATER - string result = new string(span); -#elif NET462 || NETSTANDARD2_0 - string result = new string(span.ToArray()); -#endif - ArrayPool.Shared.Return(array, clearArray: true); - return result; - } - - /// - /// Decodes a Base64Url encoded string to its raw bytes. - /// - public static byte[] Decode(ReadOnlySpan text) - { - int num = (text.Length % 4) switch - { - 2 => 2, - 3 => 1, - _ => 0, - }; - int num2 = text.Length + num; - char[] array = ArrayPool.Shared.Rent(num2); - text.CopyTo(array); - for (int i = 0; i < text.Length; i++) - { - ref char reference = ref array[i]; - switch (reference) - { - case '-': - reference = '+'; - break; - case '_': - reference = '/'; - break; - } - } - switch (num) - { - case 1: - array[num2 - 1] = '='; - break; - case 2: - array[num2 - 1] = '='; - array[num2 - 2] = '='; - break; - } - byte[] result = Convert.FromBase64CharArray(array, 0, num2); - ArrayPool.Shared.Return(array, clearArray: true); - return result; - } - - /// - /// Decodes a Base64Url encoded string to its raw bytes. - /// - public static byte[] DecodeUtf8(ReadOnlySpan text) - { - int num = (text.Length % 4) switch - { - 2 => 2, - 3 => 1, - _ => 0, - }; - int num2 = text.Length + num; - byte[] array = ArrayPool.Shared.Rent(num2); - text.CopyTo(array); - for (int i = 0; i < text.Length; i++) - { - ref byte reference = ref array[i]; - switch (reference) - { - case 45: - reference = 43; - break; - case 95: - reference = 47; - break; - } - } - switch (num) - { - case 1: - array[num2 - 1] = 61; - break; - case 2: - array[num2 - 1] = 61; - array[num2 - 2] = 61; - break; - } - Base64.DecodeFromUtf8InPlace(array.AsSpan(0, num2), out var bytesWritten); - byte[] result = array.AsSpan(0, bytesWritten).ToArray(); - ArrayPool.Shared.Return(array, clearArray: true); - return result; - } +using System.Buffers; +using System.Buffers.Text; + +namespace Passwordless; + +internal static class Base64Url +{ + /// + /// Converts arg data to a Base64Url encoded string. + /// + public static string Encode(ReadOnlySpan arg) + { + int minimumLength = (int)(((long)arg.Length + 2L) / 3 * 4); + char[] array = ArrayPool.Shared.Rent(minimumLength); + +#if NET5_0_OR_GREATER + Convert.TryToBase64Chars(arg, array, out var charsWritten); +#elif NET462 || NETSTANDARD2_0 + var charsWritten = Convert.ToBase64CharArray(arg.ToArray(), 0, minimumLength, array, 0); +#endif + Span span = array.AsSpan(0, charsWritten); + + + for (int i = 0; i < span.Length; i++) + { + ref char reference = ref span[i]; + switch (reference) + { + case '+': + reference = '-'; + break; + case '/': + reference = '_'; + break; + } + } + int num = span.IndexOf('='); + if (num > -1) + { + span = span.Slice(0, num); + } + +#if NET5_0_OR_GREATER + string result = new string(span); +#elif NET462 || NETSTANDARD2_0 + string result = new string(span.ToArray()); +#endif + ArrayPool.Shared.Return(array, clearArray: true); + return result; + } + + /// + /// Decodes a Base64Url encoded string to its raw bytes. + /// + public static byte[] Decode(ReadOnlySpan text) + { + int num = (text.Length % 4) switch + { + 2 => 2, + 3 => 1, + _ => 0, + }; + int num2 = text.Length + num; + char[] array = ArrayPool.Shared.Rent(num2); + text.CopyTo(array); + for (int i = 0; i < text.Length; i++) + { + ref char reference = ref array[i]; + switch (reference) + { + case '-': + reference = '+'; + break; + case '_': + reference = '/'; + break; + } + } + switch (num) + { + case 1: + array[num2 - 1] = '='; + break; + case 2: + array[num2 - 1] = '='; + array[num2 - 2] = '='; + break; + } + byte[] result = Convert.FromBase64CharArray(array, 0, num2); + ArrayPool.Shared.Return(array, clearArray: true); + return result; + } + + /// + /// Decodes a Base64Url encoded string to its raw bytes. + /// + public static byte[] DecodeUtf8(ReadOnlySpan text) + { + int num = (text.Length % 4) switch + { + 2 => 2, + 3 => 1, + _ => 0, + }; + int num2 = text.Length + num; + byte[] array = ArrayPool.Shared.Rent(num2); + text.CopyTo(array); + for (int i = 0; i < text.Length; i++) + { + ref byte reference = ref array[i]; + switch (reference) + { + case 45: + reference = 43; + break; + case 95: + reference = 47; + break; + } + } + switch (num) + { + case 1: + array[num2 - 1] = 61; + break; + case 2: + array[num2 - 1] = 61; + array[num2 - 2] = 61; + break; + } + Base64.DecodeFromUtf8InPlace(array.AsSpan(0, num2), out var bytesWritten); + byte[] result = array.AsSpan(0, bytesWritten).ToArray(); + ArrayPool.Shared.Return(array, clearArray: true); + return result; + } } \ No newline at end of file diff --git a/src/Sdk/Helpers/Base64UrlConverter.cs b/src/Passwordless/Helpers/Base64UrlConverter.cs similarity index 92% rename from src/Sdk/Helpers/Base64UrlConverter.cs rename to src/Passwordless/Helpers/Base64UrlConverter.cs index 6c08a7f..630429f 100644 --- a/src/Sdk/Helpers/Base64UrlConverter.cs +++ b/src/Passwordless/Helpers/Base64UrlConverter.cs @@ -1,21 +1,21 @@ -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Passwordless.Net; - -public sealed class Base64UrlConverter : JsonConverter -{ - public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (!reader.HasValueSequence) - { - return Base64Url.DecodeUtf8(reader.ValueSpan); - } - return Base64Url.Decode(reader.GetString().AsSpan()); - } - - public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options) - { - writer.WriteStringValue(Base64Url.Encode(value)); - } +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Passwordless; + +public sealed class Base64UrlConverter : JsonConverter +{ + public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (!reader.HasValueSequence) + { + return Base64Url.DecodeUtf8(reader.ValueSpan); + } + return Base64Url.Decode(reader.GetString().AsSpan()); + } + + public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options) + { + writer.WriteStringValue(Base64Url.Encode(value)); + } } \ No newline at end of file diff --git a/src/Sdk/Helpers/PasswordlessSerializerContext.cs b/src/Passwordless/Helpers/PasswordlessSerializerContext.cs similarity index 91% rename from src/Sdk/Helpers/PasswordlessSerializerContext.cs rename to src/Passwordless/Helpers/PasswordlessSerializerContext.cs index f75a51f..61d0526 100644 --- a/src/Sdk/Helpers/PasswordlessSerializerContext.cs +++ b/src/Passwordless/Helpers/PasswordlessSerializerContext.cs @@ -1,27 +1,27 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using Passwordless.Net.Models; - -namespace Passwordless.Net.Helpers; - -[JsonSourceGenerationOptions( - PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] -[JsonSerializable(typeof(AddAliasRequest))] -[JsonSerializable(typeof(RegisterTokenResponse))] -[JsonSerializable(typeof(RegisterOptions))] -[JsonSerializable(typeof(VerifyTokenRequest))] // TODO: Use this with JsonContent.Create -[JsonSerializable(typeof(VerifiedUser))] -[JsonSerializable(typeof(DeleteUserRequest))] -[JsonSerializable(typeof(ListResponse))] -[JsonSerializable(typeof(ListResponse))] -[JsonSerializable(typeof(ListResponse))] -[JsonSerializable(typeof(DeleteCredentialRequest))] -[JsonSerializable(typeof(UsersCount))] -[JsonSerializable(typeof(PasswordlessProblemDetails))] -[JsonSerializable(typeof(Dictionary))] -[JsonSerializable(typeof(JsonElement))] -internal partial class PasswordlessSerializerContext : JsonSerializerContext -{ - +using System.Text.Json; +using System.Text.Json.Serialization; +using Passwordless.Models; + +namespace Passwordless.Helpers; + +[JsonSourceGenerationOptions( + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(AddAliasRequest))] +[JsonSerializable(typeof(RegisterTokenResponse))] +[JsonSerializable(typeof(RegisterOptions))] +[JsonSerializable(typeof(VerifyTokenRequest))] // TODO: Use this with JsonContent.Create +[JsonSerializable(typeof(VerifiedUser))] +[JsonSerializable(typeof(DeleteUserRequest))] +[JsonSerializable(typeof(ListResponse))] +[JsonSerializable(typeof(ListResponse))] +[JsonSerializable(typeof(ListResponse))] +[JsonSerializable(typeof(DeleteCredentialRequest))] +[JsonSerializable(typeof(UsersCount))] +[JsonSerializable(typeof(PasswordlessProblemDetails))] +[JsonSerializable(typeof(Dictionary))] +[JsonSerializable(typeof(JsonElement))] +internal partial class PasswordlessSerializerContext : JsonSerializerContext +{ + } \ No newline at end of file diff --git a/src/Sdk/IPasswordlessClient.cs b/src/Passwordless/IPasswordlessClient.cs similarity index 97% rename from src/Sdk/IPasswordlessClient.cs rename to src/Passwordless/IPasswordlessClient.cs index ef1d6e8..de9f49c 100644 --- a/src/Sdk/IPasswordlessClient.cs +++ b/src/Passwordless/IPasswordlessClient.cs @@ -1,90 +1,90 @@ -using Passwordless.Net.Models; - -namespace Passwordless.Net; - -/// -/// Provides APIs that help you interact with Passwordless.dev. -/// -public interface IPasswordlessClient -{ - /// - /// Adds one or more aliases to an existing user. - /// - /// - /// - /// - Task AddAliasAsync(AddAliasRequest request, CancellationToken cancellationToken = default); - - /// - /// Creates a which will be used by your frontend to negotiate - /// the creation of a WebAuth credential. - /// - /// The that will be used to configure your token. - /// - /// A task object representing the asynchronous operation containing the . - /// An exception containing details abaout the reason for failure. - Task CreateRegisterTokenAsync(RegisterOptions registerOptions, CancellationToken cancellationToken = default); - - /// - /// Attempts to delete a credential via the supplied id. - /// - /// The id of a credential representing as a Base64 URL encoded . - /// - /// A task object representing the asynchronous operation. - /// An exception containing details abaout the reason for failure. - Task DeleteCredentialAsync(string id, CancellationToken cancellationToken = default); - - /// - /// Attempts to delete a credential via the supplied id. - /// - /// The id of a credential representing as a Base64 URL encoded . - /// - /// A task object representing the asynchronous operation. - /// An exception containing details abaout the reason for failure. - Task DeleteCredentialAsync(byte[] id, CancellationToken cancellationToken = default); - - /// - /// List all the for a given user. - /// - /// The userId of the user for which the aliases will be returned. - /// - /// A task object representing the asynchronous operation containing the . - /// An exception containing details abaout the reason for failure. - Task> ListAliasesAsync(string userId, CancellationToken cancellationToken = default); - - /// - /// List all the for a given user. - /// - /// The userId of the user for which the credentials will be returned. - /// - /// A task object representing the asynchronous operation containing the . - /// An exception containing details abaout the reason for failure. - Task> ListCredentialsAsync(string userId, CancellationToken cancellationToken = default); - - /// - /// List all the for the account associated with your ApiSecret. - /// - /// - /// A task object representing the asynchronous operation containing the . - /// An exception containing details abaout the reason for failure. - Task> ListUsersAsync(CancellationToken cancellationToken = default); - - /// - /// Verifies that the given token is valid and returns information packed into it. The token should have been generated - /// via calling a signInWith* method from your frontend code. - /// - /// The token to verify. - /// - /// A task object representing the asynchronous operation containing the . - /// An exception containing details abaout the reason for failure. - Task VerifyTokenAsync(string verifyToken, CancellationToken cancellationToken = default); - - /// - /// Deletes a user. - /// - /// The id of the user that should be deleted. - /// - /// A task object representing the asynchronous operation. - /// An exception containing details abaout the reason for failure. - Task DeleteUserAsync(string userId, CancellationToken cancellationToken = default); +using Passwordless.Models; + +namespace Passwordless; + +/// +/// Provides APIs that help you interact with Passwordless.dev. +/// +public interface IPasswordlessClient +{ + /// + /// Adds one or more aliases to an existing user. + /// + /// + /// + /// + Task AddAliasAsync(AddAliasRequest request, CancellationToken cancellationToken = default); + + /// + /// Creates a which will be used by your frontend to negotiate + /// the creation of a WebAuth credential. + /// + /// The that will be used to configure your token. + /// + /// A task object representing the asynchronous operation containing the . + /// An exception containing details abaout the reason for failure. + Task CreateRegisterTokenAsync(RegisterOptions registerOptions, CancellationToken cancellationToken = default); + + /// + /// Attempts to delete a credential via the supplied id. + /// + /// The id of a credential representing as a Base64 URL encoded . + /// + /// A task object representing the asynchronous operation. + /// An exception containing details abaout the reason for failure. + Task DeleteCredentialAsync(string id, CancellationToken cancellationToken = default); + + /// + /// Attempts to delete a credential via the supplied id. + /// + /// The id of a credential representing as a Base64 URL encoded . + /// + /// A task object representing the asynchronous operation. + /// An exception containing details abaout the reason for failure. + Task DeleteCredentialAsync(byte[] id, CancellationToken cancellationToken = default); + + /// + /// List all the for a given user. + /// + /// The userId of the user for which the aliases will be returned. + /// + /// A task object representing the asynchronous operation containing the . + /// An exception containing details abaout the reason for failure. + Task> ListAliasesAsync(string userId, CancellationToken cancellationToken = default); + + /// + /// List all the for a given user. + /// + /// The userId of the user for which the credentials will be returned. + /// + /// A task object representing the asynchronous operation containing the . + /// An exception containing details abaout the reason for failure. + Task> ListCredentialsAsync(string userId, CancellationToken cancellationToken = default); + + /// + /// List all the for the account associated with your ApiSecret. + /// + /// + /// A task object representing the asynchronous operation containing the . + /// An exception containing details abaout the reason for failure. + Task> ListUsersAsync(CancellationToken cancellationToken = default); + + /// + /// Verifies that the given token is valid and returns information packed into it. The token should have been generated + /// via calling a signInWith* method from your frontend code. + /// + /// The token to verify. + /// + /// A task object representing the asynchronous operation containing the . + /// An exception containing details abaout the reason for failure. + Task VerifyTokenAsync(string verifyToken, CancellationToken cancellationToken = default); + + /// + /// Deletes a user. + /// + /// The id of the user that should be deleted. + /// + /// A task object representing the asynchronous operation. + /// An exception containing details abaout the reason for failure. + Task DeleteUserAsync(string userId, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/Sdk/Models/AddAliasRequest.cs b/src/Passwordless/Models/AddAliasRequest.cs similarity index 94% rename from src/Sdk/Models/AddAliasRequest.cs rename to src/Passwordless/Models/AddAliasRequest.cs index ed07275..0c084ee 100644 --- a/src/Sdk/Models/AddAliasRequest.cs +++ b/src/Passwordless/Models/AddAliasRequest.cs @@ -1,37 +1,37 @@ -namespace Passwordless.Net.Models; - -public class AddAliasRequest -{ - public AddAliasRequest(string userId, string alias, bool hashing = true) - : this(userId, hashing) - { - if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentException($"'{nameof(alias)}' cannot be null, empty or whitespace."); - Aliases = new HashSet - { - alias ?? throw new ArgumentNullException(nameof(alias)) - }; - } - - public AddAliasRequest(string userId, HashSet aliases, bool hashing = true) - : this(userId, hashing) - { - if (aliases == null || !aliases.Any()) throw new ArgumentException($"'{nameof(aliases)}' cannot be null or empty."); - if (aliases.Any(string.IsNullOrWhiteSpace)) throw new ArgumentException("One of the aliases is null, empty or whitespace"); - Aliases = aliases; - } - - private AddAliasRequest(string userId, bool hashing = true) - { - UserId = userId ?? throw new ArgumentNullException(nameof(userId)); - Hashing = hashing; - Aliases = new HashSet(); - } - - public string UserId { get; } - public HashSet Aliases { get; } - - /// - /// If you want your aliases to be available in plain text, set the false. - /// - public bool Hashing { get; } = true; +namespace Passwordless.Models; + +public class AddAliasRequest +{ + public AddAliasRequest(string userId, string alias, bool hashing = true) + : this(userId, hashing) + { + if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentException($"'{nameof(alias)}' cannot be null, empty or whitespace."); + Aliases = new HashSet + { + alias ?? throw new ArgumentNullException(nameof(alias)) + }; + } + + public AddAliasRequest(string userId, HashSet aliases, bool hashing = true) + : this(userId, hashing) + { + if (aliases == null || !aliases.Any()) throw new ArgumentException($"'{nameof(aliases)}' cannot be null or empty."); + if (aliases.Any(string.IsNullOrWhiteSpace)) throw new ArgumentException("One of the aliases is null, empty or whitespace"); + Aliases = aliases; + } + + private AddAliasRequest(string userId, bool hashing = true) + { + UserId = userId ?? throw new ArgumentNullException(nameof(userId)); + Hashing = hashing; + Aliases = new HashSet(); + } + + public string UserId { get; } + public HashSet Aliases { get; } + + /// + /// If you want your aliases to be available in plain text, set the false. + /// + public bool Hashing { get; } = true; } \ No newline at end of file diff --git a/src/Sdk/Models/AliasPointer.cs b/src/Passwordless/Models/AliasPointer.cs similarity index 86% rename from src/Sdk/Models/AliasPointer.cs rename to src/Passwordless/Models/AliasPointer.cs index 44a26bb..f17bad8 100644 --- a/src/Sdk/Models/AliasPointer.cs +++ b/src/Passwordless/Models/AliasPointer.cs @@ -1,15 +1,15 @@ -namespace Passwordless.Net; - -public class AliasPointer -{ - public AliasPointer(string userId, string alias, string plaintext) - { - UserId = userId; - Alias = alias; - Plaintext = plaintext; - } - - public string UserId { get; } - public string Alias { get; } - public string Plaintext { get; } +namespace Passwordless; + +public class AliasPointer +{ + public AliasPointer(string userId, string alias, string plaintext) + { + UserId = userId; + Alias = alias; + Plaintext = plaintext; + } + + public string UserId { get; } + public string Alias { get; } + public string Plaintext { get; } } \ No newline at end of file diff --git a/src/Sdk/Models/Credential.cs b/src/Passwordless/Models/Credential.cs similarity index 94% rename from src/Sdk/Models/Credential.cs rename to src/Passwordless/Models/Credential.cs index 5e81ab5..2e3d806 100644 --- a/src/Sdk/Models/Credential.cs +++ b/src/Passwordless/Models/Credential.cs @@ -1,39 +1,39 @@ -namespace Passwordless.Net; - -public class Credential -{ - public Credential(CredentialDescriptor descriptor, byte[] publicKey, byte[] userHandle, uint signatureCounter, - string attestationFmt, DateTime createdAt, Guid aaGuid, DateTime lastUsedAt, string rpid, - string origin, string country, string device, string nickname, string userId) - { - Descriptor = descriptor; - PublicKey = publicKey; - UserHandle = userHandle; - SignatureCounter = signatureCounter; - AttestationFmt = attestationFmt; - CreatedAt = createdAt; - AaGuid = aaGuid; - LastUsedAt = lastUsedAt; - RPID = rpid; - Origin = origin; - Country = country; - Device = device; - Nickname = nickname; - UserId = userId; - } - - public CredentialDescriptor Descriptor { get; } - public byte[] PublicKey { get; } - public byte[] UserHandle { get; } - public uint SignatureCounter { get; } - public string AttestationFmt { get; } - public DateTime CreatedAt { get; } - public Guid AaGuid { get; } - public DateTime LastUsedAt { get; } - public string RPID { get; } - public string Origin { get; } - public string Country { get; } - public string Device { get; } - public string Nickname { get; } - public string UserId { get; } +namespace Passwordless; + +public class Credential +{ + public Credential(CredentialDescriptor descriptor, byte[] publicKey, byte[] userHandle, uint signatureCounter, + string attestationFmt, DateTime createdAt, Guid aaGuid, DateTime lastUsedAt, string rpid, + string origin, string country, string device, string nickname, string userId) + { + Descriptor = descriptor; + PublicKey = publicKey; + UserHandle = userHandle; + SignatureCounter = signatureCounter; + AttestationFmt = attestationFmt; + CreatedAt = createdAt; + AaGuid = aaGuid; + LastUsedAt = lastUsedAt; + RPID = rpid; + Origin = origin; + Country = country; + Device = device; + Nickname = nickname; + UserId = userId; + } + + public CredentialDescriptor Descriptor { get; } + public byte[] PublicKey { get; } + public byte[] UserHandle { get; } + public uint SignatureCounter { get; } + public string AttestationFmt { get; } + public DateTime CreatedAt { get; } + public Guid AaGuid { get; } + public DateTime LastUsedAt { get; } + public string RPID { get; } + public string Origin { get; } + public string Country { get; } + public string Device { get; } + public string Nickname { get; } + public string UserId { get; } } \ No newline at end of file diff --git a/src/Sdk/Models/CredentialDescriptor.cs b/src/Passwordless/Models/CredentialDescriptor.cs similarity index 85% rename from src/Sdk/Models/CredentialDescriptor.cs rename to src/Passwordless/Models/CredentialDescriptor.cs index 1aee987..a3584b2 100644 --- a/src/Sdk/Models/CredentialDescriptor.cs +++ b/src/Passwordless/Models/CredentialDescriptor.cs @@ -1,14 +1,14 @@ -using System.Text.Json.Serialization; - -namespace Passwordless.Net; - -public class CredentialDescriptor -{ - public CredentialDescriptor(byte[] id) - { - Id = id; - } - - [JsonConverter(typeof(Base64UrlConverter))] - public byte[] Id { get; set; } +using System.Text.Json.Serialization; + +namespace Passwordless; + +public class CredentialDescriptor +{ + public CredentialDescriptor(byte[] id) + { + Id = id; + } + + [JsonConverter(typeof(Base64UrlConverter))] + public byte[] Id { get; set; } } \ No newline at end of file diff --git a/src/Sdk/Models/DeleteCredentialRequest.cs b/src/Passwordless/Models/DeleteCredentialRequest.cs similarity index 80% rename from src/Sdk/Models/DeleteCredentialRequest.cs rename to src/Passwordless/Models/DeleteCredentialRequest.cs index ce60135..e2e1ea9 100644 --- a/src/Sdk/Models/DeleteCredentialRequest.cs +++ b/src/Passwordless/Models/DeleteCredentialRequest.cs @@ -1,11 +1,11 @@ -namespace Passwordless.Net.Models; - -internal class DeleteCredentialRequest -{ - public DeleteCredentialRequest(string credentialId) - { - CredentialId = credentialId; - } - - public string CredentialId { get; } +namespace Passwordless.Models; + +internal class DeleteCredentialRequest +{ + public DeleteCredentialRequest(string credentialId) + { + CredentialId = credentialId; + } + + public string CredentialId { get; } } \ No newline at end of file diff --git a/src/Sdk/Models/DeleteUserRequest.cs b/src/Passwordless/Models/DeleteUserRequest.cs similarity index 77% rename from src/Sdk/Models/DeleteUserRequest.cs rename to src/Passwordless/Models/DeleteUserRequest.cs index 8ff250a..2b98a97 100644 --- a/src/Sdk/Models/DeleteUserRequest.cs +++ b/src/Passwordless/Models/DeleteUserRequest.cs @@ -1,11 +1,11 @@ -namespace Passwordless.Net.Models; - -internal class DeleteUserRequest -{ - public DeleteUserRequest(string userId) - { - UserId = userId; - } - - public string UserId { get; } +namespace Passwordless.Models; + +internal class DeleteUserRequest +{ + public DeleteUserRequest(string userId) + { + UserId = userId; + } + + public string UserId { get; } } \ No newline at end of file diff --git a/src/Sdk/Models/ListResponse.cs b/src/Passwordless/Models/ListResponse.cs similarity index 78% rename from src/Sdk/Models/ListResponse.cs rename to src/Passwordless/Models/ListResponse.cs index 0a91c42..248f527 100644 --- a/src/Sdk/Models/ListResponse.cs +++ b/src/Passwordless/Models/ListResponse.cs @@ -1,11 +1,11 @@ -namespace Passwordless.Net.Models; - -internal class ListResponse -{ - public ListResponse(IReadOnlyList values) - { - Values = values; - } - - public IReadOnlyList Values { get; } +namespace Passwordless.Models; + +internal class ListResponse +{ + public ListResponse(IReadOnlyList values) + { + Values = values; + } + + public IReadOnlyList Values { get; } } \ No newline at end of file diff --git a/src/Sdk/Models/PasswordlessUserSummary.cs b/src/Passwordless/Models/PasswordlessUserSummary.cs similarity index 92% rename from src/Sdk/Models/PasswordlessUserSummary.cs rename to src/Passwordless/Models/PasswordlessUserSummary.cs index a285adc..1542211 100644 --- a/src/Sdk/Models/PasswordlessUserSummary.cs +++ b/src/Passwordless/Models/PasswordlessUserSummary.cs @@ -1,20 +1,20 @@ -namespace Passwordless.Net; - -public class PasswordlessUserSummary -{ - public PasswordlessUserSummary(string userId, IReadOnlyList aliases, int credentialsCount, - int aliasCount, DateTime? lastUsedAt) - { - UserId = userId; - Aliases = aliases; - CredentialsCount = credentialsCount; - AliasCount = aliasCount; - LastUsedAt = lastUsedAt; - } - - public string UserId { get; } - public IReadOnlyList Aliases { get; } - public int CredentialsCount { get; } - public int AliasCount { get; } - public DateTime? LastUsedAt { get; } +namespace Passwordless; + +public class PasswordlessUserSummary +{ + public PasswordlessUserSummary(string userId, IReadOnlyList aliases, int credentialsCount, + int aliasCount, DateTime? lastUsedAt) + { + UserId = userId; + Aliases = aliases; + CredentialsCount = credentialsCount; + AliasCount = aliasCount; + LastUsedAt = lastUsedAt; + } + + public string UserId { get; } + public IReadOnlyList Aliases { get; } + public int CredentialsCount { get; } + public int AliasCount { get; } + public DateTime? LastUsedAt { get; } } \ No newline at end of file diff --git a/src/Sdk/Models/RegisterOptions.cs b/src/Passwordless/Models/RegisterOptions.cs similarity index 96% rename from src/Sdk/Models/RegisterOptions.cs rename to src/Passwordless/Models/RegisterOptions.cs index 7a67df9..f7aecb1 100644 --- a/src/Sdk/Models/RegisterOptions.cs +++ b/src/Passwordless/Models/RegisterOptions.cs @@ -1,80 +1,80 @@ -using System.Text.Json.Serialization; - -namespace Passwordless.Net; - -/// -/// -/// -public class RegisterOptions -{ - /// - /// - /// - /// - /// - public RegisterOptions(string userId, string username) - { - UserId = userId; - Username = username; - } - - /// - /// A WebAuthn User Handle, which should be generated by your application. - /// This is used to identify your user (could be a database primary key ID or a guid). - /// Max. 64 bytes. Should not contain PII about the user. - /// - public string UserId { get; } - - /// - /// A human-palatable identifier for a user account. It is intended only for display, - /// i.e., aiding the user in determining the difference between user accounts with - /// similar displayNames. Used in Browser UI's and never stored on the server. - /// - public string Username { get; } - - /// - /// A human-palatable name for the account, which should be chosen by the user. - /// Used in Browser UI's and never stored on the server. - /// - public string? DisplayName { get; set; } - - /// - /// WebAuthn attestation conveyance preference. Only "none" (default) is supported. - /// - public string? Attestation { get; set; } - - /// - /// WebAuthn authenticator attachment modality. Can be "any" (default), "platform", - /// which triggers client device-specific options Windows Hello, FaceID, or TouchID, - /// or "cross-platform", which triggers roaming options like security keys. - /// - public string? AuthenticatorType { get; set; } - - /// - /// If true, creates a client-side Discoverable Credential that allows sign in without needing a username. - /// - public bool? Discoverable { get; set; } - - /// - /// Allows choosing preference for requiring User Verification - /// (biometrics, pin code etc) when authenticating Can be "preferred" (default), "required" or "discouraged". - /// - public string? UserVerification { get; set; } - - /// - /// Timestamp (UTC) when the registration token should expire. By default, current time + 120 seconds. - /// - public DateTime? ExpiresAt { get; set; } - - /// - /// A array of aliases for the userId, such as an email or username. Used to initiate a - /// signin on the client side with the signinWithAlias() method. An alias must be unique to the userId. - /// Defaults to an empty array []. - /// - public HashSet Aliases { get; set; } = new HashSet(); - - /// - /// Whether aliases should be hashed before being stored. Defaults to true. - /// - public bool? AliasHashing { get; set; } +using System.Text.Json.Serialization; + +namespace Passwordless; + +/// +/// +/// +public class RegisterOptions +{ + /// + /// + /// + /// + /// + public RegisterOptions(string userId, string username) + { + UserId = userId; + Username = username; + } + + /// + /// A WebAuthn User Handle, which should be generated by your application. + /// This is used to identify your user (could be a database primary key ID or a guid). + /// Max. 64 bytes. Should not contain PII about the user. + /// + public string UserId { get; } + + /// + /// A human-palatable identifier for a user account. It is intended only for display, + /// i.e., aiding the user in determining the difference between user accounts with + /// similar displayNames. Used in Browser UI's and never stored on the server. + /// + public string Username { get; } + + /// + /// A human-palatable name for the account, which should be chosen by the user. + /// Used in Browser UI's and never stored on the server. + /// + public string? DisplayName { get; set; } + + /// + /// WebAuthn attestation conveyance preference. Only "none" (default) is supported. + /// + public string? Attestation { get; set; } + + /// + /// WebAuthn authenticator attachment modality. Can be "any" (default), "platform", + /// which triggers client device-specific options Windows Hello, FaceID, or TouchID, + /// or "cross-platform", which triggers roaming options like security keys. + /// + public string? AuthenticatorType { get; set; } + + /// + /// If true, creates a client-side Discoverable Credential that allows sign in without needing a username. + /// + public bool? Discoverable { get; set; } + + /// + /// Allows choosing preference for requiring User Verification + /// (biometrics, pin code etc) when authenticating Can be "preferred" (default), "required" or "discouraged". + /// + public string? UserVerification { get; set; } + + /// + /// Timestamp (UTC) when the registration token should expire. By default, current time + 120 seconds. + /// + public DateTime? ExpiresAt { get; set; } + + /// + /// A array of aliases for the userId, such as an email or username. Used to initiate a + /// signin on the client side with the signinWithAlias() method. An alias must be unique to the userId. + /// Defaults to an empty array []. + /// + public HashSet Aliases { get; set; } = new HashSet(); + + /// + /// Whether aliases should be hashed before being stored. Defaults to true. + /// + public bool? AliasHashing { get; set; } } \ No newline at end of file diff --git a/src/Sdk/Models/RegisterTokenResponse.cs b/src/Passwordless/Models/RegisterTokenResponse.cs similarity index 80% rename from src/Sdk/Models/RegisterTokenResponse.cs rename to src/Passwordless/Models/RegisterTokenResponse.cs index ebdf77d..41c2e9a 100644 --- a/src/Sdk/Models/RegisterTokenResponse.cs +++ b/src/Passwordless/Models/RegisterTokenResponse.cs @@ -1,13 +1,13 @@ -using System.Text.Json.Serialization; - -namespace Passwordless.Net.Models; - -public class RegisterTokenResponse -{ - public RegisterTokenResponse(string token) - { - Token = token; - } - - public string Token { get; } +using System.Text.Json.Serialization; + +namespace Passwordless.Models; + +public class RegisterTokenResponse +{ + public RegisterTokenResponse(string token) + { + Token = token; + } + + public string Token { get; } } \ No newline at end of file diff --git a/src/Sdk/Models/UsersCount.cs b/src/Passwordless/Models/UsersCount.cs similarity index 76% rename from src/Sdk/Models/UsersCount.cs rename to src/Passwordless/Models/UsersCount.cs index 756983c..881e648 100644 --- a/src/Sdk/Models/UsersCount.cs +++ b/src/Passwordless/Models/UsersCount.cs @@ -1,11 +1,11 @@ -namespace Passwordless.Net; - -public class UsersCount -{ - public UsersCount(int count) - { - Count = count; - } - - public int Count { get; } +namespace Passwordless; + +public class UsersCount +{ + public UsersCount(int count) + { + Count = count; + } + + public int Count { get; } } \ No newline at end of file diff --git a/src/Sdk/Models/VerifiedUser.cs b/src/Passwordless/Models/VerifiedUser.cs similarity index 94% rename from src/Sdk/Models/VerifiedUser.cs rename to src/Passwordless/Models/VerifiedUser.cs index 84f7146..7a784d1 100644 --- a/src/Sdk/Models/VerifiedUser.cs +++ b/src/Passwordless/Models/VerifiedUser.cs @@ -1,35 +1,35 @@ -namespace Passwordless.Net; - -public class VerifiedUser -{ - public VerifiedUser(string userId, byte[] credentialId, bool success, - DateTime timestamp, string rpId, string origin, string device, - string country, string nickname, DateTime expiresAt, Guid tokenId, - string type) - { - UserId = userId; - CredentialId = credentialId; - Success = success; - Timestamp = timestamp; - RpId = rpId; - Origin = origin; - Device = device; - Country = country; - Nickname = nickname; - ExpiresAt = expiresAt; - TokenId = tokenId; - Type = type; - } - public string UserId { get; } - public byte[] CredentialId { get; } - public bool Success { get; } - public DateTime Timestamp { get; } - public string RpId { get; } - public string Origin { get; } - public string Device { get; } - public string Country { get; } - public string Nickname { get; } - public DateTime ExpiresAt { get; } - public Guid TokenId { get; } - public string Type { get; } +namespace Passwordless; + +public class VerifiedUser +{ + public VerifiedUser(string userId, byte[] credentialId, bool success, + DateTime timestamp, string rpId, string origin, string device, + string country, string nickname, DateTime expiresAt, Guid tokenId, + string type) + { + UserId = userId; + CredentialId = credentialId; + Success = success; + Timestamp = timestamp; + RpId = rpId; + Origin = origin; + Device = device; + Country = country; + Nickname = nickname; + ExpiresAt = expiresAt; + TokenId = tokenId; + Type = type; + } + public string UserId { get; } + public byte[] CredentialId { get; } + public bool Success { get; } + public DateTime Timestamp { get; } + public string RpId { get; } + public string Origin { get; } + public string Device { get; } + public string Country { get; } + public string Nickname { get; } + public DateTime ExpiresAt { get; } + public Guid TokenId { get; } + public string Type { get; } } \ No newline at end of file diff --git a/src/Sdk/Models/VerifyTokenRequest.cs b/src/Passwordless/Models/VerifyTokenRequest.cs similarity index 77% rename from src/Sdk/Models/VerifyTokenRequest.cs rename to src/Passwordless/Models/VerifyTokenRequest.cs index 89cc8b4..323b36b 100644 --- a/src/Sdk/Models/VerifyTokenRequest.cs +++ b/src/Passwordless/Models/VerifyTokenRequest.cs @@ -1,11 +1,11 @@ -namespace Passwordless.Net.Models; - -internal class VerifyTokenRequest -{ - public VerifyTokenRequest(string token) - { - Token = token; - } - - public string Token { get; } +namespace Passwordless.Models; + +internal class VerifyTokenRequest +{ + public VerifyTokenRequest(string token) + { + Token = token; + } + + public string Token { get; } } \ No newline at end of file diff --git a/src/Sdk/Sdk.csproj b/src/Passwordless/Passwordless.csproj similarity index 64% rename from src/Sdk/Sdk.csproj rename to src/Passwordless/Passwordless.csproj index b0e9f00..530ab82 100644 --- a/src/Sdk/Sdk.csproj +++ b/src/Passwordless/Passwordless.csproj @@ -1,20 +1,21 @@ - Passwordless.Net - Passwordless.Net - Passwordless net462;net6.0;net7.0;netstandard2.0 - $(TargetFrameworks);$(CurrentPreviewTfm) + $(TargetFrameworks);$(CurrentPreviewTfm) + true + + + + Bitwarden https://github.com/passwordless/passwordless-dotnet git README.md - true - + $(DefineConstants);INCLUDE_DYNAMICALLY_ACCESSED_MEMBERS @@ -23,20 +24,19 @@ - + - + - - + diff --git a/src/Sdk/PasswordlessApiException.cs b/src/Passwordless/PasswordlessApiException.cs similarity index 94% rename from src/Sdk/PasswordlessApiException.cs rename to src/Passwordless/PasswordlessApiException.cs index 0e6a409..2fb6858 100644 --- a/src/Sdk/PasswordlessApiException.cs +++ b/src/Passwordless/PasswordlessApiException.cs @@ -1,39 +1,39 @@ -using System.Diagnostics; -using System.Net.Http; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Passwordless.Net; - -public sealed class PasswordlessApiException : HttpRequestException -{ - public PasswordlessProblemDetails Details { get; } - - public PasswordlessApiException(PasswordlessProblemDetails problemDetails) : base(problemDetails.Title) - { - Details = problemDetails; - } -} - -public class PasswordlessProblemDetails -{ - public PasswordlessProblemDetails(string type, - string title, int status, string? detail, string? instance) - { - Type = type; - Title = title; - Status = status; - Detail = detail; - Instance = instance; - } - - // TODO: Include errorCode as a property once it's more common - public string Type { get; } - public string Title { get; } - public int Status { get; } - public string? Detail { get; } - public string? Instance { get; } - - [JsonExtensionData] - public Dictionary Extensions { get; set; } = new Dictionary(); +using System.Diagnostics; +using System.Net.Http; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Passwordless; + +public sealed class PasswordlessApiException : HttpRequestException +{ + public PasswordlessProblemDetails Details { get; } + + public PasswordlessApiException(PasswordlessProblemDetails problemDetails) : base(problemDetails.Title) + { + Details = problemDetails; + } +} + +public class PasswordlessProblemDetails +{ + public PasswordlessProblemDetails(string type, + string title, int status, string? detail, string? instance) + { + Type = type; + Title = title; + Status = status; + Detail = detail; + Instance = instance; + } + + // TODO: Include errorCode as a property once it's more common + public string Type { get; } + public string Title { get; } + public int Status { get; } + public string? Detail { get; } + public string? Instance { get; } + + [JsonExtensionData] + public Dictionary Extensions { get; set; } = new Dictionary(); } \ No newline at end of file diff --git a/src/Sdk/PasswordlessClient.cs b/src/Passwordless/PasswordlessClient.cs similarity index 86% rename from src/Sdk/PasswordlessClient.cs rename to src/Passwordless/PasswordlessClient.cs index e9b354e..d5c9b01 100644 --- a/src/Sdk/PasswordlessClient.cs +++ b/src/Passwordless/PasswordlessClient.cs @@ -1,192 +1,193 @@ -using System.Diagnostics; -using System.Net.Http.Json; -using System.Text; -using Passwordless.Net.Models; -using JsonContext = Passwordless.Net.Helpers.PasswordlessSerializerContext; - -namespace Passwordless.Net; - -/// -/// TODO: FILL IN -/// -[DebuggerDisplay("{DebuggerToString(),nq}")] -public class PasswordlessClient : IPasswordlessClient, IDisposable -{ - private readonly bool _needsDisposing; - private readonly HttpClient _client; - - public static PasswordlessClient Create(PasswordlessOptions options, IHttpClientFactory factory) - { - var client = factory.CreateClient(); - client.BaseAddress = new Uri(options.ApiUrl); - client.DefaultRequestHeaders.Add("ApiSecret", options.ApiSecret); - return new PasswordlessClient(client); - } - - public PasswordlessClient(PasswordlessOptions passwordlessOptions) - { - _needsDisposing = true; - _client = new HttpClient - { - BaseAddress = new Uri(passwordlessOptions.ApiUrl), - }; - _client.DefaultRequestHeaders.Add("ApiSecret", passwordlessOptions.ApiSecret); - } - - public PasswordlessClient(HttpClient client) - { - _needsDisposing = false; - _client = client; - } - - /// - public async Task AddAliasAsync(AddAliasRequest request, CancellationToken cancellationToken) - { - var res = await _client.PostAsJsonAsync("alias", - request, - JsonContext.Default.AddAliasRequest, - cancellationToken); - res.EnsureSuccessStatusCode(); - } - - /// - public async Task CreateRegisterTokenAsync(RegisterOptions registerOptions, CancellationToken cancellationToken = default) - { - var res = await _client.PostAsJsonAsync("register/token", - registerOptions, - JsonContext.Default.RegisterOptions, - cancellationToken); - res.EnsureSuccessStatusCode(); - return (await res.Content.ReadFromJsonAsync( - JsonContext.Default.RegisterTokenResponse, - cancellationToken))!; - } - - /// - public async Task VerifyTokenAsync(string verifyToken, CancellationToken cancellationToken = default) - { - var request = new HttpRequestMessage(HttpMethod.Post, "signin/verify") - { - // TODO: No JsonTypeInfo overload yet? - Content = JsonContent.Create(new VerifyTokenRequest(verifyToken)), - }; - - // We just want to return null if there is a problem. - request.SkipErrorHandling(); - var response = await _client.SendAsync(request, cancellationToken); - - if (response.IsSuccessStatusCode) - { - var res = await response.Content.ReadFromJsonAsync( - JsonContext.Default.VerifiedUser, - cancellationToken); - return res; - } - - return null; - } - - /// - public async Task DeleteUserAsync(string userId, CancellationToken cancellationToken = default) - { - await _client.PostAsJsonAsync("users/delete", - new DeleteUserRequest(userId), - JsonContext.Default.DeleteUserRequest, - cancellationToken); - } - - /// - public async Task> ListUsersAsync(CancellationToken cancellationToken = default) - { - var response = await _client.GetFromJsonAsync( - "users/list", - JsonContext.Default.ListResponsePasswordlessUserSummary, - cancellationToken); - return response!.Values; - } - - /// - public async Task> ListAliasesAsync(string userId, CancellationToken cancellationToken = default) - { - var response = await _client.GetFromJsonAsync( - $"alias/list?userid={userId}", - JsonContext.Default.ListResponseAliasPointer, - cancellationToken); - return response!.Values; - } - - /// - public async Task> ListCredentialsAsync(string userId, CancellationToken cancellationToken = default) - { - var response = await _client.GetFromJsonAsync( - $"credentials/list?userid={userId}", - JsonContext.Default.ListResponseCredential, - cancellationToken); - return response!.Values; - } - - /// - public async Task DeleteCredentialAsync(string id, CancellationToken cancellationToken = default) - { - await _client.PostAsJsonAsync("credentials/delete", - new DeleteCredentialRequest(id), - JsonContext.Default.DeleteCredentialRequest, - cancellationToken); - } - - /// - public async Task DeleteCredentialAsync(byte[] id, CancellationToken cancellationToken = default) - { - await DeleteCredentialAsync(Base64Url.Encode(id), cancellationToken); - } - - public async Task GetUsersCountAsync(CancellationToken cancellationToken = default) - { - return (await _client.GetFromJsonAsync( - "users/count", - JsonContext.Default.UsersCount, - cancellationToken))!; - } - - private string DebuggerToString() - { - var sb = new StringBuilder(); - sb.Append("ApiUrl = "); - sb.Append(_client.BaseAddress); - if (_client.DefaultRequestHeaders.TryGetValues("ApiSecret", out var values)) - { - var apiSecret = values.First(); - if (apiSecret.Length > 5) - { - sb.Append(' '); - sb.Append("ApiSecret = "); - sb.Append("***"); - sb.Append(apiSecret.Substring(apiSecret.Length - 4)); - } - } - else - { - sb.Append(' '); - sb.Append("ApiSecret = (null)"); - } - - return sb.ToString(); - } - - public void Dispose() - { - if (_needsDisposing) - { - Dispose(true); - GC.SuppressFinalize(this); - } - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _client.Dispose(); - } - } +using System.Diagnostics; +using System.Net.Http.Json; +using System.Text; +using Passwordless.Helpers; +using Passwordless.Models; +using JsonContext = Passwordless.Helpers.PasswordlessSerializerContext; + +namespace Passwordless; + +/// +/// TODO: FILL IN +/// +[DebuggerDisplay("{DebuggerToString(),nq}")] +public class PasswordlessClient : IPasswordlessClient, IDisposable +{ + private readonly bool _needsDisposing; + private readonly HttpClient _client; + + public static PasswordlessClient Create(PasswordlessOptions options, IHttpClientFactory factory) + { + var client = factory.CreateClient(); + client.BaseAddress = new Uri(options.ApiUrl); + client.DefaultRequestHeaders.Add("ApiSecret", options.ApiSecret); + return new PasswordlessClient(client); + } + + public PasswordlessClient(PasswordlessOptions passwordlessOptions) + { + _needsDisposing = true; + _client = new HttpClient + { + BaseAddress = new Uri(passwordlessOptions.ApiUrl), + }; + _client.DefaultRequestHeaders.Add("ApiSecret", passwordlessOptions.ApiSecret); + } + + public PasswordlessClient(HttpClient client) + { + _needsDisposing = false; + _client = client; + } + + /// + public async Task AddAliasAsync(AddAliasRequest request, CancellationToken cancellationToken) + { + var res = await _client.PostAsJsonAsync("alias", + request, + PasswordlessSerializerContext.Default.AddAliasRequest, + cancellationToken); + res.EnsureSuccessStatusCode(); + } + + /// + public async Task CreateRegisterTokenAsync(RegisterOptions registerOptions, CancellationToken cancellationToken = default) + { + var res = await _client.PostAsJsonAsync("register/token", + registerOptions, + PasswordlessSerializerContext.Default.RegisterOptions, + cancellationToken); + res.EnsureSuccessStatusCode(); + return (await res.Content.ReadFromJsonAsync( + PasswordlessSerializerContext.Default.RegisterTokenResponse, + cancellationToken))!; + } + + /// + public async Task VerifyTokenAsync(string verifyToken, CancellationToken cancellationToken = default) + { + var request = new HttpRequestMessage(HttpMethod.Post, "signin/verify") + { + // TODO: No JsonTypeInfo overload yet? + Content = JsonContent.Create(new VerifyTokenRequest(verifyToken)), + }; + + // We just want to return null if there is a problem. + request.SkipErrorHandling(); + var response = await _client.SendAsync(request, cancellationToken); + + if (response.IsSuccessStatusCode) + { + var res = await response.Content.ReadFromJsonAsync( + PasswordlessSerializerContext.Default.VerifiedUser, + cancellationToken); + return res; + } + + return null; + } + + /// + public async Task DeleteUserAsync(string userId, CancellationToken cancellationToken = default) + { + await _client.PostAsJsonAsync("users/delete", + new DeleteUserRequest(userId), + PasswordlessSerializerContext.Default.DeleteUserRequest, + cancellationToken); + } + + /// + public async Task> ListUsersAsync(CancellationToken cancellationToken = default) + { + var response = await _client.GetFromJsonAsync( + "users/list", + PasswordlessSerializerContext.Default.ListResponsePasswordlessUserSummary, + cancellationToken); + return response!.Values; + } + + /// + public async Task> ListAliasesAsync(string userId, CancellationToken cancellationToken = default) + { + var response = await _client.GetFromJsonAsync( + $"alias/list?userid={userId}", + PasswordlessSerializerContext.Default.ListResponseAliasPointer, + cancellationToken); + return response!.Values; + } + + /// + public async Task> ListCredentialsAsync(string userId, CancellationToken cancellationToken = default) + { + var response = await _client.GetFromJsonAsync( + $"credentials/list?userid={userId}", + PasswordlessSerializerContext.Default.ListResponseCredential, + cancellationToken); + return response!.Values; + } + + /// + public async Task DeleteCredentialAsync(string id, CancellationToken cancellationToken = default) + { + await _client.PostAsJsonAsync("credentials/delete", + new DeleteCredentialRequest(id), + PasswordlessSerializerContext.Default.DeleteCredentialRequest, + cancellationToken); + } + + /// + public async Task DeleteCredentialAsync(byte[] id, CancellationToken cancellationToken = default) + { + await DeleteCredentialAsync(Base64Url.Encode(id), cancellationToken); + } + + public async Task GetUsersCountAsync(CancellationToken cancellationToken = default) + { + return (await _client.GetFromJsonAsync( + "users/count", + PasswordlessSerializerContext.Default.UsersCount, + cancellationToken))!; + } + + private string DebuggerToString() + { + var sb = new StringBuilder(); + sb.Append("ApiUrl = "); + sb.Append(_client.BaseAddress); + if (_client.DefaultRequestHeaders.TryGetValues("ApiSecret", out var values)) + { + var apiSecret = values.First(); + if (apiSecret.Length > 5) + { + sb.Append(' '); + sb.Append("ApiSecret = "); + sb.Append("***"); + sb.Append(apiSecret.Substring(apiSecret.Length - 4)); + } + } + else + { + sb.Append(' '); + sb.Append("ApiSecret = (null)"); + } + + return sb.ToString(); + } + + public void Dispose() + { + if (_needsDisposing) + { + Dispose(true); + GC.SuppressFinalize(this); + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _client.Dispose(); + } + } } \ No newline at end of file diff --git a/src/Sdk/PasswordlessDelegatingHandler.cs b/src/Passwordless/PasswordlessDelegatingHandler.cs similarity index 91% rename from src/Sdk/PasswordlessDelegatingHandler.cs rename to src/Passwordless/PasswordlessDelegatingHandler.cs index 1e145e5..f467ed1 100644 --- a/src/Sdk/PasswordlessDelegatingHandler.cs +++ b/src/Passwordless/PasswordlessDelegatingHandler.cs @@ -1,32 +1,32 @@ -using System.Net.Http; -using System.Net.Http.Json; -using Passwordless.Net.Helpers; - -namespace Passwordless.Net; - -internal class PasswordlessDelegatingHandler : DelegatingHandler -{ - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - var response = await base.SendAsync(request, cancellationToken); - - if (request.ShouldSkipErrorHandling()) - { - return response; - } - - if (!response.IsSuccessStatusCode - && string.Equals(response.Content.Headers.ContentType?.MediaType, "application/problem+json", StringComparison.OrdinalIgnoreCase)) - { - // Attempt to read problem details - var problemDetails = await response.Content.ReadFromJsonAsync( - PasswordlessSerializerContext.Default.PasswordlessProblemDetails, - cancellationToken); - - // Throw exception - throw new PasswordlessApiException(problemDetails!); - } - - return response; - } +using System.Net.Http; +using System.Net.Http.Json; +using Passwordless.Helpers; + +namespace Passwordless; + +internal class PasswordlessDelegatingHandler : DelegatingHandler +{ + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var response = await base.SendAsync(request, cancellationToken); + + if (request.ShouldSkipErrorHandling()) + { + return response; + } + + if (!response.IsSuccessStatusCode + && string.Equals(response.Content.Headers.ContentType?.MediaType, "application/problem+json", StringComparison.OrdinalIgnoreCase)) + { + // Attempt to read problem details + var problemDetails = await response.Content.ReadFromJsonAsync( + PasswordlessSerializerContext.Default.PasswordlessProblemDetails, + cancellationToken); + + // Throw exception + throw new PasswordlessApiException(problemDetails!); + } + + return response; + } } \ No newline at end of file diff --git a/src/Sdk/PasswordlessExtensions.cs b/src/Passwordless/PasswordlessExtensions.cs similarity index 79% rename from src/Sdk/PasswordlessExtensions.cs rename to src/Passwordless/PasswordlessExtensions.cs index 1b7a855..cfa479f 100644 --- a/src/Sdk/PasswordlessExtensions.cs +++ b/src/Passwordless/PasswordlessExtensions.cs @@ -1,9 +1,9 @@ -namespace Passwordless.Net; - -public static class PasswordlessExtensions -{ - public static string ToBase64Url(this byte[] bytes) - { - return Base64Url.Encode(bytes); - } +namespace Passwordless; + +public static class PasswordlessExtensions +{ + public static string ToBase64Url(this byte[] bytes) + { + return Base64Url.Encode(bytes); + } } \ No newline at end of file diff --git a/src/Sdk/PasswordlessHttpRequestExtensions.cs b/src/Passwordless/PasswordlessHttpRequestExtensions.cs similarity index 94% rename from src/Sdk/PasswordlessHttpRequestExtensions.cs rename to src/Passwordless/PasswordlessHttpRequestExtensions.cs index 75d4fb9..0347c6a 100644 --- a/src/Sdk/PasswordlessHttpRequestExtensions.cs +++ b/src/Passwordless/PasswordlessHttpRequestExtensions.cs @@ -1,33 +1,33 @@ -using System.Net.Http; - -namespace Passwordless.Net; - -internal static class PasswordlessHttpRequestExtensions -{ -#if NET5_0_OR_GREATER - internal static HttpRequestOptionsKey SkipErrorHandlingOption = new(nameof(SkipErrorHandling)); -#elif NET462 || NETSTANDARD2_0 - internal const string SkipErrorHandlingOption = nameof(SkipErrorHandling); -#endif - - internal static HttpRequestMessage SkipErrorHandling(this HttpRequestMessage request, bool skip = true) - { -#if NET5_0_OR_GREATER - request.Options.Set(SkipErrorHandlingOption, skip); -#elif NET462 || NETSTANDARD2_0 - request.Properties[SkipErrorHandlingOption] = skip; -#endif - return request; - } - - internal static bool ShouldSkipErrorHandling(this HttpRequestMessage request) - { -#if NET5_0_OR_GREATER - return request.Options.TryGetValue(SkipErrorHandlingOption, out var doNotErrorHandle) && doNotErrorHandle; -#elif NET462 || NETSTANDARD2_0 - return request.Properties.TryGetValue(SkipErrorHandlingOption, out var shouldSkipOptionObject) - && shouldSkipOptionObject is bool shouldSkipOption - && shouldSkipOption; -#endif - } +using System.Net.Http; + +namespace Passwordless; + +internal static class PasswordlessHttpRequestExtensions +{ +#if NET5_0_OR_GREATER + internal static HttpRequestOptionsKey SkipErrorHandlingOption = new(nameof(SkipErrorHandling)); +#elif NET462 || NETSTANDARD2_0 + internal const string SkipErrorHandlingOption = nameof(SkipErrorHandling); +#endif + + internal static HttpRequestMessage SkipErrorHandling(this HttpRequestMessage request, bool skip = true) + { +#if NET5_0_OR_GREATER + request.Options.Set(SkipErrorHandlingOption, skip); +#elif NET462 || NETSTANDARD2_0 + request.Properties[SkipErrorHandlingOption] = skip; +#endif + return request; + } + + internal static bool ShouldSkipErrorHandling(this HttpRequestMessage request) + { +#if NET5_0_OR_GREATER + return request.Options.TryGetValue(SkipErrorHandlingOption, out var doNotErrorHandle) && doNotErrorHandle; +#elif NET462 || NETSTANDARD2_0 + return request.Properties.TryGetValue(SkipErrorHandlingOption, out var shouldSkipOptionObject) + && shouldSkipOptionObject is bool shouldSkipOption + && shouldSkipOption; +#endif + } } \ No newline at end of file diff --git a/src/Sdk/PasswordlessOptions.cs b/src/Passwordless/PasswordlessOptions.cs similarity index 94% rename from src/Sdk/PasswordlessOptions.cs rename to src/Passwordless/PasswordlessOptions.cs index 2ccecf9..1748ae5 100644 --- a/src/Sdk/PasswordlessOptions.cs +++ b/src/Passwordless/PasswordlessOptions.cs @@ -1,33 +1,33 @@ -namespace Passwordless.Net; - -/// -/// Represents all the options you can use to configure a backend Passwordless system. -/// -public class PasswordlessOptions -{ - /// - /// Passwordless Cloud Url - /// - public const string CloudApiUrl = "https://v4.passwordless.dev"; - - /// - /// Gets or sets the url to use for Passwordless operations. - /// - /// - /// Defaults to . - /// - public string ApiUrl { get; set; } = CloudApiUrl; - - /// - /// Gets or sets the secret API key used to authenticate with the Passwordless API. - /// - public string ApiSecret { get; set; } = default!; - - /// - /// Gets or sets the public API key used to interact with the Passwordless API. - /// - /// - /// Optional: Only used for frontend operations by the JS Client. E.g: Useful if you're using MVC/Razor pages - /// - public string? ApiKey { get; set; } +namespace Passwordless; + +/// +/// Represents all the options you can use to configure a backend Passwordless system. +/// +public class PasswordlessOptions +{ + /// + /// Passwordless Cloud Url + /// + public const string CloudApiUrl = "https://v4.passwordless.dev"; + + /// + /// Gets or sets the url to use for Passwordless operations. + /// + /// + /// Defaults to . + /// + public string ApiUrl { get; set; } = CloudApiUrl; + + /// + /// Gets or sets the secret API key used to authenticate with the Passwordless API. + /// + public string ApiSecret { get; set; } = default!; + + /// + /// Gets or sets the public API key used to interact with the Passwordless API. + /// + /// + /// Optional: Only used for frontend operations by the JS Client. E.g: Useful if you're using MVC/Razor pages + /// + public string? ApiKey { get; set; } } \ No newline at end of file diff --git a/src/Sdk/Pollyfill.cs b/src/Passwordless/Pollyfill.cs similarity index 97% rename from src/Sdk/Pollyfill.cs rename to src/Passwordless/Pollyfill.cs index 63234d9..3b9f508 100644 --- a/src/Sdk/Pollyfill.cs +++ b/src/Passwordless/Pollyfill.cs @@ -1,143 +1,143 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Diagnostics.CodeAnalysis -{ -#if INCLUDE_DYNAMICALLY_ACCESSED_MEMBERS - /// - /// Indicates that certain members on a specified are accessed dynamically, - /// for example through . - /// - /// - /// This allows tools to understand which members are being accessed during the execution - /// of a program. - /// - /// This attribute is valid on members whose type is or . - /// - /// When this attribute is applied to a location of type , the assumption is - /// that the string represents a fully qualified type name. - /// - /// When this attribute is applied to a class, interface, or struct, the members specified - /// can be accessed dynamically on instances returned from calling - /// on instances of that class, interface, or struct. - /// - /// If the attribute is applied to a method it's treated as a special case and it implies - /// the attribute should be applied to the "this" parameter of the method. As such the attribute - /// should only be used on instance methods of types assignable to System.Type (or string, but no methods - /// will use it there). - /// - [AttributeUsage( - AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | - AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | - AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, - Inherited = false)] - internal sealed class DynamicallyAccessedMembersAttribute : Attribute - { - /// - /// Initializes a new instance of the class - /// with the specified member types. - /// - /// The types of members dynamically accessed. - public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) - { - MemberTypes = memberTypes; - } - - /// - /// Gets the which specifies the type - /// of members dynamically accessed. - /// - public DynamicallyAccessedMemberTypes MemberTypes { get; } - } - - /// - /// Specifies the types of members that are dynamically accessed. - /// - /// This enumeration has a attribute that allows a - /// bitwise combination of its member values. - /// - [Flags] - internal enum DynamicallyAccessedMemberTypes - { - /// - /// Specifies no members. - /// - None = 0, - - /// - /// Specifies the default, parameterless public constructor. - /// - PublicParameterlessConstructor = 0x0001, - - /// - /// Specifies all public constructors. - /// - PublicConstructors = 0x0002 | PublicParameterlessConstructor, - - /// - /// Specifies all non-public constructors. - /// - NonPublicConstructors = 0x0004, - - /// - /// Specifies all public methods. - /// - PublicMethods = 0x0008, - - /// - /// Specifies all non-public methods. - /// - NonPublicMethods = 0x0010, - - /// - /// Specifies all public fields. - /// - PublicFields = 0x0020, - - /// - /// Specifies all non-public fields. - /// - NonPublicFields = 0x0040, - - /// - /// Specifies all public nested types. - /// - PublicNestedTypes = 0x0080, - - /// - /// Specifies all non-public nested types. - /// - NonPublicNestedTypes = 0x0100, - - /// - /// Specifies all public properties. - /// - PublicProperties = 0x0200, - - /// - /// Specifies all non-public properties. - /// - NonPublicProperties = 0x0400, - - /// - /// Specifies all public events. - /// - PublicEvents = 0x0800, - - /// - /// Specifies all non-public events. - /// - NonPublicEvents = 0x1000, - - /// - /// Specifies all interfaces implemented by the type. - /// - Interfaces = 0x2000, - - /// - /// Specifies all members. - /// - All = ~None - } -#endif +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Diagnostics.CodeAnalysis +{ +#if INCLUDE_DYNAMICALLY_ACCESSED_MEMBERS + /// + /// Indicates that certain members on a specified are accessed dynamically, + /// for example through . + /// + /// + /// This allows tools to understand which members are being accessed during the execution + /// of a program. + /// + /// This attribute is valid on members whose type is or . + /// + /// When this attribute is applied to a location of type , the assumption is + /// that the string represents a fully qualified type name. + /// + /// When this attribute is applied to a class, interface, or struct, the members specified + /// can be accessed dynamically on instances returned from calling + /// on instances of that class, interface, or struct. + /// + /// If the attribute is applied to a method it's treated as a special case and it implies + /// the attribute should be applied to the "this" parameter of the method. As such the attribute + /// should only be used on instance methods of types assignable to System.Type (or string, but no methods + /// will use it there). + /// + [AttributeUsage( + AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, + Inherited = false)] + internal sealed class DynamicallyAccessedMembersAttribute : Attribute + { + /// + /// Initializes a new instance of the class + /// with the specified member types. + /// + /// The types of members dynamically accessed. + public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) + { + MemberTypes = memberTypes; + } + + /// + /// Gets the which specifies the type + /// of members dynamically accessed. + /// + public DynamicallyAccessedMemberTypes MemberTypes { get; } + } + + /// + /// Specifies the types of members that are dynamically accessed. + /// + /// This enumeration has a attribute that allows a + /// bitwise combination of its member values. + /// + [Flags] + internal enum DynamicallyAccessedMemberTypes + { + /// + /// Specifies no members. + /// + None = 0, + + /// + /// Specifies the default, parameterless public constructor. + /// + PublicParameterlessConstructor = 0x0001, + + /// + /// Specifies all public constructors. + /// + PublicConstructors = 0x0002 | PublicParameterlessConstructor, + + /// + /// Specifies all non-public constructors. + /// + NonPublicConstructors = 0x0004, + + /// + /// Specifies all public methods. + /// + PublicMethods = 0x0008, + + /// + /// Specifies all non-public methods. + /// + NonPublicMethods = 0x0010, + + /// + /// Specifies all public fields. + /// + PublicFields = 0x0020, + + /// + /// Specifies all non-public fields. + /// + NonPublicFields = 0x0040, + + /// + /// Specifies all public nested types. + /// + PublicNestedTypes = 0x0080, + + /// + /// Specifies all non-public nested types. + /// + NonPublicNestedTypes = 0x0100, + + /// + /// Specifies all public properties. + /// + PublicProperties = 0x0200, + + /// + /// Specifies all non-public properties. + /// + NonPublicProperties = 0x0400, + + /// + /// Specifies all public events. + /// + PublicEvents = 0x0800, + + /// + /// Specifies all non-public events. + /// + NonPublicEvents = 0x1000, + + /// + /// Specifies all interfaces implemented by the type. + /// + Interfaces = 0x2000, + + /// + /// Specifies all members. + /// + All = ~None + } +#endif } \ No newline at end of file diff --git a/src/Sdk/ServiceCollectionExtensions.cs b/src/Passwordless/ServiceCollectionExtensions.cs similarity index 96% rename from src/Sdk/ServiceCollectionExtensions.cs rename to src/Passwordless/ServiceCollectionExtensions.cs index fc64789..8febcb3 100644 --- a/src/Sdk/ServiceCollectionExtensions.cs +++ b/src/Passwordless/ServiceCollectionExtensions.cs @@ -1,52 +1,52 @@ -using System.Diagnostics.CodeAnalysis; -using System.Net.Http; -using Microsoft.Extensions.Options; -using Passwordless.Net; - -// This is a trick to always show up in a class when people are registering services -namespace Microsoft.Extensions.DependencyInjection; - -public static class ServiceCollectionExtensions -{ - public static IServiceCollection AddPasswordlessSdk(this IServiceCollection services, Action configureOptions) - { - services.AddOptions() - .Configure(configureOptions) - .PostConfigure(options => options.ApiUrl ??= PasswordlessOptions.CloudApiUrl) - .Validate(options => !string.IsNullOrEmpty(options.ApiSecret), "Passwordless: Missing ApiSecret"); - - services.AddPasswordlessClientCore((sp, client) => - { - var options = sp.GetRequiredService>().Value; - - client.BaseAddress = new Uri(options.ApiUrl); - client.DefaultRequestHeaders.Add("ApiSecret", options.ApiSecret); - }); - - // TODO: Get rid of this service, all consumers should use the interface - services.AddTransient(sp => (PasswordlessClient)sp.GetRequiredService()); - - return services; - } - - /// - /// Helper method for making custom typed HttpClient implementations that also have - /// the inner handler for throwing fancy exceptions. Not intended for public use, - /// hence the hiding of it in IDE's. - /// - /// - /// This method signature is subject to change without major version bump/announcement. - /// - internal static IServiceCollection AddPasswordlessClientCore<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TClient, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(this IServiceCollection services, Action configureClient) - where TClient : class - where TImplementation : class, TClient - { - services.AddTransient(); - - services - .AddHttpClient(configureClient) - .AddHttpMessageHandler(); - - return services; - } +using System.Diagnostics.CodeAnalysis; +using System.Net.Http; +using Microsoft.Extensions.Options; +using Passwordless; + +// This is a trick to always show up in a class when people are registering services +namespace Microsoft.Extensions.DependencyInjection; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddPasswordlessSdk(this IServiceCollection services, Action configureOptions) + { + services.AddOptions() + .Configure(configureOptions) + .PostConfigure(options => options.ApiUrl ??= PasswordlessOptions.CloudApiUrl) + .Validate(options => !string.IsNullOrEmpty(options.ApiSecret), "Passwordless: Missing ApiSecret"); + + services.AddPasswordlessClientCore((sp, client) => + { + var options = sp.GetRequiredService>().Value; + + client.BaseAddress = new Uri(options.ApiUrl); + client.DefaultRequestHeaders.Add("ApiSecret", options.ApiSecret); + }); + + // TODO: Get rid of this service, all consumers should use the interface + services.AddTransient(sp => (PasswordlessClient)sp.GetRequiredService()); + + return services; + } + + /// + /// Helper method for making custom typed HttpClient implementations that also have + /// the inner handler for throwing fancy exceptions. Not intended for public use, + /// hence the hiding of it in IDE's. + /// + /// + /// This method signature is subject to change without major version bump/announcement. + /// + internal static IServiceCollection AddPasswordlessClientCore<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TClient, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(this IServiceCollection services, Action configureClient) + where TClient : class + where TImplementation : class, TClient + { + services.AddTransient(); + + services + .AddHttpClient(configureClient) + .AddHttpMessageHandler(); + + return services; + } } \ No newline at end of file diff --git a/tests/Sdk.Tests/ApiFactAttribute.cs b/tests/Passwordless.Tests/ApiFactAttribute.cs similarity index 85% rename from tests/Sdk.Tests/ApiFactAttribute.cs rename to tests/Passwordless.Tests/ApiFactAttribute.cs index 8286407..116486b 100644 --- a/tests/Sdk.Tests/ApiFactAttribute.cs +++ b/tests/Passwordless.Tests/ApiFactAttribute.cs @@ -1,14 +1,14 @@ -// Uncomment line while running API in mock mode to run tests -// #define RUNNING_API - -namespace Passwordless.Net.Tests; - -public class ApiFactAttribute : FactAttribute -{ - public ApiFactAttribute() - { -#if !RUNNING_API - Skip = "These tests are skipped unless you are running the API locally."; -#endif - } +// Uncomment line while running API in mock mode to run tests +// #define RUNNING_API + +namespace Passwordless.Tests; + +public class ApiFactAttribute : FactAttribute +{ + public ApiFactAttribute() + { +#if !RUNNING_API + Skip = "These tests are skipped unless you are running the API locally."; +#endif + } } \ No newline at end of file diff --git a/tests/Sdk.Tests/Sdk.Tests.csproj b/tests/Passwordless.Tests/Passwordless.Tests.csproj similarity index 85% rename from tests/Sdk.Tests/Sdk.Tests.csproj rename to tests/Passwordless.Tests/Passwordless.Tests.csproj index e62fda2..5d042a6 100644 --- a/tests/Sdk.Tests/Sdk.Tests.csproj +++ b/tests/Passwordless.Tests/Passwordless.Tests.csproj @@ -1,13 +1,11 @@ - false - true - Passwordless.Net.Tests - Passwordless.Net.Tests net6.0;net7.0 $(TargetFrameworks);net462 $(TargetFrameworks);$(CurrentPreviewTfm) + false + true @@ -20,7 +18,7 @@ - + diff --git a/tests/Sdk.Tests/PasswordlessClientTests.cs b/tests/Passwordless.Tests/PasswordlessClientTests.cs similarity index 95% rename from tests/Sdk.Tests/PasswordlessClientTests.cs rename to tests/Passwordless.Tests/PasswordlessClientTests.cs index c5fe3e6..032f20f 100644 --- a/tests/Sdk.Tests/PasswordlessClientTests.cs +++ b/tests/Passwordless.Tests/PasswordlessClientTests.cs @@ -1,125 +1,125 @@ -using System.Net.Http; -using System.Text.Json; -using Microsoft.Extensions.DependencyInjection; - - -namespace Passwordless.Net.Tests; - -public class PasswordlessClientTests -{ - private readonly PasswordlessClient _sut; - - public PasswordlessClientTests() - { - var services = new ServiceCollection(); - - services.AddPasswordlessSdk(options => - { - options.ApiUrl = "https://localhost:7002"; - options.ApiSecret = "test:secret:a679563b331846c79c20b114a4f56d02"; - }); - - var provider = services.BuildServiceProvider(); - - _sut = (PasswordlessClient)provider.GetRequiredService(); - } - - [ApiFact] - public async Task CreateRegisterTokenAsync_ThrowsExceptionWhenBad() - { - var exception = await Assert.ThrowsAnyAsync( - async () => await _sut.CreateRegisterTokenAsync(new RegisterOptions(null!, null!))); - } - - [ApiFact] - public async Task VerifyTokenAsync_DoesNotThrowOnBadToken() - { - var verifiedUser = await _sut.VerifyTokenAsync("bad_token"); - - Assert.Null(verifiedUser); - } - - [ApiFact] - public async Task DeleteUserAsync_BadUserId_ThrowsException() - { - var exception = await Assert.ThrowsAnyAsync( - async () => await _sut.DeleteUserAsync(null!)); - } - - [ApiFact] - public async Task ListAsiasesAsync_BadUserId_ThrowsException() - { - var exception = await Assert.ThrowsAnyAsync( - async () => await _sut.ListAliasesAsync(null!)); - } - - [ApiFact] - public async Task ListCredentialsAsync_BadUserId_ThrowsException() - { - var exception = await Assert.ThrowsAnyAsync( - async () => await _sut.ListCredentialsAsync(null!)); - - var errorCode = Assert.Contains("errorCode", (IDictionary)exception.Details.Extensions); - Assert.Equal(JsonValueKind.String, errorCode.ValueKind); - Assert.Equal("missing_userid", errorCode.GetString()); - } - - [ApiFact] - public async Task CreateRegisterTokenAsync_Works() - { - var userId = Guid.NewGuid().ToString(); - - var response = await _sut.CreateRegisterTokenAsync(new RegisterOptions(userId, "test_username")); - - Assert.NotNull(response.Token); - Assert.StartsWith("register_", response.Token); - } - - [ApiFact] - public async Task VerifyTokenAsync_Works() - { - var user = await _sut.VerifyTokenAsync("verify_valid"); - - Assert.NotNull(user); - Assert.True(user.Success); - } - - [ApiFact] - public async Task ListUsersAsync_Works() - { - var users = await _sut.ListUsersAsync(); - - Assert.NotEmpty(users); - } - - [ApiFact] - public async Task ListAliasesAsync_Works() - { - // Act - var aliases = await _sut.ListAliasesAsync("has_aliases"); - - // Assert - Assert.NotEmpty(aliases); - } - - [ApiFact] - public async Task ListCredentialsAsync_Works() - { - var credentials = await _sut.ListCredentialsAsync("has_credentials"); - - Assert.NotEmpty(credentials); - } - - [ApiFact] - public async Task DeleteCredentialAsync_Works() - { - await _sut.DeleteCredentialAsync("can_delete"); - } - - [ApiFact] - public async Task GetUsersCountAsync_Works() - { - var usersCount = await _sut.GetUsersCountAsync(); - Assert.NotEqual(0, usersCount.Count); - } +using System.Net.Http; +using System.Text.Json; +using Microsoft.Extensions.DependencyInjection; + + +namespace Passwordless.Tests; + +public class PasswordlessClientTests +{ + private readonly PasswordlessClient _sut; + + public PasswordlessClientTests() + { + var services = new ServiceCollection(); + + services.AddPasswordlessSdk(options => + { + options.ApiUrl = "https://localhost:7002"; + options.ApiSecret = "test:secret:a679563b331846c79c20b114a4f56d02"; + }); + + var provider = services.BuildServiceProvider(); + + _sut = (PasswordlessClient)provider.GetRequiredService(); + } + + [ApiFact] + public async Task CreateRegisterTokenAsync_ThrowsExceptionWhenBad() + { + var exception = await Assert.ThrowsAnyAsync( + async () => await _sut.CreateRegisterTokenAsync(new RegisterOptions(null!, null!))); + } + + [ApiFact] + public async Task VerifyTokenAsync_DoesNotThrowOnBadToken() + { + var verifiedUser = await _sut.VerifyTokenAsync("bad_token"); + + Assert.Null(verifiedUser); + } + + [ApiFact] + public async Task DeleteUserAsync_BadUserId_ThrowsException() + { + var exception = await Assert.ThrowsAnyAsync( + async () => await _sut.DeleteUserAsync(null!)); + } + + [ApiFact] + public async Task ListAsiasesAsync_BadUserId_ThrowsException() + { + var exception = await Assert.ThrowsAnyAsync( + async () => await _sut.ListAliasesAsync(null!)); + } + + [ApiFact] + public async Task ListCredentialsAsync_BadUserId_ThrowsException() + { + var exception = await Assert.ThrowsAnyAsync( + async () => await _sut.ListCredentialsAsync(null!)); + + var errorCode = Assert.Contains("errorCode", (IDictionary)exception.Details.Extensions); + Assert.Equal(JsonValueKind.String, errorCode.ValueKind); + Assert.Equal("missing_userid", errorCode.GetString()); + } + + [ApiFact] + public async Task CreateRegisterTokenAsync_Works() + { + var userId = Guid.NewGuid().ToString(); + + var response = await _sut.CreateRegisterTokenAsync(new RegisterOptions(userId, "test_username")); + + Assert.NotNull(response.Token); + Assert.StartsWith("register_", response.Token); + } + + [ApiFact] + public async Task VerifyTokenAsync_Works() + { + var user = await _sut.VerifyTokenAsync("verify_valid"); + + Assert.NotNull(user); + Assert.True(user.Success); + } + + [ApiFact] + public async Task ListUsersAsync_Works() + { + var users = await _sut.ListUsersAsync(); + + Assert.NotEmpty(users); + } + + [ApiFact] + public async Task ListAliasesAsync_Works() + { + // Act + var aliases = await _sut.ListAliasesAsync("has_aliases"); + + // Assert + Assert.NotEmpty(aliases); + } + + [ApiFact] + public async Task ListCredentialsAsync_Works() + { + var credentials = await _sut.ListCredentialsAsync("has_credentials"); + + Assert.NotEmpty(credentials); + } + + [ApiFact] + public async Task DeleteCredentialAsync_Works() + { + await _sut.DeleteCredentialAsync("can_delete"); + } + + [ApiFact] + public async Task GetUsersCountAsync_Works() + { + var usersCount = await _sut.GetUsersCountAsync(); + Assert.NotEqual(0, usersCount.Count); + } } \ No newline at end of file diff --git a/tests/Sdk.Tests/Usings.cs b/tests/Passwordless.Tests/Usings.cs similarity index 100% rename from tests/Sdk.Tests/Usings.cs rename to tests/Passwordless.Tests/Usings.cs