check method
Inspects context and returns a SecurityResult.
Implementations should be side-effect-light with respect to
context.body (avoid destructive mutation) but may freely read/write
context.metadata to communicate with later layers.
Implementation
@override
Future<SecurityResult> check(RequestContext context) async {
final body = context.getMeta<dynamic>('sanitizedBody') ?? context.body;
final amount = Validators.extractAmount(body) ?? 0.0;
final requestFrequency = context.getMeta<int>('requestFrequency') ?? 1;
final isNewDevice = await _isNewDevice(context);
final isIpAnomaly = await _isIpAnomaly(context);
final features = RiskFeatures(
amount: amount,
requestFrequency: requestFrequency,
isNewDevice: isNewDevice,
isIpAnomaly: isIpAnomaly,
);
context.setMeta('riskFeatures', features.toJson());
final score = _computeRiskScore(features);
context.setMeta('riskScore', score);
// Record this device/IP as known for the user going forward, so
// repeat requests from the same device/IP are no longer "new".
await _rememberDeviceAndIp(context);
final flags = <String>[];
if (features.amount > highAmountThreshold) flags.add('high_amount');
if (features.isNewDevice) flags.add('new_device');
if (features.isIpAnomaly) flags.add('ip_anomaly');
if (features.requestFrequency > highFrequencyThreshold) {
flags.add('high_frequency');
}
if (score > blockThreshold) {
return SecurityResult.block(
message: 'Anomaly score too high (${score.toStringAsFixed(2)})',
flags: flags,
riskScore: score,
);
}
if (score > flagThreshold) {
return SecurityResult.flag(
message:
'Request flagged for review (score ${score.toStringAsFixed(2)})',
flags: flags,
riskScore: score,
);
}
return SecurityResult.allow(
message: 'No significant anomaly detected',
riskScore: score,
flags: flags,
);
}