- 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
67 lines
2.0 KiB
C#
67 lines
2.0 KiB
C#
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");
|
|
}
|