Skip to content
Open
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
36 changes: 36 additions & 0 deletions PatchPanda.Web/Helpers/JwtHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Text.Json;

namespace PatchPanda.Web.Helpers;

public class JwtHelper
{
public static DateTime? GetJwtExpiry(string jwt)
{
try
{
var parts = jwt.Split('.');
if (parts.Length < 2) return null;
Comment on lines +7 to +12
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard against null/whitespace and malformed JWTs to avoid invalid caching.

jwt can still be null/empty at runtime, and accepting 2-part tokens can treat malformed JWTs as valid. Add an early guard and require exactly 3 parts.

🛠️ Proposed fix
public static DateTime? GetJwtExpiry(string jwt)
{
+    if (string.IsNullOrWhiteSpace(jwt)) return null;
     try
     {
         var parts = jwt.Split('.');
-        if (parts.Length < 2) return null;
+        if (parts.Length != 3) return null;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static DateTime? GetJwtExpiry(string jwt)
{
try
{
var parts = jwt.Split('.');
if (parts.Length < 2) return null;
public static DateTime? GetJwtExpiry(string jwt)
{
if (string.IsNullOrWhiteSpace(jwt)) return null;
try
{
var parts = jwt.Split('.');
if (parts.Length != 3) return null;
🤖 Prompt for AI Agents
In `@PatchPanda.Web/Helpers/JwtHelper.cs` around lines 7 - 12, GetJwtExpiry
currently accepts null/empty and 2-part strings which can treat malformed tokens
as valid; add an early guard in GetJwtExpiry to return null if the jwt parameter
is null, empty, or whitespace, then require that jwt.Split('.') yields exactly 3
parts (not >=2) before proceeding; if the parts count is not 3, return null to
avoid parsing invalid tokens and invalid caching.


var payload = parts[1];
var base64 = payload.Replace('-', '+').Replace('_', '/');
switch (base64.Length % 4)
{
case 2: base64 += "=="; break;
case 3: base64 += "="; break;
}

var bytes = Convert.FromBase64String(base64);
using var doc = JsonDocument.Parse(bytes);
if (doc.RootElement.TryGetProperty("exp", out var expElement) && expElement.TryGetInt64(out var exp))
{
return DateTimeOffset.FromUnixTimeSeconds(exp).UtcDateTime;
}
}
catch
{
return null;
}

return null;
}
}
7 changes: 5 additions & 2 deletions PatchPanda.Web/Services/PortainerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ private async Task EnsureAuthenticatedAsync()
return;
}

_logger.LogInformation("Using username and password to authenticate with Portainer...");

var payload = JsonSerializer.Serialize(new { username = _username, password = _password });
var resp = await _httpClient.PostAsync(
"/api/auth",
Expand Down Expand Up @@ -94,10 +96,11 @@ private async Task EnsureAuthenticatedAsync()
"Bearer",
_jwt
);
_jwtExpiry = DateTime.UtcNow.AddHours(8);
_jwtExpiry = JwtHelper.GetJwtExpiry(_jwt) ?? DateTime.UtcNow.AddHours(8);
_logger.LogInformation("Portainer authentication successful.");
Comment on lines +99 to +100
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider a small clock-skew buffer when storing JWT expiry.

Using the exact exp can cause near-expiry requests to fail if clocks drift or the request is in-flight.

🧩 Suggested tweak
-            _jwtExpiry = JwtHelper.GetJwtExpiry(_jwt) ?? DateTime.UtcNow.AddHours(8);
+            var exp = JwtHelper.GetJwtExpiry(_jwt);
+            _jwtExpiry = exp?.AddSeconds(-60) ?? DateTime.UtcNow.AddHours(8);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_jwtExpiry = JwtHelper.GetJwtExpiry(_jwt) ?? DateTime.UtcNow.AddHours(8);
_logger.LogInformation("Portainer authentication successful.");
var exp = JwtHelper.GetJwtExpiry(_jwt);
_jwtExpiry = exp?.AddSeconds(-60) ?? DateTime.UtcNow.AddHours(8);
_logger.LogInformation("Portainer authentication successful.");
🤖 Prompt for AI Agents
In `@PatchPanda.Web/Services/PortainerService.cs` around lines 99 - 100, Adjust
how the JWT expiry is stored to include a small clock-skew buffer: when setting
_jwtExpiry from JwtHelper.GetJwtExpiry(_jwt) (or the fallback
DateTime.UtcNow.AddHours(8)), subtract a short buffer (e.g., 1–2 minutes) so you
consider tokens expired slightly before the exact exp claim to avoid
near-expiry/in-flight failures; update the assignment to compute the expiry
value, apply TimeSpan.FromMinutes(1) (or chosen buffer) subtraction, and store
that in _jwtExpiry.

}
else
_logger.LogWarning("Failed parsing Portainer auth response");
_logger.LogWarning("Failed parsing Portainer auth response.");
}

private async Task<PortainerStackDto?> GetStack(string stackName)
Expand Down
Loading