diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index c7d93a5856..e1ddeb6b68 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1863,15 +1863,16 @@ App::post('/v1/account/tokens/magic-url') ->setRecipient($email) ->trigger(); - $queueForEvents->setPayload( - $response->output( - $token->setAttribute('secret', $tokenSecret), - Response::MODEL_TOKEN - ) - ); + // Set to unhashed secret for events and server responses + $token->setAttribute('secret', $tokenSecret); + + $queueForEvents + ->setPayload($response->output($token, Response::MODEL_TOKEN), sensitive: ['secret']); // Hide secret for clients - $token->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $tokenSecret : ''); + if (!$isPrivilegedUser && !$isAppUser) { + $token->setAttribute('secret', ''); + } if (!empty($phrase)) { $token->setAttribute('phrase', $phrase); @@ -1879,8 +1880,7 @@ App::post('/v1/account/tokens/magic-url') $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($token, Response::MODEL_TOKEN) - ; + ->dynamic($token, Response::MODEL_TOKEN); }); App::post('/v1/account/tokens/email') @@ -2092,15 +2092,16 @@ App::post('/v1/account/tokens/email') ->setRecipient($email) ->trigger(); - $queueForEvents->setPayload( - $response->output( - $token->setAttribute('secret', $tokenSecret), - Response::MODEL_TOKEN - ) - ); + // Set to unhashed secret for events and server responses + $token->setAttribute('secret', $tokenSecret); + + $queueForEvents + ->setPayload($response->output($token, Response::MODEL_TOKEN), sensitive: ['secret']); // Hide secret for clients - $token->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $tokenSecret : ''); + if (!$isPrivilegedUser && !$isAppUser) { + $token->setAttribute('secret', ''); + } if (!empty($phrase)) { $token->setAttribute('phrase', $phrase); @@ -2108,8 +2109,7 @@ App::post('/v1/account/tokens/email') $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($token, Response::MODEL_TOKEN) - ; + ->dynamic($token, Response::MODEL_TOKEN); }); App::put('/v1/account/sessions/magic-url') @@ -2326,20 +2326,18 @@ App::post('/v1/account/tokens/phone') ->setRecipients([$phone]) ->setProviderType(MESSAGE_TYPE_SMS); - $queueForEvents->setPayload( - $response->output( - $token->setAttribute('secret', $secret), - Response::MODEL_TOKEN - ) - ); + // Set to unhashed secret for events and server responses + $token->setAttribute('secret', $secret); + + $queueForEvents + ->setPayload($response->output($token, Response::MODEL_TOKEN), sensitive: ['secret']); // Hide secret for clients $token->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? Auth::encodeSession($user->getId(), $secret) : ''); $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->dynamic($token, Response::MODEL_TOKEN) - ; + ->dynamic($token, Response::MODEL_TOKEN); }); App::post('/v1/account/jwt') @@ -2987,18 +2985,19 @@ App::post('/v1/account/recovery') ->setSubject($subject) ->trigger(); + // Set to unhashed secret for events and server responses + $recovery->setAttribute('secret', $secret); + $queueForEvents ->setParam('userId', $profile->getId()) ->setParam('tokenId', $recovery->getId()) ->setUser($profile) - ->setPayload($response->output( - $recovery->setAttribute('secret', $secret), - Response::MODEL_TOKEN - )) - ; + ->setPayload($response->output($recovery, Response::MODEL_TOKEN), sensitive: ['secret']); // Hide secret for clients - $recovery->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $secret : ''); + if (!$isPrivilegedUser && !$isAppUser) { + $recovery->setAttribute('secret', ''); + } $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -3169,6 +3168,7 @@ App::post('/v1/account/verification') ->setParam('{{footer}}', $locale->getText("emails.verification.footer")) ->setParam('{{thanks}}', $locale->getText("emails.verification.thanks")) ->setParam('{{signature}}', $locale->getText("emails.verification.signature")); + $body = $message->render(); $smtp = $project->getAttribute('smtp', []); @@ -3235,16 +3235,18 @@ App::post('/v1/account/verification') ->setName($user->getAttribute('name') ?? '') ->trigger(); + // Set to unhashed secret for events and server responses + $verification->setAttribute('secret', $verificationSecret); + $queueForEvents ->setParam('userId', $user->getId()) ->setParam('tokenId', $verification->getId()) - ->setPayload($response->output( - $verification->setAttribute('secret', $verificationSecret), - Response::MODEL_TOKEN - )); + ->setPayload($response->output($verification, Response::MODEL_TOKEN), sensitive: ['secret']); // Hide secret for clients - $verification->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $verificationSecret : ''); + if (!$isPrivilegedUser && !$isAppUser) { + $verification->setAttribute('secret', ''); + } $response ->setStatusCode(Response::STATUS_CODE_CREATED) @@ -3294,7 +3296,7 @@ App::put('/v1/account/verification') $user->setAttributes($profile->getArrayCopy()); - $verificationDocument = $dbForProject->getDocument('tokens', $verifiedToken->getId()); + $verification = $dbForProject->getDocument('tokens', $verifiedToken->getId()); /** * We act like we're updating and validating @@ -3305,10 +3307,9 @@ App::put('/v1/account/verification') $queueForEvents ->setParam('userId', $userId) - ->setParam('tokenId', $verificationDocument->getId()) - ; + ->setParam('tokenId', $verification->getId()); - $response->dynamic($verificationDocument, Response::MODEL_TOKEN); + $response->dynamic($verification, Response::MODEL_TOKEN); }); App::post('/v1/account/verification/phone') @@ -3406,17 +3407,18 @@ App::post('/v1/account/verification/phone') ->setRecipients([$user->getAttribute('phone')]) ->setProviderType(MESSAGE_TYPE_SMS); + // Set to unhashed secret for events and server responses + $verification->setAttribute('secret', $secret); + $queueForEvents ->setParam('userId', $user->getId()) ->setParam('tokenId', $verification->getId()) - ->setPayload($response->output( - $verification->setAttribute('secret', $secret), - Response::MODEL_TOKEN - )) - ; + ->setPayload($response->output($verification, Response::MODEL_TOKEN), sensitive: ['secret']); // Hide secret for clients - $verification->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $secret : ''); + if (!$isPrivilegedUser && !$isAppUser) { + $verification->setAttribute('secret', ''); + } $response ->setStatusCode(Response::STATUS_CODE_CREATED) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index d8899822d8..bb9408e692 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -586,7 +586,7 @@ App::shutdown() Realtime::send( projectId: $target['projectId'] ?? $project->getId(), - payload: $queueForEvents->getPayload(), + payload: $queueForEvents->getRealtimePayload(), events: $allEvents, channels: $target['channels'], roles: $target['roles'], diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index b80ba9333d..5e73378743 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -49,6 +49,7 @@ class Event protected string $class = ''; protected string $event = ''; protected array $params = []; + protected array $sensitive = []; protected array $payload = []; protected array $context = []; protected ?Document $project = null; @@ -158,12 +159,17 @@ class Event * Set payload for this event. * * @param array $payload + * @param array $sensitive * @return self */ - public function setPayload(array $payload): self + public function setPayload(array $payload, array $sensitive = []): self { $this->payload = $payload; + foreach ($sensitive as $key) { + $this->sensitive[$key] = true; + } + return $this; } @@ -177,6 +183,19 @@ class Event return $this->payload; } + public function getRealtimePayload(): array + { + $payload = []; + + foreach ($this->payload as $key => $value) { + if (!isset($this->sensitive[$key])) { + $payload[$key] = $value; + } + } + + return $payload; + } + /** * Set context for this event. * @@ -239,6 +258,13 @@ class Event return $this; } + public function setParamSensitive(string $key): self + { + $this->sensitive[$key] = true; + + return $this; + } + /** * Get param of event. * @@ -291,6 +317,7 @@ class Event public function reset(): self { $this->params = []; + $this->sensitive = []; return $this; } diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 62f5cfe435..8cdd325501 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -1282,8 +1282,8 @@ class ProjectsConsoleClientTest extends Scope 'name' => $name, ]); - $this->assertEquals($response['body']['type'], Exception::USER_COUNT_EXCEEDED); - $this->assertEquals($response['headers']['status-code'], 400); + $this->assertEquals(Exception::USER_COUNT_EXCEEDED, $response['body']['type']); + $this->assertEquals(400, $response['headers']['status-code']); /** diff --git a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php index 9660893074..c3372b98c5 100644 --- a/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeCustomClientTest.php @@ -290,7 +290,6 @@ class RealtimeCustomClientTest extends Scope $this->assertEquals($name, $response['data']['payload']['name']); - /** * Test Account Password Event */ @@ -376,6 +375,7 @@ class RealtimeCustomClientTest extends Scope $this->assertNotEmpty($response['data']); $this->assertCount(2, $response['data']['channels']); $this->assertArrayHasKey('timestamp', $response['data']); + $this->assertArrayNotHasKey('secret', $response['data']); $this->assertContains('account', $response['data']['channels']); $this->assertContains('account.' . $userId, $response['data']['channels']); $this->assertContains("users.{$userId}.verification.{$verificationId}.create", $response['data']['events']); diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php index f331db905b..a170492551 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomClientTest.php @@ -44,7 +44,7 @@ class WebhooksCustomClientTest extends Scope $webhook = $this->getLastRequest(); $signatureKey = $this->getProject()['signatureKey']; $payload = json_encode($webhook['data']); - $url = $webhook['url']; + $url = $webhook['url']; $signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $signatureKey, true)); $this->assertEquals($webhook['method'], 'POST'); @@ -120,7 +120,7 @@ class WebhooksCustomClientTest extends Scope $webhook = $this->getLastRequest(); $signatureKey = $this->getProject()['signatureKey']; $payload = json_encode($webhook['data']); - $url = $webhook['url']; + $url = $webhook['url']; $signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $signatureKey, true)); $this->assertEquals($webhook['method'], 'POST'); @@ -174,7 +174,7 @@ class WebhooksCustomClientTest extends Scope $webhook = $this->getLastRequest(); $signatureKey = $this->getProject()['signatureKey']; $payload = json_encode($webhook['data']); - $url = $webhook['url']; + $url = $webhook['url']; $signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $signatureKey, true)); $this->assertEquals($webhook['method'], 'POST'); @@ -263,7 +263,7 @@ class WebhooksCustomClientTest extends Scope $webhook = $this->getLastRequest(); $signatureKey = $this->getProject()['signatureKey']; $payload = json_encode($webhook['data']); - $url = $webhook['url']; + $url = $webhook['url']; $signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $signatureKey, true)); $this->assertEquals($webhook['method'], 'POST'); @@ -349,7 +349,7 @@ class WebhooksCustomClientTest extends Scope $webhook = $this->getLastRequest(); $signatureKey = $this->getProject()['signatureKey']; $payload = json_encode($webhook['data']); - $url = $webhook['url']; + $url = $webhook['url']; $signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $signatureKey, true)); $this->assertEquals($webhook['method'], 'POST'); @@ -440,7 +440,7 @@ class WebhooksCustomClientTest extends Scope $webhook = $this->getLastRequest(); $signatureKey = $this->getProject()['signatureKey']; $payload = json_encode($webhook['data']); - $url = $webhook['url']; + $url = $webhook['url']; $signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $signatureKey, true)); @@ -494,7 +494,7 @@ class WebhooksCustomClientTest extends Scope $webhook = $this->getLastRequest(); $signatureKey = $this->getProject()['signatureKey']; $payload = json_encode($webhook['data']); - $url = $webhook['url']; + $url = $webhook['url']; $signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $signatureKey, true)); $this->assertEquals($webhook['method'], 'POST'); @@ -549,7 +549,7 @@ class WebhooksCustomClientTest extends Scope $webhook = $this->getLastRequest(); $signatureKey = $this->getProject()['signatureKey']; $payload = json_encode($webhook['data']); - $url = $webhook['url']; + $url = $webhook['url']; $signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $signatureKey, true)); $this->assertEquals($webhook['method'], 'POST'); @@ -605,7 +605,7 @@ class WebhooksCustomClientTest extends Scope $webhook = $this->getLastRequest(); $signatureKey = $this->getProject()['signatureKey']; $payload = json_encode($webhook['data']); - $url = $webhook['url']; + $url = $webhook['url']; $signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $signatureKey, true)); $this->assertEquals($webhook['method'], 'POST'); @@ -661,7 +661,7 @@ class WebhooksCustomClientTest extends Scope $webhook = $this->getLastRequest(); $signatureKey = $this->getProject()['signatureKey']; $payload = json_encode($webhook['data']); - $url = $webhook['url']; + $url = $webhook['url']; $signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $signatureKey, true)); $this->assertEquals($webhook['method'], 'POST'); @@ -719,7 +719,7 @@ class WebhooksCustomClientTest extends Scope $webhook = $this->getLastRequest(); $signatureKey = $this->getProject()['signatureKey']; $payload = json_encode($webhook['data']); - $url = $webhook['url']; + $url = $webhook['url']; $signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $signatureKey, true)); $this->assertEquals($webhook['method'], 'POST'); @@ -774,7 +774,7 @@ class WebhooksCustomClientTest extends Scope $webhook = $this->getLastRequest(); $signatureKey = $this->getProject()['signatureKey']; $payload = json_encode($webhook['data']); - $url = $webhook['url']; + $url = $webhook['url']; $signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $signatureKey, true)); $this->assertEquals($webhook['method'], 'POST'); @@ -833,7 +833,7 @@ class WebhooksCustomClientTest extends Scope $webhook = $this->getLastRequest(); $signatureKey = $this->getProject()['signatureKey']; $payload = json_encode($webhook['data']); - $url = $webhook['url']; + $url = $webhook['url']; $signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $signatureKey, true)); $this->assertEquals($webhook['method'], 'POST'); @@ -891,7 +891,7 @@ class WebhooksCustomClientTest extends Scope $webhook = $this->getLastRequest(); $signatureKey = $this->getProject()['signatureKey']; $payload = json_encode($webhook['data']); - $url = $webhook['url']; + $url = $webhook['url']; $signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $signatureKey, true)); $this->assertEquals($webhook['method'], 'POST');