Skip to content

Commit c3dfbe3

Browse files
committed
passkey first working version
1 parent 402c051 commit c3dfbe3

20 files changed

Lines changed: 386 additions & 4930 deletions

NpgsqlRestClient/Builder.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -766,14 +766,14 @@ public void BuildPasskeyAuthentication()
766766
ChallengeRegistrationCommand = _config.GetConfigStr("ChallengeRegistrationCommand", passkeyCfg) ?? "select * from passkey_challenge_registration($1)",
767767
ChallengeAuthenticationCommand = _config.GetConfigStr("ChallengeAuthenticationCommand", passkeyCfg) ?? "select * from passkey_challenge_authentication($1,$2)",
768768
// GROUP 2: Challenge Verify Command
769-
ChallengeVerifyCommand = _config.GetConfigStr("ChallengeVerifyCommand", passkeyCfg) ?? "select * from passkey_verify_challenge($1,$2)",
769+
VerifyChallengeCommand = _config.GetConfigStr("VerifyChallengeCommand", passkeyCfg) ?? "select * from passkey_verify_challenge($1,$2)",
770770
ValidateSignCount = _config.GetConfigBool("ValidateSignCount", passkeyCfg, true),
771771
// GROUP 3: Authentication Data Command
772772
AuthenticateDataCommand = _config.GetConfigStr("AuthenticateDataCommand", passkeyCfg) ?? "select * from passkey_authenticate_data($1)",
773773
// GROUP 4: Complete Commands
774-
AddExistingUserCompleteCommand = _config.GetConfigStr("AddExistingUserCompleteCommand", passkeyCfg) ?? "select * from passkey_add_existing_complete($1,$2,$3,$4,$5,$6,$7,$8)",
775-
RegistrationCompleteCommand = _config.GetConfigStr("RegistrationCompleteCommand", passkeyCfg) ?? "select * from passkey_registration_complete($1,$2,$3,$4,$5,$6,$7,$8)",
776-
AuthenticateCompleteCommand = _config.GetConfigStr("AuthenticateCompleteCommand", passkeyCfg) ?? "select * from passkey_authenticate_complete($1,$2,$3,$4)",
774+
CompleteAddExistingUserCommand = _config.GetConfigStr("CompleteAddExistingUserCommand", passkeyCfg) ?? "select * from passkey_complete_add_existing($1,$2,$3,$4,$5,$6,$7,$8)",
775+
CompleteRegistrationCommand = _config.GetConfigStr("CompleteRegistrationCommand", passkeyCfg) ?? "select * from passkey_complete_registration($1,$2,$3,$4,$5,$6,$7,$8)",
776+
CompleteAuthenticateCommand = _config.GetConfigStr("CompleteAuthenticateCommand", passkeyCfg) ?? "select * from passkey_complete_authenticate($1,$2,$3,$4)",
777777
ClientAnalyticsIpKey = _config.GetConfigStr("ClientAnalyticsIpKey", passkeyCfg) ?? "ip",
778778
StatusColumnName = _config.GetConfigStr("StatusColumnName", passkeyCfg) ?? "status",
779779
MessageColumnName = _config.GetConfigStr("MessageColumnName", passkeyCfg) ?? "message",

NpgsqlRestClient/ConfigDefaults.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -229,19 +229,15 @@ private static JsonObject GetAuthDefaults()
229229
["UserVerificationRequirement"] = "preferred",
230230
["ResidentKeyRequirement"] = "preferred",
231231
["AttestationConveyance"] = "none",
232-
// GROUP 1: Challenge Commands
233232
["ChallengeAddExistingUserCommand"] = "select * from passkey_challenge_add_existing($1,$2)",
234233
["ChallengeRegistrationCommand"] = "select * from passkey_challenge_registration($1)",
235234
["ChallengeAuthenticationCommand"] = "select * from passkey_challenge_authentication($1,$2)",
236-
// GROUP 2: Challenge Verify Command
237-
["ChallengeVerifyCommand"] = "select * from passkey_verify_challenge($1,$2)",
235+
["VerifyChallengeCommand"] = "select * from passkey_verify_challenge($1,$2)",
238236
["ValidateSignCount"] = true,
239-
// GROUP 3: Authentication Data Command
240237
["AuthenticateDataCommand"] = "select * from passkey_authenticate_data($1)",
241-
// GROUP 4: Complete Commands
242-
["AddExistingUserCompleteCommand"] = "select * from passkey_add_existing_complete($1,$2,$3,$4,$5,$6,$7,$8)",
243-
["RegistrationCompleteCommand"] = "select * from passkey_registration_complete($1,$2,$3,$4,$5,$6,$7,$8)",
244-
["AuthenticateCompleteCommand"] = "select * from passkey_authenticate_complete($1,$2,$3,$4)",
238+
["CompleteAddExistingUserCommand"] = "select * from passkey_complete_add_existing($1,$2,$3,$4,$5,$6,$7,$8)",
239+
["CompleteRegistrationCommand"] = "select * from passkey_complete_registration($1,$2,$3,$4,$5,$6,$7,$8)",
240+
["CompleteAuthenticateCommand"] = "select * from passkey_complete_authenticate($1,$2,$3,$4)",
245241
["ClientAnalyticsIpKey"] = "ip",
246242
["StatusColumnName"] = "status",
247243
["MessageColumnName"] = "message",

