feat: add authenticators, Account, and ClientPermissions (Tasks 3-7, 9)
- Account: per-account SubList and client tracking - IAuthenticator interface, AuthResult, ClientAuthContext - TokenAuthenticator: constant-time token comparison - UserPasswordAuthenticator: multi-user with bcrypt/plain support - SimpleUserPasswordAuthenticator: single user/pass config - NKeyAuthenticator: Ed25519 nonce signature verification - ClientPermissions: SubList-based publish/subscribe authorization
This commit is contained in:
66
src/NATS.Server/Auth/UserPasswordAuthenticator.cs
Normal file
66
src/NATS.Server/Auth/UserPasswordAuthenticator.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace NATS.Server.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// Authenticates clients by looking up username in a dictionary and comparing
|
||||
/// the password using bcrypt (for $2-prefixed hashes) or constant-time comparison
|
||||
/// (for plain text passwords).
|
||||
/// Reference: golang/nats-server/server/auth.go checkClientPassword.
|
||||
/// </summary>
|
||||
public sealed class UserPasswordAuthenticator : IAuthenticator
|
||||
{
|
||||
private readonly Dictionary<string, User> _users;
|
||||
|
||||
public UserPasswordAuthenticator(IEnumerable<User> users)
|
||||
{
|
||||
_users = new Dictionary<string, User>(StringComparer.Ordinal);
|
||||
foreach (var user in users)
|
||||
_users[user.Username] = user;
|
||||
}
|
||||
|
||||
public AuthResult? Authenticate(ClientAuthContext context)
|
||||
{
|
||||
var username = context.Opts.Username;
|
||||
if (string.IsNullOrEmpty(username))
|
||||
return null;
|
||||
|
||||
if (!_users.TryGetValue(username, out var user))
|
||||
return null;
|
||||
|
||||
var clientPassword = context.Opts.Password ?? string.Empty;
|
||||
|
||||
if (!ComparePasswords(user.Password, clientPassword))
|
||||
return null;
|
||||
|
||||
return new AuthResult
|
||||
{
|
||||
Identity = user.Username,
|
||||
AccountName = user.Account,
|
||||
Permissions = user.Permissions,
|
||||
Expiry = user.ConnectionDeadline,
|
||||
};
|
||||
}
|
||||
|
||||
private static bool ComparePasswords(string serverPassword, string clientPassword)
|
||||
{
|
||||
if (IsBcrypt(serverPassword))
|
||||
{
|
||||
try
|
||||
{
|
||||
return BCrypt.Net.BCrypt.Verify(clientPassword, serverPassword);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var serverBytes = Encoding.UTF8.GetBytes(serverPassword);
|
||||
var clientBytes = Encoding.UTF8.GetBytes(clientPassword);
|
||||
return CryptographicOperations.FixedTimeEquals(serverBytes, clientBytes);
|
||||
}
|
||||
|
||||
private static bool IsBcrypt(string password) => password.StartsWith("$2");
|
||||
}
|
||||
Reference in New Issue
Block a user