using Connected.Annotations; using Connected.Configuration; using Connected.Middleware; using Connected.Security.Cryptography; using Connected.Security.Identity; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; namespace Connected.Security.Authentication.Middleware; /// /// The default implementation of the . /// [Priority(0)] internal sealed class DefaultAuthenticationMiddleware : MiddlewareComponent, IAuthenticationMiddleware { public DefaultAuthenticationMiddleware(IUserService userService, IRoleService roleService, IConfigurationService configurationService, ICryptographyService cryptographyService) { UserService = userService; RoleService = roleService; ConfigurationService = configurationService; CryptographyService = cryptographyService; } private IUserService UserService { get; } private IRoleService RoleService { get; } public IConfigurationService ConfigurationService { get; } public ICryptographyService CryptographyService { get; } public async Task Authenticate(AuthenticationArgs args) { if (args is BasicAuthenticationArgs basic) return await AuthenticateBasic(basic); else if (args is PinAuthenticationArgs pin) return await AuthenticatePin(pin); else if (args is BearerAuthenticationArgs bearer) return await AuthenticateBearer(bearer); else if (args is SsoAuthenticationArgs sso) return await AuthenticateSso(sso); else throw new NotSupportedException(); } private async Task AuthenticateSso(SsoAuthenticationArgs sso) { if (await UserService.Resolve(new UserResolveArgs { Criteria = sso.Token }) is not IUserPassport user) return AuthenticationResult.Fail(AuthenticationResultReason.InvalidCredentials); return AuthenticationResult.OK(user, new JwtSecurityTokenHandler().WriteToken(CreateToken(user))); } private async Task AuthenticateBearer(BearerAuthenticationArgs bearer) { throw new NotImplementedException(); } private async Task AuthenticatePin(PinAuthenticationArgs pin) { if (await UserService.Resolve(new UserResolveArgs { Criteria = pin.User }) is not IUserPassport user) return AuthenticationResult.Fail(AuthenticationResultReason.NotFound); if (Validate(user, false) is IAuthenticationResult result && !result.Success) return result; if (!CryptographyService.Verify(pin.Pin, user.Pin)) return AuthenticationResult.Fail(AuthenticationResultReason.InvalidCredentials); return AuthenticationResult.OK(user, new JwtSecurityTokenHandler().WriteToken(CreateToken(user))); } private async Task AuthenticateBasic(BasicAuthenticationArgs basic) { if (await UserService.Resolve(new UserResolveArgs { Criteria = basic.User }) is not IUserPassport user) return AuthenticationResult.Fail(AuthenticationResultReason.NotFound); if (Validate(user, true) is IAuthenticationResult result && !result.Success) return result; if (!CryptographyService.Verify(basic.Password, user.Password)) return AuthenticationResult.Fail(AuthenticationResultReason.InvalidPassword); return AuthenticationResult.OK(user, new JwtSecurityTokenHandler().WriteToken(CreateToken(user))); } private static IAuthenticationResult? Validate(IUserPassport user, bool validatePassword) { if (user.Status == UserStatus.Inactive) return AuthenticationResult.Fail(AuthenticationResultReason.Inactive); if (user.Status == UserStatus.Locked) return AuthenticationResult.Fail(AuthenticationResultReason.Locked); if (validatePassword) { if (user.Password is null || !user.Password.Any()) return AuthenticationResult.Fail(AuthenticationResultReason.NoPassword); if (user.PasswordExpiration != DateTime.MinValue && user.PasswordExpiration < DateTime.UtcNow) return AuthenticationResult.Fail(AuthenticationResultReason.PasswordExpired); } return null; } private JwtSecurityToken CreateToken(IUserPassport user) { var config = ConfigurationService.Authentication.JwToken; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.Key)); var cred = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var claims = new[] { new Claim(ClaimTypes.NameIdentifier, user.AuthenticationToken.ToString()) }; return new JwtSecurityToken(issuer: config.Issuer, audience: config.Audience, claims: claims, expires: DateTime.Now.AddDays(Math.Max(1, config.Duration)), signingCredentials: cred); } }