diff --git a/demo/WebAuthn.Net.Demo.FidoConformance/WebAuthn.Net.Demo.FidoConformance.csproj b/demo/WebAuthn.Net.Demo.FidoConformance/WebAuthn.Net.Demo.FidoConformance.csproj index e16eeb3..b796cab 100644 --- a/demo/WebAuthn.Net.Demo.FidoConformance/WebAuthn.Net.Demo.FidoConformance.csproj +++ b/demo/WebAuthn.Net.Demo.FidoConformance/WebAuthn.Net.Demo.FidoConformance.csproj @@ -35,7 +35,7 @@ - + diff --git a/src/WebAuthn.Net.Storage.MySql/README.md b/src/WebAuthn.Net.Storage.MySql/README.md index 9e63573..14c9bb7 100644 --- a/src/WebAuthn.Net.Storage.MySql/README.md +++ b/src/WebAuthn.Net.Storage.MySql/README.md @@ -40,11 +40,17 @@ CREATE TABLE `CredentialRecords` ) CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; -CREATE UNIQUE INDEX `IX_CredentialRecords_RpId_UserHandle_CredentialId` ON `CredentialRecords` +CREATE UNIQUE INDEX `IX_CredentialRecords_UserHandle_CredentialId_RpId` ON `CredentialRecords` ( - `RpId`, `UserHandle`, - `CredentialId` + `CredentialId`, + `RpId` +); + +CREATE UNIQUE INDEX `IX_CredentialRecords_CredentialId_RpId` ON `CredentialRecords` +( + `CredentialId`, + `RpId` ); ``` diff --git a/src/WebAuthn.Net.Storage.MySql/Storage/CredentialStorage/DefaultMySqlCredentialStorage.cs b/src/WebAuthn.Net.Storage.MySql/Storage/CredentialStorage/DefaultMySqlCredentialStorage.cs index 29aee73..579a80e 100644 --- a/src/WebAuthn.Net.Storage.MySql/Storage/CredentialStorage/DefaultMySqlCredentialStorage.cs +++ b/src/WebAuthn.Net.Storage.MySql/Storage/CredentialStorage/DefaultMySqlCredentialStorage.cs @@ -46,8 +46,9 @@ public virtual async Task FindDescriptorsAsync( ArgumentNullException.ThrowIfNull(context); cancellationToken.ThrowIfCancellationRequested(); var dbPublicKeysEnumerable = await context.Connection.QueryAsync(new(@" -SELECT `Type`, `CredentialId`, `Transports`, `CreatedAtUnixTime` FROM `CredentialRecords` -WHERE `RpId` = @rpId AND `UserHandle` = @userHandle;", +SELECT `Type`, `CredentialId`, `Transports`, `CreatedAtUnixTime` +FROM `CredentialRecords` +WHERE `UserHandle` = @userHandle AND `RpId` = @rpId;", new { rpId, @@ -93,7 +94,7 @@ public virtual async Task FindDescriptorsAsync( var exisingId = await context.Connection.QuerySingleOrDefaultAsync(new(@" SELECT `Id` FROM `CredentialRecords` -WHERE `RpId` = @rpId AND `UserHandle` = @userHandle AND `CredentialId` = @credentialId;", +WHERE `UserHandle` = @userHandle AND `CredentialId` = @credentialId AND `RpId` = @rpId;", new { rpId, @@ -168,10 +169,9 @@ public virtual async Task SaveIfNotRegisteredForOtherUserAsync( cancellationToken.ThrowIfCancellationRequested(); var existingCount = await context.Connection.ExecuteScalarAsync(new( @" -SELECT COUNT(`Id`) FROM `CredentialRecords` -WHERE - `RpId` = @rpId - AND `CredentialId` = @credentialId;", +SELECT COUNT(`CredentialId`) +FROM `CredentialRecords` +WHERE `CredentialId` = @credentialId AND `RpId` = @rpId;", new { rpId = credential.RpId, @@ -290,11 +290,9 @@ public virtual async Task UpdateCredentialAsync( cancellationToken.ThrowIfCancellationRequested(); var recordIdToUpdate = await context.Connection.QuerySingleOrDefaultAsync(new( @" -SELECT `Id` FROM `CredentialRecords` -WHERE - `RpId` = @rpId - AND `UserHandle` = @userHandle - AND `CredentialId` = @credentialId;", +SELECT `Id` +FROM `CredentialRecords` +WHERE `UserHandle` = @userHandle AND `CredentialId` = @credentialId AND `RpId` = @rpId;", new { rpId = credential.RpId, diff --git a/src/WebAuthn.Net.Storage.MySql/WebAuthn.Net.Storage.MySql.csproj b/src/WebAuthn.Net.Storage.MySql/WebAuthn.Net.Storage.MySql.csproj index fafb23a..0c3d6b9 100644 --- a/src/WebAuthn.Net.Storage.MySql/WebAuthn.Net.Storage.MySql.csproj +++ b/src/WebAuthn.Net.Storage.MySql/WebAuthn.Net.Storage.MySql.csproj @@ -32,6 +32,6 @@ - + diff --git a/src/WebAuthn.Net.Storage.PostgreSql/README.md b/src/WebAuthn.Net.Storage.PostgreSql/README.md index dc33760..f97a0ff 100644 --- a/src/WebAuthn.Net.Storage.PostgreSql/README.md +++ b/src/WebAuthn.Net.Storage.PostgreSql/README.md @@ -38,7 +38,8 @@ CREATE TABLE "CredentialRecords" ( CONSTRAINT "PK_CredentialRecords" PRIMARY KEY ("Id") ); -CREATE UNIQUE INDEX "IX_CredentialRecords_RpId_UserHandle_CredentialId" ON "CredentialRecords" ("RpId", "UserHandle", "CredentialId"); +CREATE UNIQUE INDEX "IX_CredentialRecords_UserHandle_CredentialId_RpId" ON "CredentialRecords" ("UserHandle", "CredentialId", "RpId"); +CREATE UNIQUE INDEX "IX_CredentialRecords_CredentialId_RpId" ON "CredentialRecords" ("CredentialId", "RpId"); ``` ## Local dev environment diff --git a/src/WebAuthn.Net.Storage.PostgreSql/Storage/DefaultPostgreSqlCredentialStorage.cs b/src/WebAuthn.Net.Storage.PostgreSql/Storage/DefaultPostgreSqlCredentialStorage.cs index f60363b..789791e 100644 --- a/src/WebAuthn.Net.Storage.PostgreSql/Storage/DefaultPostgreSqlCredentialStorage.cs +++ b/src/WebAuthn.Net.Storage.PostgreSql/Storage/DefaultPostgreSqlCredentialStorage.cs @@ -47,8 +47,9 @@ public virtual async Task FindDescriptorsAsync( ArgumentNullException.ThrowIfNull(context); cancellationToken.ThrowIfCancellationRequested(); var dbPublicKeysEnumerable = await context.Connection.QueryAsync(new(@" -SELECT ""Type"", ""CredentialId"", ""Transports"", ""CreatedAtUnixTime"" FROM ""CredentialRecords"" -WHERE ""RpId"" = @rpId AND ""UserHandle"" = @userHandle;", +SELECT ""Type"", ""CredentialId"", ""Transports"", ""CreatedAtUnixTime"" +FROM ""CredentialRecords"" +WHERE ""UserHandle"" = @userHandle AND ""RpId"" = @rpId;", new { rpId, @@ -94,7 +95,7 @@ public virtual async Task FindDescriptorsAsync( var exisingId = await context.Connection.QuerySingleOrDefaultAsync(new(@" SELECT ""Id"" FROM ""CredentialRecords"" -WHERE ""RpId"" = @rpId AND ""UserHandle"" = @userHandle AND ""CredentialId"" = @credentialId;", +WHERE ""UserHandle"" = @userHandle AND ""CredentialId"" = @credentialId AND ""RpId"" = @rpId;", new { rpId, @@ -169,10 +170,9 @@ public virtual async Task SaveIfNotRegisteredForOtherUserAsync( cancellationToken.ThrowIfCancellationRequested(); var existingCount = await context.Connection.ExecuteScalarAsync(new( @" -SELECT COUNT(""Id"") FROM ""CredentialRecords"" -WHERE - ""RpId"" = @rpId - AND ""CredentialId"" = @credentialId;", +SELECT COUNT(""CredentialId"") +FROM ""CredentialRecords"" +WHERE ""CredentialId"" = @credentialId AND ""RpId"" = @rpId;", new { rpId = credential.RpId, @@ -291,11 +291,9 @@ public virtual async Task UpdateCredentialAsync( cancellationToken.ThrowIfCancellationRequested(); var recordIdToUpdate = await context.Connection.QuerySingleOrDefaultAsync(new( @" -SELECT ""Id"" FROM ""CredentialRecords"" -WHERE - ""RpId"" = @rpId - AND ""UserHandle"" = @userHandle - AND ""CredentialId"" = @credentialId;", +SELECT ""Id"" +FROM ""CredentialRecords"" +WHERE ""UserHandle"" = @userHandle AND ""CredentialId"" = @credentialId AND ""RpId"" = @rpId;", new { rpId = credential.RpId, diff --git a/src/WebAuthn.Net.Storage.SqlServer/README.md b/src/WebAuthn.Net.Storage.SqlServer/README.md index 3e89e2f..76efe2b 100644 --- a/src/WebAuthn.Net.Storage.SqlServer/README.md +++ b/src/WebAuthn.Net.Storage.SqlServer/README.md @@ -15,34 +15,36 @@ As the library is intended to be integrated into existing applications, they may ```tsql CREATE TABLE [CredentialRecords] ( - [Id] uniqueidentifier NOT NULL, - [RpId] nvarchar(256) NOT NULL, - [UserHandle] varbinary(128) NOT NULL, - [CredentialId] varbinary(1024) NOT NULL, - [Type] int NOT NULL, - [Kty] int NOT NULL, - [Alg] int NOT NULL, - [Ec2Crv] int NULL, - [Ec2X] varbinary(256) NULL, - [Ec2Y] varbinary(256) NULL, - [RsaModulusN] varbinary(1024) NULL, - [RsaExponentE] varbinary(32) NULL, - [OkpCrv] int NULL, - [OkpX] varbinary(32) NULL, - [SignCount] bigint NOT NULL, - [Transports] nvarchar(max) NOT NULL, - [UvInitialized] bit NOT NULL, - [BackupEligible] bit NOT NULL, - [BackupState] bit NOT NULL, - [AttestationObject] varbinary(max) NULL, - [AttestationClientDataJson] varbinary(max) NULL, - [Description] nvarchar(200) NULL, - [CreatedAtUnixTime] bigint NOT NULL, - [UpdatedAtUnixTime] bigint NOT NULL, + [Id] uniqueidentifier NOT NULL, + [RpId] nvarchar(256) NOT NULL, + [UserHandle] varbinary(128) NOT NULL, + [CredentialId] varbinary(1024) NOT NULL, + [Type] int NOT NULL, + [Kty] int NOT NULL, + [Alg] int NOT NULL, + [Ec2Crv] int NULL, + [Ec2X] varbinary(256) NULL, + [Ec2Y] varbinary(256) NULL, + [RsaModulusN] varbinary(1024) NULL, + [RsaExponentE] varbinary(32) NULL, + [OkpCrv] int NULL, + [OkpX] varbinary(32) NULL, + [SignCount] bigint NOT NULL, + [Transports] nvarchar(max) NOT NULL, + [UvInitialized] bit NOT NULL, + [BackupEligible] bit NOT NULL, + [BackupState] bit NOT NULL, + [AttestationObject] varbinary(max) NULL, + [AttestationClientDataJson] varbinary(max) NULL, + [Description] nvarchar(200) NULL, + [CreatedAtUnixTime] bigint NOT NULL, + [UpdatedAtUnixTime] bigint NOT NULL, CONSTRAINT [PK_CredentialRecords] PRIMARY KEY ([Id]) ); -ALTER TABLE [CredentialRecords] ADD CONSTRAINT [Transports should be formatted as JSON] CHECK (ISJSON(Transports)=1); -CREATE UNIQUE INDEX [IX_CredentialRecords_RpId_UserHandle_CredentialId] ON [CredentialRecords] ([RpId], [UserHandle], [CredentialId]); +ALTER TABLE [CredentialRecords] + ADD CONSTRAINT [Transports should be formatted as JSON] CHECK (ISJSON(Transports) = 1); +CREATE UNIQUE INDEX [IX_CredentialRecords_UserHandle_CredentialId_RpId] ON [CredentialRecords] ([UserHandle], [CredentialId], [RpId]); +CREATE UNIQUE INDEX [IX_CredentialRecords_CredentialId_RpId] ON [CredentialRecords] ([CredentialId], [RpId]); ``` ## Local dev environment diff --git a/src/WebAuthn.Net.Storage.SqlServer/Storage/DefaultSqlServerCredentialStorage.cs b/src/WebAuthn.Net.Storage.SqlServer/Storage/DefaultSqlServerCredentialStorage.cs index 45f5ea4..49ac670 100644 --- a/src/WebAuthn.Net.Storage.SqlServer/Storage/DefaultSqlServerCredentialStorage.cs +++ b/src/WebAuthn.Net.Storage.SqlServer/Storage/DefaultSqlServerCredentialStorage.cs @@ -46,8 +46,9 @@ public virtual async Task FindDescriptorsAsync( ArgumentNullException.ThrowIfNull(context); cancellationToken.ThrowIfCancellationRequested(); var dbPublicKeysEnumerable = await context.Connection.QueryAsync(new(@" -SELECT Type, CredentialId, Transports, CreatedAtUnixTime FROM CredentialRecords -WHERE RpId = @rpId AND UserHandle = @userHandle;", +SELECT Type, CredentialId, Transports, CreatedAtUnixTime +FROM CredentialRecords +WHERE UserHandle = @userHandle AND RpId = @rpId;", new { rpId, @@ -92,7 +93,7 @@ public virtual async Task FindDescriptorsAsync( var exisingId = await context.Connection.QuerySingleOrDefaultAsync(new(@" SELECT Id FROM CredentialRecords -WHERE RpId = @rpId AND UserHandle = @userHandle AND CredentialId = @credentialId;", +WHERE UserHandle = @userHandle AND CredentialId = @credentialId AND RpId = @rpId;", new { rpId, @@ -166,10 +167,9 @@ public virtual async Task SaveIfNotRegisteredForOtherUserAsync( cancellationToken.ThrowIfCancellationRequested(); var existingCount = await context.Connection.ExecuteScalarAsync(new( @" -SELECT COUNT(Id) FROM CredentialRecords -WHERE - RpId = @rpId - AND CredentialId = @credentialId;", +SELECT COUNT(CredentialId) +FROM CredentialRecords +WHERE CredentialId = @credentialId AND RpId = @rpId;", new { rpId = credential.RpId, @@ -288,11 +288,9 @@ public virtual async Task UpdateCredentialAsync( cancellationToken.ThrowIfCancellationRequested(); var recordIdToUpdate = await context.Connection.QuerySingleOrDefaultAsync(new( @" -SELECT Id FROM CredentialRecords -WHERE - RpId = @rpId - AND UserHandle = @userHandle - AND CredentialId = @credentialId;", +SELECT Id +FROM CredentialRecords +WHERE UserHandle = @userHandle AND CredentialId = @credentialId AND RpId = @rpId;", new { rpId = credential.RpId, diff --git a/src/WebAuthn.Net.Storage.SqlServer/WebAuthn.Net.Storage.SqlServer.csproj b/src/WebAuthn.Net.Storage.SqlServer/WebAuthn.Net.Storage.SqlServer.csproj index 434cbf0..31f46e4 100644 --- a/src/WebAuthn.Net.Storage.SqlServer/WebAuthn.Net.Storage.SqlServer.csproj +++ b/src/WebAuthn.Net.Storage.SqlServer/WebAuthn.Net.Storage.SqlServer.csproj @@ -23,7 +23,7 @@ - + diff --git a/tests/WebAuthn.Net.Tests.Unit/WebAuthn.Net.Tests.Unit.csproj b/tests/WebAuthn.Net.Tests.Unit/WebAuthn.Net.Tests.Unit.csproj index dce86aa..856c62b 100644 --- a/tests/WebAuthn.Net.Tests.Unit/WebAuthn.Net.Tests.Unit.csproj +++ b/tests/WebAuthn.Net.Tests.Unit/WebAuthn.Net.Tests.Unit.csproj @@ -9,9 +9,9 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive