feat: implement RadiusService to manage guest user registration, session limits, and RADIUS authentication attributes
Build and Push Docker Image to Gitea Container Registry / build-and-push (push) Successful in 1m4s

This commit is contained in:
Tygozwolle
2026-07-24 18:20:50 +02:00
parent 0e4189985e
commit b730dced16
+52 -13
View File
@@ -106,13 +106,14 @@ public class RadiusService : IRadiusService
Value = password Value = password
}); });
// Set Session-Timeout to 60 seconds to force frequent re-authentication (validation) // Set Session-Timeout to the full allowed time. When this expires, the AP will drop the user natively.
long sessionTimeoutSeconds = (long)config.DefaultSessionTimeMinutes * 60;
_db.RadReply.Add(new RadReply _db.RadReply.Add(new RadReply
{ {
Username = username, Username = username,
Attribute = "Session-Timeout", Attribute = "Session-Timeout",
Op = "=", Op = "=",
Value = "60" Value = sessionTimeoutSeconds.ToString()
}); });
// Set Max-Octets (in bytes) // Set Max-Octets (in bytes)
@@ -134,14 +135,7 @@ public class RadiusService : IRadiusService
Value = "60" Value = "60"
}); });
// Re-authenticate (instead of disconnect) when Session-Timeout fires
_db.RadReply.Add(new RadReply
{
Username = username,
Attribute = "Termination-Action",
Op = "=",
Value = "1"
});
// Create Guest Profile record // Create Guest Profile record
var profile = new GuestProfile var profile = new GuestProfile
@@ -233,11 +227,11 @@ public class RadiusService : IRadiusService
profile.SessionTimeMinutes = newSessionTimeMinutes; profile.SessionTimeMinutes = newSessionTimeMinutes;
profile.DataLimitMb = newDataLimitMb; profile.DataLimitMb = newDataLimitMb;
// Ensure Session-Timeout is strictly 60 seconds for frequent validation // Update RadReply Session-Timeout to the full allowed time
var sessionReply = await _db.RadReply.FirstOrDefaultAsync(rr => rr.Username == profile.Username && rr.Attribute == "Session-Timeout"); var sessionReply = await _db.RadReply.FirstOrDefaultAsync(rr => rr.Username == profile.Username && rr.Attribute == "Session-Timeout");
if (sessionReply != null) if (sessionReply != null)
{ {
sessionReply.Value = "60"; sessionReply.Value = (newSessionTimeMinutes * 60).ToString();
} }
else else
{ {
@@ -246,7 +240,7 @@ public class RadiusService : IRadiusService
Username = profile.Username, Username = profile.Username,
Attribute = "Session-Timeout", Attribute = "Session-Timeout",
Op = "=", Op = "=",
Value = "60" Value = (newSessionTimeMinutes * 60).ToString()
}); });
} }
@@ -291,6 +285,9 @@ public class RadiusService : IRadiusService
}); });
await _db.SaveChangesAsync(); await _db.SaveChangesAsync();
// Disconnect active sessions immediately
await DisconnectActiveSessionsAsync(profile.Username);
} }
public async Task DeleteUserAsync(int profileId) public async Task DeleteUserAsync(int profileId)
@@ -308,6 +305,9 @@ public class RadiusService : IRadiusService
_db.GuestProfiles.Remove(profile); _db.GuestProfiles.Remove(profile);
await _db.SaveChangesAsync(); await _db.SaveChangesAsync();
// Disconnect active sessions immediately
await DisconnectActiveSessionsAsync(username);
} }
public async Task ResetUserPasswordAsync(int profileId, string newPassword) public async Task ResetUserPasswordAsync(int profileId, string newPassword)
@@ -374,4 +374,43 @@ public class RadiusService : IRadiusService
} }
return new string(result); return new string(result);
} }
private async Task DisconnectActiveSessionsAsync(string username)
{
var activeSessions = await _db.RadAcct
.Where(ra => ra.Username == username && ra.AcctStopTime == null)
.Select(ra => ra.NasIpAddress)
.Distinct()
.ToListAsync();
var radiusSecret = Environment.GetEnvironmentVariable("RADIUS_SECRET") ?? "radpass";
foreach (var nasIp in activeSessions)
{
if (string.IsNullOrWhiteSpace(nasIp)) continue;
try
{
var process = new System.Diagnostics.Process
{
StartInfo = new System.Diagnostics.ProcessStartInfo
{
FileName = "sh",
Arguments = $"-c \"echo 'User-Name={username}' | radclient -x {nasIp}:3799 disconnect '{radiusSecret}'\"",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
process.Start();
await process.WaitForExitAsync();
_logger.LogInformation("Sent manual RADIUS Disconnect-Request to NAS {NasIp} for user {Username}", nasIp, username);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to send manual RADIUS Disconnect-Request to NAS {NasIp}", nasIp);
}
}
}
} }