Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move more model classes to records #108

Merged
merged 1 commit into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 16 additions & 34 deletions src/Passwordless/Models/ApplicationEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,19 @@ namespace Passwordless.Models;
/// <summary>
/// An event that occured using Passwordless library.
/// </summary>
public class ApplicationEvent
{
public Guid Id { get; set; }

/// <summary>
/// When the record was performed. This will be in UTC.
/// </summary>
public DateTime PerformedAt { get; set; }

/// <summary>
/// The type of event <see href="https://github.com/passwordless/passwordless-server/blob/main/src/Common/EventLog/Enums/EventType.cs" />
/// </summary>
public string EventType { get; set; } = string.Empty;

/// <summary>
/// Description of the event
/// </summary>
public string Message { get; set; } = string.Empty;

/// <summary>
/// Severity of the event <see href="https://github.com/passwordless/passwordless-server/blob/main/src/Common/EventLog/Enums/Severity.cs"/>
/// </summary>
public string Severity { get; set; } = string.Empty;

/// <summary>
/// The target of the event. Can be in reference to a user or the application.
/// </summary>
public string Subject { get; set; } = string.Empty;

/// <summary>
/// Last 4 characters of the api key (public/secret) used to perform the event.
/// </summary>
public string ApiKeyId { get; set; } = string.Empty;
}
/// <param name="Id">Event ID.</param>
/// <param name="PerformedAt">When the event was performed.</param>
/// <param name="EventType">The type of event. <see href="https://github.com/passwordless/passwordless-server/blob/main/src/Common/EventLog/Enums/EventType.cs"/></param>
/// <param name="Message">Description of the event.</param>
/// <param name="Severity">Severity of the event. <see href="https://github.com/passwordless/passwordless-server/blob/main/src/Common/EventLog/Enums/Severity.cs"/></param>
/// <param name="Subject">The target of the event. Can be in reference to a user or the application.</param>
/// <param name="ApiKeyId">Last 4 characters of the api key (public/secret) used to perform the event.</param>
public record ApplicationEvent(
Guid Id,
DateTime PerformedAt,
string EventType,
string Message,
string Severity,
string Subject,
string ApiKeyId
abergs marked this conversation as resolved.
Show resolved Hide resolved
);
115 changes: 34 additions & 81 deletions src/Passwordless/Models/Credential.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,95 +6,48 @@ namespace Passwordless;
/// The passkey credential stored by Passwordless.
/// <see href="https://www.w3.org/TR/webauthn-2/#public-key-credential"/>
/// </summary>
public class 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)
/// <param name="Descriptor">Descriptor of the credential as defined by the WebAuthn specification. <see href="https://w3c.github.io/webauthn/#enumdef-publickeycredentialtype" /></param>
/// <param name="PublicKey">Public key of the passkey pair.</param>
/// <param name="UserHandle">Byte array of user identifier.</param>
/// <param name="SignatureCounter">WebAuthn SignatureCounter, used for anti forgery.</param>
/// <param name="AttestationFmt">Attestation Statement format used to create credential.</param>
/// <param name="CreatedAt">When the credential was created.</param>
/// <param name="AaGuid">The AAGUID of the authenticator. Can be used to identify the make and model of the authenticator. <see href="https://www.w3.org/TR/webauthn/#aaguid"/></param>
/// <param name="LastUsedAt">Last time credential was used.</param>
/// <param name="RpId">Relying Party identifier. <see href="https://w3c.github.io/webauthn/#dom-publickeycredentialcreationoptions-rp"/></param>
/// <param name="Origin">Domain credential was created for.</param>
/// <param name="Country">Optional country credential was created in.</param>
/// <param name="Device">Device the credential was created on.</param>
/// <param name="Nickname">Friendly name for credential.</param>
/// <param name="UserId">Identifier for the user.</param>
public record 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)
{
/// <summary>
/// Descriptor of the credential as defined by the WebAuthn specification
/// <see href="https://w3c.github.io/webauthn/#enumdef-publickeycredentialtype" />
/// </summary>
public CredentialDescriptor Descriptor { get; } = descriptor;

/// <summary>
/// Public key of the passkey pair.
/// </summary>
public byte[] PublicKey { get; } = publicKey;

/// <summary>
/// Byte array of user identifier
/// </summary>
public byte[] UserHandle { get; } = userHandle;

/// <summary>
/// WebAuthn SignatureCounter, used for anti forgery.
/// </summary>
public uint SignatureCounter { get; } = signatureCounter;

/// <summary>
/// Attestation Statement format used to create credential
/// </summary>
public string AttestationFmt { get; } = attestationFmt;

/// <summary>
/// When the credential was created
/// </summary>
public DateTime CreatedAt { get; } = createdAt;

/// <summary>
/// The AAGUID of the authenticator. Can be used to identify the make and model of the authenticator.
/// <see href="https://www.w3.org/TR/webauthn/#aaguid"/>
/// </summary>
public Guid AaGuid { get; } = aaGuid;

/// <summary>
/// Last time credential was used
/// </summary>
public DateTime LastUsedAt { get; } = lastUsedAt;

/// <summary>
/// Relying Party identifier
/// <see href="https://w3c.github.io/webauthn/#dom-publickeycredentialcreationoptions-rp"/>
/// </summary>
public string RpId { get; } = rpId;

/// <summary>
/// Domain credential was created for
/// </summary>
public string Origin { get; } = origin;

/// <summary>
/// Optional country credential was created in
/// </summary>
public string Country { get; } = country;

/// <summary>
/// Device the credential was created on
/// </summary>
public string Device { get; } = device;

/// <summary>
/// Friendly name for credential.
/// </summary>
public string Nickname { get; } = nickname;

/// <summary>
/// Identifier for the user
/// </summary>
public string UserId { get; } = userId;

/// <summary>
/// Whether the credential is synced (or backed up or not).
/// </summary>
public bool? BackupState { get; set; }
public bool? BackupState { get; init; }

/// <summary>
/// Whether the credential is eligible for backup or syncing
/// Whether the credential is eligible for backup or syncing.
/// </summary>
public bool? IsBackupEligible { get; set; }
public bool? IsBackupEligible { get; init; }

/// <summary>
/// Whether the credential is discoverable
/// Whether the credential is discoverable.
/// </summary>
public bool? IsDiscoverable { get; set; }
public bool? IsDiscoverable { get; init; }
}
16 changes: 3 additions & 13 deletions src/Passwordless/Models/GetEventLogRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,6 @@ namespace Passwordless.Models;
/// <summary>
/// Request for getting the event logs for an application.
/// </summary>
public class GetEventLogRequest
{
/// <summary>
/// Page number for retrieving event log records.
/// </summary>
public int PageNumber { get; set; }

/// <summary>
/// This is the max number of results that will be returned. Must be between 1-1000.
/// </summary>
[Range(1, 1000)]
public int? NumberOfResults { get; set; }
}
/// <param name="PageNumber">Page number for retrieving event log records.</param>
/// <param name="NumberOfResults">This is the max number of results that will be returned. Must be between 1-1000.</param>
public record GetEventLogRequest(int PageNumber, int? NumberOfResults = null);
26 changes: 8 additions & 18 deletions src/Passwordless/Models/GetEventLogResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,11 @@ namespace Passwordless.Models;
/// <summary>
/// Response from GetEventLog. Contains list of events for the application.
/// </summary>
public class GetEventLogResponse
{
/// <summary>
/// Name of application the events correspond to.
/// </summary>
public string TenantId { get; set; } = string.Empty;

/// <summary>
/// List of events for the application based on the request pagination parameters.
/// This will always be sorted by PerformedAt in descending order.
/// </summary>
public IReadOnlyList<ApplicationEvent> Events { get; set; } = new List<ApplicationEvent>();

/// <summary>
/// Total number of events for the application.
/// </summary>
public int TotalEventCount { get; set; }
}
/// <param name="TenantId">Name of application the events correspond to.</param>
/// <param name="Events">List of events for the application based on the request pagination parameters. This will always be sorted by PerformedAt in descending order.</param>
/// <param name="TotalEventCount">Total number of events for the application.</param>
public record GetEventLogResponse(
string TenantId,
IReadOnlyList<ApplicationEvent> Events,
int TotalEventCount
);
36 changes: 12 additions & 24 deletions src/Passwordless/Models/RegisterOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,67 +4,55 @@
namespace Passwordless;

