Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refresh token support #581

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Application/Accounts/Commands/CreateAccountCommand.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.ComponentModel.DataAnnotations;
using Hippo.Application.Common.Interfaces;
using Hippo.Application.Identity;
using MediatR;

namespace Hippo.Application.Accounts.Commands;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Text.RegularExpressions;
using FluentValidation;
using Hippo.Application.Common.Interfaces;
using Hippo.Application.Identity;

namespace Hippo.Application.Accounts.Commands;

Expand Down Expand Up @@ -32,7 +32,7 @@ public async Task<bool> BeUniqueUserName(string userName, CancellationToken canc
{
try
{
await _identityService.GetUserIdAsync(userName);
await _identityService.FindByIdAsync(userName);
return false;
}
catch (Exception)
Expand Down
37 changes: 0 additions & 37 deletions src/Application/Accounts/Commands/CreateTokenCommand.cs

This file was deleted.

26 changes: 15 additions & 11 deletions src/Application/Accounts/Commands/LoginAccountCommand.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,45 @@
using System.ComponentModel.DataAnnotations;
using Hippo.Application.Common.Exceptions;
using Hippo.Application.Common.Interfaces;
using Hippo.Application.Identity;
using MediatR;

namespace Hippo.Application.Accounts.Commands;

public class LoginAccountCommand : IRequest
public class LoginAccountCommand : IRequest<ApiToken>
{
[Required]
public string UserName { get; set; } = "";

[Required]
public string Password { get; set; } = "";

public bool RememberMe { get; set; }
}

public class LoginAccountCommandHandler : IRequestHandler<LoginAccountCommand>
public class LoginAccountCommandHandler : IRequestHandler<LoginAccountCommand, ApiToken>
{
private readonly IAuthenticateService _authenticateService;
private readonly IIdentityService _identityService;
private readonly ISignInService _signInService;

public LoginAccountCommandHandler(IIdentityService identityService, ISignInService signInService)
public LoginAccountCommandHandler(IAuthenticateService authenticateService, IIdentityService identityService, ISignInService signInService)
{
_authenticateService = authenticateService;
_identityService = identityService;
_signInService = signInService;
}

public async Task<Unit> Handle(LoginAccountCommand request, CancellationToken cancellationToken)
public async Task<ApiToken> Handle(LoginAccountCommand request, CancellationToken cancellationToken)
{
var result = await _signInService.PasswordSignInAsync(request.UserName!, request.Password!, request.RememberMe);
var user = await _identityService.FindByIdAsync(request.UserName);
if (user is null)
{
throw new NotFoundException(nameof(Account), request.UserName);
}

if (!result.Succeeded)
if ((await _signInService.PasswordSignInAsync(request.UserName, request.Password, false)).Succeeded)
{
throw new LoginFailedException(result.Errors);
return await _authenticateService.Authenticate(user, cancellationToken);
}

return Unit.Value;
throw new LoginFailedException();
}
}
2 changes: 1 addition & 1 deletion src/Application/Accounts/Commands/LogoutAccountCommand.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Hippo.Application.Common.Interfaces;
using Hippo.Application.Identity;
using MediatR;

namespace Hippo.Application.Accounts.Commands;
Expand Down
59 changes: 59 additions & 0 deletions src/Application/Accounts/Commands/RefreshCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.ComponentModel.DataAnnotations;
using Hippo.Application.Common.Exceptions;
using Hippo.Application.Common.Interfaces;
using Hippo.Application.Identity;
using MediatR;
using Microsoft.EntityFrameworkCore;

namespace Hippo.Application.Accounts.Commands;

public class RefreshCommand : IRequest<ApiToken>
{
[Required]
public string RefreshToken { get; set; } = "";
}

public class RefreshCommandHandler : IRequestHandler<RefreshCommand, ApiToken>
{
private readonly IApplicationDbContext _applicationDbContext;
private readonly IAuthenticateService _authenticateService;
private readonly IIdentityService _identityService;
private readonly IRefreshTokenValidator _refreshTokenValidator;

public RefreshCommandHandler(IAuthenticateService authenticateService, IRefreshTokenValidator refreshTokenValidator, IIdentityService identityService, IApplicationDbContext applicationDbContext)
{
_authenticateService = authenticateService;
_refreshTokenValidator = refreshTokenValidator;
_identityService = identityService;
_applicationDbContext = applicationDbContext;
}

public async Task<ApiToken> Handle(RefreshCommand request, CancellationToken cancellationToken)
{
var isValidRefreshToken = _refreshTokenValidator.Validate(request.RefreshToken);

if (!isValidRefreshToken)
{
throw new InvalidRefreshTokenException();
}

var refreshToken = await _applicationDbContext.RefreshTokens.FirstOrDefaultAsync(x => x.Token == request.RefreshToken, cancellationToken);

if (refreshToken is null)
{
throw new InvalidRefreshTokenException();
}

_applicationDbContext.RefreshTokens.Remove(refreshToken);
await _applicationDbContext.SaveChangesAsync(cancellationToken);

var user = await _identityService.FindByIdAsync(refreshToken.UserId);

if (user is null)
{
throw new NotFoundException(nameof(Account), refreshToken.UserId);
}

return await _authenticateService.Authenticate(user, cancellationToken);
}
}
1 change: 1 addition & 0 deletions src/Application/Application.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="10.4.0" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.3" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
Expand Down
6 changes: 3 additions & 3 deletions src/Application/Channels/Commands/CreateChannelCommand.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.ComponentModel.DataAnnotations;
using Hippo.Application.Common.Config;
using Hippo.Application.Common.Configuration;
using Hippo.Application.Common.Exceptions;
using Hippo.Application.Common.Interfaces;
using Hippo.Core.Entities;
Expand Down Expand Up @@ -34,9 +34,9 @@ public class CreateChannelCommandHandler : IRequestHandler<CreateChannelCommand,
{
private readonly IApplicationDbContext _context;

private readonly HippoConfig _config;
private readonly HippoConfiguration _config;

public CreateChannelCommandHandler(IApplicationDbContext context, HippoConfig config)
public CreateChannelCommandHandler(IApplicationDbContext context, HippoConfiguration config)
{
_context = context;
_config = config;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Reflection;
using Hippo.Application.Common.Exceptions;
using Hippo.Application.Common.Interfaces;
using Hippo.Application.Common.Security;
using Hippo.Application.Identity;
using MediatR;

namespace Hippo.Application.Common.Behaviours;
Expand Down
2 changes: 1 addition & 1 deletion src/Application/Common/Behaviours/LoggingBehaviour.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Hippo.Application.Common.Interfaces;
using Hippo.Application.Identity;
using MediatR.Pipeline;
using Microsoft.Extensions.Logging;

Expand Down
2 changes: 1 addition & 1 deletion src/Application/Common/Behaviours/PerformanceBehaviour.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using System.Diagnostics;
using Hippo.Application.Common.Interfaces;
using Hippo.Application.Identity;
using MediatR;
using Microsoft.Extensions.Logging;

Expand Down
6 changes: 0 additions & 6 deletions src/Application/Common/Config/HippoConfig.cs

This file was deleted.

6 changes: 6 additions & 0 deletions src/Application/Common/Configuration/HippoConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Hippo.Application.Common.Configuration;

public class HippoConfiguration
{
public string PlatformDomain { get; set; } = "hippofactory.io";
}
16 changes: 16 additions & 0 deletions src/Application/Common/Configuration/JwtConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Hippo.Application.Common.Configuration;

public class JwtConfiguration
{
public string AccessTokenSecret { get; set; } = "ceci n'est pas une jeton";

public string RefreshTokenSecret { get; set; } = "ceci n'est pas une jeton";

public double AccessTokenExpirationMinutes { get; set; } = 0.3;

public double RefreshTokenExpirationMinutes { get; set; } = 60;

public string Issuer { get; set; } = "localhost";

public string Audience { get; set; } = "localhost";
}
25 changes: 25 additions & 0 deletions src/Application/Common/Exceptions/InvalidRefreshTokenException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Hippo.Application.Common.Exceptions;

public class InvalidRefreshTokenException : Exception
{
public InvalidRefreshTokenException()
: base($"Invalid refresh token.")
{
}

public InvalidRefreshTokenException(string message)
: base(message)
{
}

// TODO: write unit tests using multiple reasons as input
public InvalidRefreshTokenException(string[] reasons)
: base("Invalid refresh token: " + string.Join(", ", reasons))
{
}

public InvalidRefreshTokenException(string message, Exception innerException)
: base(message, innerException)
{
}
}
2 changes: 2 additions & 0 deletions src/Application/Common/Interfaces/IApplicationDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public interface IApplicationDbContext

DbSet<EnvironmentVariable> EnvironmentVariables { get; }

DbSet<RefreshToken> RefreshTokens { get; }

DbSet<Revision> Revisions { get; }

Task<int> SaveChangesAsync(CancellationToken cancellationToken);
Expand Down
20 changes: 0 additions & 20 deletions src/Application/Common/Interfaces/IIdentityService.cs

This file was deleted.

19 changes: 0 additions & 19 deletions src/Application/Common/Interfaces/ITokenService.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Identity;

namespace Hippo.Infrastructure.Identity;
namespace Hippo.Application.Identity;

public class Account : IdentityUser
{
Expand Down
14 changes: 14 additions & 0 deletions src/Application/Identity/ApiToken.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Hippo.Application.Identity;

public class ApiToken
{
public string AccessToken { get; }

public string RefreshToken { get; }

public ApiToken(string accessToken, string refreshToken)
{
AccessToken = accessToken;
RefreshToken = refreshToken;
}
}
6 changes: 6 additions & 0 deletions src/Application/Identity/IAuthenticateService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Hippo.Application.Identity;

public interface IAuthenticateService
{
Task<ApiToken> Authenticate(Account account, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Hippo.Application.Common.Interfaces;
namespace Hippo.Application.Identity;

public interface ICurrentUserService
{
Expand Down
20 changes: 20 additions & 0 deletions src/Application/Identity/IIdentityService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Hippo.Application.Common.Models;

namespace Hippo.Application.Identity;

public interface IIdentityService
{
Task<string> GetUserNameAsync(string id);

Task<Account> FindByIdAsync(string id);

Task<bool> IsInRoleAsync(string id, string role);

Task<bool> AuthorizeAsync(string id, string policyName);

Task<(Result Result, string UserId)> CreateUserAsync(string userName, string password);

Task<Result> DeleteUserAsync(string id);

Task<bool> CheckPasswordAsync(string userName, string password);
}
Loading