:drunk: No idea what did AI did
This commit is contained in:
@ -0,0 +1,195 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using DysonNetwork.Common.Models;
|
||||
using DysonNetwork.Pass.Data;
|
||||
using DysonNetwork.Pass.Features.Auth.Interfaces;
|
||||
using DysonNetwork.Pass.Features.Auth.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Pass.Features.Auth.Services;
|
||||
|
||||
public class AuthenticationService : IAuthenticationService
|
||||
{
|
||||
private readonly PassDatabase _db;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ISessionService _sessionService;
|
||||
private readonly IOidcService _oidcService;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public AuthenticationService(
|
||||
PassDatabase db,
|
||||
IConfiguration configuration,
|
||||
ISessionService sessionService,
|
||||
IOidcService oidcService,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_db = db;
|
||||
_configuration = configuration;
|
||||
_sessionService = sessionService;
|
||||
_oidcService = oidcService;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public async Task<AuthResult> AuthenticateAsync(string username, string password)
|
||||
{
|
||||
// First try to find by username (Name in the Account model)
|
||||
var account = await _db.Accounts
|
||||
.Include(a => a.Profile) // Include Profile for email lookup
|
||||
.FirstOrDefaultAsync(a => a.Name == username);
|
||||
|
||||
// If not found by username, try to find by email in the Profile
|
||||
if (account == null)
|
||||
{
|
||||
account = await _db.Accounts
|
||||
.Include(a => a.Profile)
|
||||
.FirstOrDefaultAsync(a => a.Profile != null && a.Profile.Email == username);
|
||||
}
|
||||
|
||||
if (account == null || !await VerifyPasswordAsync(account, password))
|
||||
{
|
||||
return new AuthResult { Success = false, Error = "Invalid username/email or password" };
|
||||
}
|
||||
|
||||
return await CreateAuthResult(account);
|
||||
}
|
||||
|
||||
private async Task<bool> VerifyPasswordAsync(Account account, string password)
|
||||
{
|
||||
// Find password auth factor for the account
|
||||
var passwordFactor = await _db.AccountAuthFactors
|
||||
.FirstOrDefaultAsync(f => f.AccountId == account.Id && f.FactorType == AccountAuthFactorType.Password);
|
||||
|
||||
if (passwordFactor == null)
|
||||
return false;
|
||||
|
||||
return BCrypt.Net.BCrypt.Verify(password, passwordFactor.Secret);
|
||||
}
|
||||
|
||||
public async Task<AuthResult> AuthenticateWithOidcAsync(string provider, string code, string state)
|
||||
{
|
||||
return await _oidcService.AuthenticateAsync(provider, code, state);
|
||||
}
|
||||
|
||||
public async Task<AuthResult> RefreshTokenAsync(string refreshToken)
|
||||
{
|
||||
var session = await _db.AuthSessions
|
||||
.FirstOrDefaultAsync(s => s.RefreshToken == refreshToken && !s.IsRevoked);
|
||||
|
||||
if (session == null || session.RefreshTokenExpiryTime <= SystemClock.Instance.GetCurrentInstant())
|
||||
{
|
||||
return new AuthResult { Success = false, Error = "Invalid or expired refresh token" };
|
||||
}
|
||||
|
||||
var account = await _db.Accounts.FindAsync(session.AccountId);
|
||||
if (account == null)
|
||||
{
|
||||
return new AuthResult { Success = false, Error = "Account not found" };
|
||||
}
|
||||
|
||||
// Invalidate the old session
|
||||
await _sessionService.InvalidateSessionAsync(session.Id);
|
||||
|
||||
// Create a new session
|
||||
return await CreateAuthResult(account);
|
||||
}
|
||||
|
||||
public async Task<bool> ValidateTokenAsync(string token)
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]!);
|
||||
|
||||
try
|
||||
{
|
||||
tokenHandler.ValidateToken(token, new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(key),
|
||||
ValidateIssuer = true,
|
||||
ValidateAudience = false,
|
||||
ClockSkew = TimeSpan.Zero,
|
||||
ValidIssuer = _configuration["Jwt:Issuer"]
|
||||
}, out _);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task LogoutAsync(Guid sessionId)
|
||||
{
|
||||
await _sessionService.InvalidateSessionAsync(sessionId);
|
||||
}
|
||||
|
||||
public async Task<bool> ValidateSessionAsync(Guid sessionId)
|
||||
{
|
||||
return await _sessionService.ValidateSessionAsync(sessionId);
|
||||
}
|
||||
|
||||
public async Task<AuthSession> GetSessionAsync(Guid sessionId)
|
||||
{
|
||||
var session = await _sessionService.GetSessionAsync(sessionId);
|
||||
if (session == null)
|
||||
throw new Exception("Session not found");
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
private async Task<AuthResult> CreateAuthResult(Account account)
|
||||
{
|
||||
var ipAddress = _httpContextAccessor.HttpContext?.Connection.RemoteIpAddress?.ToString() ?? string.Empty;
|
||||
var userAgent = _httpContextAccessor.HttpContext?.Request.Headers.UserAgent.ToString() ?? string.Empty;
|
||||
|
||||
var session = await _sessionService.CreateSessionAsync(account.Id, ipAddress, userAgent);
|
||||
var token = GenerateJwtToken(account, session.Id);
|
||||
|
||||
return new AuthResult
|
||||
{
|
||||
Success = true,
|
||||
AccessToken = token,
|
||||
RefreshToken = session.RefreshToken,
|
||||
Session = session
|
||||
};
|
||||
}
|
||||
|
||||
private string GenerateJwtToken(Account account, Guid sessionId)
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]!);
|
||||
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(new[]
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, account.Id.ToString()),
|
||||
new Claim(ClaimTypes.Name, account.Username),
|
||||
new Claim("session_id", sessionId.ToString())
|
||||
}),
|
||||
Expires = DateTime.UtcNow.AddDays(7),
|
||||
SigningCredentials = new SigningCredentials(
|
||||
new SymmetricSecurityKey(key),
|
||||
SecurityAlgorithms.HmacSha256Signature
|
||||
),
|
||||
Issuer = _configuration["Jwt:Issuer"],
|
||||
Audience = _configuration["Jwt:Audience"]
|
||||
};
|
||||
|
||||
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||
return tokenHandler.WriteToken(token);
|
||||
}
|
||||
|
||||
private bool VerifyPasswordHash(string password, byte[] storedHash, byte[] storedSalt)
|
||||
{
|
||||
using var hmac = new HMACSHA512(storedSalt);
|
||||
var computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(password));
|
||||
return computedHash.SequenceEqual(storedHash);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user