Fixes for api routes
This commit is contained in:
parent
52875f13a5
commit
40f2741407
|
|
@ -401,13 +401,19 @@ class SNSController extends BaseController
|
|||
*/
|
||||
private function handleSubscriptionConfirmation(array $snsData)
|
||||
{
|
||||
$subscribeUrl = $snsData['SubscribeURL'] ?? null;
|
||||
// Verify the subscription confirmation payload
|
||||
$verificationResult = $this->verifySubscriptionConfirmationPayload($snsData);
|
||||
|
||||
if (!$subscribeUrl) {
|
||||
nlog('SNS Subscription confirmation: Missing SubscribeURL');
|
||||
return response()->json(['error' => 'Missing SubscribeURL'], 400);
|
||||
if (!$verificationResult['valid']) {
|
||||
nlog('SNS Subscription confirmation: Payload verification failed', [
|
||||
'errors' => $verificationResult['errors'],
|
||||
'payload' => $snsData
|
||||
]);
|
||||
return response()->json(['error' => 'Invalid subscription confirmation payload', 'details' => $verificationResult['errors']], 400);
|
||||
}
|
||||
|
||||
$subscribeUrl = $snsData['SubscribeURL'];
|
||||
|
||||
nlog('SNS Subscription confirmation received', [
|
||||
'topic_arn' => $snsData['TopicArn'] ?? 'unknown',
|
||||
'subscribe_url' => $subscribeUrl
|
||||
|
|
@ -416,7 +422,7 @@ class SNSController extends BaseController
|
|||
// You can optionally make an HTTP request to confirm the subscription
|
||||
// This is required by AWS to complete the SNS subscription setup
|
||||
try {
|
||||
$response = file_get_contents($subscribeUrl);
|
||||
$response = \Illuminate\Support\Facades\Http::timeout(10)->get($subscribeUrl);
|
||||
nlog('SNS Subscription confirmed', ['response' => $response]);
|
||||
} catch (\Exception $e) {
|
||||
nlog('SNS Subscription confirmation failed', ['error' => $e->getMessage()]);
|
||||
|
|
@ -425,6 +431,133 @@ class SNSController extends BaseController
|
|||
return response()->json(['status' => 'subscription_confirmed']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verify SNS subscription confirmation payload structure and content
|
||||
*
|
||||
* @param array $snsData
|
||||
* @return array ['valid' => bool, 'errors' => array]
|
||||
*/
|
||||
private function verifySubscriptionConfirmationPayload(array $snsData): array
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
// Required fields for subscription confirmation
|
||||
$requiredFields = [
|
||||
'Type' => 'SubscriptionConfirmation',
|
||||
'MessageId' => 'string',
|
||||
'TopicArn' => 'string',
|
||||
'SubscribeURL' => 'string',
|
||||
'Timestamp' => 'string',
|
||||
'Token' => 'string'
|
||||
];
|
||||
|
||||
// Validate required fields exist and have correct types
|
||||
foreach ($requiredFields as $field => $expectedType) {
|
||||
if (!isset($snsData[$field])) {
|
||||
$errors[] = "Missing required field: {$field}";
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $snsData[$field];
|
||||
|
||||
// Type-specific validation
|
||||
if ($expectedType === 'string' && !is_string($value)) {
|
||||
$errors[] = "Field '{$field}' must be a string";
|
||||
} elseif ($expectedType === 'SubscriptionConfirmation' && $value !== 'SubscriptionConfirmation') {
|
||||
$errors[] = "Field '{$field}' must be 'SubscriptionConfirmation'";
|
||||
}
|
||||
}
|
||||
|
||||
// Validate specific field formats
|
||||
if (isset($snsData['MessageId']) && !$this->isValidMessageId($snsData['MessageId'])) {
|
||||
$errors[] = 'Invalid MessageId format';
|
||||
}
|
||||
|
||||
if (isset($snsData['TopicArn']) && !$this->isValidTopicArn($snsData['TopicArn'])) {
|
||||
$errors[] = 'Invalid TopicArn format';
|
||||
}
|
||||
|
||||
if (isset($snsData['SubscribeURL']) && !$this->isValidSubscribeUrl($snsData['SubscribeURL'])) {
|
||||
$errors[] = 'Invalid SubscribeURL format or domain';
|
||||
}
|
||||
|
||||
if (isset($snsData['Timestamp']) && !$this->isValidISOTimestamp($snsData['Timestamp'])) {
|
||||
$errors[] = 'Invalid Timestamp format (must be ISO 8601)';
|
||||
}
|
||||
|
||||
if (isset($snsData['Token']) && !$this->isValidSubscriptionToken($snsData['Token'])) {
|
||||
$errors[] = 'Invalid Token format';
|
||||
}
|
||||
|
||||
// Validate TopicArn matches expected if configured
|
||||
if (isset($snsData['TopicArn']) && !empty($this->expectedTopicArn)) {
|
||||
if ($snsData['TopicArn'] !== $this->expectedTopicArn) {
|
||||
$errors[] = 'TopicArn does not match expected value';
|
||||
}
|
||||
}
|
||||
|
||||
// Check for replay attacks (messages older than 15 minutes)
|
||||
if (isset($snsData['Timestamp'])) {
|
||||
$messageTimestamp = strtotime($snsData['Timestamp']);
|
||||
$currentTimestamp = time();
|
||||
if (($currentTimestamp - $messageTimestamp) > 900) { // 15 minutes
|
||||
$errors[] = 'Message timestamp is too old (potential replay attack)';
|
||||
}
|
||||
}
|
||||
|
||||
// Check for suspicious content patterns
|
||||
if ($this->containsSuspiciousContent($snsData)) {
|
||||
$errors[] = 'Payload contains suspicious content patterns';
|
||||
}
|
||||
|
||||
return [
|
||||
'valid' => empty($errors),
|
||||
'errors' => $errors
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate MessageId format (should be a UUID-like string)
|
||||
*
|
||||
* @param string $messageId
|
||||
* @return bool
|
||||
*/
|
||||
private function isValidMessageId(string $messageId): bool
|
||||
{
|
||||
// AWS SNS MessageId is typically a UUID format
|
||||
return preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $messageId) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate TopicArn format
|
||||
*
|
||||
* @param string $topicArn
|
||||
* @return bool
|
||||
*/
|
||||
private function isValidTopicArn(string $topicArn): bool
|
||||
{
|
||||
// AWS SNS Topic ARN format: arn:aws:sns:region:account-id:topic-name
|
||||
return preg_match('/^arn:aws:sns:[a-z0-9-]+:[0-9]{12}:[a-zA-Z0-9_-]+$/', $topicArn) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate subscription token format
|
||||
*
|
||||
* @param string $token
|
||||
* @return bool
|
||||
*/
|
||||
private function isValidSubscriptionToken(string $token): bool
|
||||
{
|
||||
// AWS SNS subscription tokens are typically long alphanumeric strings
|
||||
if (strlen($token) < 20 || strlen($token) > 200) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Should contain only alphanumeric characters and common symbols
|
||||
return preg_match('/^[a-zA-Z0-9+\/=\-_]+$/', $token) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle SES notification from SNS
|
||||
*
|
||||
|
|
|
|||
|
|
@ -467,8 +467,8 @@ Route::group(['middleware' => ['throttle:api', 'token_auth', 'valid_json','local
|
|||
|
||||
});
|
||||
|
||||
Route::post('api/v1/sms_reset', [TwilioController::class, 'generate2faResetCode'])->name('sms_reset.generate')->middleware('throttle:1,3');
|
||||
Route::post('api/v1/sms_reset/confirm', [TwilioController::class, 'confirm2faResetCode'])->name('sms_reset.confirm')->middleware('throttle:2,1');
|
||||
Route::post('api/v1/sms_reset', [TwilioController::class, 'generate2faResetCode'])->name('sms_reset.generate')->middleware('throttle:1,2');
|
||||
Route::post('api/v1/sms_reset/confirm', [TwilioController::class, 'confirm2faResetCode'])->name('sms_reset.confirm')->middleware('throttle:5,1');
|
||||
|
||||
Route::match(['get', 'post'], 'payment_webhook/{company_key}/{company_gateway_id}', PaymentWebhookController::class)
|
||||
->middleware('throttle:1000,1')
|
||||
|
|
|
|||
Loading…
Reference in New Issue