This commit is contained in:
162
server/AyaNova/Controllers/AuthController.cs
Normal file
162
server/AyaNova/Controllers/AuthController.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using AyaNova.Models;
|
||||
using AyaNova.Util;
|
||||
using AyaNova.Api.ControllerHelpers;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using App.Metrics;
|
||||
|
||||
//required to inject configuration in constructor
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace AyaNova.Api.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Authentication controller
|
||||
/// </summary>
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/[controller]")]
|
||||
[Produces("application/json")]
|
||||
public class AuthController : Controller
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<AuthController> log;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ApiServerState serverState;
|
||||
private readonly IMetrics metrics;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="configuration"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
/// <param name="Metrics"></param>
|
||||
public AuthController(AyContext context, ILogger<AuthController> logger, IConfiguration configuration, ApiServerState apiServerState, IMetrics Metrics)//these two are injected, see startup.cs
|
||||
{
|
||||
ct = context;
|
||||
log = logger;
|
||||
_configuration = configuration;
|
||||
serverState = apiServerState;
|
||||
metrics = Metrics;
|
||||
}
|
||||
|
||||
//AUTHENTICATE CREDS
|
||||
//RETURN JWT
|
||||
|
||||
/// <summary>
|
||||
/// Post credentials to receive a JSON web token
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This route is used to authenticate to the AyaNova API.
|
||||
/// Once you have a token you need to include it in all requests that require authentication like this:
|
||||
/// <code>Authorization: Bearer [TOKEN]</code>
|
||||
/// Note the space between Bearer and the token. Also, do not include the square brackets
|
||||
/// </remarks>
|
||||
/// <param name="creds"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostCreds([FromBody] AuthController.CredentialsParam creds) //if was a json body then //public JsonResult PostCreds([FromBody] string login, [FromBody] string password)
|
||||
{
|
||||
//a bit different as ops users can still login if the state is opsonly
|
||||
//so the only real barrier here would be a completely closed api
|
||||
if (!serverState.IsOpenOrOpsOnly)
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
int nFailedAuthDelay = 10000;
|
||||
#if (DEBUG)
|
||||
nFailedAuthDelay = 1;
|
||||
#endif
|
||||
|
||||
if (string.IsNullOrWhiteSpace(creds.Login) || string.IsNullOrWhiteSpace(creds.Password))
|
||||
{
|
||||
metrics.Measure.Meter.Mark(MetricsRegistry.FailedLoginMeter);
|
||||
//Make a failed pw wait
|
||||
await Task.Delay(nFailedAuthDelay);
|
||||
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
|
||||
|
||||
//Multiple users are allowed the same password and login
|
||||
//Salt will differentiate them so get all users that match login, then try to match pw
|
||||
var users = await ct.User.AsNoTracking().Where(m => m.Login == creds.Login).ToListAsync();
|
||||
|
||||
foreach (User u in users)
|
||||
{
|
||||
string hashed = Hasher.hash(u.Salt, creds.Password);
|
||||
if (hashed == u.Password)
|
||||
{
|
||||
//Restrict auth due to server state?
|
||||
//If we're here the server state is not closed, but it might be ops only
|
||||
|
||||
//If the server is ops only then this user needs to be ops or else they are not allowed in
|
||||
if (serverState.IsOpsOnly &&
|
||||
!u.Roles.HasFlag(Biz.AuthorizationRoles.OpsAdminFull) &&
|
||||
!u.Roles.HasFlag(Biz.AuthorizationRoles.OpsAdminLimited))
|
||||
{
|
||||
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
|
||||
}
|
||||
|
||||
|
||||
|
||||
//build the key (JWT set in startup.cs)
|
||||
byte[] secretKey = System.Text.Encoding.ASCII.GetBytes(ServerBootConfig.AYANOVA_JWT_SECRET);
|
||||
|
||||
//create a new datetime offset of now in utc time
|
||||
var iat = new DateTimeOffset(DateTime.Now.ToUniversalTime(), TimeSpan.Zero);//timespan zero means zero time off utc / specifying this is a UTC datetime
|
||||
var exp = new DateTimeOffset(DateTime.Now.AddDays(30).ToUniversalTime(), TimeSpan.Zero);
|
||||
|
||||
var payload = new Dictionary<string, object>()
|
||||
{
|
||||
{ "iat", iat.ToUnixTimeSeconds().ToString() },
|
||||
{ "exp", exp.ToUnixTimeSeconds().ToString() },//in payload exp must be in unix epoch time per standard
|
||||
{ "iss", "AyaNova" },
|
||||
{ "id", u.Id.ToString() }
|
||||
};
|
||||
|
||||
|
||||
//NOTE: probably don't need Jose.JWT as am using Microsoft jwt stuff to validate routes so it should also be able to
|
||||
//issue tokens as well, but it looked cmplex and this works so unless need to remove in future keeping it.
|
||||
string token = Jose.JWT.Encode(payload, secretKey, Jose.JwsAlgorithm.HS256);
|
||||
|
||||
|
||||
log.LogInformation($"User number \"{u.Id}\" logged in from \"{Util.StringUtil.MaskIPAddress(HttpContext.Connection.RemoteIpAddress.ToString())}\" ok");
|
||||
metrics.Measure.Meter.Mark(MetricsRegistry.SuccessfulLoginMeter);
|
||||
return Ok(new ApiOkResponse(new
|
||||
{
|
||||
ok = 1,
|
||||
issued = iat,
|
||||
expires = exp,
|
||||
token = token,
|
||||
id = u.Id
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
//No users matched, it's a failed login
|
||||
//Make a failed pw wait
|
||||
metrics.Measure.Meter.Mark(MetricsRegistry.FailedLoginMeter);
|
||||
await Task.Delay(nFailedAuthDelay);
|
||||
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
|
||||
public class CredentialsParam
|
||||
{
|
||||
[System.ComponentModel.DataAnnotations.Required]
|
||||
public string Login { get; set; }
|
||||
[System.ComponentModel.DataAnnotations.Required]
|
||||
public string Password { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
Reference in New Issue
Block a user