diff --git a/JobTrackerApi/Controllers/TwoFactorController.cs b/JobTrackerApi/Controllers/TwoFactorController.cs index ec2e02f..059ebcd 100644 --- a/JobTrackerApi/Controllers/TwoFactorController.cs +++ b/JobTrackerApi/Controllers/TwoFactorController.cs @@ -222,13 +222,14 @@ public sealed class TwoFactorController : ControllerBase if (user is null) return Unauthorized(); var currentHash = TrustedDeviceService.CurrentDeviceTokenHash(Request); + // SQLite/Pomelo cannot translate DateTimeOffset ORDER BY to SQL (same issue as the + // expiry check in TrustedDeviceService), so sort after materializing. var devices = await _db.TrustedDevices .Where(x => x.UserId == user.Id) - .OrderByDescending(x => x.LastSeenAtUtc) .Select(x => new TrustedDeviceDto(x.Id, x.DeviceLabel, x.CreatedAtUtc, x.LastSeenAtUtc, x.ExpiresAtUtc, currentHash != null && x.TokenHash == currentHash)) .ToListAsync(cancellationToken); - return Ok(devices); + return Ok(devices.OrderByDescending(x => x.LastSeenAtUtc).ToList()); } [HttpDelete("trusted-devices/{id:int}")] diff --git a/JobTrackerApi/Services/TrustedDeviceService.cs b/JobTrackerApi/Services/TrustedDeviceService.cs index f3af7ec..1b84c4f 100644 --- a/JobTrackerApi/Services/TrustedDeviceService.cs +++ b/JobTrackerApi/Services/TrustedDeviceService.cs @@ -26,10 +26,13 @@ public static class TrustedDeviceService var hash = HashToken(token); var now = DateTimeOffset.UtcNow; + // SQLite/Pomelo cannot translate DateTimeOffset relational comparisons (>) to SQL, so the + // expiry check has to happen after materializing the row -- fine here since the equality + // filters (UserId, TokenHash) already narrow this to at most one row. var match = await db.TrustedDevices .IgnoreQueryFilters() - .FirstOrDefaultAsync(x => x.UserId == userId && x.TokenHash == hash && x.ExpiresAtUtc > now, cancellationToken); - if (match is null) return false; + .FirstOrDefaultAsync(x => x.UserId == userId && x.TokenHash == hash, cancellationToken); + if (match is null || match.ExpiresAtUtc <= now) return false; match.LastSeenAtUtc = now; await db.SaveChangesAsync(cancellationToken);