feat: sign in with a passkey (WebAuthn, ES256, discoverable credentials) and manage your passkeys [CI]

This commit is contained in:
justme committed 2026-10-07 21:57:19 +08:00
1 parent ddb6608e32
commit 6385c1b9a3
31 files changed
+2296 -42

No files matched your search

@@ -2,7 +2,7 @@ namespace MoShiro.Backend.Application;
public sealed record UserRecord(string Id, string Username, byte[] PasswordHash, byte[] PasswordSalt, int Iterations, long CreatedAtMs);
public sealed record SessionRecord(byte[] IdSha256, string UserId, byte[] CsrfToken, long CreatedAtMs, long ExpiresAtMs);
public sealed record SessionRecord(byte[] IdSha256, string UserId, byte[] CsrfToken, long CreatedAtMs, long ExpiresAtMs, string AuthMethod = AuthMethods.Password);
public sealed record ApiTokenRecord(string Id, string Name, IReadOnlyList<string> Capabilities, byte[] TokenSha256, long CreatedAtMs, long? LastUsedAtMs, long? ExpiresAtMs, long? RevokedAtMs);
@@ -31,6 +31,18 @@ public interface IAuthStore
ValueTask CreateApiTokenAsync(ApiTokenRecord token, CancellationToken ct);
/// <summary>凭证 id 已经存在时返回 false。</summary>
ValueTask<bool> AddPasskeyAsync(PasskeyRecord passkey, CancellationToken ct);
ValueTask<PasskeyRecord?> FindPasskeyAsync(string credentialId, CancellationToken ct);
ValueTask<IReadOnlyList<PasskeyRecord>> ListPasskeysAsync(string userId, CancellationToken ct);
/// <summary>只能删自己的。</summary>
ValueTask<bool> DeletePasskeyAsync(string userId, string credentialId, CancellationToken ct);
ValueTask TouchPasskeyAsync(string credentialId, long signCount, long nowMs, CancellationToken ct);
ValueTask<ApiTokenRecord?> FindApiTokenAsync(byte[] tokenSha256, CancellationToken ct);
ValueTask<IReadOnlyList<ApiTokenRecord>> ListApiTokensAsync(CancellationToken ct);