feat(radius): add RADIUS server with MAC authentication (MAB), VLAN assignment, accounting and OpsServer API handlers
This commit is contained in:
@@ -2,4 +2,5 @@ export * from './admin.handler.js';
|
||||
export * from './config.handler.js';
|
||||
export * from './logs.handler.js';
|
||||
export * from './security.handler.js';
|
||||
export * from './stats.handler.js';
|
||||
export * from './stats.handler.js';
|
||||
export * from './radius.handler.js';
|
||||
405
ts/opsserver/handlers/radius.handler.ts
Normal file
405
ts/opsserver/handlers/radius.handler.ts
Normal file
@@ -0,0 +1,405 @@
|
||||
import * as plugins from '../../plugins.js';
|
||||
import type { OpsServer } from '../classes.opsserver.js';
|
||||
import * as interfaces from '../../../ts_interfaces/index.js';
|
||||
|
||||
export class RadiusHandler {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(private opsServerRef: OpsServer) {
|
||||
// Add this handler's router to the parent
|
||||
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
this.registerHandlers();
|
||||
}
|
||||
|
||||
private registerHandlers(): void {
|
||||
// ========================================================================
|
||||
// RADIUS Client Management
|
||||
// ========================================================================
|
||||
|
||||
// Get all RADIUS clients
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusClients>(
|
||||
'getRadiusClients',
|
||||
async (dataArg, toolsArg) => {
|
||||
const radiusServer = this.opsServerRef.dcRouterRef.radiusServer;
|
||||
|
||||
if (!radiusServer) {
|
||||
return { clients: [] };
|
||||
}
|
||||
|
||||
const clients = radiusServer.getClients();
|
||||
return {
|
||||
clients: clients.map(c => ({
|
||||
name: c.name,
|
||||
ipRange: c.ipRange,
|
||||
description: c.description,
|
||||
enabled: c.enabled,
|
||||
})),
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Add or update a RADIUS client
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SetRadiusClient>(
|
||||
'setRadiusClient',
|
||||
async (dataArg, toolsArg) => {
|
||||
const radiusServer = this.opsServerRef.dcRouterRef.radiusServer;
|
||||
|
||||
if (!radiusServer) {
|
||||
return { success: false, message: 'RADIUS server not configured' };
|
||||
}
|
||||
|
||||
try {
|
||||
await radiusServer.addClient(dataArg.client);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Remove a RADIUS client
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RemoveRadiusClient>(
|
||||
'removeRadiusClient',
|
||||
async (dataArg, toolsArg) => {
|
||||
const radiusServer = this.opsServerRef.dcRouterRef.radiusServer;
|
||||
|
||||
if (!radiusServer) {
|
||||
return { success: false, message: 'RADIUS server not configured' };
|
||||
}
|
||||
|
||||
const removed = radiusServer.removeClient(dataArg.name);
|
||||
return {
|
||||
success: removed,
|
||||
message: removed ? undefined : 'Client not found',
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// ========================================================================
|
||||
// VLAN Mapping Management
|
||||
// ========================================================================
|
||||
|
||||
// Get all VLAN mappings
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetVlanMappings>(
|
||||
'getVlanMappings',
|
||||
async (dataArg, toolsArg) => {
|
||||
const radiusServer = this.opsServerRef.dcRouterRef.radiusServer;
|
||||
|
||||
if (!radiusServer) {
|
||||
return {
|
||||
mappings: [],
|
||||
config: { defaultVlan: 1, allowUnknownMacs: true },
|
||||
};
|
||||
}
|
||||
|
||||
const vlanManager = radiusServer.getVlanManager();
|
||||
const mappings = vlanManager.getAllMappings();
|
||||
const config = vlanManager.getConfig();
|
||||
|
||||
return {
|
||||
mappings: mappings.map(m => ({
|
||||
mac: m.mac,
|
||||
vlan: m.vlan,
|
||||
description: m.description,
|
||||
enabled: m.enabled,
|
||||
createdAt: m.createdAt,
|
||||
updatedAt: m.updatedAt,
|
||||
})),
|
||||
config: {
|
||||
defaultVlan: config.defaultVlan,
|
||||
allowUnknownMacs: config.allowUnknownMacs,
|
||||
},
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Add or update a VLAN mapping
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SetVlanMapping>(
|
||||
'setVlanMapping',
|
||||
async (dataArg, toolsArg) => {
|
||||
const radiusServer = this.opsServerRef.dcRouterRef.radiusServer;
|
||||
|
||||
if (!radiusServer) {
|
||||
return { success: false, message: 'RADIUS server not configured' };
|
||||
}
|
||||
|
||||
try {
|
||||
const vlanManager = radiusServer.getVlanManager();
|
||||
const mapping = await vlanManager.addMapping(dataArg.mapping);
|
||||
return {
|
||||
success: true,
|
||||
mapping: {
|
||||
mac: mapping.mac,
|
||||
vlan: mapping.vlan,
|
||||
description: mapping.description,
|
||||
enabled: mapping.enabled,
|
||||
createdAt: mapping.createdAt,
|
||||
updatedAt: mapping.updatedAt,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return { success: false, message: error.message };
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Remove a VLAN mapping
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RemoveVlanMapping>(
|
||||
'removeVlanMapping',
|
||||
async (dataArg, toolsArg) => {
|
||||
const radiusServer = this.opsServerRef.dcRouterRef.radiusServer;
|
||||
|
||||
if (!radiusServer) {
|
||||
return { success: false, message: 'RADIUS server not configured' };
|
||||
}
|
||||
|
||||
const vlanManager = radiusServer.getVlanManager();
|
||||
const removed = await vlanManager.removeMapping(dataArg.mac);
|
||||
return {
|
||||
success: removed,
|
||||
message: removed ? undefined : 'Mapping not found',
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Update VLAN configuration
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateVlanConfig>(
|
||||
'updateVlanConfig',
|
||||
async (dataArg, toolsArg) => {
|
||||
const radiusServer = this.opsServerRef.dcRouterRef.radiusServer;
|
||||
|
||||
if (!radiusServer) {
|
||||
return {
|
||||
success: false,
|
||||
config: { defaultVlan: 1, allowUnknownMacs: true },
|
||||
};
|
||||
}
|
||||
|
||||
const vlanManager = radiusServer.getVlanManager();
|
||||
vlanManager.updateConfig({
|
||||
defaultVlan: dataArg.defaultVlan,
|
||||
allowUnknownMacs: dataArg.allowUnknownMacs,
|
||||
});
|
||||
|
||||
const config = vlanManager.getConfig();
|
||||
return {
|
||||
success: true,
|
||||
config: {
|
||||
defaultVlan: config.defaultVlan,
|
||||
allowUnknownMacs: config.allowUnknownMacs,
|
||||
},
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Test VLAN assignment
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_TestVlanAssignment>(
|
||||
'testVlanAssignment',
|
||||
async (dataArg, toolsArg) => {
|
||||
const radiusServer = this.opsServerRef.dcRouterRef.radiusServer;
|
||||
|
||||
if (!radiusServer) {
|
||||
return { assigned: false, vlan: 0, isDefault: false };
|
||||
}
|
||||
|
||||
const vlanManager = radiusServer.getVlanManager();
|
||||
const result = vlanManager.assignVlan(dataArg.mac);
|
||||
|
||||
return {
|
||||
assigned: result.assigned,
|
||||
vlan: result.vlan,
|
||||
isDefault: result.isDefault,
|
||||
matchedRule: result.matchedRule
|
||||
? {
|
||||
mac: result.matchedRule.mac,
|
||||
vlan: result.matchedRule.vlan,
|
||||
description: result.matchedRule.description,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// ========================================================================
|
||||
// Accounting / Session Management
|
||||
// ========================================================================
|
||||
|
||||
// Get active sessions
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusSessions>(
|
||||
'getRadiusSessions',
|
||||
async (dataArg, toolsArg) => {
|
||||
const radiusServer = this.opsServerRef.dcRouterRef.radiusServer;
|
||||
|
||||
if (!radiusServer) {
|
||||
return { sessions: [], totalCount: 0 };
|
||||
}
|
||||
|
||||
const accountingManager = radiusServer.getAccountingManager();
|
||||
let sessions = accountingManager.getActiveSessions();
|
||||
|
||||
// Apply filters
|
||||
if (dataArg.filter) {
|
||||
if (dataArg.filter.username) {
|
||||
sessions = sessions.filter(s => s.username === dataArg.filter!.username);
|
||||
}
|
||||
if (dataArg.filter.nasIpAddress) {
|
||||
sessions = sessions.filter(s => s.nasIpAddress === dataArg.filter!.nasIpAddress);
|
||||
}
|
||||
if (dataArg.filter.vlanId !== undefined) {
|
||||
sessions = sessions.filter(s => s.vlanId === dataArg.filter!.vlanId);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sessions: sessions.map(s => ({
|
||||
sessionId: s.sessionId,
|
||||
username: s.username,
|
||||
macAddress: s.macAddress,
|
||||
nasIpAddress: s.nasIpAddress,
|
||||
nasIdentifier: s.nasIdentifier,
|
||||
vlanId: s.vlanId,
|
||||
framedIpAddress: s.framedIpAddress,
|
||||
startTime: s.startTime,
|
||||
lastUpdateTime: s.lastUpdateTime,
|
||||
status: s.status,
|
||||
inputOctets: s.inputOctets,
|
||||
outputOctets: s.outputOctets,
|
||||
sessionTime: s.sessionTime,
|
||||
})),
|
||||
totalCount: sessions.length,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Disconnect a session
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DisconnectRadiusSession>(
|
||||
'disconnectRadiusSession',
|
||||
async (dataArg, toolsArg) => {
|
||||
const radiusServer = this.opsServerRef.dcRouterRef.radiusServer;
|
||||
|
||||
if (!radiusServer) {
|
||||
return { success: false, message: 'RADIUS server not configured' };
|
||||
}
|
||||
|
||||
const accountingManager = radiusServer.getAccountingManager();
|
||||
const disconnected = await accountingManager.disconnectSession(
|
||||
dataArg.sessionId,
|
||||
dataArg.reason || 'AdminReset'
|
||||
);
|
||||
|
||||
return {
|
||||
success: disconnected,
|
||||
message: disconnected ? undefined : 'Session not found',
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Get accounting summary
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusAccountingSummary>(
|
||||
'getRadiusAccountingSummary',
|
||||
async (dataArg, toolsArg) => {
|
||||
const radiusServer = this.opsServerRef.dcRouterRef.radiusServer;
|
||||
|
||||
if (!radiusServer) {
|
||||
return {
|
||||
summary: {
|
||||
periodStart: dataArg.startTime,
|
||||
periodEnd: dataArg.endTime,
|
||||
totalSessions: 0,
|
||||
activeSessions: 0,
|
||||
totalInputBytes: 0,
|
||||
totalOutputBytes: 0,
|
||||
totalSessionTime: 0,
|
||||
averageSessionDuration: 0,
|
||||
uniqueUsers: 0,
|
||||
sessionsByVlan: {},
|
||||
topUsersByTraffic: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const accountingManager = radiusServer.getAccountingManager();
|
||||
const summary = await accountingManager.getSummary(dataArg.startTime, dataArg.endTime);
|
||||
|
||||
return { summary };
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// ========================================================================
|
||||
// Statistics
|
||||
// ========================================================================
|
||||
|
||||
// Get RADIUS statistics
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRadiusStatistics>(
|
||||
'getRadiusStatistics',
|
||||
async (dataArg, toolsArg) => {
|
||||
const radiusServer = this.opsServerRef.dcRouterRef.radiusServer;
|
||||
|
||||
if (!radiusServer) {
|
||||
return {
|
||||
stats: {
|
||||
running: false,
|
||||
uptime: 0,
|
||||
authRequests: 0,
|
||||
authAccepts: 0,
|
||||
authRejects: 0,
|
||||
accountingRequests: 0,
|
||||
activeSessions: 0,
|
||||
vlanMappings: 0,
|
||||
clients: 0,
|
||||
},
|
||||
vlanStats: {
|
||||
totalMappings: 0,
|
||||
enabledMappings: 0,
|
||||
exactMatches: 0,
|
||||
ouiPatterns: 0,
|
||||
wildcardPatterns: 0,
|
||||
},
|
||||
accountingStats: {
|
||||
activeSessions: 0,
|
||||
totalSessionsStarted: 0,
|
||||
totalSessionsStopped: 0,
|
||||
totalInputBytes: 0,
|
||||
totalOutputBytes: 0,
|
||||
interimUpdatesReceived: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const stats = radiusServer.getStats();
|
||||
const vlanStats = radiusServer.getVlanManager().getStats();
|
||||
const accountingStats = radiusServer.getAccountingManager().getStats();
|
||||
|
||||
return {
|
||||
stats,
|
||||
vlanStats,
|
||||
accountingStats,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user