Files
raven/server/AyaNova/Controllers/AuthController.cs
2021-03-12 18:53:59 +00:00

710 lines
30 KiB
C#

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using TwoFactorAuthNet;
using QRCoder;
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 AyaNova.Biz;
//required to inject configuration in constructor
using Microsoft.Extensions.Configuration;
namespace AyaNova.Api.Controllers
{
/// <summary>
/// Authentication controller
/// </summary>
[ApiController]
[ApiVersion("8.0")]
[Route("api/v{version:apiVersion}/auth")]
[Produces("application/json")]
[Authorize]
public class AuthController : ControllerBase
{
private readonly AyContext ct;
private readonly ILogger<AuthController> log;
private readonly IConfiguration _configuration;
private readonly ApiServerState serverState;
private const int JWT_LIFETIME_DAYS = 7;
/// <summary>
/// ctor
/// </summary>
/// <param name="context"></param>
/// <param name="logger"></param>
/// <param name="configuration"></param>
/// <param name="apiServerState"></param>
public AuthController(AyContext context, ILogger<AuthController> logger, IConfiguration configuration, ApiServerState apiServerState)
{
ct = context;
log = logger;
_configuration = configuration;
serverState = apiServerState;
}
//AUTHENTICATE CREDS
//RETURN JWT
/// <summary>
/// Create 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]
[AllowAnonymous]
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.IsClosed && AyaNova.Core.License.ActiveKey.KeyDoesNotNeedAttention)
{
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
}
#if (DEBUG)
#region TESTING
//TEST JWT's with various flaws for testing purposes:
if (creds.Login == "INTEGRATION_TEST")
{
//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);
string Issuer = "ayanova.com";
var Algorithm = Jose.JwsAlgorithm.HS256;
//Pre JWT creation test payloads
switch (creds.Password)
{
case "EXPIRED":
exp = new DateTimeOffset(DateTime.Now.AddDays(-30).ToUniversalTime(), TimeSpan.Zero);
break;
case "WRONG_ISSUER":
Issuer = "Bogus";
break;
case "NO_ALGORITHM":
Algorithm = Jose.JwsAlgorithm.none;
break;
case "WRONG_SECRET":
secretKey = System.Text.Encoding.ASCII.GetBytes("xxxxxxThisIsObviouslyWrongxxxxxx");
break;
}
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", Issuer },
{ "id", "1" }
};
string TestToken = Jose.JWT.Encode(payload, secretKey, Algorithm);
//Post JWT creation test payloads
switch (creds.Password)
{
case "TRUNCATED_SIGNATURE":
TestToken = TestToken.Substring(0, TestToken.Length - 3);
break;
case "TRANSPOSE_SIGNATURE":
//Transpose two characters in the signature
int len = TestToken.Length;
var Transposed = TestToken.Substring(0, len - 5) + TestToken[len - 4] + TestToken[len - 5] + TestToken.Substring(len - 3, 3);
TestToken = Transposed;
break;
}
return Ok(ApiOkResponse.Response(new
{
token = TestToken,
name = "SuperUser Account - TESTING",
roles = "0"
}));
}
#endregion testing
#endif
if (string.IsNullOrWhiteSpace(creds.Login) || string.IsNullOrWhiteSpace(creds.Password))
{
//Make a failed pw wait
await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);
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.Where(z => z.Login == creds.Login && z.Active == true).ToListAsync();
foreach (User u in users)
{
string hashed = Hasher.hash(u.Salt, creds.Password);
if (hashed == u.Password)
{
//TWO FACTOR ENABLED??
//if 2fa enabled then need to validate it before sending token, so we're halfway there and need to send a 2fa prompt
if (u.TwoFactorEnabled)
{
//Generate a temporary token to identify and verify this is the same user
u.TempToken = Hasher.GenerateSalt().Replace("=", "").Replace("+", "");
await ct.SaveChangesAsync();
var UOpt=await ct.UserOptions.AsNoTracking().FirstAsync(z=>z.UserId==u.Id);
List<string> TranslationKeysToFetch = new List<string> { "AuthTwoFactor", "AuthEnterPin", "AuthVerifyCode", "Cancel" };
var LT = await TranslationBiz.GetSubsetStaticAsync(TranslationKeysToFetch, UOpt.TranslationId);
return Ok(ApiOkResponse.Response(new
{
AuthTwoFactor = LT["AuthTwoFactor"],
AuthEnterPin = LT["AuthEnterPin"],
AuthVerifyCode = LT["AuthVerifyCode"],
Cancel = LT["Cancel"],
tfa = true,
tt = u.TempToken
}));
}
//Not 2fa, Valid password, user is authorized
return await ReturnUserCredsOnSuccessfulAuthentication(u);
}
}
//No users matched, it's a failed login
//Make a failed pw wait
await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
}
/// <summary>
/// Verify tfa code
/// </summary>
/// <remarks>
/// This route is used to authenticate to the AyaNova API via tfa code.
/// 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="pin"></param>
/// <returns></returns>
[HttpPost("tfa-authenticate")]
[AllowAnonymous]
public async Task<IActionResult> TfaAuthenticate([FromBody] TFAPinParam pin)
{
//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.IsClosed && AyaNova.Core.License.ActiveKey.KeyDoesNotNeedAttention)
{
return StatusCode(503, new ApiErrorResponse(ApiErrorCode.API_CLOSED, null, serverState.Reason));
}
if (string.IsNullOrWhiteSpace(pin.Pin))
{
//Make a failed pw wait
await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
}
//Match to temp token that would have been set by initial credentialed login for 2fa User
var user = await ct.User.Where(z => z.TempToken == pin.TempToken && z.Active == true && z.TwoFactorEnabled == true).FirstOrDefaultAsync();
if (user != null)
{
//Valid temp token, now check the pin code is right
if (string.IsNullOrWhiteSpace(user.TotpSecret))
{
await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "generalerror", "2fa not activated"));
}
//ok, something to validate, let's validate it
var tfa = new TwoFactorAuth("AyaNova");
if (!tfa.VerifyCode(user.TotpSecret, pin.Pin.Replace(" ", "").Trim()))
{
await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
}
//User is valid and authenticated
//clear temp token
user.TempToken = string.Empty;
await ct.SaveChangesAsync();
return await ReturnUserCredsOnSuccessfulAuthentication(user);
}
//No users matched, it's a failed login
//Make a failed pw wait
await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
}
private async Task<IActionResult> ReturnUserCredsOnSuccessfulAuthentication(User u)
{
//check if server available to SuperUser account only (closed or migrate mode)
//if it is it means we got here either because there is no license
//and only *the* SuperUser account can login now or we're in migrate mode
if (serverState.IsClosed || serverState.IsMigrateMode)
{
//if not SuperUser account then boot closed
//SuperUser account is always ID 1
if (u.Id != 1)
{
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
}
}
//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(serverState.ApiErrorCode, 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(JWT_LIFETIME_DAYS).ToUniversalTime(), TimeSpan.Zero);
//=============== download token ===================
//Generate a download token and store it with the user account
//string DownloadToken = Convert.ToBase64String(Guid.NewGuid().ToByteArray());
string DownloadToken = Hasher.GenerateSalt();
DownloadToken = DownloadToken.Replace("=", "");
DownloadToken = DownloadToken.Replace("+", "");
u.DlKey = DownloadToken;
u.DlKeyExpire = exp.DateTime;
//=======================================================
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.com" },
{ "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);
//save auth token to ensure single sign on only
u.CurrentAuthToken = token;
u.LastLogin = DateTime.UtcNow;
await ct.SaveChangesAsync();
//KEEP this, masked version of IP address
//Not sure if this is necessary or not but if it turns out to be then make it a boot option
// log.LogInformation($"User number \"{u.Id}\" logged in from \"{Util.StringUtil.MaskIPAddress(HttpContext.Connection.RemoteIpAddress.ToString())}\" ok");
log.LogInformation($"User \"{u.Name}\" logged in from \"{HttpContext.Connection.RemoteIpAddress.ToString()}\" ok");
return Ok(ApiOkResponse.Response(new
{
token = token,
name = u.Name,
usertype = u.UserType,
roles = ((int)u.Roles).ToString(),
dlt = DownloadToken,
tfa = u.TwoFactorEnabled
}));
//------------------------ /STANDARD BLOCK -------------------------
}
/// <summary>
/// Change Password
/// </summary>
/// <param name="changecreds"></param>
/// <returns></returns>
[HttpPost("change-password")]
public async Task<IActionResult> ChangePassword([FromBody] AuthController.ChangePasswordParam changecreds)
{
//Note: need to be authenticated to use this, only called from own user's UI
//it still asks for old creds in case someone attempts to do this on another user's logged in session
//Also it checks here that this is in fact the same user account calling this method as the user attempting to be modified
if (!serverState.IsOpen)
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
if (!ModelState.IsValid)
return BadRequest(new ApiErrorResponse(ModelState));
if (string.IsNullOrWhiteSpace(changecreds.OldPassword) || string.IsNullOrWhiteSpace(changecreds.LoginName))
{
await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
}
if (string.IsNullOrWhiteSpace(changecreds.NewPassword))
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, "NewPassword"));
if (changecreds.NewPassword != changecreds.ConfirmPassword)
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, "ConfirmPassword", "NewPassword does not match ConfirmPassword"));
//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(z => z.Login == changecreds.LoginName).ToListAsync();
foreach (User u in users)
{
string hashed = Hasher.hash(u.Salt, changecreds.OldPassword);
if (hashed == u.Password)
{
//If the user is inactive they may not login
if (!u.Active)
{
//respond like bad creds so as not to leak information
await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
}
//double check it's the currently logged in User's own User object only
//otherwise it's feasible someone could change someone else's password through their own change password form with a mis-type or intentional hack
if (u.Id != UserIdFromContext.Id(HttpContext.Items))
{
await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);
return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
}
//fetch and update user
//Instantiate the business object handler
UserBiz biz = UserBiz.GetBiz(ct, HttpContext);
await biz.ChangePasswordAsync(u.Id, changecreds.NewPassword);
return NoContent();
}
}
//No users matched, it's a failed login
//Make a failed pw wait
await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);
return BadRequest(new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED));
}
/// <summary>
/// Change Password via reset token
/// </summary>
/// <param name="resetcreds"></param>
/// <returns></returns>
[HttpPost("reset-password")]
[AllowAnonymous]
public async Task<IActionResult> ResetPassword([FromBody] AuthController.ResetPasswordParam resetcreds)
{
if (!serverState.IsOpen)
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
if (!ModelState.IsValid)
{
return BadRequest(new ApiErrorResponse(ModelState));
}
if (string.IsNullOrWhiteSpace(resetcreds.PasswordResetCode))
{
//Make a fail wait
await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, "PasswordResetCode", "Reset code is required"));
}
if (string.IsNullOrWhiteSpace(resetcreds.Password))
{
await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_REQUIRED, "Password", "Password is required"));
}
//look for user with this reset code
var user = await ct.User.AsNoTracking().Where(z => z.PasswordResetCode == resetcreds.PasswordResetCode).FirstOrDefaultAsync();
if (user == null)
{
await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, "PasswordResetCode", "Reset code not valid"));
}
if (string.IsNullOrWhiteSpace(user.PasswordResetCode) || user.PasswordResetCodeExpire == null)
{
await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);
return BadRequest(new ApiErrorResponse(ApiErrorCode.VALIDATION_INVALID_VALUE, "PasswordResetCode", "Reset code not valid"));
}
//vet the expiry
var utcNow = new DateTimeOffset(DateTime.Now.ToUniversalTime(), TimeSpan.Zero);
if (user.PasswordResetCodeExpire < utcNow.DateTime)
{//if reset code expired before now
await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);
return BadRequest(new ApiErrorResponse(ApiErrorCode.NOT_AUTHORIZED, "PasswordResetCodeExpire", "Reset code has expired"));
}
//Ok, were in, it's all good, accept the new password and update the user record
UserBiz biz = UserBiz.GetBiz(ct, HttpContext);
await biz.ChangePasswordAsync(user.Id, resetcreds.Password);
return NoContent();
}
/// <summary>
/// Generate time limited password reset code for User
/// and email link to them so they can set their password
///
/// </summary>
/// <param name="id">User id</param>
/// <param name="apiVersion">From route path</param>
/// <returns>New concurrency code</returns>
[HttpPost("request-reset-password/{id}")]
public async Task<IActionResult> SendPasswordResetCode([FromRoute] long id, ApiVersion apiVersion)
{
//Note: this is not allowed for an anonymous users because it's only intended for now to work for staff user's who will send the request on behalf of the User
if (!serverState.IsOpen)
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
UserBiz biz = UserBiz.GetBiz(ct, HttpContext);
if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
return StatusCode(403, new ApiNotAuthorizedResponse());
if (!ModelState.IsValid)
return BadRequest(new ApiErrorResponse(ModelState));
uint res = await biz.SendPasswordResetCode(id);
if (res == 0)
{
await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);
return BadRequest(new ApiErrorResponse(biz.Errors));
}
else
return Ok(ApiOkResponse.Response(new
{
concurrency = res
}));
}
/// <summary>
/// Generate TOTP secret and return for use in auth app
///
/// </summary>
/// <param name="apiVersion">From route path</param>
/// <returns>Authentication app activation code</returns>
[HttpGet("totp")]
public async Task<IActionResult> GenerateAndSendTOTP(ApiVersion apiVersion)
{
if (!serverState.IsOpen)
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
UserBiz biz = UserBiz.GetBiz(ct, HttpContext);
if (!ModelState.IsValid)
return BadRequest(new ApiErrorResponse(ModelState));
//get user and save the secret
var UserId = UserIdFromContext.Id(HttpContext.Items);
var u = await ct.User.FirstOrDefaultAsync(z => z.Id == UserId);
if (u == null)//should never happen but ?
return StatusCode(403, new ApiNotAuthorizedResponse());
//this is to stop someone from messing up someone's login accidentally or maliciously by simply hitting the route logged in as them
if (u.TwoFactorEnabled)
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "generalerror", "2fa already enabled"));
var tfa = new TwoFactorAuth("AyaNova");
u.TotpSecret = tfa.CreateSecret(160);
await ct.SaveChangesAsync();
//https://github.com/google/google-authenticator/wiki/Key-Uri-Format
//otpauth://totp/ACME%20Co:john.doe@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30
//this format tested and works with Google, Microsoft Authy, Duo authenticators
string payload = $"otpauth://totp/AyaNova:{u.Name}?secret={u.TotpSecret}&issuer=AyaNova&algorithm=SHA1&digits=6&period=30";
QRCodeGenerator qrGenerator = new QRCodeGenerator();
QRCodeData qrCodeData = qrGenerator.CreateQrCode(payload, QRCodeGenerator.ECCLevel.Q);
Base64QRCode qrCode = new Base64QRCode(qrCodeData);
string qrCodeImageAsBase64 = qrCode.GetGraphic(3);
return Ok(ApiOkResponse.Response(new
{
s = u.TotpSecret,
qr = qrCodeImageAsBase64
}));
}
/// <summary>
/// Confirm 2fa ready to use
///
/// </summary>
/// <param name="pin">Auth app 6 digit passcode</param>
/// <param name="apiVersion">From route path</param>
/// <returns>OK on success</returns>
[HttpPost("totp-validate")]
public async Task<IActionResult> ValidateTOTP([FromBody] TFAPinParam pin, ApiVersion apiVersion)
{
if (!serverState.IsOpen)
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
if (!ModelState.IsValid)
return BadRequest(new ApiErrorResponse(ModelState));
//get user
var UserId = UserIdFromContext.Id(HttpContext.Items);
var u = await ct.User.FirstOrDefaultAsync(z => z.Id == UserId);
if (u == null)//should never happen but ?
{
await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);
return StatusCode(403, new ApiNotAuthorizedResponse());
}
if (u.TwoFactorEnabled)
{
await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "generalerror", "2fa already enabled"));
}
if (string.IsNullOrWhiteSpace(u.TotpSecret))
{
await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);
return BadRequest(new ApiErrorResponse(ApiErrorCode.INVALID_OPERATION, "generalerror", "2fa activation code not requested yet (missed a step?)"));
}
//ok, something to validate, let's validate it
var tfa = new TwoFactorAuth("AyaNova");
var ret = tfa.VerifyCode(u.TotpSecret, pin.Pin.Replace(" ", "").Trim());
if (ret == true)
{
//enable 2fa on user account
u.TwoFactorEnabled = true;
await ct.SaveChangesAsync();
}
else
{
await Task.Delay(AyaNova.Util.ServerBootConfig.FAILED_AUTH_DELAY);
}
return Ok(ApiOkResponse.Response(new
{
ok = ret
}));
}
/// <summary>
/// Disable (turn off) 2fa for current user account
///
/// </summary>
/// <param name="apiVersion">From route path</param>
/// <returns>OK on success</returns>
[HttpPost("totp-disable")]
public async Task<IActionResult> DisableTOTP(ApiVersion apiVersion)
{
if (!serverState.IsOpen)
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
if (!ModelState.IsValid)
return BadRequest(new ApiErrorResponse(ModelState));
//get user
var UserId = UserIdFromContext.Id(HttpContext.Items);
var u = await ct.User.FirstOrDefaultAsync(z => z.Id == UserId);
if (u == null)//should never happen but ?
return StatusCode(403, new ApiNotAuthorizedResponse());
u.TotpSecret = null;
u.TwoFactorEnabled = false;
await ct.SaveChangesAsync();
return NoContent();
}
//------------------------------------------------------
public class CredentialsParam
{
[System.ComponentModel.DataAnnotations.Required]
public string Login { get; set; }
[System.ComponentModel.DataAnnotations.Required]
public string Password { get; set; }
}
public class ChangePasswordParam
{
[System.ComponentModel.DataAnnotations.Required]
public string LoginName { get; set; }
[System.ComponentModel.DataAnnotations.Required]
public string OldPassword { get; set; }
[System.ComponentModel.DataAnnotations.Required]
public string NewPassword { get; set; }
[System.ComponentModel.DataAnnotations.Required]
public string ConfirmPassword { get; set; }
}
public class ResetPasswordParam
{
[System.ComponentModel.DataAnnotations.Required]
public string PasswordResetCode { get; set; }
[System.ComponentModel.DataAnnotations.Required]
public string Password { get; set; }
}
public class TFAPinParam
{
[System.ComponentModel.DataAnnotations.Required]
public string Pin { get; set; }
public string TempToken { get; set; }
}
}//eoc
}//eons