191 lines
6.2 KiB
TypeScript
191 lines
6.2 KiB
TypeScript
|
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||
|
|
import {
|
||
|
|
RadiusPacket,
|
||
|
|
ERadiusCode,
|
||
|
|
ERadiusAttributeType,
|
||
|
|
} from '../../ts_server/index.js';
|
||
|
|
|
||
|
|
tap.test('should encode and decode a basic Access-Request packet', async () => {
|
||
|
|
const identifier = 42;
|
||
|
|
const secret = 'testing123';
|
||
|
|
const attributes = [
|
||
|
|
{ type: ERadiusAttributeType.UserName, value: 'testuser' },
|
||
|
|
{ type: ERadiusAttributeType.UserPassword, value: 'testpass' },
|
||
|
|
{ type: ERadiusAttributeType.NasIpAddress, value: '192.168.1.1' },
|
||
|
|
{ type: ERadiusAttributeType.NasPort, value: 1 },
|
||
|
|
];
|
||
|
|
|
||
|
|
const packet = RadiusPacket.createAccessRequest(identifier, secret, attributes);
|
||
|
|
|
||
|
|
// Verify minimum packet size (20 bytes header)
|
||
|
|
expect(packet.length).toBeGreaterThanOrEqual(RadiusPacket.MIN_PACKET_SIZE);
|
||
|
|
|
||
|
|
// Verify maximum packet size
|
||
|
|
expect(packet.length).toBeLessThanOrEqual(RadiusPacket.MAX_PACKET_SIZE);
|
||
|
|
|
||
|
|
// Verify header
|
||
|
|
expect(packet[0]).toEqual(ERadiusCode.AccessRequest);
|
||
|
|
expect(packet[1]).toEqual(identifier);
|
||
|
|
|
||
|
|
// Decode the packet
|
||
|
|
const decoded = RadiusPacket.decode(packet);
|
||
|
|
expect(decoded.code).toEqual(ERadiusCode.AccessRequest);
|
||
|
|
expect(decoded.identifier).toEqual(identifier);
|
||
|
|
expect(decoded.authenticator.length).toEqual(16);
|
||
|
|
expect(decoded.attributes.length).toBeGreaterThan(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should handle packet length validation', async () => {
|
||
|
|
// Packet too short
|
||
|
|
const shortPacket = Buffer.alloc(19);
|
||
|
|
let error: Error | undefined;
|
||
|
|
try {
|
||
|
|
RadiusPacket.decode(shortPacket);
|
||
|
|
} catch (e) {
|
||
|
|
error = e as Error;
|
||
|
|
}
|
||
|
|
expect(error).toBeDefined();
|
||
|
|
expect(error!.message).toInclude('too short');
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should handle invalid length in header', async () => {
|
||
|
|
const packet = Buffer.alloc(20);
|
||
|
|
packet[0] = ERadiusCode.AccessRequest;
|
||
|
|
packet[1] = 1; // identifier
|
||
|
|
packet.writeUInt16BE(10, 2); // length too small
|
||
|
|
|
||
|
|
let error: Error | undefined;
|
||
|
|
try {
|
||
|
|
RadiusPacket.decode(packet);
|
||
|
|
} catch (e) {
|
||
|
|
error = e as Error;
|
||
|
|
}
|
||
|
|
expect(error).toBeDefined();
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should handle maximum packet size', async () => {
|
||
|
|
const secret = 'testing123';
|
||
|
|
const identifier = 1;
|
||
|
|
|
||
|
|
// Create a packet that would exceed max size
|
||
|
|
const hugeAttributes: Array<{ type: number; value: Buffer }> = [];
|
||
|
|
// Each attribute can be max 255 bytes. Create enough to exceed 4096
|
||
|
|
for (let i = 0; i < 20; i++) {
|
||
|
|
hugeAttributes.push({
|
||
|
|
type: ERadiusAttributeType.ReplyMessage,
|
||
|
|
value: Buffer.alloc(250, 65), // 250 bytes of 'A'
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// This should throw because packet would be too large
|
||
|
|
let error: Error | undefined;
|
||
|
|
try {
|
||
|
|
// Manually build the packet to test size limit
|
||
|
|
const rawAttrs = hugeAttributes.map((a) => ({
|
||
|
|
type: a.type,
|
||
|
|
value: a.value,
|
||
|
|
}));
|
||
|
|
|
||
|
|
RadiusPacket.encode({
|
||
|
|
code: ERadiusCode.AccessRequest,
|
||
|
|
identifier,
|
||
|
|
authenticator: Buffer.alloc(16),
|
||
|
|
attributes: rawAttrs,
|
||
|
|
});
|
||
|
|
} catch (e) {
|
||
|
|
error = e as Error;
|
||
|
|
}
|
||
|
|
expect(error).toBeDefined();
|
||
|
|
expect(error!.message).toInclude('too large');
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should parse and encode attributes correctly', async () => {
|
||
|
|
const secret = 'testing123';
|
||
|
|
const identifier = 1;
|
||
|
|
|
||
|
|
// Create packet with various attribute types
|
||
|
|
const packet = RadiusPacket.createAccessRequest(identifier, secret, [
|
||
|
|
{ type: 'User-Name', value: 'john.doe' }, // text
|
||
|
|
{ type: 'NAS-IP-Address', value: '10.0.0.1' }, // address
|
||
|
|
{ type: 'NAS-Port', value: 5060 }, // integer
|
||
|
|
{ type: 'NAS-Identifier', value: 'nas01.example.com' }, // text
|
||
|
|
]);
|
||
|
|
|
||
|
|
const decoded = RadiusPacket.decodeAndParse(packet);
|
||
|
|
|
||
|
|
// Find username
|
||
|
|
const usernameAttr = decoded.parsedAttributes.find(
|
||
|
|
(a) => a.type === ERadiusAttributeType.UserName
|
||
|
|
);
|
||
|
|
expect(usernameAttr).toBeDefined();
|
||
|
|
expect(usernameAttr!.value).toEqual('john.doe');
|
||
|
|
|
||
|
|
// Find NAS-IP-Address
|
||
|
|
const nasIpAttr = decoded.parsedAttributes.find(
|
||
|
|
(a) => a.type === ERadiusAttributeType.NasIpAddress
|
||
|
|
);
|
||
|
|
expect(nasIpAttr).toBeDefined();
|
||
|
|
expect(nasIpAttr!.value).toEqual('10.0.0.1');
|
||
|
|
|
||
|
|
// Find NAS-Port
|
||
|
|
const nasPortAttr = decoded.parsedAttributes.find(
|
||
|
|
(a) => a.type === ERadiusAttributeType.NasPort
|
||
|
|
);
|
||
|
|
expect(nasPortAttr).toBeDefined();
|
||
|
|
expect(nasPortAttr!.value).toEqual(5060);
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should create Access-Accept packet', async () => {
|
||
|
|
const identifier = 1;
|
||
|
|
const requestAuth = Buffer.alloc(16);
|
||
|
|
const secret = 'testing123';
|
||
|
|
|
||
|
|
const packet = RadiusPacket.createAccessAccept(identifier, requestAuth, secret, [
|
||
|
|
{ type: ERadiusAttributeType.ReplyMessage, value: 'Welcome!' },
|
||
|
|
{ type: ERadiusAttributeType.SessionTimeout, value: 3600 },
|
||
|
|
]);
|
||
|
|
|
||
|
|
const decoded = RadiusPacket.decode(packet);
|
||
|
|
expect(decoded.code).toEqual(ERadiusCode.AccessAccept);
|
||
|
|
expect(decoded.identifier).toEqual(identifier);
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should create Access-Reject packet', async () => {
|
||
|
|
const identifier = 1;
|
||
|
|
const requestAuth = Buffer.alloc(16);
|
||
|
|
const secret = 'testing123';
|
||
|
|
|
||
|
|
const packet = RadiusPacket.createAccessReject(identifier, requestAuth, secret, [
|
||
|
|
{ type: ERadiusAttributeType.ReplyMessage, value: 'Invalid credentials' },
|
||
|
|
]);
|
||
|
|
|
||
|
|
const decoded = RadiusPacket.decode(packet);
|
||
|
|
expect(decoded.code).toEqual(ERadiusCode.AccessReject);
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should create Access-Challenge packet', async () => {
|
||
|
|
const identifier = 1;
|
||
|
|
const requestAuth = Buffer.alloc(16);
|
||
|
|
const secret = 'testing123';
|
||
|
|
const state = Buffer.from('challenge-state-123');
|
||
|
|
|
||
|
|
const packet = RadiusPacket.createAccessChallenge(identifier, requestAuth, secret, [
|
||
|
|
{ type: ERadiusAttributeType.ReplyMessage, value: 'Enter OTP' },
|
||
|
|
{ type: ERadiusAttributeType.State, value: state },
|
||
|
|
]);
|
||
|
|
|
||
|
|
const decoded = RadiusPacket.decode(packet);
|
||
|
|
expect(decoded.code).toEqual(ERadiusCode.AccessChallenge);
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('should get code name', async () => {
|
||
|
|
expect(RadiusPacket.getCodeName(ERadiusCode.AccessRequest)).toEqual('Access-Request');
|
||
|
|
expect(RadiusPacket.getCodeName(ERadiusCode.AccessAccept)).toEqual('Access-Accept');
|
||
|
|
expect(RadiusPacket.getCodeName(ERadiusCode.AccessReject)).toEqual('Access-Reject');
|
||
|
|
expect(RadiusPacket.getCodeName(ERadiusCode.AccountingRequest)).toEqual('Accounting-Request');
|
||
|
|
expect(RadiusPacket.getCodeName(ERadiusCode.AccountingResponse)).toEqual('Accounting-Response');
|
||
|
|
expect(RadiusPacket.getCodeName(ERadiusCode.AccessChallenge)).toEqual('Access-Challenge');
|
||
|
|
});
|
||
|
|
|
||
|
|
export default tap.start();
|