using Knoks.Api.Authentication; using Knoks.Api.Controllers.Base; using Knoks.Api.Entities; using Knoks.Core.Logic.Interfaces; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System; using System.Linq; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Threading.Tasks; using static Knoks.Api.Authentication.IdentityConsts; namespace Knoks.Api.Controllers { [Route("v1/[controller]")] public class TokenController : ApiConsumerController { private readonly ILogger _logger; private readonly IUserManager _userManager; private readonly IOperatorManager _operatorManager; private readonly JwtIssuerOptions _jwtOptions; public TokenController( ILogger logger, IUserManager userManager, IOperatorManager operatorManager, JwtIssuerOptions jwtOptions) { _logger = logger; _userManager = userManager; _operatorManager = operatorManager; _jwtOptions = jwtOptions; ValidateOptions(_jwtOptions); } #region class methods private static void ValidateOptions(JwtIssuerOptions options) { if (options == null) throw new ArgumentNullException(nameof(options)); if (options.ValidFor <= TimeSpan.Zero) throw new ArgumentException("Must be a non-zero TimeSpan.", nameof(JwtIssuerOptions.ValidFor)); if (options.SigningCredentials == null) throw new ArgumentNullException(nameof(JwtIssuerOptions.SigningCredentials)); if (options.JtiGenerator == null) throw new ArgumentNullException(nameof(JwtIssuerOptions.JtiGenerator)); } private async Task CreateToken(long? userId = null, int? operatorId = null) { return await CreateToken( new Claim(ApiIdentifierClaim, ApiConsumer.ApiIdentifier.ToString("N")), userId.HasValue ? new Claim(UserIdClaim, userId.ToString(), ClaimValueTypes.Integer64) : null, operatorId.HasValue ? new Claim(OperatorIdClaim, operatorId.ToString(), ClaimValueTypes.Integer32) : null); } private async Task RefreshToken() { return await CreateToken( User.FindFirst(ApiIdentifierClaim), User.FindFirst(UserIdClaim), User.FindFirst(OperatorIdClaim)); } private async Task CreateToken(params Claim[] claims) { var claimList = new List { new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()), new Claim(JwtRegisteredClaimNames.Iat, _jwtOptions.IssuedAt.ToUnixEpochDate().ToString(), ClaimValueTypes.Integer64), new Claim(JwtRegisteredClaimNames.Sub, Guid.NewGuid().ToString("N")) }; claimList.AddRange(claims.Where(claim => claim != null)); return new JwtSecurityTokenHandler().WriteToken(new JwtSecurityToken( issuer: _jwtOptions.Issuer, audience: _jwtOptions.Audience, notBefore: _jwtOptions.NotBefore, expires: _jwtOptions.Expiration, signingCredentials: _jwtOptions.SigningCredentials, claims: claimList)); } private async Task GetTokenResult(UserCredentials userCredentials = null, OperatorCredentials operatorCredentials = null) { long? userId = null; if (userCredentials != null) { userId = (await _userManager.AuthenticateUser(ApiConsumer.Id, userCredentials.Email, userCredentials.Password))?.UserId; if (userId == null) { _logger.LogInformation($"Invalid username or password. Email = '{userCredentials.Email}'"); throw new ApiResponseException(ApiErrorType.InvalidUsernameOrPassword, $"Invalid username or password. Email = '{userCredentials.Email}'"); } } int? operatorId = null; if (operatorCredentials != null) { operatorId = (await _operatorManager.AuthenticateOperator(ApiConsumer.Id, operatorCredentials.OperatorName, operatorCredentials.OperatorPassword))?.OperatorId; if (operatorId == null) { _logger.LogInformation($"Invalid username or password. Username = '{operatorCredentials.OperatorName}'"); throw new ApiResponseException(ApiErrorType.InvalidUsernameOrPassword, $"Invalid username or password. Username = '{operatorCredentials.OperatorName}'"); } } return new TokenResponse { Token = await CreateToken(userId, operatorId), Expiry = (int)_jwtOptions.ValidFor.TotalSeconds, IsUser = userId != null, IsOperator = operatorId != null }; } #endregion [AllowAnonymous] [HttpGet("{apiIdentifier}")] public async Task GenerateToken() { return await GetTokenResult(); } [AllowAnonymous] [HttpPost("{apiIdentifier}")] public async Task GenerateToken([FromBody]UserCredentials credentials) { return await GetTokenResult(userCredentials: credentials); } [AllowAnonymous] [HttpPost("{apiIdentifier}/Operator")] public async Task GenerateOperatorToken([FromBody]OperatorCredentials credentials) { return await GetTokenResult(operatorCredentials: credentials); } [HttpGet("refresh")] public async Task Refresh() { return new TokenResponse { Token = await RefreshToken(), Expiry = (int)_jwtOptions.ValidFor.TotalSeconds, IsUser = User.FindFirst(UserIdClaim) != null, IsOperator = User.FindFirst(OperatorIdClaim) != null }; } [HttpGet("heartbit")] public ApiObject Heartbit() { return ApiObject(true); } } }