/// <summary>
///
/// Options for registering a new WebAuthn credential.
/// </summary>
public class RegisterOptions(string userId, string username)
/// <param name="UserId">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.</param>
/// <param name="Username">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.</param>
public record RegisterOptions(string UserId, string Username)
{
/// <summary>
/// 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.
/// </summary>
public string UserId { get; } = userId;

/// <summary>
/// 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.
/// </summary>
public string Username { get; } = username;

/// <summary>
/// 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.
/// </summary>
public string? DisplayName { get; set; }
public string? DisplayName { get; init; }

/// <summary>
/// WebAuthn attestation conveyance preference. Only "none" (default) is supported.
/// </summary>
public string? Attestation { get; set; }
public string? Attestation { get; init; }

/// <summary>
/// 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.
/// </summary>
public string? AuthenticatorType { get; set; }
public string? AuthenticatorType { get; init; }

/// <summary>
/// If true, creates a client-side Discoverable Credential that allows sign in without needing a username.
/// </summary>
public bool? Discoverable { get; set; }
public bool? Discoverable { get; init; }

/// <summary>
/// Allows choosing preference for requiring User Verification
/// (biometrics, pin code etc) when authenticating Can be "preferred" (default), "required" or "discouraged".
/// </summary>
public string? UserVerification { get; set; }
public string? UserVerification { get; init; }

/// <summary>
/// Timestamp (UTC) when the registration token should expire. By default, current time + 120 seconds.
/// </summary>
public DateTime? ExpiresAt { get; set; }
public DateTime? ExpiresAt { get; init; }

/// <summary>
/// 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 [].
/// </summary>
public HashSet<string> Aliases { get; set; } = [];
public HashSet<string> Aliases { get; init; } = [];

/// <summary>
/// Whether aliases should be hashed before being stored. Defaults to true.
/// </summary>
public bool? AliasHashing { get; set; }
public bool? AliasHashing { get; init; }
}
19 changes: 9 additions & 10 deletions src/Passwordless/Models/SetAliasRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@

