Skip to content

Commit

Permalink
Move things to correct services
Browse files Browse the repository at this point in the history
  • Loading branch information
A-Guldborg committed Oct 22, 2024
1 parent e3226e9 commit fcc8943
Show file tree
Hide file tree
Showing 8 changed files with 53 additions and 75 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,6 @@ protected override void Up(MigrationBuilder migrationBuilder)
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));

migrationBuilder.AddColumn<int>(
name: "PreviousTokenId",
schema: "dbo",
table: "Tokens",
type: "int",
nullable: true);

migrationBuilder.AddColumn<bool>(
name: "Revoked",
schema: "dbo",
Expand All @@ -51,11 +44,6 @@ protected override void Down(MigrationBuilder migrationBuilder)
schema: "dbo",
table: "Tokens");

migrationBuilder.DropColumn(
name: "PreviousTokenId",
schema: "dbo",
table: "Tokens");

migrationBuilder.DropColumn(
name: "Revoked",
schema: "dbo",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,9 +309,6 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property<DateTime>("Expires")
.HasColumnType("datetime2");

b.Property<int?>("PreviousTokenId")
.HasColumnType("int");

b.Property<bool>("Revoked")
.HasColumnType("bit");

Expand Down
52 changes: 4 additions & 48 deletions coffeecard/CoffeeCard.Library/Services/v2/AccountService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -320,11 +320,8 @@ public async Task SendMagicLinkEmail(string email, LoginType loginType)
public async Task<UserLoginResponse> LoginByMagicLink(string token)
{
// Validate token in DB
var foundToken = await GetTokenByMagicLink(token);
if (foundToken.Revoked)
{
throw new ApiException("Token already used", 401);
}
var foundToken = await _tokenServiceV2.GetValidTokenByHashAsync(token);

// Invalidate token in DB
foundToken.Revoked = true;
await _context.SaveChangesAsync();
Expand All @@ -347,12 +344,8 @@ public async Task<UserLoginResponse> LoginByMagicLink(string token)

public async Task<UserLoginResponse> RefreshToken(string token)
{
var foundToken = await GetRefreshToken(token);
if (foundToken.Revoked)
{
await InvalidateTokenChain(foundToken.Id);
throw new ApiException("Token already used", 401);
}
var foundToken = await _tokenServiceV2.GetValidTokenByHashAsync(token);

// Invalidate token in DB
foundToken.Revoked = true;
await _context.SaveChangesAsync();
Expand All @@ -372,42 +365,5 @@ public async Task<UserLoginResponse> RefreshToken(string token)

return new UserLoginResponse() { Jwt = jwt, RefreshToken = refreshToken };
}

private async Task<Token> GetRefreshToken(string token)
{
var foundToken = await _context.Tokens
.Include(t => t.User)
.FirstOrDefaultAsync(t => t.TokenHash == token);
if (foundToken?.User == null)
{
throw new ApiException("Invalid token", 401);
}

return foundToken;
}

private async Task<Token> GetTokenByMagicLink(string token)
{
var foundToken = await _context.Tokens
.Include(t => t.User)
.FirstOrDefaultAsync(t => t.TokenHash == token);
if (foundToken?.User == null)
{
throw new ApiException("Invalid token", 401);
}

return foundToken;
}

private async Task InvalidateTokenChain(int tokenId)
{
// todo: invalidate all from user instead of recursion
var newerToken = _context.Tokens.FirstOrDefault(t => t.PreviousTokenId == tokenId);
if (newerToken != null)
{
newerToken.Revoked = true;
await InvalidateTokenChain(newerToken.Id);
}
}
}
}
1 change: 1 addition & 0 deletions coffeecard/CoffeeCard.Library/Services/v2/ITokenService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ public interface ITokenService
string GenerateMagicLink(User user);
Task<string> GenerateRefreshTokenAsync(User user);
Task<string> ValidateTokenAsync(string token);
Task<Token> GetValidTokenByHashAsync(string tokenHash);
}
}
29 changes: 27 additions & 2 deletions coffeecard/CoffeeCard.Library/Services/v2/TokenService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public string GenerateMagicLink(User user)
public async Task<string> GenerateRefreshTokenAsync(User user)
{
var refreshToken = Guid.NewGuid().ToString();
_context.Tokens.Add(new Token(refreshToken, TokenType.Refresh));
_context.Tokens.Add(new Token(refreshToken, TokenType.Refresh) { User = user });
await _context.SaveChangesAsync();
return refreshToken;
}
Expand All @@ -42,9 +42,34 @@ public async Task<string> ValidateTokenAsync(string refreshToken)
var token = await _context.Tokens.FirstOrDefaultAsync(t => t.TokenHash == refreshToken);
if (token.Revoked)
{
// TODO: Invalidate chain of tokens
await InvalidateRefreshTokensForUser(token.User);
throw new ApiException("Refresh token is already used", 401);
}
throw new NotImplementedException();
}

