mirror of
https://github.com/appwrite/appwrite
synced 2026-05-22 00:18:25 +00:00
Merge pull request #10911 from appwrite/feat-multiple-app-domains
feat: multiple app domains
This commit is contained in:
commit
61d1546d14
64 changed files with 943 additions and 663 deletions
4
.env
4
.env
|
|
@ -22,7 +22,7 @@ _APP_OPTIONS_FORCE_HTTPS=disabled
|
|||
_APP_OPTIONS_ROUTER_FORCE_HTTPS=disabled
|
||||
_APP_OPENSSL_KEY_V1=your-secret-key
|
||||
_APP_DNS=8.8.8.8
|
||||
_APP_DOMAIN=traefik
|
||||
_APP_DOMAIN=appwrite.test
|
||||
_APP_CONSOLE_DOMAIN=localhost
|
||||
_APP_DOMAIN_FUNCTIONS=functions.localhost
|
||||
_APP_DOMAIN_SITES=sites.localhost
|
||||
|
|
@ -124,4 +124,4 @@ _APP_MESSAGE_PUSH_TEST_DSN=
|
|||
_APP_WEBHOOK_MAX_FAILED_ATTEMPTS=10
|
||||
_APP_PROJECT_REGIONS=default
|
||||
_APP_FUNCTIONS_CREATION_ABUSE_LIMIT=5000
|
||||
_APP_STATS_USAGE_DUAL_WRITING_DBS=database_db_main
|
||||
_APP_STATS_USAGE_DUAL_WRITING_DBS=database_db_main
|
||||
|
|
|
|||
21
app/config/platform.php
Normal file
21
app/config/platform.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
use Utopia\System\System;
|
||||
|
||||
/**
|
||||
* Platform configuration
|
||||
*/
|
||||
return [
|
||||
'domain' => System::getEnv('_APP_DOMAIN', 'localhost'),
|
||||
'consoleDomain' => System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', 'localhost')),
|
||||
'platformName' => APP_EMAIL_PLATFORM_NAME,
|
||||
'logoUrl' => APP_EMAIL_LOGO_URL,
|
||||
'accentColor' => APP_EMAIL_ACCENT_COLOR,
|
||||
'footerImageUrl' => APP_EMAIL_FOOTER_IMAGE_URL,
|
||||
'twitterUrl' => APP_SOCIAL_TWITTER,
|
||||
'discordUrl' => APP_SOCIAL_DISCORD,
|
||||
'githubUrl' => APP_SOCIAL_GITHUB,
|
||||
'termsUrl' => APP_EMAIL_TERMS_URL,
|
||||
'privacyUrl' => APP_EMAIL_PRIVACY_URL,
|
||||
'websiteUrl' => 'https://' . APP_DOMAIN,
|
||||
];
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\System\System;
|
||||
|
||||
/**
|
||||
|
|
@ -7,12 +8,8 @@ use Utopia\System\System;
|
|||
*/
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_DOMAIN', '');
|
||||
|
||||
// Temporary fix until we can set _APP_DOMAIN to "localhost" instead of "traefik"
|
||||
if (System::getEnv('_APP_ENV', 'development') === 'development') {
|
||||
$hostname = 'localhost';
|
||||
}
|
||||
$platform = Config::getParam('platform', []);
|
||||
$hostname = $platform['consoleDomain'] ?? '';
|
||||
|
||||
$url = $protocol . '://' . $hostname;
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ return [
|
|||
],
|
||||
[
|
||||
'name' => '_APP_DOMAIN',
|
||||
'description' => 'Your Appwrite domain address. When setting a public suffix domain, Appwrite will attempt to issue a valid SSL certificate automatically. When used with a dev domain, Appwrite will assign a self-signed SSL certificate. The default value is \'localhost\'.',
|
||||
'description' => 'Your Appwrite domain address. When setting a public suffix domain, Appwrite will attempt to issue a valid SSL certificate automatically. When used with a dev domain, Appwrite will assign a self-signed SSL certificate. The default value is \'localhost\'. Multiple domains can be separated by commas.',
|
||||
'introduction' => '',
|
||||
'default' => 'localhost',
|
||||
'required' => true,
|
||||
|
|
|
|||
|
|
@ -64,11 +64,11 @@ use Utopia\Emails\Email;
|
|||
use Utopia\Locale\Locale;
|
||||
use Utopia\Storage\Validator\FileName;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
$oauthDefaultSuccess = '/console/auth/oauth2/success';
|
||||
|
|
@ -1283,13 +1283,14 @@ App::get('/v1/account/sessions/oauth2/:provider')
|
|||
->label('abuse-limit', 50)
|
||||
->label('abuse-key', 'ip:{ip}')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn ($node) => (!$node['mock'])))) . '.')
|
||||
->param('success', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey'])
|
||||
->param('failure', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey'])
|
||||
->param('success', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator'])
|
||||
->param('failure', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator'])
|
||||
->param('scopes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->action(function (string $provider, string $success, string $failure, array $scopes, Request $request, Response $response, Document $project) use ($oauthDefaultSuccess, $oauthDefaultFailure) {
|
||||
->inject('platform')
|
||||
->action(function (string $provider, string $success, string $failure, array $scopes, Request $request, Response $response, Document $project, array $platform) use ($oauthDefaultSuccess, $oauthDefaultFailure) {
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$port = $request->getPort();
|
||||
$callbackBase = $protocol . '://' . $request->getHostname();
|
||||
|
|
@ -1324,7 +1325,7 @@ App::get('/v1/account/sessions/oauth2/:provider')
|
|||
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
|
||||
}
|
||||
|
||||
$host = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
|
||||
$host = $platform['consoleDomain'] ?? '';
|
||||
$redirectBase = $protocol . '://' . $host;
|
||||
if ($protocol === 'https' && $port !== '443') {
|
||||
$redirectBase .= ':' . $port;
|
||||
|
|
@ -1443,7 +1444,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('platforms')
|
||||
->inject('redirectValidator')
|
||||
->inject('devKey')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
|
|
@ -1452,7 +1453,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
->inject('store')
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForToken')
|
||||
->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, array $platforms, Document $devKey, User $user, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) use ($oauthDefaultSuccess) {
|
||||
->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Validator $redirectValidator, Document $devKey, User $user, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) use ($oauthDefaultSuccess) {
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$port = $request->getPort();
|
||||
$callbackBase = $protocol . '://' . $request->getHostname();
|
||||
|
|
@ -1463,7 +1464,6 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
}
|
||||
$callback = $callbackBase . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
|
||||
$defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => ''];
|
||||
$redirect = new Redirect($platforms);
|
||||
$appId = $project->getAttribute('oAuthProviders', [])[$provider . 'Appid'] ?? '';
|
||||
$appSecret = $project->getAttribute('oAuthProviders', [])[$provider . 'Secret'] ?? '{}';
|
||||
$providerEnabled = $project->getAttribute('oAuthProviders', [])[$provider . 'Enabled'] ?? false;
|
||||
|
|
@ -1490,11 +1490,11 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
$state = $defaultState;
|
||||
}
|
||||
|
||||
if ($devKey->isEmpty() && !$redirect->isValid($state['success'])) {
|
||||
if ($devKey->isEmpty() && !$redirectValidator->isValid($state['success'])) {
|
||||
throw new Exception(Exception::PROJECT_INVALID_SUCCESS_URL);
|
||||
}
|
||||
|
||||
if ($devKey->isEmpty() && !empty($state['failure']) && !$redirect->isValid($state['failure'])) {
|
||||
if ($devKey->isEmpty() && !empty($state['failure']) && !$redirectValidator->isValid($state['failure'])) {
|
||||
throw new Exception(Exception::PROJECT_INVALID_FAILURE_URL);
|
||||
}
|
||||
$failure = [];
|
||||
|
|
@ -1945,23 +1945,15 @@ App::get('/v1/account/tokens/oauth2/:provider')
|
|||
->label('abuse-limit', 50)
|
||||
->label('abuse-key', 'ip:{ip}')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn ($node) => (!$node['mock'])))) . '.')
|
||||
->param('success', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey'])
|
||||
->param('failure', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey'])
|
||||
->param('success', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator'])
|
||||
->param('failure', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator'])
|
||||
->param('scopes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->action(function (string $provider, string $success, string $failure, array $scopes, Request $request, Response $response, Document $project) use ($oauthDefaultSuccess, $oauthDefaultFailure) {
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$port = $request->getPort();
|
||||
$callbackBase = $protocol . '://' . $request->getHostname();
|
||||
if ($protocol === 'https' && $port !== '443') {
|
||||
$callbackBase .= ':' . $port;
|
||||
} elseif ($protocol === 'http' && $port !== '80') {
|
||||
$callbackBase .= ':' . $port;
|
||||
}
|
||||
|
||||
$callback = $callbackBase . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
|
||||
->inject('platform')
|
||||
->action(function (string $provider, string $success, string $failure, array $scopes, Request $request, Response $response, Document $project, array $platform) use ($oauthDefaultSuccess, $oauthDefaultFailure) {
|
||||
$callback = $platform['endpoint'] . '/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
|
||||
$providerEnabled = $project->getAttribute('oAuthProviders', [])[$provider . 'Enabled'] ?? false;
|
||||
|
||||
if (!$providerEnabled) {
|
||||
|
|
@ -1986,7 +1978,9 @@ App::get('/v1/account/tokens/oauth2/:provider')
|
|||
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
|
||||
}
|
||||
|
||||
$host = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
|
||||
$host = $platform['consoleDomain'] ?? '';
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$port = $request->getPort();
|
||||
$redirectBase = $protocol . '://' . $host;
|
||||
if ($protocol === 'https' && $port !== '443') {
|
||||
$redirectBase .= ':' . $port;
|
||||
|
|
@ -2041,7 +2035,7 @@ App::post('/v1/account/tokens/magic-url')
|
|||
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
|
||||
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars. If the email address has never been used, a new account is created using the provided userId. Otherwise, if the email address is already attached to an account, the user ID is ignored.')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('url', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey'])
|
||||
->param('url', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator'])
|
||||
->param('phrase', false, new Boolean(), 'Toggle for security phrase. If enabled, email will be send with a randomly generated phrase and the phrase will also be included in the response. Confirming phrases match increases the security of your authentication flow.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
|
|
@ -2052,7 +2046,8 @@ App::post('/v1/account/tokens/magic-url')
|
|||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->inject('proofForPassword')
|
||||
->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, User $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword) {
|
||||
->inject('platform')
|
||||
->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, User $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword, array $platform) {
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
|
||||
}
|
||||
|
|
@ -2158,7 +2153,7 @@ App::post('/v1/account/tokens/magic-url')
|
|||
|
||||
if (empty($url)) {
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$host = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
|
||||
$host = $platform['consoleDomain'] ?? '';
|
||||
$port = $request->getPort();
|
||||
$callbackBase = $protocol . '://' . $host;
|
||||
if ($protocol === 'https' && $port !== '443') {
|
||||
|
|
@ -3463,7 +3458,7 @@ App::post('/v1/account/recovery')
|
|||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('url', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['platforms', 'devKey'])
|
||||
->param('url', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['redirectValidator'])
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
|
|
@ -3759,7 +3754,7 @@ App::post('/v1/account/verifications/email')
|
|||
])
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'url:{url},userId:{userId}')
|
||||
->param('url', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['platforms', 'devKey']) // TODO add built-in confirm page
|
||||
->param('url', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['redirectValidator']) // TODO add built-in confirm page
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
|
|
|
|||
|
|
@ -43,9 +43,6 @@ App::get('/v1/console/variables')
|
|||
))
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
$validator = new Domain(System::getEnv('_APP_DOMAIN'));
|
||||
$isDomainValid = !empty(System::getEnv('_APP_DOMAIN', '')) && $validator->isKnown() && !$validator->isTest();
|
||||
|
||||
$validator = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME'));
|
||||
$isCNAMEValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_CNAME', '')) && $validator->isKnown() && !$validator->isTest();
|
||||
|
||||
|
|
@ -55,9 +52,7 @@ App::get('/v1/console/variables')
|
|||
$validator = new IP(IP::V6);
|
||||
$isAAAAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_AAAA', '')) && $validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA'));
|
||||
|
||||
$isDomainEnabled = $isDomainValid && (
|
||||
$isAAAAValid || $isAValid || $isCNAMEValid
|
||||
);
|
||||
$isDomainEnabled = $isAAAAValid || $isAValid || $isCNAMEValid;
|
||||
|
||||
$isVcsEnabled = !empty(System::getEnv('_APP_VCS_GITHUB_APP_NAME', ''))
|
||||
&& !empty(System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY', ''))
|
||||
|
|
|
|||
|
|
@ -3495,7 +3495,8 @@ App::post('/v1/messaging/messages/push')
|
|||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('response')
|
||||
->action(function (string $messageId, string $title, string $body, ?array $topics, ?array $users, ?array $targets, ?array $data, string $action, string $image, string $icon, string $sound, string $color, string $tag, int $badge, bool $draft, ?string $scheduledAt, bool $contentAvailable, bool $critical, string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
->inject('platform')
|
||||
->action(function (string $messageId, string $title, string $body, ?array $topics, ?array $users, ?array $targets, ?array $data, string $action, string $image, string $icon, string $sound, string $color, string $tag, int $badge, bool $draft, ?string $scheduledAt, bool $contentAvailable, bool $critical, string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response, array $platform) {
|
||||
$messageId = $messageId == 'unique()'
|
||||
? ID::unique()
|
||||
: $messageId;
|
||||
|
|
@ -3551,7 +3552,6 @@ App::post('/v1/messaging/messages/push')
|
|||
throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED);
|
||||
}
|
||||
|
||||
$host = System::getEnv('_APP_DOMAIN', 'localhost');
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
|
||||
$scheduleTime = $currentScheduledAt ?? $scheduledAt;
|
||||
|
|
@ -3572,7 +3572,7 @@ App::post('/v1/messaging/messages/push')
|
|||
$image = [
|
||||
'bucketId' => $bucket->getId(),
|
||||
'fileId' => $file->getId(),
|
||||
'url' => "{$protocol}://{$host}/v1/storage/buckets/{$bucket->getId()}/files/{$file->getId()}/push?project={$project->getId()}&jwt={$jwt}",
|
||||
'url' => "{$platform['endpoint']}/storage/buckets/{$bucket->getId()}/files/{$file->getId()}/push?project={$project->getId()}&jwt={$jwt}",
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -4378,7 +4378,8 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
|||
->inject('project')
|
||||
->inject('queueForMessaging')
|
||||
->inject('response')
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $title, ?string $body, ?array $data, ?string $action, ?string $image, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?bool $draft, ?string $scheduledAt, ?bool $contentAvailable, ?bool $critical, ?string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response) {
|
||||
->inject('platform')
|
||||
->action(function (string $messageId, ?array $topics, ?array $users, ?array $targets, ?string $title, ?string $body, ?array $data, ?string $action, ?string $image, ?string $icon, ?string $sound, ?string $color, ?string $tag, ?int $badge, ?bool $draft, ?string $scheduledAt, ?bool $contentAvailable, ?bool $critical, ?string $priority, Event $queueForEvents, Database $dbForProject, Database $dbForPlatform, Document $project, Messaging $queueForMessaging, Response $response, array $platform) {
|
||||
$message = $dbForProject->getDocument('messages', $messageId);
|
||||
|
||||
if ($message->isEmpty()) {
|
||||
|
|
@ -4546,9 +4547,6 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
|||
throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED);
|
||||
}
|
||||
|
||||
$host = System::getEnv('_APP_DOMAIN', 'localhost');
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
|
||||
$scheduleTime = $currentScheduledAt ?? $scheduledAt;
|
||||
if (!\is_null($scheduleTime)) {
|
||||
$expiry = (new \DateTime($scheduleTime))->add(new \DateInterval('P15D'))->format('U');
|
||||
|
|
@ -4567,7 +4565,7 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
|||
$pushData['image'] = [
|
||||
'bucketId' => $bucket->getId(),
|
||||
'fileId' => $file->getId(),
|
||||
'url' => "{$protocol}://{$host}/v1/storage/buckets/{$bucket->getId()}/files/{$file->getId()}/push?project={$project->getId()}&jwt={$jwt}"
|
||||
'url' => "{$platform['endpoint']}/storage/buckets/{$bucket->getId()}/files/{$file->getId()}/push?project={$project->getId()}&jwt={$jwt}",
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,6 @@ use Utopia\Validator\ArrayList;
|
|||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
App::post('/v1/teams')
|
||||
|
|
@ -486,7 +485,7 @@ App::post('/v1/teams/:teamId/memberships')
|
|||
}
|
||||
return new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE);
|
||||
}, 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.', false, ['project'])
|
||||
->param('url', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey']) // TODO add our own built-in confirm page
|
||||
->param('url', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator']) // TODO add our own built-in confirm page
|
||||
->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ use Appwrite\Auth\OAuth2\Github as OAuth2Github;
|
|||
use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Redirect;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
|
|
@ -77,7 +76,7 @@ use Utopia\VCS\Exception\RepositoryNotFound;
|
|||
|
||||
use function Swoole\Coroutine\batch;
|
||||
|
||||
$createGitDeployments = function (GitHub $github, string $providerInstallationId, array $repositories, string $providerBranch, string $providerBranchUrl, string $providerRepositoryName, string $providerRepositoryUrl, string $providerRepositoryOwner, string $providerCommitHash, string $providerCommitAuthor, string $providerCommitAuthorUrl, string $providerCommitMessage, string $providerCommitUrl, string $providerPullRequestId, bool $external, Database $dbForPlatform, Build $queueForBuilds, callable $getProjectDB, Request $request) {
|
||||
$createGitDeployments = function (GitHub $github, string $providerInstallationId, array $repositories, string $providerBranch, string $providerBranchUrl, string $providerRepositoryName, string $providerRepositoryUrl, string $providerRepositoryOwner, string $providerCommitHash, string $providerCommitAuthor, string $providerCommitAuthorUrl, string $providerCommitMessage, string $providerCommitUrl, string $providerPullRequestId, bool $external, Database $dbForPlatform, Build $queueForBuilds, callable $getProjectDB, array $platform) {
|
||||
$errors = [];
|
||||
foreach ($repositories as $repository) {
|
||||
try {
|
||||
|
|
@ -133,7 +132,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
|||
|
||||
$commentStatus = $isAuthorized ? 'waiting' : 'failed';
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
|
||||
$hostname = $platform['consoleDomain'] ?? '';
|
||||
|
||||
$authorizeUrl = $protocol . '://' . $hostname . "/console/git/authorize-contributor?projectId={$projectId}&installationId={$installationId}&repositoryId={$repositoryId}&providerPullRequestId={$providerPullRequestId}";
|
||||
|
||||
|
|
@ -175,7 +174,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
|||
if ($lockAcquired) {
|
||||
// Wrap in try/finally to ensure lock file gets deleted
|
||||
try {
|
||||
$comment = new Comment();
|
||||
$comment = new Comment($platform);
|
||||
$comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId));
|
||||
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '');
|
||||
|
||||
|
|
@ -185,7 +184,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
|||
}
|
||||
}
|
||||
} else {
|
||||
$comment = new Comment();
|
||||
$comment = new Comment($platform);
|
||||
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '');
|
||||
$latestCommentId = \strval($github->createComment($owner, $repositoryName, $providerPullRequestId, $comment->generateComment()));
|
||||
|
||||
|
|
@ -246,7 +245,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
|||
if ($lockAcquired) {
|
||||
// Wrap in try/finally to ensure lock file gets deleted
|
||||
try {
|
||||
$comment = new Comment();
|
||||
$comment = new Comment($platform);
|
||||
$comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId));
|
||||
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, '');
|
||||
|
||||
|
|
@ -467,7 +466,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
|||
$previewUrl = !empty($rule) ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : '';
|
||||
|
||||
if (!empty($previewUrl)) {
|
||||
$comment = new Comment();
|
||||
$comment = new Comment($platform);
|
||||
$comment->parseComment($github->getComment($owner, $repositoryName, $latestCommentId));
|
||||
$comment->addBuild($project, $resource, $resourceType, $commentStatus, $deploymentId, $action, $previewUrl);
|
||||
$github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment());
|
||||
|
|
@ -542,12 +541,12 @@ App::get('/v1/vcs/github/authorize')
|
|||
type: MethodType::WEBAUTH,
|
||||
hide: true,
|
||||
))
|
||||
->param('success', '', fn ($platforms) => new Redirect($platforms), 'URL to redirect back to console after a successful installation attempt.', true, ['platforms'])
|
||||
->param('failure', '', fn ($platforms) => new Redirect($platforms), 'URL to redirect back to console after a failed installation attempt.', true, ['platforms'])
|
||||
->inject('request')
|
||||
->param('success', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to console after a successful installation attempt.', true, ['redirectValidator'])
|
||||
->param('failure', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to console after a failed installation attempt.', true, ['redirectValidator'])
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->action(function (string $success, string $failure, Request $request, Response $response, Document $project) {
|
||||
->inject('platform')
|
||||
->action(function (string $success, string $failure, Response $response, Document $project, array $platform) {
|
||||
$state = \json_encode([
|
||||
'projectId' => $project->getId(),
|
||||
'success' => $success,
|
||||
|
|
@ -556,7 +555,7 @@ App::get('/v1/vcs/github/authorize')
|
|||
|
||||
$appName = System::getEnv('_APP_VCS_GITHUB_APP_NAME');
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
|
||||
$hostname = $platform['consoleDomain'] ?? '';
|
||||
|
||||
if (empty($appName)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'GitHub App name is not configured. Please configure VCS (Version Control System) variables in .env file.');
|
||||
|
|
@ -585,10 +584,10 @@ App::get('/v1/vcs/github/callback')
|
|||
->inject('gitHub')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $providerInstallationId, string $setupAction, string $state, string $code, GitHub $github, Document $user, Document $project, Request $request, Response $response, Database $dbForPlatform) {
|
||||
->inject('platform')
|
||||
->action(function (string $providerInstallationId, string $setupAction, string $state, string $code, GitHub $github, Document $user, Document $project, Response $response, Database $dbForPlatform, array $platform) {
|
||||
if (empty($state)) {
|
||||
$error = 'Installation requests from organisation members for the Appwrite GitHub App are currently unsupported. To proceed with the installation, login to the Appwrite Console and install the GitHub App.';
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $error);
|
||||
|
|
@ -615,7 +614,7 @@ App::get('/v1/vcs/github/callback')
|
|||
|
||||
$region = $project->getAttribute('region', 'default');
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
|
||||
$hostname = $platform['consoleDomain'] ?? '';
|
||||
|
||||
$defaultState = [
|
||||
'success' => $protocol . '://' . $hostname . "/console/project-$region-$projectId/settings/git-installations",
|
||||
|
|
@ -1479,8 +1478,9 @@ App::post('/v1/vcs/github/events')
|
|||
->inject('dbForPlatform')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForBuilds')
|
||||
->inject('platform')
|
||||
->action(
|
||||
function (GitHub $github, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) {
|
||||
function (GitHub $github, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Build $queueForBuilds, array $platform) use ($createGitDeployments) {
|
||||
$payload = $request->getRawPayload();
|
||||
$signatureRemote = $request->getHeader('x-hub-signature-256', '');
|
||||
$signatureLocal = System::getEnv('_APP_VCS_GITHUB_WEBHOOK_SECRET', '');
|
||||
|
|
@ -1523,7 +1523,7 @@ App::post('/v1/vcs/github/events')
|
|||
|
||||
// create new deployment only on push (not committed by us) and not when branch is created or deleted
|
||||
if ($providerCommitAuthorEmail !== APP_VCS_GITHUB_EMAIL && !$providerBranchCreated && !$providerBranchDeleted) {
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthorName, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $queueForBuilds, $getProjectDB, $request);
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthorName, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $queueForBuilds, $getProjectDB, $platform);
|
||||
}
|
||||
} elseif ($event == $github::EVENT_INSTALLATION) {
|
||||
if ($parsedPayload["action"] == "deleted") {
|
||||
|
|
@ -1579,7 +1579,7 @@ App::post('/v1/vcs/github/events')
|
|||
Query::orderDesc('$createdAt')
|
||||
]));
|
||||
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForPlatform, $queueForBuilds, $getProjectDB, $request);
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForPlatform, $queueForBuilds, $getProjectDB, $platform);
|
||||
} elseif ($parsedPayload["action"] == "closed") {
|
||||
// Allowed external contributions cleanup
|
||||
|
||||
|
|
@ -1783,13 +1783,13 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
|
|||
->param('repositoryId', '', new Text(256), 'VCS Repository Id')
|
||||
->param('providerPullRequestId', '', new Text(256), 'GitHub Pull Request Id')
|
||||
->inject('gitHub')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForPlatform')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForBuilds')
|
||||
->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForPlatform, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) {
|
||||
->inject('platform')
|
||||
->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Response $response, Document $project, Database $dbForPlatform, callable $getProjectDB, Build $queueForBuilds, array $platform) use ($createGitDeployments) {
|
||||
$installation = $dbForPlatform->getDocument('installations', $installationId);
|
||||
|
||||
if ($installation->isEmpty()) {
|
||||
|
|
@ -1837,8 +1837,16 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
|
|||
|
||||
$providerBranch = \explode(':', $pullRequestResponse['head']['label'])[1] ?? '';
|
||||
$providerCommitHash = $pullRequestResponse['head']['sha'] ?? '';
|
||||
$providerBranchUrl = $pullRequestResponse['head']['repo']['html_url'] ?? '';
|
||||
$providerRepositoryName = $pullRequestResponse['head']['repo']['name'] ?? '';
|
||||
$providerRepositoryUrl = $pullRequestResponse['head']['repo']['html_url'] ?? '';
|
||||
$providerRepositoryOwner = $pullRequestResponse['head']['repo']['owner']['login'] ?? '';
|
||||
$providerCommitAuthor = $pullRequestResponse['head']['user']['login'] ?? '';
|
||||
$providerCommitAuthorUrl = $pullRequestResponse['head']['user']['html_url'] ?? '';
|
||||
$providerCommitMessage = $pullRequestResponse['title'] ?? '';
|
||||
$providerCommitUrl = $pullRequestResponse['html_url'] ?? '';
|
||||
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerCommitHash, $providerPullRequestId, true, $dbForPlatform, $queueForBuilds, $getProjectDB, $request);
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, true, $dbForPlatform, $queueForBuilds, $getProjectDB, $platform);
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use Appwrite\Event\Event;
|
|||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Network\Cors;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
|
|
@ -39,6 +39,7 @@ use Utopia\Config\Config;
|
|||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
|
|
@ -51,33 +52,35 @@ use Utopia\Logger\Log\User;
|
|||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Service;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
Config::setParam('domainVerification', false);
|
||||
Config::setParam('cookieDomain', 'localhost');
|
||||
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
|
||||
|
||||
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey)
|
||||
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, array $domains)
|
||||
{
|
||||
$host = $request->getHostname() ?? '';
|
||||
if (!empty($previewHostname)) {
|
||||
$host = $previewHostname;
|
||||
}
|
||||
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
|
||||
$rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($host)));
|
||||
} else {
|
||||
$rule = Authorization::skip(
|
||||
fn () => $dbForPlatform->find('rules', [
|
||||
Query::equal('domain', [$host]),
|
||||
Query::limit(1)
|
||||
])
|
||||
)[0] ?? new Document();
|
||||
}
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$rule = Authorization::skip(function () use ($dbForPlatform, $host, $isMd5) {
|
||||
if ($isMd5) {
|
||||
return $dbForPlatform->getDocument('rules', md5($host));
|
||||
}
|
||||
|
||||
return $dbForPlatform->findOne('rules', [
|
||||
Query::equal('domain', [$host]),
|
||||
]) ?? new Document();
|
||||
});
|
||||
|
||||
$errorView = __DIR__ . '/../views/general/error.phtml';
|
||||
$url = (System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https') . '://' . System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$url = $protocol . '://' . $platform['consoleDomain'];
|
||||
|
||||
if ($rule->isEmpty()) {
|
||||
$appDomainFunctionsFallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', '');
|
||||
|
|
@ -98,10 +101,8 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
throw $exception;
|
||||
}
|
||||
|
||||
if (System::getEnv('_APP_OPTIONS_ROUTER_PROTECTION', 'disabled') === 'enabled') {
|
||||
if ($host !== 'localhost' && $host !== APP_HOSTNAME_INTERNAL && $host !== System::getEnv('_APP_CONSOLE_DOMAIN', '')) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'Router protection does not allow accessing Appwrite over this domain. Please add it as custom domain to your project or disable _APP_OPTIONS_ROUTER_PROTECTION environment variable.', view: $errorView);
|
||||
}
|
||||
if (!in_array($host, $domains)) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'Router protection does not allow accessing Appwrite over this domain. Please add it as custom domain to your project or disable _APP_OPTIONS_ROUTER_PROTECTION environment variable.', view: $errorView);
|
||||
}
|
||||
|
||||
// Act as API - no Proxy logic
|
||||
|
|
@ -143,7 +144,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
|
||||
if ($type === 'deployment') {
|
||||
if (System::getEnv('_APP_OPTIONS_ROUTER_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
|
||||
if ($request->getProtocol() !== 'https' && $request->getHostname() !== APP_HOSTNAME_INTERNAL) {
|
||||
if ($request->getProtocol() !== 'https') {
|
||||
if ($request->getMethod() !== Request::METHOD_GET) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP. Please use HTTPS instead.', view: $errorView);
|
||||
}
|
||||
|
|
@ -268,7 +269,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
}
|
||||
|
||||
if (!$authorized) {
|
||||
$url = (System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https') . "://" . System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
|
||||
$url = $protocol . "://" . $platform['consoleDomain'];
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
|
|
@ -456,14 +457,10 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
|
||||
}
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_DOMAIN');
|
||||
$endpoint = $protocol . '://' . $hostname . "/v1";
|
||||
|
||||
// Appwrite vars
|
||||
if ($type === 'function') {
|
||||
$vars = \array_merge($vars, [
|
||||
'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint,
|
||||
'APPWRITE_FUNCTION_API_ENDPOINT' => $platform['endpoint'],
|
||||
'APPWRITE_FUNCTION_ID' => $resource->getId(),
|
||||
'APPWRITE_FUNCTION_NAME' => $resource->getAttribute('name'),
|
||||
'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(),
|
||||
|
|
@ -475,7 +472,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
|||
]);
|
||||
} elseif ($type === 'site') {
|
||||
$vars = \array_merge($vars, [
|
||||
'APPWRITE_SITE_API_ENDPOINT' => $endpoint,
|
||||
'APPWRITE_SITE_API_ENDPOINT' => $platform['endpoint'],
|
||||
'APPWRITE_SITE_ID' => $resource->getId(),
|
||||
'APPWRITE_SITE_NAME' => $resource->getAttribute('name'),
|
||||
'APPWRITE_SITE_DEPLOYMENT' => $deployment->getId(),
|
||||
|
|
@ -845,34 +842,31 @@ App::init()
|
|||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('log')
|
||||
->inject('console')
|
||||
->inject('project')
|
||||
->inject('dbForPlatform')
|
||||
->inject('getProjectDB')
|
||||
->inject('locale')
|
||||
->inject('localeCodes')
|
||||
->inject('platforms')
|
||||
->inject('geodb')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForCertificates')
|
||||
->inject('queueForFunctions')
|
||||
->inject('executor')
|
||||
->inject('platform')
|
||||
->inject('isResourceBlocked')
|
||||
->inject('previewHostname')
|
||||
->inject('devKey')
|
||||
->inject('apiKey')
|
||||
->inject('httpReferrer')
|
||||
->inject('httpReferrerSafe')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $console, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, array $platforms, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, Executor $executor, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, string $httpReferrer, string $httpReferrerSafe) {
|
||||
->inject('cors')
|
||||
->inject('domains')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Func $queueForFunctions, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors, array $domains) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
$host = $request->getHostname() ?? '';
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$hostname = $request->getHostname() ?? '';
|
||||
// Only run Router when external domain
|
||||
if ($host !== $mainDomain || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
|
||||
if (!in_array($hostname, $domains) || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey, $domains)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
|
|
@ -912,98 +906,12 @@ App::init()
|
|||
}
|
||||
}
|
||||
|
||||
$domain = $request->getHostname();
|
||||
$domains = Config::getParam('domains', []);
|
||||
if (!array_key_exists($domain, $domains)) {
|
||||
$domain = new Domain(!empty($domain) ? $domain : '');
|
||||
|
||||
if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) {
|
||||
$domains[$domain->get()] = false;
|
||||
Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.');
|
||||
} elseif (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) {
|
||||
Console::warning('Skipping SSL certificates generation on ACME challenge.');
|
||||
} else {
|
||||
Authorization::disable();
|
||||
|
||||
$envDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$mainDomain = null;
|
||||
if (!empty($envDomain) && $envDomain !== 'localhost') {
|
||||
$mainDomain = $envDomain;
|
||||
} else {
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
|
||||
$domainDocument = $dbForPlatform->getDocument('rules', md5($envDomain));
|
||||
} else {
|
||||
$domainDocument = $dbForPlatform->findOne('rules', [Query::orderAsc('$id')]);
|
||||
}
|
||||
$mainDomain = !$domainDocument->isEmpty() ? $domainDocument->getAttribute('domain') : $domain->get();
|
||||
}
|
||||
|
||||
if ($mainDomain !== $domain->get()) {
|
||||
Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.');
|
||||
} else {
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
|
||||
$domainDocument = $dbForPlatform->getDocument('rules', md5($domain->get()));
|
||||
} else {
|
||||
$domainDocument = $dbForPlatform->findOne('rules', [
|
||||
Query::equal('domain', [$domain->get()])
|
||||
]);
|
||||
}
|
||||
|
||||
$owner = '';
|
||||
$functionsDomainFallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
$siteDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
if (!empty($functionsDomainFallback) && \str_ends_with($host, $functionsDomainFallback)) {
|
||||
$functionsDomain = $functionsDomainFallback;
|
||||
}
|
||||
|
||||
if (
|
||||
(!empty($functionsDomain) && \str_ends_with($domain->get(), $functionsDomain)) ||
|
||||
(!empty($siteDomain) && \str_ends_with($domain->get(), $siteDomain))
|
||||
) {
|
||||
$owner = 'Appwrite';
|
||||
}
|
||||
|
||||
if ($domainDocument->isEmpty()) {
|
||||
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
|
||||
$domainDocument = new Document([
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
'$id' => $ruleId,
|
||||
'domain' => $domain->get(),
|
||||
'type' => 'api',
|
||||
'status' => 'verifying',
|
||||
'projectId' => $console->getId(),
|
||||
'projectInternalId' => $console->getSequence(),
|
||||
'search' => implode(' ', [$ruleId, $domain->get()]),
|
||||
'owner' => $owner,
|
||||
'region' => $console->getAttribute('region')
|
||||
]);
|
||||
|
||||
$domainDocument = $dbForPlatform->createDocument('rules', $domainDocument);
|
||||
|
||||
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
|
||||
|
||||
$queueForCertificates
|
||||
->setDomain($domainDocument)
|
||||
->setSkipRenewCheck(true)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
$domains[$domain->get()] = true;
|
||||
|
||||
Authorization::reset(); // ensure authorization is re-enabled
|
||||
}
|
||||
Config::setParam('domains', $domains);
|
||||
}
|
||||
|
||||
$localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', ''));
|
||||
if (\in_array($localeParam, $localeCodes)) {
|
||||
$locale->setDefault($localeParam);
|
||||
}
|
||||
|
||||
$origin = \parse_url($request->getOrigin($httpReferrer), PHP_URL_HOST);
|
||||
$origin = \parse_url($request->getOrigin($request->getReferer('')), PHP_URL_HOST);
|
||||
$selfDomain = new Domain($request->getHostname());
|
||||
$endDomain = new Domain((string)$origin);
|
||||
Config::setParam(
|
||||
|
|
@ -1032,8 +940,8 @@ App::init()
|
|||
$warnings = [];
|
||||
|
||||
/*
|
||||
* Response format
|
||||
*/
|
||||
* Response format
|
||||
*/
|
||||
$responseFormat = $request->getHeader('x-appwrite-response-format', System::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
|
||||
if ($responseFormat) {
|
||||
if (version_compare($responseFormat, '1.4.0', '<')) {
|
||||
|
|
@ -1053,14 +961,13 @@ App::init()
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Security Headers
|
||||
*
|
||||
* As recommended at:
|
||||
* @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers
|
||||
*/
|
||||
// Add Appwrite warning headers
|
||||
if (!empty($warnings)) {
|
||||
$response->addHeader('X-Appwrite-Warning', implode(';', $warnings));
|
||||
}
|
||||
|
||||
if (System::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS
|
||||
if ($request->getProtocol() !== 'https' && ($swooleRequest->header['host'] ?? '') !== 'localhost' && ($swooleRequest->header['host'] ?? '') !== APP_HOSTNAME_INTERNAL) { // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations
|
||||
if ($request->getProtocol() !== 'https' && ($swooleRequest->header['host'] ?? '') !== 'localhost') { // localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations
|
||||
if ($request->getMethod() !== Request::METHOD_GET) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP. Please use HTTPS instead.');
|
||||
}
|
||||
|
|
@ -1068,49 +975,150 @@ App::init()
|
|||
return $response->redirect('https://' . $request->getHostname() . $request->getURI());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if ($request->getProtocol() === 'https') {
|
||||
$response->addHeader('Strict-Transport-Security', 'max-age=' . (60 * 60 * 24 * 126)); // 126 days
|
||||
/**
|
||||
* Security headers
|
||||
*
|
||||
* @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers
|
||||
*/
|
||||
App::init()
|
||||
->groups(['api', 'web'])
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('cors')
|
||||
->inject('devKey')
|
||||
->inject('originValidator')
|
||||
->action(function (Request $request, Response $response, Cors $cors, Document $devKey, Validator $originValidator) {
|
||||
// CORS headers
|
||||
foreach ($cors->headers($request->getOrigin()) as $name => $value) {
|
||||
$response->addHeader($name, $value);
|
||||
}
|
||||
|
||||
// Security headers
|
||||
$response
|
||||
->addHeader('Server', 'Appwrite')
|
||||
->addHeader('X-Content-Type-Options', 'nosniff')
|
||||
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Dev-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Forwarded-For, X-Forwarded-User-Agent')
|
||||
->addHeader('Access-Control-Expose-Headers', 'X-Appwrite-Session, X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Origin', $httpReferrerSafe)
|
||||
->addHeader('Access-Control-Allow-Credentials', 'true');
|
||||
->addHeader('X-Content-Type-Options', 'nosniff');
|
||||
|
||||
if (!$devKey->isEmpty()) {
|
||||
$response->addHeader('Access-Control-Allow-Origin', '*');
|
||||
if ($request->getProtocol() === 'https') {
|
||||
$maxAge = 60 * 60 * 24 * 126; // 126 days
|
||||
$response->addHeader('Strict-Transport-Security', "max-age=$maxAge");
|
||||
}
|
||||
|
||||
if (!empty($warnings)) {
|
||||
$response->addHeader('X-Appwrite-Warning', implode(';', $warnings));
|
||||
// Application level CSRF protection
|
||||
$origin = $request->getOrigin();
|
||||
if (empty($origin) || !$devKey->isEmpty() || !empty($request->getHeader('x-appwrite-key'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate Client Domain - Check to avoid CSRF attack
|
||||
* Adding Appwrite API domains to allow XDOMAIN communication
|
||||
* Skip this check for non-web platforms which are not required to send an origin header
|
||||
*/
|
||||
$origin = $request->getOrigin($request->getReferer(''));
|
||||
$originValidator = new Origin($platforms);
|
||||
|
||||
if (
|
||||
$devKey->isEmpty()
|
||||
&& !empty($origin)
|
||||
&& !$originValidator->isValid($origin)
|
||||
&& \in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_PATCH, Request::METHOD_DELETE])
|
||||
&& $route->getLabel('origin', false) !== '*'
|
||||
&& empty($request->getHeader('x-appwrite-key', ''))
|
||||
&& \parse_url($httpReferrerSafe, PHP_URL_HOST) === 'localhost'
|
||||
) {
|
||||
$route = $request->getRoute();
|
||||
if ($route->getLabel('origin', false) === '*') {
|
||||
return;
|
||||
}
|
||||
if (!$originValidator->isValid($origin)) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_UNKNOWN_ORIGIN, $originValidator->getDescription());
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Automatic certificate generation
|
||||
*/
|
||||
App::init()
|
||||
->groups(['api', 'web'])
|
||||
->inject('request')
|
||||
->inject('console')
|
||||
->inject('dbForPlatform')
|
||||
->inject('queueForCertificates')
|
||||
->inject('domains')
|
||||
->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $domains) {
|
||||
$hostname = $request->getHostname();
|
||||
$cache = Config::getParam('domains', []);
|
||||
|
||||
// 1. Cache hit
|
||||
if (array_key_exists($hostname, $cache)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Domain validation
|
||||
$domain = new Domain(!empty($hostname) ? $hostname : '');
|
||||
if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) {
|
||||
$cache[$domain->get()] = false;
|
||||
Config::setParam('domains', $cache);
|
||||
Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) {
|
||||
Console::warning('Skipping SSL certificates generation on ACME challenge.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Check if domain is a main domain
|
||||
if (!in_array($domain->get(), $domains)) {
|
||||
Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Check/create rule (requires DB access)
|
||||
Authorization::disable();
|
||||
try {
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$document = $isMd5
|
||||
? $dbForPlatform->getDocument('rules', md5($domain->get()))
|
||||
: $dbForPlatform->findOne('rules', [
|
||||
Query::equal('domain', [$domain->get()]),
|
||||
]);
|
||||
|
||||
if (!$document->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. Create new rule
|
||||
$owner = '';
|
||||
$fallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', '');
|
||||
$funcDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
$siteDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
|
||||
if (!empty($fallback) && \str_ends_with($domain->get(), $fallback)) {
|
||||
$funcDomain = $fallback;
|
||||
}
|
||||
|
||||
if (
|
||||
(!empty($funcDomain) && \str_ends_with($domain->get(), $funcDomain)) ||
|
||||
(!empty($siteDomain) && \str_ends_with($domain->get(), $siteDomain))
|
||||
) {
|
||||
$owner = 'Appwrite';
|
||||
}
|
||||
|
||||
$ruleId = $isMd5 ? md5($domain->get()) : ID::unique();
|
||||
$document = new Document([
|
||||
'$id' => $ruleId,
|
||||
'domain' => $domain->get(),
|
||||
'type' => 'api',
|
||||
'status' => 'verifying',
|
||||
'projectId' => $console->getId(),
|
||||
'projectInternalId' => $console->getSequence(),
|
||||
'search' => implode(' ', [$ruleId, $domain->get()]),
|
||||
'owner' => $owner,
|
||||
'region' => $console->getAttribute('region')
|
||||
]);
|
||||
|
||||
$dbForPlatform->createDocument('rules', $document);
|
||||
|
||||
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
|
||||
$queueForCertificates
|
||||
->setDomain($document)
|
||||
->setSkipRenewCheck(true)
|
||||
->trigger();
|
||||
} catch (Duplicate $e) {
|
||||
Console::info('Certificate already exists');
|
||||
} finally {
|
||||
$cache[$domain->get()] = true;
|
||||
Config::setParam('domains', $cache);
|
||||
Authorization::reset();
|
||||
}
|
||||
});
|
||||
|
||||
App::options()
|
||||
->inject('utopia')
|
||||
->inject('swooleRequest')
|
||||
|
|
@ -1125,38 +1133,32 @@ App::options()
|
|||
->inject('executor')
|
||||
->inject('geodb')
|
||||
->inject('isResourceBlocked')
|
||||
->inject('platform')
|
||||
->inject('previewHostname')
|
||||
->inject('project')
|
||||
->inject('devKey')
|
||||
->inject('apiKey')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey) {
|
||||
->inject('domains')
|
||||
->inject('cors')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, array $domains, Cors $cors) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
$host = $request->getHostname() ?? '';
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
// Only run Router when external domain
|
||||
if ($host !== $mainDomain || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
|
||||
if (!in_array($request->getHostname(), $domains) || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
|
||||
$origin = $request->getOrigin();
|
||||
foreach ($cors->headers($request->getOrigin()) as $name => $value) {
|
||||
$response->addHeader($name, $value);
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Server', 'Appwrite')
|
||||
->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE')
|
||||
->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Dev-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Appwrite-Session, X-Fallback-Cookies, X-Forwarded-For, X-Forwarded-User-Agent')
|
||||
->addHeader('Access-Control-Expose-Headers', 'X-Appwrite-Session, X-Fallback-Cookies')
|
||||
->addHeader('Access-Control-Allow-Origin', $origin)
|
||||
->addHeader('Access-Control-Allow-Credentials', 'true')
|
||||
->noContent();
|
||||
|
||||
if (!$devKey->isEmpty()) {
|
||||
$response->addHeader('Access-Control-Allow-Origin', '*');
|
||||
}
|
||||
|
||||
/** OPTIONS requests in utopia do not execute shutdown handlers, as a result we need to track the OPTIONS requests explicitly
|
||||
* @see https://github.com/utopia-php/http/blob/0.33.16/src/App.php#L825-L855
|
||||
*/
|
||||
|
|
@ -1443,18 +1445,16 @@ App::get('/robots.txt')
|
|||
->inject('executor')
|
||||
->inject('geodb')
|
||||
->inject('isResourceBlocked')
|
||||
->inject('platform')
|
||||
->inject('previewHostname')
|
||||
->inject('apiKey')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey) {
|
||||
$host = $request->getHostname() ?? '';
|
||||
$consoleDomain = System::getEnv('_APP_CONSOLE_DOMAIN', '');
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
|
||||
if (($host === $consoleDomain || $host === $mainDomain || $host === 'localhost') && empty($previewHostname)) {
|
||||
->inject('domains')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, array $domains) {
|
||||
if (in_array($request->getHostname(), $domains) || !empty($previewHostname)) {
|
||||
$template = new View(__DIR__ . '/../views/general/robots.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
|
|
@ -1477,18 +1477,16 @@ App::get('/humans.txt')
|
|||
->inject('executor')
|
||||
->inject('geodb')
|
||||
->inject('isResourceBlocked')
|
||||
->inject('platform')
|
||||
->inject('previewHostname')
|
||||
->inject('apiKey')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey) {
|
||||
$host = $request->getHostname() ?? '';
|
||||
$consoleDomain = System::getEnv('_APP_CONSOLE_DOMAIN', '');
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
|
||||
if (($host === $consoleDomain || $host === $mainDomain || $host === 'localhost') && empty($previewHostname)) {
|
||||
->inject('domains')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, array $domains) {
|
||||
if (in_array($request->getHostname(), $domains) || !empty($previewHostname)) {
|
||||
$template = new View(__DIR__ . '/../views/general/humans.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ use Utopia\Database\Helpers\Role;
|
|||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Host;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\VCS\Adapter\Git\GitHub;
|
||||
|
|
@ -27,7 +26,7 @@ App::get('/v1/mock/tests/general/oauth2')
|
|||
->label('docs', false)
|
||||
->label('mock', true)
|
||||
->param('client_id', '', new Text(100), 'OAuth2 Client ID.')
|
||||
->param('redirect_uri', '', new Host(['localhost']), 'OAuth2 Redirect URI.') // Important to deny an open redirect attack
|
||||
->param('redirect_uri', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator']) // Important to deny an open redirect attack
|
||||
->param('scope', '', new Text(100), 'OAuth2 scope list.')
|
||||
->param('state', '', new Text(1024), 'OAuth2 state.')
|
||||
->inject('response')
|
||||
|
|
@ -64,7 +63,7 @@ App::get('/v1/mock/tests/general/oauth2/token')
|
|||
->param('client_id', '', new Text(100), 'OAuth2 Client ID.')
|
||||
->param('client_secret', '', new Text(100), 'OAuth2 scope list.')
|
||||
->param('grant_type', 'authorization_code', new WhiteList(['refresh_token', 'authorization_code']), 'OAuth2 Grant Type.', true)
|
||||
->param('redirect_uri', '', new Host(['localhost']), 'OAuth2 Redirect URI.', true)
|
||||
->param('redirect_uri', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['redirectValidator'])
|
||||
->param('code', '', new Text(100), 'OAuth2 state.', true)
|
||||
->param('refresh_token', '', new Text(100), 'OAuth2 refresh token.', true)
|
||||
->inject('response')
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ use Appwrite\Event\Database as EventDatabase;
|
|||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Migration;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Event\Webhook;
|
||||
|
|
@ -495,6 +497,9 @@ App::init()
|
|||
->inject('queueForDatabase')
|
||||
->inject('queueForBuilds')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForMails')
|
||||
->inject('queueForMigrations')
|
||||
->inject('dbForProject')
|
||||
->inject('timelimit')
|
||||
->inject('resourceToken')
|
||||
|
|
@ -503,7 +508,8 @@ App::init()
|
|||
->inject('plan')
|
||||
->inject('devKey')
|
||||
->inject('telemetry')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry) use ($usageDatabaseListener, $eventDatabaseListener) {
|
||||
->inject('platform')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Mail $queueForMails, Migration $queueForMigrations, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform) use ($usageDatabaseListener, $eventDatabaseListener) {
|
||||
|
||||
$route = $utopia->getRoute();
|
||||
|
||||
|
|
@ -577,6 +583,10 @@ App::init()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: (@loks0n)
|
||||
* Avoid mutating the message across file boundaries - it's difficult to reason about at scale.
|
||||
*/
|
||||
/*
|
||||
* Background Jobs
|
||||
*/
|
||||
|
|
@ -607,10 +617,18 @@ App::init()
|
|||
}
|
||||
}
|
||||
|
||||
/* Auto-set projects */
|
||||
$queueForDeletes->setProject($project);
|
||||
$queueForDatabase->setProject($project);
|
||||
$queueForBuilds->setProject($project);
|
||||
$queueForMessaging->setProject($project);
|
||||
$queueForFunctions->setProject($project);
|
||||
$queueForBuilds->setProject($project);
|
||||
|
||||
/* Auto-set platforms */
|
||||
$queueForFunctions->setPlatform($platform);
|
||||
$queueForBuilds->setPlatform($platform);
|
||||
$queueForMails->setPlatform($platform);
|
||||
$queueForMigrations->setPlatform($platform);
|
||||
|
||||
// Clone the queues, to prevent events triggered by the database listener
|
||||
// from overwriting the events that are supposed to be triggered in the shutdown hook.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ App::get('/versions')
|
|||
->label('scope', 'public')
|
||||
->inject('response')
|
||||
->action(function (Response $response) {
|
||||
$platforms = Config::getParam('platforms');
|
||||
$platforms = Config::getParam('sdks');
|
||||
|
||||
$versions = [
|
||||
'server' => APP_VERSION_STABLE,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ Config::load('auth', __DIR__ . '/../config/auth.php', $configAdapter);
|
|||
Config::load('apis', __DIR__ . '/../config/apis.php', $configAdapter); // List of APIs
|
||||
Config::load('errors', __DIR__ . '/../config/errors.php', $configAdapter);
|
||||
Config::load('oAuthProviders', __DIR__ . '/../config/oAuthProviders.php', $configAdapter);
|
||||
Config::load('platforms', __DIR__ . '/../config/platforms.php', $configAdapter);
|
||||
Config::load('sdks', __DIR__ . '/../config/sdks.php', $configAdapter);
|
||||
Config::load('platform', __DIR__ . '/../config/platform.php', $configAdapter);
|
||||
Config::load('console', __DIR__ . '/../config/console.php', $configAdapter);
|
||||
Config::load('collections', __DIR__ . '/../config/collections.php', $configAdapter);
|
||||
Config::load('frameworks', __DIR__ . '/../config/frameworks.php', $configAdapter);
|
||||
|
|
|
|||
|
|
@ -4,12 +4,17 @@ use Appwrite\Platform\Modules\Compute\Specification;
|
|||
|
||||
const APP_NAME = 'Appwrite';
|
||||
const APP_DOMAIN = 'appwrite.io';
|
||||
|
||||
// Email
|
||||
const APP_EMAIL_TEAM = 'team@localhost.test'; // Default email address
|
||||
const APP_EMAIL_SECURITY = ''; // Default security email address
|
||||
const APP_EMAIL_LOGO_URL = 'https://cloud.appwrite.io/images/mails/logo.png';
|
||||
const APP_EMAIL_ACCENT_COLOR = '#fd366e';
|
||||
const APP_EMAIL_TERMS_URL = 'https://appwrite.io/terms';
|
||||
const APP_EMAIL_PRIVACY_URL = 'https://appwrite.io/privacy';
|
||||
const APP_EMAIL_PLATFORM_NAME = 'Appwrite';
|
||||
const APP_EMAIL_FOOTER_IMAGE_URL = 'https://appwrite.io/email/footer.png';
|
||||
|
||||
const APP_USERAGENT = APP_NAME . '-Server v%s. Please report abuse at %s';
|
||||
const APP_MODE_DEFAULT = 'default';
|
||||
const APP_MODE_ADMIN = 'admin';
|
||||
|
|
@ -81,7 +86,6 @@ const APP_SOCIAL_DISCORD_CHANNEL = '564160730845151244';
|
|||
const APP_SOCIAL_DEV = 'https://dev.to/appwrite';
|
||||
const APP_SOCIAL_STACKSHARE = 'https://stackshare.io/appwrite';
|
||||
const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=1';
|
||||
const APP_HOSTNAME_INTERNAL = 'appwrite';
|
||||
const APP_COMPUTE_CPUS_DEFAULT = 0.5;
|
||||
const APP_COMPUTE_MEMORY_DEFAULT = 512;
|
||||
const APP_COMPUTE_SPECIFICATION_DEFAULT = Specification::S_1VCPU_512MB;
|
||||
|
|
|
|||
|
|
@ -20,8 +20,10 @@ use Appwrite\Event\StatsUsage;
|
|||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\GraphQL\Schema;
|
||||
use Appwrite\Network\Cors;
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Network\Validator\Redirect;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
|
|
@ -43,7 +45,6 @@ use Utopia\Database\Adapter\Pool as DatabasePool;
|
|||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime as DatabaseDateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\DSN\DSN;
|
||||
|
|
@ -64,7 +65,7 @@ use Utopia\Storage\Storage;
|
|||
use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter as Telemetry;
|
||||
use Utopia\Telemetry\Adapter\None as NoTelemetry;
|
||||
use Utopia\Validator\Hostname;
|
||||
use Utopia\Validator\URL;
|
||||
use Utopia\Validator\WhiteList;
|
||||
use Utopia\VCS\Adapter\Git\GitHub as VcsGitHub;
|
||||
|
||||
|
|
@ -159,79 +160,189 @@ App::setResource('queueForMigrations', function (Publisher $publisher) {
|
|||
App::setResource('queueForStatsResources', function (Publisher $publisher) {
|
||||
return new StatsResources($publisher);
|
||||
}, ['publisher']);
|
||||
App::setResource('platforms', function (Request $request, Document $console, Document $project, Database $dbForPlatform) {
|
||||
$console->setAttribute('platforms', [ // Always allow current host
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'name' => 'Current Host',
|
||||
'type' => Platform::TYPE_WEB,
|
||||
'hostname' => $request->getHostname(),
|
||||
], Document::SET_TYPE_APPEND);
|
||||
|
||||
$hostnames = explode(',', System::getEnv('_APP_CONSOLE_HOSTNAMES', ''));
|
||||
$validator = new Hostname();
|
||||
foreach ($hostnames as $hostname) {
|
||||
$hostname = trim($hostname);
|
||||
if (!$validator->isValid($hostname)) {
|
||||
continue;
|
||||
}
|
||||
$console->setAttribute('platforms', [
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'type' => Platform::TYPE_WEB,
|
||||
'name' => $hostname,
|
||||
'hostname' => $hostname,
|
||||
], Document::SET_TYPE_APPEND);
|
||||
/**
|
||||
* List of domains served by the application.
|
||||
*/
|
||||
App::setResource('domains', fn () => array_unique(array_filter([
|
||||
...\explode(',', System::getEnv('_APP_DOMAIN', 'localhost')),
|
||||
...\explode(',', System::getEnv('_APP_CONSOLE_DOMAIN', 'localhost'))
|
||||
])));
|
||||
|
||||
/**
|
||||
* Platform configuration
|
||||
*/
|
||||
App::setResource('platform', function (Request $request) {
|
||||
$platform = Config::getParam('platform', []);
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
|
||||
$port = '';
|
||||
if ($request->getPort() === '443' && $protocol !== 'https') {
|
||||
$port = ':443';
|
||||
}
|
||||
if ($request->getPort() === '80' && $protocol !== 'http') {
|
||||
$port = ':80';
|
||||
}
|
||||
$platform['endpoint'] = "$protocol://{$platform['domain']}{$port}/v1";
|
||||
|
||||
return $platform;
|
||||
}, ['request']);
|
||||
|
||||
/**
|
||||
* Safe request origin used to construct urls
|
||||
*/
|
||||
App::setResource('origin', function (Request $request) {
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
|
||||
$port = '';
|
||||
if ($request->getPort() === '443' && $protocol !== 'https') {
|
||||
$port = ':443';
|
||||
}
|
||||
if ($request->getPort() === '80' && $protocol !== 'http') {
|
||||
$port = ':80';
|
||||
}
|
||||
|
||||
// Add `exp` and `appwrite-callback-{projectId}` schemes
|
||||
return "$protocol://{$request->getHostname()}{$port}";
|
||||
}, ['request']);
|
||||
|
||||
/**
|
||||
* List of allowed request hostnames for the request.
|
||||
*/
|
||||
App::setResource('allowedHostnames', function (array $domains, Document $project, Document $rule, Document $devKey, Request $request) {
|
||||
$allowed = [...$domains];
|
||||
|
||||
/* Add platform configured hostnames */
|
||||
if (!$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$project->setAttribute('platforms', [
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'type' => Platform::TYPE_SCHEME,
|
||||
'name' => 'Expo',
|
||||
'key' => 'exp',
|
||||
], Document::SET_TYPE_APPEND);
|
||||
$project->setAttribute('platforms', [
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'type' => Platform::TYPE_SCHEME,
|
||||
'name' => 'Appwrite Callback',
|
||||
'key' => 'appwrite-callback-' . $project->getId(),
|
||||
], Document::SET_TYPE_APPEND);
|
||||
$platforms = $project->getAttribute('platforms', []);
|
||||
$hostnames = Platform::getHostnames($platforms);
|
||||
$allowed = [...$allowed, ...$hostnames];
|
||||
}
|
||||
|
||||
$origin = \parse_url($request->getOrigin(), PHP_URL_HOST);
|
||||
|
||||
if (empty($origin)) {
|
||||
$origin = \parse_url($request->getReferer(), PHP_URL_HOST);
|
||||
/* Add the request hostname if a dev key is found */
|
||||
if (!$devKey->isEmpty()) {
|
||||
$allowed[] = $request->getHostname();
|
||||
}
|
||||
|
||||
// Safe if rule with same project ID exists
|
||||
if (!empty($origin)) {
|
||||
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
|
||||
$rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($origin ?? '')));
|
||||
} else {
|
||||
$rule = Authorization::skip(
|
||||
fn () => $dbForPlatform->find('rules', [
|
||||
Query::equal('domain', [$origin]),
|
||||
Query::limit(1)
|
||||
])
|
||||
)[0] ?? new Document();
|
||||
/* Allow the request origin if a dev key or rule is found */
|
||||
$originHostname = parse_url($request->getOrigin(), PHP_URL_HOST);
|
||||
if ((!$rule->isEmpty() || !$devKey->isEmpty()) && !empty($originHostname)) {
|
||||
$allowed[] = $originHostname;
|
||||
}
|
||||
|
||||
return array_unique($allowed);
|
||||
}, ['domains', 'project', 'rule', 'devKey', 'request']);
|
||||
|
||||
/**
|
||||
* List of allowed request schemes for the request.
|
||||
*/
|
||||
App::setResource('allowedSchemes', function (Document $project) {
|
||||
$allowed = [];
|
||||
|
||||
if (!$project->isEmpty() && $project->getId() !== 'console') {
|
||||
/* Add hardcoded schemes */
|
||||
$allowed[] = 'exp';
|
||||
$allowed[] = 'appwrite-callback-' . $project->getId();
|
||||
|
||||
/* Add platform configured schemes */
|
||||
$platforms = $project->getAttribute('platforms', []);
|
||||
$schemes = Platform::getSchemes($platforms);
|
||||
$allowed = [...$allowed, ...$schemes];
|
||||
}
|
||||
|
||||
return array_unique($allowed);
|
||||
}, ['project']);
|
||||
|
||||
/**
|
||||
* Rule associated with a request origin.
|
||||
*/
|
||||
App::setResource('rule', function (Request $request, Database $dbForPlatform, Document $project) {
|
||||
$domain = \parse_url($request->getOrigin(), PHP_URL_HOST);
|
||||
if (empty($domain)) {
|
||||
return new Document();
|
||||
}
|
||||
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$rule = Authorization::skip(function () use ($dbForPlatform, $domain, $isMd5) {
|
||||
if ($isMd5) {
|
||||
return $dbForPlatform->getDocument('rules', md5($domain));
|
||||
}
|
||||
|
||||
if (!$rule->isEmpty() && $rule->getAttribute('projectInternalId') === $project->getSequence()) {
|
||||
$project->setAttribute('platforms', [
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'type' => Platform::TYPE_WEB,
|
||||
'name' => $origin,
|
||||
'hostname' => $origin,
|
||||
], Document::SET_TYPE_APPEND);
|
||||
}
|
||||
return $dbForPlatform->findOne('rules', [
|
||||
Query::equal('domain', [$domain]),
|
||||
]) ?? new Document();
|
||||
});
|
||||
|
||||
if ($rule->getAttribute('projectInternalId') !== $project->getSequence()) {
|
||||
return new Document();
|
||||
}
|
||||
|
||||
return [
|
||||
...$console->getAttribute('platforms', []),
|
||||
...$project->getAttribute('platforms', []),
|
||||
];
|
||||
}, ['request', 'console', 'project', 'dbForPlatform']);
|
||||
return $rule;
|
||||
}, ['request', 'dbForPlatform', 'project']);
|
||||
|
||||
/**
|
||||
* CORS service
|
||||
*/
|
||||
App::setResource('cors', fn (array $allowedHostnames) => new Cors(
|
||||
$allowedHostnames,
|
||||
allowedMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
|
||||
allowedHeaders: [
|
||||
'Accept',
|
||||
'Origin',
|
||||
'Cookie',
|
||||
'Set-Cookie',
|
||||
// Content
|
||||
'Content-Type',
|
||||
'Content-Range',
|
||||
// Appwrite
|
||||
'X-Appwrite-Project',
|
||||
'X-Appwrite-Key',
|
||||
'X-Appwrite-Dev-Key',
|
||||
'X-Appwrite-Locale',
|
||||
'X-Appwrite-Mode',
|
||||
'X-Appwrite-JWT',
|
||||
'X-Appwrite-Response-Format',
|
||||
'X-Appwrite-Timeout',
|
||||
'X-Appwrite-ID',
|
||||
'X-Appwrite-Timestamp',
|
||||
'X-Appwrite-Session',
|
||||
// SDK generator
|
||||
'X-SDK-Version',
|
||||
'X-SDK-Name',
|
||||
'X-SDK-Language',
|
||||
'X-SDK-Platform',
|
||||
'X-SDK-GraphQL',
|
||||
// Caching
|
||||
'Range',
|
||||
'Cache-Control',
|
||||
'Expires',
|
||||
'Pragma',
|
||||
// Server to server
|
||||
'X-Fallback-Cookies',
|
||||
'X-Requested-With',
|
||||
'X-Forwarded-For',
|
||||
'X-Forwarded-User-Agent',
|
||||
],
|
||||
allowCredentials: true,
|
||||
exposedHeaders: [
|
||||
'X-Appwrite-Session',
|
||||
'X-Fallback-Cookies',
|
||||
],
|
||||
), ['allowedHostnames']);
|
||||
|
||||
App::setResource('originValidator', function (Document $devKey, array $allowedHostnames, array $allowedSchemes) {
|
||||
if (!$devKey->isEmpty()) {
|
||||
return new URL();
|
||||
}
|
||||
return new Origin($allowedHostnames, $allowedSchemes);
|
||||
}, ['devKey', 'allowedHostnames', 'allowedSchemes']);
|
||||
|
||||
App::setResource('redirectValidator', function (Document $devKey, array $allowedHostnames, array $allowedSchemes) {
|
||||
if (!$devKey->isEmpty()) {
|
||||
return new URL();
|
||||
}
|
||||
return new Redirect($allowedHostnames, $allowedSchemes);
|
||||
}, ['devKey', 'allowedHostnames', 'allowedSchemes']);
|
||||
|
||||
App::setResource('user', function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForPlatform, Store $store, Token $proofForToken) {
|
||||
/**
|
||||
|
|
@ -738,7 +849,7 @@ App::setResource('passwordsDictionary', function ($register) {
|
|||
|
||||
|
||||
App::setResource('servers', function () {
|
||||
$platforms = Config::getParam('platforms');
|
||||
$platforms = Config::getParam('sdks');
|
||||
$server = $platforms[APP_PLATFORM_SERVER];
|
||||
|
||||
$languages = array_map(function ($language) {
|
||||
|
|
@ -839,24 +950,6 @@ App::setResource('schema', function ($utopia, $dbForProject) {
|
|||
);
|
||||
}, ['utopia', 'dbForProject']);
|
||||
|
||||
App::setResource('contributors', function () {
|
||||
$path = 'app/config/contributors.json';
|
||||
$list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
|
||||
return $list;
|
||||
});
|
||||
|
||||
App::setResource('employees', function () {
|
||||
$path = 'app/config/employees.json';
|
||||
$list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
|
||||
return $list;
|
||||
});
|
||||
|
||||
App::setResource('heroes', function () {
|
||||
$path = 'app/config/heroes.json';
|
||||
$list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
|
||||
return $list;
|
||||
});
|
||||
|
||||
App::setResource('gitHub', function (Cache $cache) {
|
||||
return new VcsGitHub($cache);
|
||||
}, ['cache']);
|
||||
|
|
@ -923,6 +1016,7 @@ App::setResource('devKey', function (Request $request, Document $project, array
|
|||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
}
|
||||
}
|
||||
|
||||
return $key;
|
||||
}, ['request', 'project', 'servers', 'dbForPlatform']);
|
||||
|
||||
|
|
@ -1063,37 +1157,6 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
|
|||
return new Document([]);
|
||||
}, ['project', 'dbForProject', 'request']);
|
||||
|
||||
App::setResource('httpReferrer', function (Request $request): string {
|
||||
$referrer = $request->getReferer();
|
||||
return $referrer;
|
||||
}, ['request']);
|
||||
|
||||
App::setResource('httpReferrerSafe', function (Request $request, string $httpReferrer, array $platforms, Database $dbForPlatform, Document $project, App $utopia): string {
|
||||
$origin = \parse_url($request->getOrigin($httpReferrer), PHP_URL_HOST);
|
||||
$protocol = \parse_url($request->getOrigin($httpReferrer), PHP_URL_SCHEME);
|
||||
$port = \parse_url($request->getOrigin($httpReferrer), PHP_URL_PORT);
|
||||
$referrer = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : '');
|
||||
|
||||
// Safe if route is publicly accessible
|
||||
$route = $utopia->getRoute();
|
||||
if ($route->getLabel('origin', false)) {
|
||||
return $referrer;
|
||||
}
|
||||
|
||||
// Safe if added as web platform
|
||||
$originValidator = new Origin($platforms);
|
||||
if ($originValidator->isValid($request->getOrigin($httpReferrer))) {
|
||||
return $referrer;
|
||||
}
|
||||
|
||||
// Unsafe; Localhost is always safe for ease of local development
|
||||
$origin = 'localhost';
|
||||
$protocol = \parse_url($request->getOrigin($httpReferrer), PHP_URL_SCHEME);
|
||||
$port = \parse_url($request->getOrigin($httpReferrer), PHP_URL_PORT);
|
||||
$referrer = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : '');
|
||||
return $referrer;
|
||||
}, ['request', 'httpReferrer', 'platforms', 'dbForPlatform', 'project', 'utopia']);
|
||||
|
||||
App::setResource('transactionState', function (Database $dbForProject) {
|
||||
return new TransactionState($dbForProject);
|
||||
}, ['dbForProject']);
|
||||
|
|
|
|||
|
|
@ -543,7 +543,6 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
|||
}
|
||||
|
||||
$timelimit = $app->getResource('timelimit');
|
||||
$platforms = $app->getResource('platforms');
|
||||
$user = $app->getResource('user'); /** @var User $user */
|
||||
|
||||
/*
|
||||
|
|
@ -568,7 +567,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
|||
* Skip this check for non-web platforms which are not required to send an origin header.
|
||||
*/
|
||||
$origin = $request->getOrigin();
|
||||
$originValidator = new Origin($platforms);
|
||||
$originValidator = $app->getResource('originValidator');
|
||||
|
||||
if (!empty($origin) && !$originValidator->isValid($origin) && $project->getId() !== 'console') {
|
||||
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\System\System;
|
||||
|
||||
$development = $this->getParam('development', false);
|
||||
|
|
@ -15,7 +16,8 @@ $labelClass = '';
|
|||
$buttons = [];
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
|
||||
$platform = Config::getParam('platform', []);
|
||||
$hostname = $platform['consoleDomain'] ?? '';
|
||||
// TODO: remove this later
|
||||
if (System::getEnv('_APP_ENV') === 'development') {
|
||||
$hostname = 'localhost';
|
||||
|
|
@ -537,4 +539,4 @@ switch ($type) {
|
|||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -38,9 +38,15 @@ services:
|
|||
depends_on:
|
||||
- appwrite
|
||||
networks:
|
||||
- gateway
|
||||
- appwrite
|
||||
- runtimes
|
||||
appwrite:
|
||||
aliases:
|
||||
- appwrite.test
|
||||
gateway:
|
||||
aliases:
|
||||
- appwrite.test
|
||||
runtimes:
|
||||
aliases:
|
||||
- appwrite.test
|
||||
|
||||
appwrite:
|
||||
container_name: appwrite
|
||||
|
|
|
|||
|
|
@ -115,7 +115,8 @@ class Build extends Event
|
|||
'resource' => $this->resource,
|
||||
'deployment' => $this->deployment,
|
||||
'type' => $this->type,
|
||||
'template' => $this->template
|
||||
'template' => $this->template,
|
||||
'platform' => $this->platform
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -130,6 +131,7 @@ class Build extends Event
|
|||
$this->resource = null;
|
||||
$this->deployment = null;
|
||||
$this->template = null;
|
||||
$this->platform = [];
|
||||
parent::reset();
|
||||
|
||||
return $this;
|
||||
|
|
|
|||
|
|
@ -52,9 +52,11 @@ class Event
|
|||
protected array $sensitive = [];
|
||||
protected array $payload = [];
|
||||
protected array $context = [];
|
||||
protected array $platform = [];
|
||||
protected ?Document $project = null;
|
||||
protected ?Document $user = null;
|
||||
protected ?string $userId = null;
|
||||
|
||||
protected bool $paused = false;
|
||||
|
||||
/** @var bool Non-critical events will not throw an exception when enqueuing of the event fails. */
|
||||
|
|
@ -153,6 +155,28 @@ class Event
|
|||
return $this->project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set platform for this event.
|
||||
*
|
||||
* @param array $platform
|
||||
* @return self
|
||||
*/
|
||||
public function setPlatform(array $platform): self
|
||||
{
|
||||
$this->platform = $platform;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get platform for this event.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPlatform(): array
|
||||
{
|
||||
return $this->platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set user for this event.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ class Func extends Event
|
|||
/**
|
||||
* Sets custom headers for the function event.
|
||||
*
|
||||
* @param string $headers
|
||||
* @param array $headers
|
||||
* @return self
|
||||
*/
|
||||
public function setHeaders(array $headers): self
|
||||
|
|
@ -217,6 +217,7 @@ class Func extends Event
|
|||
'path' => $this->path,
|
||||
'headers' => $this->headers,
|
||||
'method' => $this->method,
|
||||
'platform' => $this->platform
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ class Migration extends Event
|
|||
'project' => $this->project,
|
||||
'user' => $this->user,
|
||||
'migration' => $this->migration,
|
||||
'platform' => $this->platform,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
88
src/Appwrite/Network/Cors.php
Normal file
88
src/Appwrite/Network/Cors.php
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Network;
|
||||
|
||||
/**
|
||||
* Generate CORS response headers for an incoming request.
|
||||
*
|
||||
* Allowed origins are matched by hostname only. Arrays passed to the
|
||||
* constructor (methods, headers, exposed headers) are formatted into
|
||||
* comma-separated header strings.
|
||||
*/
|
||||
final class Cors
|
||||
{
|
||||
public const string HEADER_ALLOW_ORIGIN = 'Access-Control-Allow-Origin';
|
||||
public const string HEADER_ALLOW_METHODS = 'Access-Control-Allow-Methods';
|
||||
public const string HEADER_ALLOW_HEADERS = 'Access-Control-Allow-Headers';
|
||||
public const string HEADER_ALLOW_CREDENTIALS = 'Access-Control-Allow-Credentials';
|
||||
public const string HEADER_EXPOSE_HEADERS = 'Access-Control-Expose-Headers';
|
||||
public const string HEADER_MAX_AGE = 'Access-Control-Max-Age';
|
||||
|
||||
/**
|
||||
* @param array<string> $allowedHosts Array of allowed hosts
|
||||
* @param array<string> $allowedMethods Array of allowed methods
|
||||
* @param array<string> $allowedHeaders Array of allowed header
|
||||
* @param array<string> $exposedHeaders Array of exposed headers
|
||||
* @param bool $allowCredentials Whether to allow credentials (default: false)
|
||||
* @param int $maxAge Maximum age of the preflight response (default: 86400 seconds)
|
||||
*/
|
||||
public function __construct(
|
||||
private array $allowedHosts,
|
||||
private array $allowedMethods,
|
||||
private array $allowedHeaders,
|
||||
private array $exposedHeaders,
|
||||
private bool $allowCredentials = false,
|
||||
private int $maxAge = 86400,
|
||||
) {
|
||||
$this->allowedHosts = \array_map('strtolower', $this->allowedHosts);
|
||||
|
||||
if ($this->allowedHosts === ['*'] && $allowCredentials === true) {
|
||||
throw new \InvalidArgumentException(
|
||||
'CORS invariant violated: cannot use wildcard origin "*" when credentials are enabled.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build CORS headers for a given request origin.
|
||||
*
|
||||
* @return array<string,string>
|
||||
*/
|
||||
public function headers(string $origin): array
|
||||
{
|
||||
$headers = [
|
||||
self::HEADER_ALLOW_METHODS => implode(', ', $this->allowedMethods),
|
||||
self::HEADER_ALLOW_HEADERS => implode(', ', $this->allowedHeaders),
|
||||
self::HEADER_EXPOSE_HEADERS => implode(', ', $this->exposedHeaders),
|
||||
self::HEADER_ALLOW_CREDENTIALS => $this->allowCredentials ? 'true' : 'false',
|
||||
self::HEADER_MAX_AGE => $this->maxAge,
|
||||
];
|
||||
|
||||
// Wildcard allow-all
|
||||
if ($this->allowedHosts === ['*']) {
|
||||
$headers[self::HEADER_ALLOW_ORIGIN] = $origin;
|
||||
return $headers;
|
||||
}
|
||||
|
||||
// Normal origin handling
|
||||
$origin = strtolower(trim($origin));
|
||||
if ($origin === '') {
|
||||
return $headers;
|
||||
}
|
||||
|
||||
$host = parse_url($origin, PHP_URL_HOST);
|
||||
if (!\is_string($host) || $host === '') {
|
||||
return $headers;
|
||||
}
|
||||
|
||||
// Match only by host
|
||||
if (!\in_array($host, $this->allowedHosts, true)) {
|
||||
return $headers;
|
||||
}
|
||||
|
||||
// Accepted
|
||||
$headers[self::HEADER_ALLOW_ORIGIN] = $origin;
|
||||
|
||||
return $headers;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,8 +8,6 @@ use Utopia\Validator\Hostname;
|
|||
|
||||
class Origin extends Validator
|
||||
{
|
||||
protected array $hostnames = [];
|
||||
protected array $schemes = [];
|
||||
protected ?string $scheme = null;
|
||||
protected ?string $host = null;
|
||||
protected string $origin = '';
|
||||
|
|
@ -17,12 +15,11 @@ class Origin extends Validator
|
|||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param array<\Utopia\Database\Document> $platforms
|
||||
* @param array<string> $allowedHostnames
|
||||
* @param array<string> $allowedSchemes
|
||||
*/
|
||||
public function __construct(array $platforms)
|
||||
public function __construct(protected array $allowedHostnames, protected array $allowedSchemes)
|
||||
{
|
||||
$this->hostnames = Platform::getHostnames($platforms);
|
||||
$this->schemes = Platform::getSchemes($platforms);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -53,11 +50,11 @@ class Origin extends Validator
|
|||
Platform::SCHEME_EDGE_EXTENSION,
|
||||
];
|
||||
if (in_array($this->scheme, $webPlatforms, true)) {
|
||||
$validator = new Hostname($this->hostnames);
|
||||
$validator = new Hostname($this->allowedHostnames);
|
||||
return $validator->isValid($this->host);
|
||||
}
|
||||
|
||||
if (!empty($this->scheme) && in_array($this->scheme, $this->schemes, true)) {
|
||||
if (!empty($this->scheme) && in_array($this->scheme, $this->allowedSchemes, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -235,8 +235,9 @@ class Base extends Action
|
|||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$domain = ID::unique() . "." . $sitesDomain;
|
||||
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique();
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$ruleId = $isMd5 ? md5($domain) : ID::unique();
|
||||
|
||||
Authorization::skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ class Get extends Action
|
|||
->param('type', '', new WhiteList(['rules']), 'Resource type.')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('domains')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
|
|
@ -66,7 +67,8 @@ class Get extends Action
|
|||
string $value,
|
||||
string $type,
|
||||
Response $response,
|
||||
Database $dbForPlatform
|
||||
Database $dbForPlatform,
|
||||
array $domains
|
||||
) {
|
||||
if ($type === 'rules') {
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
|
|
@ -89,13 +91,7 @@ class Get extends Action
|
|||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.');
|
||||
}
|
||||
|
||||
$deniedDomains = [
|
||||
'localhost',
|
||||
APP_HOSTNAME_INTERNAL
|
||||
];
|
||||
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$deniedDomains[] = $mainDomain;
|
||||
$deniedDomains = [...$domains];
|
||||
|
||||
if (!empty($sitesDomain)) {
|
||||
$deniedDomains[] = $sitesDomain;
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ class Create extends Base
|
|||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->inject('executor')
|
||||
->inject('platform')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
|
|
@ -121,7 +122,8 @@ class Create extends Base
|
|||
Reader $geodb,
|
||||
Store $store,
|
||||
Token $proofForToken,
|
||||
Executor $executor
|
||||
Executor $executor,
|
||||
array $platform
|
||||
) {
|
||||
$async = \strval($async) === 'true' || \strval($async) === '1';
|
||||
|
||||
|
|
@ -366,13 +368,9 @@ class Create extends Base
|
|||
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
|
||||
}
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_DOMAIN');
|
||||
$endpoint = $protocol . '://' . $hostname . "/v1";
|
||||
|
||||
// Appwrite vars
|
||||
$vars = \array_merge($vars, [
|
||||
'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint,
|
||||
'APPWRITE_FUNCTION_API_ENDPOINT' => $platform['endpoint'],
|
||||
'APPWRITE_FUNCTION_ID' => $functionId,
|
||||
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name'),
|
||||
'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(),
|
||||
|
|
|
|||
|
|
@ -362,8 +362,9 @@ class Create extends Base
|
|||
if (!empty($functionsDomain)) {
|
||||
$routeSubdomain = ID::unique();
|
||||
$domain = "{$routeSubdomain}.{$functionsDomain}";
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique();
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$ruleId = $isMd5 ? md5($domain) : ID::unique();
|
||||
|
||||
$rule = Authorization::skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
|
|
|
|||
|
|
@ -129,6 +129,8 @@ class Builds extends Action
|
|||
$resource = new Document($payload['resource'] ?? []);
|
||||
$deployment = new Document($payload['deployment'] ?? []);
|
||||
$template = new Document($payload['template'] ?? []);
|
||||
$platform = $payload['platform'] ?? [];
|
||||
|
||||
|
||||
$log->addTag('projectId', $project->getId());
|
||||
$log->addTag('type', $type);
|
||||
|
|
@ -157,7 +159,8 @@ class Builds extends Action
|
|||
$isResourceBlocked,
|
||||
$log,
|
||||
$executor,
|
||||
$plan
|
||||
$plan,
|
||||
$platform
|
||||
);
|
||||
break;
|
||||
|
||||
|
|
@ -209,7 +212,8 @@ class Builds extends Action
|
|||
callable $isResourceBlocked,
|
||||
Log $log,
|
||||
Executor $executor,
|
||||
array $plan
|
||||
array $plan,
|
||||
array $platform
|
||||
): void {
|
||||
Console::info('Deployment action started');
|
||||
|
||||
|
|
@ -532,7 +536,7 @@ class Builds extends Action
|
|||
|
||||
Console::log('Git source uploaded');
|
||||
|
||||
$this->runGitAction('processing', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime);
|
||||
$this->runGitAction('processing', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime, $platform);
|
||||
}
|
||||
|
||||
Console::log('Status marked as building');
|
||||
|
|
@ -550,7 +554,7 @@ class Builds extends Action
|
|||
->trigger();
|
||||
|
||||
if ($isVcsEnabled) {
|
||||
$this->runGitAction('building', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime);
|
||||
$this->runGitAction('building', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime, $platform);
|
||||
}
|
||||
|
||||
$deploymentModel = new Deployment();
|
||||
|
|
@ -612,10 +616,6 @@ class Builds extends Action
|
|||
'scopes' => $resource->getAttribute('scopes', [])
|
||||
]);
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_DOMAIN');
|
||||
$endpoint = $protocol . '://' . $hostname . "/v1";
|
||||
|
||||
// Appwrite vars
|
||||
$vars = \array_merge($vars, [
|
||||
'APPWRITE_VERSION' => APP_VERSION_STABLE,
|
||||
|
|
@ -639,7 +639,7 @@ class Builds extends Action
|
|||
case 'functions':
|
||||
$vars = [
|
||||
...$vars,
|
||||
'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint,
|
||||
'APPWRITE_FUNCTION_API_ENDPOINT' => $platform['endpoint'],
|
||||
'APPWRITE_FUNCTION_API_KEY' => API_KEY_DYNAMIC . '_' . $apiKey,
|
||||
'APPWRITE_FUNCTION_ID' => $resource->getId(),
|
||||
'APPWRITE_FUNCTION_NAME' => $resource->getAttribute('name'),
|
||||
|
|
@ -654,7 +654,7 @@ class Builds extends Action
|
|||
case 'sites':
|
||||
$vars = [
|
||||
...$vars,
|
||||
'APPWRITE_SITE_API_ENDPOINT' => $endpoint,
|
||||
'APPWRITE_SITE_API_ENDPOINT' => $platform['endpoint'],
|
||||
'APPWRITE_SITE_API_KEY' => API_KEY_DYNAMIC . '_' . $apiKey,
|
||||
'APPWRITE_SITE_ID' => $resource->getId(),
|
||||
'APPWRITE_SITE_NAME' => $resource->getAttribute('name'),
|
||||
|
|
@ -1108,7 +1108,7 @@ class Builds extends Action
|
|||
}
|
||||
|
||||
if ($isVcsEnabled) {
|
||||
$this->runGitAction('ready', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime);
|
||||
$this->runGitAction('ready', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime, $platform);
|
||||
}
|
||||
|
||||
/** Set auto deploy */
|
||||
|
|
@ -1337,7 +1337,7 @@ class Builds extends Action
|
|||
->trigger();
|
||||
|
||||
if ($isVcsEnabled) {
|
||||
$this->runGitAction('failed', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime);
|
||||
$this->runGitAction('failed', $github, $providerCommitHash, $owner, $repositoryName, $project, $resource, $deployment->getId(), $dbForProject, $dbForPlatform, $queueForRealtime, $platform);
|
||||
}
|
||||
} finally {
|
||||
$queueForRealtime
|
||||
|
|
@ -1489,6 +1489,8 @@ class Builds extends Action
|
|||
* @param string $deploymentId
|
||||
* @param Database $dbForProject
|
||||
* @param Database $dbForPlatform
|
||||
* @param Realtime $queueForRealtime
|
||||
* @param array $platform
|
||||
* @return void
|
||||
* @throws Structure
|
||||
* @throws \Utopia\Database\Exception
|
||||
|
|
@ -1508,6 +1510,7 @@ class Builds extends Action
|
|||
Database $dbForProject,
|
||||
Database $dbForPlatform,
|
||||
Realtime $queueForRealtime,
|
||||
array $platform
|
||||
): void {
|
||||
try {
|
||||
if ($resource->getAttribute('providerSilentMode', false) === true) {
|
||||
|
|
@ -1593,7 +1596,7 @@ class Builds extends Action
|
|||
default => throw new \Exception('Invalid resource type')
|
||||
};
|
||||
|
||||
$comment = new Comment();
|
||||
$comment = new Comment($platform);
|
||||
$comment->parseComment($github->getComment($owner, $repositoryName, $commentId));
|
||||
$comment->addBuild($project, $resource, $resourceType, $status, $deployment->getId(), ['type' => 'logs'], $previewUrl);
|
||||
$github->updateComment($owner, $repositoryName, $commentId, $comment->generateComment());
|
||||
|
|
|
|||
|
|
@ -67,10 +67,11 @@ class Create extends Action
|
|||
->inject('queueForCertificates')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('domains')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $domain, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform)
|
||||
public function action(string $domain, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, array $domains)
|
||||
{
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
|
@ -90,13 +91,7 @@ class Create extends Action
|
|||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.');
|
||||
}
|
||||
|
||||
$deniedDomains = [
|
||||
'localhost',
|
||||
APP_HOSTNAME_INTERNAL
|
||||
];
|
||||
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$deniedDomains[] = $mainDomain;
|
||||
$deniedDomains = [...$domains];
|
||||
|
||||
if (!empty($sitesDomain)) {
|
||||
$deniedDomains[] = $sitesDomain;
|
||||
|
|
@ -125,8 +120,9 @@ class Create extends Action
|
|||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain may not start with http:// or https://.');
|
||||
}
|
||||
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$ruleId = $isMd5 ? md5($domain->get()) : ID::unique();
|
||||
|
||||
$status = 'created';
|
||||
if (\str_ends_with($domain->get(), $functionsDomain) || \str_ends_with($domain->get(), $sitesDomain)) {
|
||||
|
|
|
|||
|
|
@ -72,10 +72,11 @@ class Create extends Action
|
|||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
->inject('domains')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $domain, string $functionId, string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject)
|
||||
public function action(string $domain, string $functionId, string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject, array $domains)
|
||||
{
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
|
@ -95,13 +96,7 @@ class Create extends Action
|
|||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.');
|
||||
}
|
||||
|
||||
$deniedDomains = [
|
||||
'localhost',
|
||||
APP_HOSTNAME_INTERNAL
|
||||
];
|
||||
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$deniedDomains[] = $mainDomain;
|
||||
$deniedDomains = [...$domains];
|
||||
|
||||
if (!empty($sitesDomain)) {
|
||||
$deniedDomains[] = $sitesDomain;
|
||||
|
|
@ -137,8 +132,9 @@ class Create extends Action
|
|||
|
||||
$deployment = $dbForProject->getDocument('deployments', $function->getAttribute('deploymentId', ''));
|
||||
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$ruleId = $isMd5 ? md5($domain->get()) : ID::unique();
|
||||
|
||||
$status = 'created';
|
||||
if (\str_ends_with($domain->get(), $functionsDomain) || \str_ends_with($domain->get(), $sitesDomain)) {
|
||||
|
|
|
|||
|
|
@ -75,10 +75,11 @@ class Create extends Action
|
|||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
->inject('domains')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $domain, string $url, int $statusCode, string $resourceId, string $resourceType, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject)
|
||||
public function action(string $domain, string $url, int $statusCode, string $resourceId, string $resourceType, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject, array $domains)
|
||||
{
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
|
@ -98,13 +99,7 @@ class Create extends Action
|
|||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.');
|
||||
}
|
||||
|
||||
$deniedDomains = [
|
||||
'localhost',
|
||||
APP_HOSTNAME_INTERNAL
|
||||
];
|
||||
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$deniedDomains[] = $mainDomain;
|
||||
$deniedDomains = [...$domains];
|
||||
|
||||
if (!empty($sitesDomain)) {
|
||||
$deniedDomains[] = $sitesDomain;
|
||||
|
|
@ -142,8 +137,9 @@ class Create extends Action
|
|||
throw new Exception(Exception::RULE_RESOURCE_NOT_FOUND);
|
||||
}
|
||||
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$ruleId = $isMd5 ? md5($domain->get()) : ID::unique();
|
||||
|
||||
$status = 'created';
|
||||
if (\str_ends_with($domain->get(), $functionsDomain) || \str_ends_with($domain->get(), $sitesDomain)) {
|
||||
|
|
|
|||
|
|
@ -72,10 +72,11 @@ class Create extends Action
|
|||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
->inject('domains')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $domain, string $siteId, string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject)
|
||||
public function action(string $domain, string $siteId, string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject, array $domains)
|
||||
{
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
|
@ -95,13 +96,7 @@ class Create extends Action
|
|||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please use a different domain.');
|
||||
}
|
||||
|
||||
$deniedDomains = [
|
||||
'localhost',
|
||||
APP_HOSTNAME_INTERNAL
|
||||
];
|
||||
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$deniedDomains[] = $mainDomain;
|
||||
$deniedDomains = [...$domains];
|
||||
|
||||
if (!empty($sitesDomain)) {
|
||||
$deniedDomains[] = $sitesDomain;
|
||||
|
|
@ -137,8 +132,9 @@ class Create extends Action
|
|||
|
||||
$deployment = $dbForProject->getDocument('deployments', $site->getAttribute('deploymentId', ''));
|
||||
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain->get()) : ID::unique();
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$ruleId = $isMd5 ? md5($domain->get()) : ID::unique();
|
||||
|
||||
$status = 'created';
|
||||
if (\str_ends_with($domain->get(), $functionsDomain) || \str_ends_with($domain->get(), $sitesDomain)) {
|
||||
|
|
|
|||
|
|
@ -272,8 +272,9 @@ class Create extends Action
|
|||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$domain = ID::unique() . "." . $sitesDomain;
|
||||
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique();
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$ruleId = $isMd5 ? md5($domain) : ID::unique();
|
||||
|
||||
Authorization::skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
|
|
|
|||
|
|
@ -143,8 +143,9 @@ class Create extends Action
|
|||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$domain = ID::unique() . "." . $sitesDomain;
|
||||
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique();
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$ruleId = $isMd5 ? md5($domain) : ID::unique();
|
||||
|
||||
Authorization::skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ class Create extends Base
|
|||
name: 'createTemplateDeployment',
|
||||
description: <<<EOT
|
||||
Create a deployment based on a template.
|
||||
|
||||
|
||||
Use this endpoint with combination of [listTemplates](https://appwrite.io/docs/products/sites/templates) to find the template details.
|
||||
EOT,
|
||||
auth: [AuthType::KEY],
|
||||
|
|
@ -185,8 +185,9 @@ class Create extends Base
|
|||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$domain = ID::unique() . "." . $sitesDomain;
|
||||
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
$ruleId = System::getEnv('_APP_RULES_FORMAT') === 'md5' ? md5($domain) : ID::unique();
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$ruleId = $isMd5 ? md5($domain) : ID::unique();
|
||||
|
||||
Authorization::skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
|
|
|
|||
|
|
@ -129,15 +129,16 @@ class Maintenance extends Action
|
|||
if (\count($certificates) > 0) {
|
||||
Console::info("[{$time}] Found " . \count($certificates) . " certificates for renewal, scheduling jobs.");
|
||||
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
|
||||
foreach ($certificates as $certificate) {
|
||||
$domain = $certificate->getAttribute('domain');
|
||||
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
|
||||
$rule = $dbForPlatform->getDocument('rules', md5($domain));
|
||||
} else {
|
||||
$rule = $dbForPlatform->findOne('rules', [
|
||||
$rule = $isMd5
|
||||
? $dbForPlatform->getDocument('rules', md5($domain))
|
||||
: $dbForPlatform->findOne('rules', [
|
||||
Query::equal('domain', [$domain]),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($rule->isEmpty() || $rule->getAttribute('region') !== System::getEnv('_APP_REGION', 'default')) {
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ class SDKs extends Action
|
|||
throw new \Exception('Unknown version given');
|
||||
}
|
||||
|
||||
$platforms = Config::getParam('platforms');
|
||||
$platforms = Config::getParam('sdks');
|
||||
foreach ($platforms as $key => $platform) {
|
||||
if ($selectedPlatform !== $key && $selectedPlatform !== '*' && ($sdks === null)) {
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -430,14 +430,13 @@ class Certificates extends Action
|
|||
Func $queueForFunctions,
|
||||
Realtime $queueForRealtime
|
||||
): void {
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
|
||||
$rule = $dbForPlatform->getDocument('rules', md5($domain));
|
||||
} else {
|
||||
$rule = $dbForPlatform->findOne('rules', [
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$rule = $isMd5
|
||||
? $dbForPlatform->getDocument('rules', md5($domain))
|
||||
: $dbForPlatform->findOne('rules', [
|
||||
Query::equal('domain', [$domain]),
|
||||
]);
|
||||
}
|
||||
|
||||
if (!$rule->isEmpty()) {
|
||||
$rule->setAttribute('certificateId', $certificateId);
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ class Functions extends Action
|
|||
$events = $payload['events'] ?? [];
|
||||
$data = $payload['body'] ?? '';
|
||||
$eventData = $payload['payload'] ?? '';
|
||||
$platform = $payload['platform'] ?? '';
|
||||
$function = new Document($payload['function'] ?? []);
|
||||
$functionId = $payload['functionId'] ?? '';
|
||||
$user = new Document($payload['user'] ?? []);
|
||||
|
|
@ -166,6 +167,7 @@ class Functions extends Action
|
|||
'user-agent' => 'Appwrite/' . APP_VERSION_STABLE,
|
||||
'content-type' => 'application/json'
|
||||
],
|
||||
platform: $platform,
|
||||
data: null,
|
||||
user: $user,
|
||||
jwt: null,
|
||||
|
|
@ -206,6 +208,7 @@ class Functions extends Action
|
|||
path: $path,
|
||||
method: $method,
|
||||
headers: $headers,
|
||||
platform: $platform,
|
||||
data: $data,
|
||||
user: $user,
|
||||
jwt: $jwt,
|
||||
|
|
@ -231,6 +234,7 @@ class Functions extends Action
|
|||
path: $path,
|
||||
method: $method,
|
||||
headers: $headers,
|
||||
platform: $platform,
|
||||
data: $data,
|
||||
user: $user,
|
||||
jwt: $jwt,
|
||||
|
|
@ -346,6 +350,7 @@ class Functions extends Action
|
|||
string $path,
|
||||
string $method,
|
||||
array $headers,
|
||||
array $platform,
|
||||
string $data = null,
|
||||
?Document $user = null,
|
||||
string $jwt = null,
|
||||
|
|
@ -486,13 +491,9 @@ class Functions extends Action
|
|||
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
|
||||
}
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_DOMAIN');
|
||||
$endpoint = $protocol . '://' . $hostname . "/v1";
|
||||
|
||||
// Appwrite vars
|
||||
$vars = \array_merge($vars, [
|
||||
'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint,
|
||||
'APPWRITE_FUNCTION_API_ENDPOINT' => $platform['endpoint'],
|
||||
'APPWRITE_FUNCTION_ID' => $functionId,
|
||||
'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name'),
|
||||
'APPWRITE_FUNCTION_DEPLOYMENT' => $deploymentId,
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ class Mails extends Action
|
|||
$log->addTag('type', empty($smtp) ? 'cloud' : 'smtp');
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_DOMAIN');
|
||||
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN');
|
||||
|
||||
$recipient = $payload['recipient'];
|
||||
$subject = $payload['subject'];
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ class Migrations extends Action
|
|||
|
||||
protected array $plan;
|
||||
|
||||
protected array $platform;
|
||||
|
||||
/**
|
||||
* @var array<string, int>
|
||||
*/
|
||||
|
|
@ -106,6 +108,7 @@ class Migrations extends Action
|
|||
$this->deviceForMigrations = $deviceForMigrations;
|
||||
$this->deviceForFiles = $deviceForFiles;
|
||||
$this->plan = $plan;
|
||||
$this->platform = $payload['platform'] ?? [];
|
||||
|
||||
if (empty($payload)) {
|
||||
throw new Exception('Missing payload');
|
||||
|
|
@ -141,6 +144,7 @@ class Migrations extends Action
|
|||
$credentials = $migration->getAttribute('credentials');
|
||||
$migrationOptions = $migration->getAttribute('options');
|
||||
$dataSource = Appwrite::SOURCE_API;
|
||||
$endpoint = $this->platform['endpoint'] ?: ($credentials['endpoint'] ?? 'http://appwrite.test/v1');
|
||||
$database = null;
|
||||
$queries = [];
|
||||
|
||||
|
|
@ -174,7 +178,7 @@ class Migrations extends Action
|
|||
),
|
||||
SourceAppwrite::getName() => new SourceAppwrite(
|
||||
$credentials['projectId'],
|
||||
$credentials['endpoint'] === 'http://localhost/v1' ? 'http://appwrite/v1' : $credentials['endpoint'],
|
||||
$endpoint,
|
||||
$credentials['apiKey'],
|
||||
$dataSource,
|
||||
$database,
|
||||
|
|
@ -205,7 +209,7 @@ class Migrations extends Action
|
|||
return match ($destination) {
|
||||
DestinationAppwrite::getName() => new DestinationAppwrite(
|
||||
$this->project->getId(),
|
||||
'http://appwrite/v1',
|
||||
$this->platform['endpoint'],
|
||||
$apiKey,
|
||||
$this->dbForProject,
|
||||
Config::getParam('collections', [])['databases']['collections'],
|
||||
|
|
@ -309,7 +313,7 @@ class Migrations extends Action
|
|||
) {
|
||||
$credentials = $migration->getAttribute('credentials', []);
|
||||
$credentials['projectId'] = $credentials['projectId'] ?? $project->getId();
|
||||
$credentials['endpoint'] = $credentials['endpoint'] ?? 'http://appwrite/v1';
|
||||
$credentials['endpoint'] = $credentials['endpoint'] ?? $this->platform['endpoint'];
|
||||
$credentials['apiKey'] = $credentials['apiKey'] ?? $tempAPIKey;
|
||||
$migration->setAttribute('credentials', $credentials);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,11 @@ use Utopia\System\System;
|
|||
|
||||
class Comment
|
||||
{
|
||||
public function __construct(
|
||||
private array $platform
|
||||
) {
|
||||
}
|
||||
|
||||
// TODO: Add more tips
|
||||
protected array $tips = [
|
||||
'Appwrite has crossed the 50K GitHub stars milestone with hundreds of active contributors',
|
||||
|
|
@ -114,7 +119,7 @@ class Comment
|
|||
$i = 0;
|
||||
foreach ($projects as $projectId => $project) {
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
|
||||
$hostname = $this->platform['consoleDomain'] ?? '';
|
||||
|
||||
$text .= "## {$project['name']}\n\n";
|
||||
$text .= "Project ID: `{$projectId}`\n\n";
|
||||
|
|
@ -228,7 +233,7 @@ class Comment
|
|||
public function generatImage(string $pathLight, string $pathDark, string $alt, int $width): string
|
||||
{
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$hostname = System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', ''));
|
||||
$hostname = $this->platform['consoleDomain'] ?? '';
|
||||
|
||||
$imageLight = $protocol . '://' . $hostname . $pathLight;
|
||||
$imageDark = $protocol . '://' . $hostname . $pathDark;
|
||||
|
|
|
|||
|
|
@ -200,10 +200,6 @@ class Executor
|
|||
?int $requestTimeout = null,
|
||||
string $responseFormat = self::RESPONSE_FORMAT_OBJECT_HEADERS
|
||||
) {
|
||||
if (empty($headers['host'])) {
|
||||
$headers['host'] = System::getEnv('_APP_DOMAIN', '');
|
||||
}
|
||||
|
||||
$runtimeId = "$projectId-$deploymentId";
|
||||
$route = '/runtimes/' . $runtimeId . '/executions';
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,9 @@ class CompressionTest extends Scope
|
|||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertArrayHasKey('content-encoding', $response['headers'], 'Content encoding should be gzip, headers received: ' . json_encode($response['headers'], JSON_PRETTY_PRINT));
|
||||
$this->assertLessThan(2000, intval($response['headers']['content-length']));
|
||||
$this->assertArrayHasKey('content-length', $response['headers'], 'Compressed response should provide content length, headers received: ' . json_encode($response['headers'], JSON_PRETTY_PRINT));
|
||||
$this->assertLessThan(2000, \intval($response['headers']['content-length']));
|
||||
$this->assertArrayNotHasKey('transfer-encoding', $response['headers'], 'Compressed response should not be chunked, headers received: ' . json_encode($response['headers'], JSON_PRETTY_PRINT));
|
||||
|
||||
// get prefs without compression
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $userId . '/prefs', array_merge([
|
||||
|
|
@ -83,8 +85,15 @@ class CompressionTest extends Scope
|
|||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertGreaterThanOrEqual(2000, intval($response['headers']['content-length']));
|
||||
$this->assertArrayNotHasKey('content-encoding', $response['headers']);
|
||||
$this->assertEquals('chunked', $response['headers']['transfer-encoding'] ?? null, 'Uncompressed response should use chunked transfer, headers received: ' . json_encode($response['headers'], JSON_PRETTY_PRINT));
|
||||
$this->assertArrayNotHasKey('content-length', $response['headers'], 'Uncompressed response should not send content length when chunked.');
|
||||
|
||||
$this->assertArrayHasKey('longValue', $response['body'], 'Prefs payload should expose longValue at the top level, body received: ' . json_encode($response['body'], JSON_PRETTY_PRINT));
|
||||
|
||||
$prefsPayload = $response['body']['longValue'];
|
||||
$payloadLength = \strlen($prefsPayload);
|
||||
$this->assertGreaterThanOrEqual(2000, $payloadLength, 'Prefs payload should be at least 2000 bytes.');
|
||||
}
|
||||
|
||||
public function testImageResponse()
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class HTTPTest extends Scope
|
|||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->client->setEndpoint('http://traefik');
|
||||
$this->client->setEndpoint('http://appwrite.test');
|
||||
}
|
||||
|
||||
public function testOptions()
|
||||
|
|
@ -31,7 +31,7 @@ class HTTPTest extends Scope
|
|||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
$this->assertEquals('Appwrite', $response['headers']['server']);
|
||||
$this->assertEquals('GET, POST, PUT, PATCH, DELETE', $response['headers']['access-control-allow-methods']);
|
||||
$this->assertEquals('Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Dev-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Appwrite-Session, X-Fallback-Cookies, X-Forwarded-For, X-Forwarded-User-Agent', $response['headers']['access-control-allow-headers']);
|
||||
$this->assertEquals('Accept, Origin, Cookie, Set-Cookie, Content-Type, Content-Range, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Dev-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-Appwrite-ID, X-Appwrite-Timestamp, X-Appwrite-Session, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, Range, Cache-Control, Expires, Pragma, X-Fallback-Cookies, X-Requested-With, X-Forwarded-For, X-Forwarded-User-Agent', $response['headers']['access-control-allow-headers']);
|
||||
$this->assertEquals('X-Appwrite-Session, X-Fallback-Cookies', $response['headers']['access-control-expose-headers']);
|
||||
$this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin']);
|
||||
$this->assertEquals('true', $response['headers']['access-control-allow-credentials']);
|
||||
|
|
@ -66,16 +66,10 @@ class HTTPTest extends Scope
|
|||
|
||||
public function testAcmeChallenge()
|
||||
{
|
||||
// Preparation
|
||||
$previousEndpoint = $this->client->getEndpoint();
|
||||
$this->client->setEndpoint("http://localhost");
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/.well-known/acme-challenge/8DdIKX257k6Dih5s_saeVMpTnjPJdKO5Ase0OCiJrIg', \array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
]));
|
||||
$response = $this->client->call(Client::METHOD_GET, '/.well-known/acme-challenge/8DdIKX257k6Dih5s_saeVMpTnjPJdKO5Ase0OCiJrIg');
|
||||
|
||||
// 'Unknown path', but validation passed
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
|
|
@ -83,15 +77,10 @@ class HTTPTest extends Scope
|
|||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/.well-known/acme-challenge/../../../../../../../etc/passwd', \array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
]));
|
||||
$response = $this->client->call(Client::METHOD_GET, '/.well-known/acme-challenge/../../../../../../../etc/passwd');
|
||||
|
||||
// Check for too many path segments
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
// Cleanup
|
||||
$this->client->setEndpoint($previousEndpoint);
|
||||
// 'Unknown path', but validation passed
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testSpecs()
|
||||
|
|
@ -172,48 +161,30 @@ class HTTPTest extends Scope
|
|||
|
||||
public function testCors()
|
||||
{
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
||||
$endpoint = '/v1/projects'; // Can be any non-404 route
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, $endpoint);
|
||||
|
||||
$this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin']);
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, $endpoint, [
|
||||
'origin' => 'http://localhost',
|
||||
]);
|
||||
|
||||
$this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, $endpoint, [
|
||||
'origin' => 'http://appwrite.io',
|
||||
]);
|
||||
|
||||
$this->assertEquals('http://appwrite.io', $response['headers']['access-control-allow-origin']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, $endpoint, [
|
||||
'origin' => 'https://appwrite.io',
|
||||
]);
|
||||
|
||||
$this->assertEquals('https://appwrite.io', $response['headers']['access-control-allow-origin']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, $endpoint, [
|
||||
'origin' => 'http://cloud.appwrite.io',
|
||||
]);
|
||||
|
||||
$this->assertEquals('http://cloud.appwrite.io', $response['headers']['access-control-allow-origin']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
// you should not return a fallback origin for a no host
|
||||
$response = $this->client->call(Client::METHOD_GET, $endpoint);
|
||||
$this->assertNull($response['headers']['access-control-allow-origin'] ?? null);
|
||||
|
||||
// you should not return a fallback origin for a no host
|
||||
$response = $this->client->call(Client::METHOD_GET, $endpoint, [
|
||||
'origin' => 'http://google.com',
|
||||
]);
|
||||
$this->assertNull($response['headers']['access-control-allow-origin'] ?? null);
|
||||
|
||||
$this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin']);
|
||||
}
|
||||
|
||||
public function testConsoleRedirect()
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class HooksTest extends Scope
|
|||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->client->setEndpoint('http://traefik');
|
||||
$this->client->setEndpoint('http://appwrite.test');
|
||||
}
|
||||
|
||||
public function testProjectHooks()
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ abstract class Scope extends TestCase
|
|||
public const REQUEST_TYPE_SMS = 'sms';
|
||||
|
||||
protected ?Client $client = null;
|
||||
protected string $endpoint = 'http://localhost/v1';
|
||||
protected string $endpoint = 'http://appwrite.test/v1';
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
|
|
|
|||
|
|
@ -50,21 +50,6 @@ trait AccountBase
|
|||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
// Deny request from blocked IP
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
'x-forwarded-for' => '31.6.14.220' // Test IP for denied access region
|
||||
]), [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
]);
|
||||
|
||||
$this->assertEquals(451, $response['headers']['status-code']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
|
|
|
|||
|
|
@ -2130,10 +2130,15 @@ class AccountCustomClientTest extends Scope
|
|||
'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure',
|
||||
]);
|
||||
|
||||
$session = $response['cookies']['a_session_' . $this->getProject()['$id']];
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('success', $response['body']['result']);
|
||||
|
||||
$sessionCookieKey = 'a_session_' . $this->getProject()['$id'];
|
||||
$this->assertArrayHasKey(
|
||||
$sessionCookieKey,
|
||||
$response['cookies'],
|
||||
"Failed asserting that session cookie '$sessionCookieKey' is set. Cookies: " . json_encode($response['cookies'])
|
||||
);
|
||||
$session = $response['cookies'][$sessionCookieKey];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
|
|
|
|||
|
|
@ -459,7 +459,7 @@ class FunctionsCustomServerTest extends Scope
|
|||
$this->assertEquals("completed", $execution['body']['status']);
|
||||
$this->assertEquals(200, $execution['body']['responseStatusCode']);
|
||||
$this->assertEquals("Pong", $execution['body']['responseBody']);
|
||||
$this->assertEmpty($execution['body']['errors']);
|
||||
$this->assertEmpty($execution['body']['errors'], 'Failed to execute function, ' . json_encode($execution['body']['errors']));
|
||||
|
||||
// Test execution logged correct total users
|
||||
$users = $this->client->call(Client::METHOD_GET, '/users', array_merge([
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class ScopeTest extends Scope
|
|||
'x-appwrite-key' => $apiKey,
|
||||
], $gqlPayload);
|
||||
|
||||
$message = "app.{$projectId}@service.localhost (role: applications) missing scopes ([\"databases.write\"])";
|
||||
$message = "app.{$projectId}@service.appwrite.test (role: applications) missing scopes ([\"databases.write\"])";
|
||||
$this->assertArrayHasKey('errors', $database['body']);
|
||||
$this->assertEquals($message, $database['body']['errors'][0]['message']);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ trait MigrationsBase
|
|||
{
|
||||
$response = $this->performMigrationSync([
|
||||
'resources' => Appwrite::getSupportedResources(),
|
||||
'endpoint' => 'http://localhost/v1',
|
||||
'endpoint' => $this->endpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
|
@ -126,7 +126,7 @@ trait MigrationsBase
|
|||
'resources' => [
|
||||
Resource::TYPE_USER,
|
||||
],
|
||||
'endpoint' => 'http://localhost/v1',
|
||||
'endpoint' => $this->endpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
|
@ -188,7 +188,7 @@ trait MigrationsBase
|
|||
'resources' => [
|
||||
Resource::TYPE_USER,
|
||||
],
|
||||
'endpoint' => 'http://localhost/v1',
|
||||
'endpoint' => $this->endpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
|
@ -277,7 +277,7 @@ trait MigrationsBase
|
|||
Resource::TYPE_TEAM,
|
||||
Resource::TYPE_MEMBERSHIP,
|
||||
],
|
||||
'endpoint' => 'http://localhost/v1',
|
||||
'endpoint' => $this->endpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
|
@ -393,7 +393,7 @@ trait MigrationsBase
|
|||
'resources' => [
|
||||
Resource::TYPE_DATABASE,
|
||||
],
|
||||
'endpoint' => 'http://localhost/v1',
|
||||
'endpoint' => $this->endpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
|
@ -484,7 +484,7 @@ trait MigrationsBase
|
|||
Resource::TYPE_TABLE,
|
||||
Resource::TYPE_COLUMN,
|
||||
],
|
||||
'endpoint' => 'http://localhost/v1',
|
||||
'endpoint' => $this->endpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
|
@ -571,7 +571,7 @@ trait MigrationsBase
|
|||
Resource::TYPE_COLUMN,
|
||||
Resource::TYPE_ROW,
|
||||
],
|
||||
'endpoint' => 'http://localhost/v1',
|
||||
'endpoint' => $this->endpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
|
@ -651,7 +651,7 @@ trait MigrationsBase
|
|||
'resources' => [
|
||||
Resource::TYPE_BUCKET
|
||||
],
|
||||
'endpoint' => 'http://localhost/v1',
|
||||
'endpoint' => $this->endpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
|
@ -747,7 +747,7 @@ trait MigrationsBase
|
|||
Resource::TYPE_BUCKET,
|
||||
Resource::TYPE_FILE
|
||||
],
|
||||
'endpoint' => 'http://localhost/v1',
|
||||
'endpoint' => $this->endpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
|
@ -818,7 +818,7 @@ trait MigrationsBase
|
|||
Resource::TYPE_FUNCTION,
|
||||
Resource::TYPE_DEPLOYMENT
|
||||
],
|
||||
'endpoint' => 'http://localhost/v1',
|
||||
'endpoint' => $this->endpoint,
|
||||
'projectId' => $this->getProject()['$id'],
|
||||
'apiKey' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
|
@ -1119,7 +1119,7 @@ trait MigrationsBase
|
|||
// all data exists, pass.
|
||||
$migration = $this->performCsvMigration(
|
||||
[
|
||||
'endpoint' => 'http://localhost/v1',
|
||||
'endpoint' => $this->endpoint,
|
||||
'fileId' => $fileIds['default'],
|
||||
'bucketId' => $bucketIds['default'],
|
||||
'resourceId' => $databaseId . ':' . $tableId,
|
||||
|
|
@ -1161,7 +1161,7 @@ trait MigrationsBase
|
|||
// all data exists and includes internals, pass.
|
||||
$migration = $this->performCsvMigration(
|
||||
[
|
||||
'endpoint' => 'http://localhost/v1',
|
||||
'endpoint' => $this->endpoint,
|
||||
'fileId' => $fileIds['documents-internals'],
|
||||
'bucketId' => $bucketIds['documents-internals'],
|
||||
'resourceId' => $databaseId . ':' . $tableId,
|
||||
|
|
|
|||
|
|
@ -4889,7 +4889,8 @@ class ProjectsConsoleClientTest extends Scope
|
|||
|
||||
$this->assertEquals(403, $response['headers']['status-code']);
|
||||
$this->assertNotEquals($origin, $response['headers']['access-control-allow-origin'] ?? null);
|
||||
$this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin'] ?? null);
|
||||
// you should not return a fallback origin for a disallowed host
|
||||
$this->assertNull($response['headers']['access-control-allow-origin'] ?? null);
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -4906,7 +4907,7 @@ class ProjectsConsoleClientTest extends Scope
|
|||
]);
|
||||
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
$this->assertEquals('*', $response['headers']['access-control-allow-origin'] ?? null);
|
||||
$this->assertEquals($origin, $response['headers']['access-control-allow-origin'] ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -81,16 +81,11 @@ class ProjectsCustomServerTest extends Scope
|
|||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
||||
$deniedDomains = [
|
||||
$mainDomain,
|
||||
$sitesDomain,
|
||||
$functionsDomain,
|
||||
'localhost',
|
||||
APP_HOSTNAME_INTERNAL,
|
||||
'sites.localhost',
|
||||
'functions.localhost',
|
||||
'appwrite.test',
|
||||
'localhost'
|
||||
];
|
||||
|
||||
foreach ($deniedDomains as $deniedDomain) {
|
||||
|
|
|
|||
|
|
@ -103,12 +103,11 @@ class ProxyCustomServerTest extends Scope
|
|||
$domain = \uniqid() . '-api.custom.localhost';
|
||||
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://' . $domain);
|
||||
$proxyClient->setEndpoint('http://appwrite.test');
|
||||
$proxyClient->addHeader('x-appwrite-hostname', $domain);
|
||||
|
||||
// We should ideally assert 400, but server allows unknown domains, and serves API by default
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/versions');
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(APP_VERSION_STABLE, $response['body']['server']);
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
|
||||
$ruleId = $this->setupAPIRule($domain);
|
||||
|
||||
|
|
@ -138,11 +137,11 @@ class ProxyCustomServerTest extends Scope
|
|||
$domain = \uniqid() . '-redirect.custom.localhost';
|
||||
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://appwrite');
|
||||
$proxyClient->setEndpoint('http://appwrite.test');
|
||||
$proxyClient->addHeader('x-appwrite-hostname', $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/todos/1');
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
|
||||
$siteId = $this->setupSite()['siteId'];
|
||||
|
||||
|
|
@ -166,7 +165,7 @@ class ProxyCustomServerTest extends Scope
|
|||
$this->assertNotEmpty($ruleId);
|
||||
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://appwrite');
|
||||
$proxyClient->setEndpoint('http://appwrite.test');
|
||||
$proxyClient->addHeader('x-appwrite-hostname', $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/', followRedirects: false);
|
||||
|
|
@ -193,11 +192,11 @@ class ProxyCustomServerTest extends Scope
|
|||
$domain = \uniqid() . '-function.custom.localhost';
|
||||
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://appwrite');
|
||||
$proxyClient->setEndpoint('http://appwrite.test');
|
||||
$proxyClient->addHeader('x-appwrite-hostname', $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/ping');
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
|
||||
$setup = $this->setupFunction();
|
||||
$functionId = $setup['functionId'];
|
||||
|
|
@ -248,11 +247,11 @@ class ProxyCustomServerTest extends Scope
|
|||
$domain = \uniqid() . '-site.custom.localhost';
|
||||
|
||||
$proxyClient = new Client();
|
||||
$proxyClient->setEndpoint('http://appwrite');
|
||||
$proxyClient->setEndpoint('http://appwrite.test');
|
||||
$proxyClient->addHeader('x-appwrite-hostname', $domain);
|
||||
|
||||
$response = $proxyClient->call(Client::METHOD_GET, '/contact');
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
|
||||
$setup = $this->setupSite();
|
||||
$siteId = $setup['siteId'];
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ trait RealtimeBase
|
|||
];
|
||||
|
||||
return new WebSocketClient(
|
||||
"ws://appwrite-traefik/v1/realtime?" . http_build_query($query),
|
||||
"ws://appwrite.test/v1/realtime?" . http_build_query($query),
|
||||
[
|
||||
"headers" => $headers,
|
||||
"timeout" => 30,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ use Tests\E2E\Client;
|
|||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideClient;
|
||||
use Utopia\System\System;
|
||||
|
||||
class SitesCustomClientTest extends Scope
|
||||
{
|
||||
|
|
@ -122,7 +121,6 @@ class SitesCustomClientTest extends Scope
|
|||
* Test for SUCCESS
|
||||
*/
|
||||
$template = $this->getTemplate('starter-for-react');
|
||||
$hostname = System::getEnv('_APP_DOMAIN') ?: '';
|
||||
$this->assertEquals(200, $template['headers']['status-code']);
|
||||
$this->assertIsArray($template['body']);
|
||||
$this->assertEquals('starter-for-react', $template['body']['key']);
|
||||
|
|
|
|||
|
|
@ -1962,7 +1962,7 @@ class SitesCustomServerTest extends Scope
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'referer' => $url,
|
||||
'origin' => $url
|
||||
'origin' => $url,
|
||||
]));
|
||||
|
||||
$this->assertEquals($url, $response['headers']['access-control-allow-origin']);
|
||||
|
|
@ -1971,11 +1971,10 @@ class SitesCustomServerTest extends Scope
|
|||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'unknown',
|
||||
'referer' => $url,
|
||||
'origin' => $url
|
||||
'origin' => $url,
|
||||
]));
|
||||
|
||||
$this->assertNotEquals($url, $response['headers']['access-control-allow-origin']);
|
||||
$this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin']);
|
||||
$this->assertArrayNotHasKey('access-control-allow-origin', $response['headers']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
|
@ -1984,8 +1983,7 @@ class SitesCustomServerTest extends Scope
|
|||
'origin' => 'http://unknown.com'
|
||||
]));
|
||||
|
||||
$this->assertNotEquals($url, $response['headers']['access-control-allow-origin']);
|
||||
$this->assertEquals('http://localhost', $response['headers']['access-control-allow-origin']);
|
||||
$this->assertArrayNotHasKey('access-control-allow-origin', $response['headers']);
|
||||
}
|
||||
|
||||
public function testSiteDownload(): void
|
||||
|
|
|
|||
|
|
@ -197,7 +197,6 @@ class WebhooksCustomClientTest extends Scope
|
|||
$this->assertNotEmpty($webhook['data']['$id']);
|
||||
$this->assertNotEmpty($webhook['data']['userId']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($webhook['data']['expire']));
|
||||
$this->assertEquals($webhook['data']['ip'], '127.0.0.1');
|
||||
$this->assertNotEmpty($webhook['data']['osCode']);
|
||||
$this->assertIsString($webhook['data']['osCode']);
|
||||
$this->assertNotEmpty($webhook['data']['osName']);
|
||||
|
|
@ -286,7 +285,6 @@ class WebhooksCustomClientTest extends Scope
|
|||
$this->assertNotEmpty($webhook['data']['$id']);
|
||||
$this->assertNotEmpty($webhook['data']['userId']);
|
||||
$this->assertIsString($webhook['data']['expire']);
|
||||
$this->assertEquals($webhook['data']['ip'], '127.0.0.1');
|
||||
$this->assertNotEmpty($webhook['data']['osCode']);
|
||||
$this->assertIsString($webhook['data']['osCode']);
|
||||
$this->assertNotEmpty($webhook['data']['osName']);
|
||||
|
|
@ -372,7 +370,6 @@ class WebhooksCustomClientTest extends Scope
|
|||
$this->assertNotEmpty($webhook['data']['$id']);
|
||||
$this->assertNotEmpty($webhook['data']['userId']);
|
||||
$this->assertEquals(true, (new DatetimeValidator())->isValid($webhook['data']['expire']));
|
||||
$this->assertEquals($webhook['data']['ip'], '127.0.0.1');
|
||||
$this->assertNotEmpty($webhook['data']['osCode']);
|
||||
$this->assertIsString($webhook['data']['osCode']);
|
||||
$this->assertNotEmpty($webhook['data']['osName']);
|
||||
|
|
|
|||
150
tests/unit/Network/CorsTest.php
Normal file
150
tests/unit/Network/CorsTest.php
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Network;
|
||||
|
||||
use Appwrite\Network\Cors;
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class CorsTest extends TestCase
|
||||
{
|
||||
public function testWildcardWithCredentialsThrows(): void
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
|
||||
new Cors(
|
||||
allowedHosts: ['*'],
|
||||
allowedMethods: ['GET'],
|
||||
allowedHeaders: ['X-Test'],
|
||||
exposedHeaders: [],
|
||||
allowCredentials: true
|
||||
);
|
||||
}
|
||||
|
||||
public function testWildcardAllowsAnyOrigin(): void
|
||||
{
|
||||
$cors = new Cors(
|
||||
allowedHosts: ['*'],
|
||||
allowedMethods: ['GET'],
|
||||
allowedHeaders: ['X-Test'],
|
||||
exposedHeaders: [],
|
||||
allowCredentials: false
|
||||
);
|
||||
|
||||
$result = $cors->headers('https://foo.com');
|
||||
|
||||
$this->assertSame('https://foo.com', $result[Cors::HEADER_ALLOW_ORIGIN]);
|
||||
}
|
||||
|
||||
public function testEmptyOriginReturnsStaticHeadersOnly(): void
|
||||
{
|
||||
$cors = new Cors(
|
||||
allowedHosts: ['example.com'],
|
||||
allowedMethods: ['GET'],
|
||||
allowedHeaders: ['X-Test'],
|
||||
exposedHeaders: [],
|
||||
allowCredentials: false
|
||||
);
|
||||
|
||||
$result = $cors->headers('');
|
||||
|
||||
$this->assertArrayNotHasKey(Cors::HEADER_ALLOW_ORIGIN, $result);
|
||||
$this->assertSame('false', $result[Cors::HEADER_ALLOW_CREDENTIALS]);
|
||||
$this->assertSame('GET', $result[Cors::HEADER_ALLOW_METHODS]);
|
||||
}
|
||||
|
||||
public function testInvalidOriginReturnsStaticHeadersOnly(): void
|
||||
{
|
||||
$cors = new Cors(
|
||||
allowedHosts: ['example.com'],
|
||||
allowedMethods: ['GET'],
|
||||
allowedHeaders: ['X-Test'],
|
||||
exposedHeaders: [],
|
||||
allowCredentials: false
|
||||
);
|
||||
|
||||
$result = $cors->headers('%%%not-a-url%%%');
|
||||
|
||||
$this->assertArrayNotHasKey(Cors::HEADER_ALLOW_ORIGIN, $result);
|
||||
}
|
||||
|
||||
public function testUnlistedOriginReturnsStaticHeadersOnly(): void
|
||||
{
|
||||
$cors = new Cors(
|
||||
allowedHosts: ['allowed.com'],
|
||||
allowedMethods: ['GET'],
|
||||
allowedHeaders: ['X-Test'],
|
||||
exposedHeaders: [],
|
||||
allowCredentials: false
|
||||
);
|
||||
|
||||
$result = $cors->headers('https://forbidden.com');
|
||||
|
||||
$this->assertArrayNotHasKey(Cors::HEADER_ALLOW_ORIGIN, $result);
|
||||
}
|
||||
|
||||
public function testAllowedOriginIsReturned(): void
|
||||
{
|
||||
$cors = new Cors(
|
||||
allowedHosts: ['example.com'],
|
||||
allowedMethods: ['POST'],
|
||||
allowedHeaders: ['X-Test'],
|
||||
exposedHeaders: [],
|
||||
allowCredentials: true
|
||||
);
|
||||
|
||||
$result = $cors->headers('https://example.com');
|
||||
|
||||
$this->assertSame('https://example.com', $result[Cors::HEADER_ALLOW_ORIGIN]);
|
||||
}
|
||||
|
||||
public function testOriginIsLowercasedForMatching(): void
|
||||
{
|
||||
$cors = new Cors(
|
||||
allowedHosts: ['example.com'],
|
||||
allowedMethods: ['GET'],
|
||||
allowedHeaders: ['X-Test'],
|
||||
exposedHeaders: [],
|
||||
allowCredentials: false
|
||||
);
|
||||
|
||||
$result = $cors->headers('HTTPS://EXAMPLE.COM');
|
||||
|
||||
// Lowercase logic is in the class
|
||||
$this->assertSame('https://example.com', $result[Cors::HEADER_ALLOW_ORIGIN]);
|
||||
}
|
||||
|
||||
public function testHeaderFormatting(): void
|
||||
{
|
||||
$cors = new Cors(
|
||||
allowedHosts: ['example.com'],
|
||||
allowedMethods: ['GET', 'POST'],
|
||||
allowedHeaders: ['X-A', 'X-B'],
|
||||
exposedHeaders: ['E1', 'E2'],
|
||||
allowCredentials: true
|
||||
);
|
||||
|
||||
$result = $cors->headers('https://example.com');
|
||||
|
||||
$this->assertSame('GET, POST', $result[Cors::HEADER_ALLOW_METHODS]);
|
||||
$this->assertSame('X-A, X-B', $result[Cors::HEADER_ALLOW_HEADERS]);
|
||||
$this->assertSame('E1, E2', $result[Cors::HEADER_EXPOSE_HEADERS]);
|
||||
$this->assertSame('true', $result[Cors::HEADER_ALLOW_CREDENTIALS]);
|
||||
}
|
||||
|
||||
public function testMaxAgeIncluded(): void
|
||||
{
|
||||
$cors = new Cors(
|
||||
allowedHosts: ['example.com'],
|
||||
allowedMethods: ['GET'],
|
||||
allowedHeaders: ['X-Test'],
|
||||
exposedHeaders: [],
|
||||
allowCredentials: false,
|
||||
maxAge: 999
|
||||
);
|
||||
|
||||
$result = $cors->headers('https://example.com');
|
||||
|
||||
$this->assertSame(999, $result[Cors::HEADER_MAX_AGE]);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,53 +2,17 @@
|
|||
|
||||
namespace Tests\Unit\Network\Validators;
|
||||
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
|
||||
class OriginTest extends TestCase
|
||||
{
|
||||
public function testValues(): void
|
||||
{
|
||||
$validator = new Origin([
|
||||
[
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'name' => 'Production',
|
||||
'type' => Platform::TYPE_WEB,
|
||||
'hostname' => 'appwrite.io',
|
||||
],
|
||||
[
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'name' => 'Development',
|
||||
'type' => Platform::TYPE_WEB,
|
||||
'hostname' => 'appwrite.test',
|
||||
],
|
||||
[
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'name' => 'Localhost',
|
||||
'type' => Platform::TYPE_WEB,
|
||||
'hostname' => 'localhost',
|
||||
],
|
||||
[
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'name' => 'Flutter',
|
||||
'type' => Platform::TYPE_FLUTTER_WEB,
|
||||
'hostname' => 'appwrite.flutter',
|
||||
],
|
||||
[
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'name' => 'Expo',
|
||||
'type' => Platform::TYPE_SCHEME,
|
||||
'key' => 'exp',
|
||||
],
|
||||
[
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'name' => 'Appwrite Callback',
|
||||
'type' => Platform::TYPE_SCHEME,
|
||||
'key' => 'appwrite-callback-123',
|
||||
],
|
||||
]);
|
||||
$validator = new Origin(
|
||||
allowedHostnames: ['appwrite.io', 'appwrite.test', 'localhost', 'appwrite.flutter'],
|
||||
allowedSchemes: ['exp', 'appwrite-callback-123']
|
||||
);
|
||||
|
||||
$this->assertEquals(false, $validator->isValid(''));
|
||||
$this->assertEquals(false, $validator->isValid('/'));
|
||||
|
|
|
|||
Loading…
Reference in a new issue