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