public async Task<Token> GetValidTokenByHashAsync(string tokenHash)
{
var foundToken = await _context.Tokens.Include(t => t.User).FirstOrDefaultAsync(t => t.TokenHash == tokenHash);
if (foundToken == null || foundToken.Revoked || foundToken.Expired())
{
await InvalidateRefreshTokensForUser(foundToken?.User);
throw new ApiException("Invalid token", 401);
}
return foundToken;
}

private async Task InvalidateRefreshTokensForUser(User user)
{
if (user is null) return;

var tokens = _context.Tokens.Where(t => t.UserId == user.Id && t.Type == TokenType.Refresh);

_context.Tokens.UpdateRange(tokens);
foreach (var token in tokens)
{
token.Revoked = true;
}
await _context.SaveChangesAsync();
}
}
7 changes: 5 additions & 2 deletions coffeecard/CoffeeCard.Models/Entities/Token.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ public class Token(string tokenHash, TokenType type)

public bool Revoked { get; set; } = false;

public int? PreviousTokenId { get; set; }

public override bool Equals(object? obj)
{
if (obj is Token newToken) return TokenHash.Equals(newToken.TokenHash);
Expand All @@ -32,5 +30,10 @@ public override int GetHashCode()
{
return HashCode.Combine(Id, TokenHash, User);
}

public bool Expired()
{
return DateTime.UtcNow > Expires;
}
}
}
19 changes: 15 additions & 4 deletions coffeecard/CoffeeCard.WebApi/Controllers/v2/AccountController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,12 +258,23 @@ public async Task<ActionResult<UserLoginResponse>> AuthToken([FromRoute] string

[HttpPost]
[AuthorizeRoles(UserGroup.Customer, UserGroup.Barista, UserGroup.Manager, UserGroup.Board)]
[Route("auth/refresh")]
public async Task<ActionResult<UserLoginResponse>> Refresh()
[Route("auth/refresh/loginType={loginType}")]
public async Task<ActionResult<UserLoginResponse>> Refresh([FromRoute] LoginType loginType, string refreshToken = null)
{
var refreshToken = HttpContext.Request.Cookies.FirstOrDefault(c => c.Key == "refreshToken").Value;
switch (loginType)
{
case LoginType.App:
if (refreshToken is null) return NotFound(new MessageResponseDto { Message = "Refresh token required for app refresh." });
break;
case LoginType.Shifty:
refreshToken = HttpContext.Request.Cookies.FirstOrDefault(c => c.Key == "refreshToken").Value;
break;
default:
return NotFound(new MessageResponseDto { Message = "Cannot determine application to login." });
}

var token = await _accountService.RefreshToken(refreshToken);
return Ok(token);
return Tokenize(loginType, token);
}

private ActionResult<UserLoginResponse> Tokenize(LoginType loginType, UserLoginResponse token)
Expand Down

0 comments on commit fcc8943

Please sign in to comment.