NpgsqlRestClient/Fido2/AuthenticatorData.cs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,19 +66,10 @@ public class AuthenticatorData
6666
public static class AuthenticatorDataFlags
6767
{
6868
public const byte UP = 0x01;
69-
70-
public const byte RFU1 = 0x02;
71-
7269
public const byte UV = 0x04;
73-
7470
public const byte BE = 0x08;
75-
7671
public const byte BS = 0x10;
77-
78-
public const byte RFU2 = 0x20;
79-
8072
public const byte AT = 0x40;
81-
8273
public const byte ED = 0x80;
8374
}
8475

NpgsqlRestClient/Fido2/CoseKey.cs

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,5 @@ namespace NpgsqlRestClient.Fido2;
33
public static class CoseAlgorithm
44
{
55
public const int ES256 = -7;
6-
7-
public const int ES384 = -35;
8-
9-
public const int ES512 = -36;
10-
116
public const int RS256 = -257;
12-
13-
public const int RS384 = -258;
14-
15-
public const int RS512 = -259;
16-
17-
public const int PS256 = -37;
18-
19-
public const int PS384 = -38;
20-
21-
public const int PS512 = -39;
22-
}
23-
24-
public static class CoseKeyType
25-
{
26-
public const int OKP = 1;
27-
28-
public const int EC2 = 2;
29-
30-
public const int RSA = 3;
31-
}
32-
33-
public static class CoseCurve
34-
{
35-
public const int P256 = 1;
36-
37-
public const int P384 = 2;
38-
39-
public const int P521 = 3;
407
}

NpgsqlRestClient/Fido2/Endpoints/AddPasskeyEndpoint.cs

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,14 @@ public sealed class AddPasskeyEndpoint(PasskeyEndpointContext ctx)
5757
private const string LogChallengeVerify = "PasskeyAuth.ChallengeVerify";
5858
private const string LogCredentialStore = "PasskeyAuth.CredentialStore";
5959

60-
private const string ErrorAuthenticationRequired = "authentication_required";
61-
private const string ErrorInvalidRequest = "invalid_request";
62-
private const string ErrorChallengeInvalid = "challenge_invalid";
63-
private const string ErrorAttestationInvalid = "attestation_invalid";
64-
private const string ErrorStoreFailed = "store_failed";
65-
6660
public async Task InvokeAsync(HttpContext context)
6761
{
6862
var config = ctx.Config;
6963

7064
if (context.User.Identity?.IsAuthenticated is not true)
7165
{
7266
await WriteErrorResponseAsync(context, HttpStatusCode.Unauthorized,
73-
ErrorAuthenticationRequired, "Authentication required to add a passkey");
67+
PasskeyErrorCode.AuthenticationRequired, "Authentication required to add a passkey");
7468
return;
7569
}
7670

@@ -83,7 +77,7 @@ await WriteErrorResponseAsync(context, HttpStatusCode.Unauthorized,
8377
catch
8478
{
8579
await WriteErrorResponseAsync(context, HttpStatusCode.BadRequest,
86-
ErrorInvalidRequest, "Invalid request body");
80+
PasskeyErrorCode.InvalidRequest, "Invalid request body");
8781
return;
8882
}
8983

@@ -94,7 +88,7 @@ await WriteErrorResponseAsync(context, HttpStatusCode.BadRequest,
9488
string.IsNullOrEmpty(request.UserContext))
9589
{
9690
await WriteErrorResponseAsync(context, HttpStatusCode.BadRequest,
97-
ErrorInvalidRequest, "Missing required fields");
91+
PasskeyErrorCode.InvalidRequest, "Missing required fields");
9892
return;
9993
}
10094