namespace Passwordless.Models;

public class SetAliasRequest(string userId, HashSet<string> aliases, bool hashing = true)
/// <summary>
/// Sets aliases for a given user.
/// </summary>
/// <param name="UserId">User ID.</param>
/// <param name="Aliases">List of user aliases to overwrite the current aliases (if any) with.</param>
/// <param name="Hashing">If you want your aliases to be available in plain text, set the <see cref="bool"/> false.</param>
public record SetAliasRequest(string UserId, IReadOnlyCollection<string> Aliases, bool Hashing = true)
{
/// <summary>
/// Sets a single alias for a given user, and removes any other aliases that may exist.
Expand All @@ -13,16 +19,9 @@ public SetAliasRequest(string userId, string alias, bool hashing = true)
{
}

public string UserId { get; } = userId;

public IReadOnlyCollection<string> Aliases { get; } = aliases == null
public IReadOnlyCollection<string> Aliases { get; } = Aliases == null
? []
: new HashSet<string>(aliases.Where(x => !string.IsNullOrWhiteSpace(x)));

/// <summary>
/// If you want your aliases to be available in plain text, set the <see cref="bool"/> false.
/// </summary>
public bool Hashing { get; } = hashing;
: new HashSet<string>(Aliases.Where(x => !string.IsNullOrWhiteSpace(x)));

/// <summary>
/// Removes all aliases from a user.
Expand Down
2 changes: 1 addition & 1 deletion src/Passwordless/PasswordlessClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public async Task<VerifiedUser> VerifyAuthenticationTokenAsync(
public async Task<GetEventLogResponse> GetEventLogAsync(GetEventLogRequest request, CancellationToken cancellationToken = default) =>
(await _http.GetFromJsonAsync($"events?pageNumber={request.PageNumber}&numberOfResults={request.NumberOfResults}",
PasswordlessSerializerContext.Default.GetEventLogResponse,
cancellationToken)) ?? new GetEventLogResponse();
cancellationToken))!;
Tyrrrz marked this conversation as resolved.
Show resolved Hide resolved

/// <inheritdoc />
public async Task<UsersCount> GetUsersCountAsync(CancellationToken cancellationToken = default) =>
Expand Down
2 changes: 1 addition & 1 deletion tests/Passwordless.Tests/ApplicationEventLogsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public async Task I_can_view_application_event_logs_when_event_logs_are_enabled(

// Act
var response = await passwordless.GetEventLogAsync(
new GetEventLogRequest { PageNumber = 1, NumberOfResults = 100 }
new GetEventLogRequest(1, 100)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking-nit: I find this less readable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same. It's possible to keep the same signature with required and init but that involves more boilerplate though. We can still do it if it's important.

);

// Assert
Expand Down
Loading