168 lines
6.4 KiB
C#
168 lines
6.4 KiB
C#
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<TokenController> _logger;
|
|
private readonly IUserManager _userManager;
|
|
private readonly IOperatorManager _operatorManager;
|
|
private readonly JwtIssuerOptions _jwtOptions;
|
|
|
|
public TokenController(
|
|
ILogger<TokenController> 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<string> 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<string> RefreshToken()
|
|
{
|
|
return await CreateToken(
|
|
User.FindFirst(ApiIdentifierClaim),
|
|
User.FindFirst(UserIdClaim),
|
|
User.FindFirst(OperatorIdClaim));
|
|
}
|
|
|
|
private async Task<string> CreateToken(params Claim[] claims)
|
|
{
|
|
var claimList = new List<Claim>
|
|
{
|
|
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<TokenResponse> 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<TokenResponse> GenerateToken()
|
|
{
|
|
return await GetTokenResult();
|
|
}
|
|
|
|
[AllowAnonymous]
|
|
[HttpPost("{apiIdentifier}")]
|
|
public async Task<TokenResponse> GenerateToken([FromBody]UserCredentials credentials)
|
|
{
|
|
return await GetTokenResult(userCredentials: credentials);
|
|
}
|
|
|
|
[AllowAnonymous]
|
|
[HttpPost("{apiIdentifier}/Operator")]
|
|
public async Task<TokenResponse> GenerateOperatorToken([FromBody]OperatorCredentials credentials)
|
|
{
|
|
return await GetTokenResult(operatorCredentials: credentials);
|
|
}
|
|
|
|
[HttpGet("refresh")]
|
|
public async Task<TokenResponse> 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<bool> Heartbit()
|
|
{
|
|
return ApiObject(true);
|
|
}
|
|
}
|
|
}
|