@@ -103,7 +97,7 @@ await WriteErrorResponseAsync(context, HttpStatusCode.BadRequest,
10397
await ExecuteTransactionCommandAsync(connection, "BEGIN", context.RequestAborted);
10498

10599
await using var verifyCommand = connection.CreateCommand();
106-
verifyCommand.CommandText = config.ChallengeVerifyCommand;
100+
verifyCommand.CommandText = config.VerifyChallengeCommand;
107101
if (long.TryParse(request.ChallengeId, out var challengeIdLong))
108102
{
109103
verifyCommand.Parameters.AddWithValue(challengeIdLong);
@@ -125,7 +119,7 @@ await WriteErrorResponseAsync(context, HttpStatusCode.BadRequest,
125119
{
126120
await ExecuteTransactionCommandAsync(connection, "ROLLBACK", context.RequestAborted);
127121
await WriteErrorResponseAsync(context, HttpStatusCode.BadRequest,
128-
ErrorChallengeInvalid, ValidationError.ChallengeNotFound);
122+
PasskeyErrorCode.ChallengeInvalid, ValidationError.ChallengeNotFound);
129123
return;
130124
}
131125

@@ -138,7 +132,7 @@ await WriteErrorResponseAsync(context, HttpStatusCode.BadRequest,
138132
{
139133
await ExecuteTransactionCommandAsync(connection, "ROLLBACK", context.RequestAborted);
140134
await WriteErrorResponseAsync(context, HttpStatusCode.BadRequest,
141-
ErrorInvalidRequest, "Invalid base64url encoding");
135+
PasskeyErrorCode.InvalidRequest, "Invalid base64url encoding");
142136
return;
143137
}
144138

@@ -162,12 +156,29 @@ await WriteErrorResponseAsync(context, HttpStatusCode.BadRequest,
162156
ctx.Logger?.LogWarning("Attestation validation failed: {Error}", result.Error);
163157
await ExecuteTransactionCommandAsync(connection, "ROLLBACK", context.RequestAborted);
164158
await WriteErrorResponseAsync(context, HttpStatusCode.BadRequest,
165-
ErrorAttestationInvalid, result.Error ?? "Attestation validation failed");
159+
PasskeyErrorCode.AttestationInvalid, result.Error ?? "Attestation validation failed");
166160
return;
167161
}
168162

163+
// Extract userHandle from userContext (injected by AddPasskeyOptionsEndpoint)
164+
byte[] userHandle = [];
165+
try
166+
{
167+
var userContext = JsonNode.Parse(request.UserContext);
168+
var userHandleBase64 = userContext?["userHandle"]?.GetValue<string>();
169+
if (!string.IsNullOrEmpty(userHandleBase64))
170+
{
171+
userHandle = AttestationValidator.Base64UrlDecode(userHandleBase64)
172+
?? Convert.FromBase64String(userHandleBase64);
173+
}
174+
}
175+
catch
176+
{
177+
// userHandle will remain empty if parsing fails
178+
}
179+
169180
await using var storeCommand = connection.CreateCommand();
170-
storeCommand.CommandText = config.AddExistingUserCompleteCommand;
181+
storeCommand.CommandText = config.CompleteAddExistingUserCommand;
171182

172183
var paramCount = storeCommand.CommandText.PgCountParams();
173184
if (paramCount >= 1)
@@ -176,7 +187,7 @@ await WriteErrorResponseAsync(context, HttpStatusCode.BadRequest,
176187
}
177188
if (paramCount >= 2)
178189
{
179-
storeCommand.Parameters.AddWithValue(result.UserHandle ?? []);
190+
storeCommand.Parameters.AddWithValue(userHandle);
180191
}
181192
if (paramCount >= 3)
182193
{
@@ -241,25 +252,26 @@ await WriteErrorResponseAsync(context, HttpStatusCode.BadRequest,
241252

242253
CommandLogger.LogCommand(storeCommand, ctx.Logger, LogCredentialStore);
243254

244-
await using var storeReader = await storeCommand.ExecuteReaderAsync(context.RequestAborted);
245-
246255
int storeStatus = 200;
247256
string storeMessage = "Passkey added successfully";
248257

249-
if (await storeReader.ReadAsync(context.RequestAborted))
258+
await using (var storeReader = await storeCommand.ExecuteReaderAsync(context.RequestAborted))
250259
{
251-
storeStatus = storeReader.GetInt32(storeReader.GetOrdinal(config.StatusColumnName));
252-
if (!storeReader.IsDBNull(storeReader.GetOrdinal(config.MessageColumnName)))
260+
if (await storeReader.ReadAsync(context.RequestAborted))
253261
{
254-
storeMessage = storeReader.GetString(storeReader.GetOrdinal(config.MessageColumnName));
262+
storeStatus = storeReader.GetInt32(storeReader.GetOrdinal(config.StatusColumnName));
263+
if (!storeReader.IsDBNull(storeReader.GetOrdinal(config.MessageColumnName)))
264+
{
265+
storeMessage = storeReader.GetString(storeReader.GetOrdinal(config.MessageColumnName));
266+
}
255267
}
256268
}
257269

258270
if (storeStatus != 200)
259271
{
260272
await ExecuteTransactionCommandAsync(connection, "ROLLBACK", context.RequestAborted);
261273
await WriteErrorResponseAsync(context, (HttpStatusCode)storeStatus,
262-
ErrorStoreFailed, storeMessage);
274+
PasskeyErrorCode.StoreFailed, storeMessage);
263275
return;
264276
}
265277

0 commit comments

Comments
 (0)