mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cb86c57ef | ||
|
|
e4a7b1b103 | ||
|
|
c6ffa181a2 | ||
|
|
cd8e93db60 | ||
|
|
ceb2108012 | ||
|
|
383f0c1ef1 | ||
|
|
52ccbfcdf7 | ||
|
|
d502ab0cd5 | ||
|
|
e738bae523 | ||
|
|
decf2c7a1b | ||
|
|
43d57bbe4e | ||
|
|
27ede0079f | ||
|
|
a94d33649b | ||
|
|
6d25eb81b6 | ||
|
|
8d7c76a434 | ||
|
|
c927a1f459 | ||
|
|
88239a23f9 | ||
|
|
651af6a2c5 |
@@ -125,4 +125,3 @@ _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_TRUSTED_HEADERS=
|
||||
|
||||
@@ -164,7 +164,6 @@ Getting started with Appwrite is as easy as creating a new project, choosing you
|
||||
| | [Quick start for PHP](https://appwrite.io/docs/quick-starts/php) |
|
||||
| | [Quick start for Kotlin](https://appwrite.io/docs/quick-starts/kotlin) |
|
||||
| | [Quick start for Swift](https://appwrite.io/docs/quick-starts/swift) |
|
||||
| | [Quick start for Go](https://appwrite.io/docs/quick-starts/go) |
|
||||
|
||||
### Products
|
||||
|
||||
@@ -206,7 +205,6 @@ Below is a list of currently supported platforms and languages. If you would lik
|
||||
- :white_check_mark: [Kotlin](https://github.com/appwrite/sdk-for-kotlin) (Maintained by the Appwrite Team)
|
||||
- :white_check_mark: [Swift](https://github.com/appwrite/sdk-for-swift) (Maintained by the Appwrite Team)
|
||||
- :white_check_mark: [.NET](https://github.com/appwrite/sdk-for-dotnet) - **Beta** (Maintained by the Appwrite Team)
|
||||
- :white_check_mark: [Go](https://github.com/appwrite/sdk-for-go) - **Beta** (Maintained by the Appwrite Team)
|
||||
|
||||
#### Community
|
||||
|
||||
|
||||
@@ -6,8 +6,12 @@ use Utopia\System\System;
|
||||
* Platform configuration
|
||||
*/
|
||||
return [
|
||||
'domain' => System::getEnv('_APP_DOMAIN', 'localhost'),
|
||||
'consoleDomain' => System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', 'localhost')),
|
||||
'apiHostname' => System::getEnv('_APP_DOMAIN', 'localhost'),
|
||||
'consoleHostname' => System::getEnv('_APP_CONSOLE_DOMAIN', System::getEnv('_APP_DOMAIN', 'localhost')),
|
||||
'hostnames' => array_filter(array_unique([
|
||||
System::getEnv('_APP_DOMAIN', 'localhost'),
|
||||
System::getEnv('_APP_CONSOLE_DOMAIN', 'localhost'),
|
||||
])),
|
||||
'platformName' => APP_EMAIL_PLATFORM_NAME,
|
||||
'logoUrl' => APP_EMAIL_LOGO_URL,
|
||||
'accentColor' => APP_EMAIL_ACCENT_COLOR,
|
||||
|
||||
@@ -9,7 +9,7 @@ use Utopia\System\System;
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$platform = Config::getParam('platform', []);
|
||||
$hostname = $platform['consoleDomain'] ?? '';
|
||||
$hostname = $platform['consoleHostname'] ?? '';
|
||||
|
||||
$url = $protocol . '://' . $hostname;
|
||||
|
||||
@@ -22,6 +22,8 @@ class UseCases
|
||||
public const DOCUMENTATION = 'documentation';
|
||||
public const BLOG = 'blog';
|
||||
public const AI = 'artificial intelligence';
|
||||
public const FORMS = 'forms';
|
||||
public const DASHBOARD = 'dashboard';
|
||||
}
|
||||
|
||||
const TEMPLATE_FRAMEWORKS = [
|
||||
@@ -1469,4 +1471,135 @@ return [
|
||||
],
|
||||
]
|
||||
],
|
||||
[
|
||||
'key' => 'crm-dashboard-react-admin',
|
||||
'name' => 'CRM dashboard with React Admin',
|
||||
'tagline' => 'A React-based admin dashboard template with CRM features.',
|
||||
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::DASHBOARD],
|
||||
'screenshotDark' => $url . '/images/sites/templates/crm-dashboard-react-admin-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/crm-dashboard-react-admin-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('REACT', [
|
||||
'providerRootDirectory' => './react/react-admin',
|
||||
'installCommand' => 'pnpm install',
|
||||
'buildCommand' => 'pnpm build && pnpm db-seed',
|
||||
'outputDirectory' => './dist',
|
||||
]),
|
||||
],
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_ENDPOINT',
|
||||
'description' => 'Endpoint of Appwrite server',
|
||||
'value' => '{apiEndpoint}',
|
||||
'placeholder' => '{apiEndpoint}',
|
||||
'required' => true,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_PROJECT_ID',
|
||||
'description' => 'Your Appwrite project ID',
|
||||
'value' => '{projectId}',
|
||||
'placeholder' => '{projectId}',
|
||||
'required' => true,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'APPWRITE_API_KEY',
|
||||
'description' => 'Your Appwrite API key (for seeding only)',
|
||||
'value' => '',
|
||||
'placeholder' => 'a0b1...',
|
||||
'required' => true,
|
||||
'type' => 'password'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_DATABASE_ID',
|
||||
'description' => 'Database ID (default: admin)',
|
||||
'value' => 'admin',
|
||||
'placeholder' => 'admin',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_TABLE_REVIEWS',
|
||||
'description' => 'Table ID for reviews table',
|
||||
'value' => 'reviews',
|
||||
'placeholder' => 'reviews',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_TABLE_INVOICES',
|
||||
'description' => 'Table ID for invoices table',
|
||||
'value' => 'invoices',
|
||||
'placeholder' => 'invoices',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_TABLE_ORDERS',
|
||||
'description' => 'Table ID for orders table',
|
||||
'value' => 'orders',
|
||||
'placeholder' => 'orders',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_TABLE_PRODUCTS',
|
||||
'description' => 'Table ID for products table',
|
||||
'value' => 'products',
|
||||
'placeholder' => 'products',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_TABLE_CATEGORIES',
|
||||
'description' => 'Table ID for categories table',
|
||||
'value' => 'categories',
|
||||
'placeholder' => 'categories',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'name' => 'VITE_APPWRITE_TABLE_CUSTOMERS',
|
||||
'description' => 'Table ID for customers table',
|
||||
'value' => 'customers',
|
||||
'placeholder' => 'customers',
|
||||
'required' => false,
|
||||
'type' => 'text'
|
||||
],
|
||||
]
|
||||
],
|
||||
[
|
||||
'key' => 'job-applications-formspree',
|
||||
'name' => 'Job applications form with Formspree',
|
||||
'tagline' => 'A simple form submission template using Formspree.',
|
||||
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::FORMS],
|
||||
'screenshotDark' => $url . '/images/sites/templates/job-applications-formspree-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/job-applications-formspree-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('REACT', [
|
||||
'providerRootDirectory' => './react/formspree',
|
||||
]),
|
||||
],
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.7.*',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => 'VITE_FORMSPREE_FORM_ID',
|
||||
'description' => 'Your Formspree form ID',
|
||||
'value' => '',
|
||||
'placeholder' => 'xrgkpqld',
|
||||
'required' => true,
|
||||
'type' => 'text'
|
||||
],
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
@@ -357,15 +357,6 @@ return [
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
],
|
||||
[
|
||||
'name' => '_APP_TRUSTED_HEADERS',
|
||||
'description' => 'This option allows you to set the list of trusted headers, the value is a comma‑separated list of HTTP header names, evaluated left-to-right for the first valid IP. Header names are treated case-insensitively.',
|
||||
'introduction' => '1.8.0',
|
||||
'default' => 'x-forwarded-for',
|
||||
'required' => false,
|
||||
'question' => '',
|
||||
'filter' => ''
|
||||
]
|
||||
],
|
||||
],
|
||||
|
||||
@@ -194,7 +194,7 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc
|
||||
}
|
||||
;
|
||||
|
||||
$createSession = function (string $userId, string $secret, Request $request, Response $response, User $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Store $store, ProofsToken $proofForToken, ProofsCode $proofForCode, string $trustedIp) {
|
||||
$createSession = function (string $userId, string $secret, Request $request, Response $response, User $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Store $store, ProofsToken $proofForToken, ProofsCode $proofForCode) {
|
||||
|
||||
/** @var Appwrite\Utopia\Database\Documents\User $userFromRequest */
|
||||
$userFromRequest = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId));
|
||||
@@ -214,7 +214,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res
|
||||
|
||||
$duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($trustedIp);
|
||||
$record = $geodb->get($request->getIP());
|
||||
$sessionSecret = $proofForToken->generate();
|
||||
|
||||
$factor = (match ($verifiedToken->getAttribute('type')) {
|
||||
@@ -243,7 +243,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res
|
||||
'provider' => $provider,
|
||||
'secret' => $proofForToken->hash($sessionSecret), // One way hash encryption to protect DB leak
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $trustedIp,
|
||||
'ip' => $request->getIP(),
|
||||
'factors' => [$factor],
|
||||
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
|
||||
'expire' => DateTime::addSeconds(new \DateTime(), $duration)
|
||||
@@ -358,13 +358,13 @@ App::post('/v1/account')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be between 8 and 256 chars.', false, ['project', 'passwordsDictionary'])
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('hooks')
|
||||
->inject('trustedIp')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $user, Document $project, Database $dbForProject, Hooks $hooks, string $trustedIp) {
|
||||
->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Hooks $hooks) {
|
||||
|
||||
$email = \strtolower($email);
|
||||
if ('console' === $project->getId()) {
|
||||
@@ -375,7 +375,7 @@ App::post('/v1/account')
|
||||
throw new Exception(Exception::USER_EMAIL_NOT_WHITELISTED);
|
||||
}
|
||||
|
||||
if (!empty($whitelistIPs) && !\in_array($trustedIp, $whitelistIPs)) {
|
||||
if (!empty($whitelistIPs) && !\in_array($request->getIP(), $whitelistIPs)) {
|
||||
throw new Exception(Exception::USER_IP_NOT_WHITELISTED);
|
||||
}
|
||||
}
|
||||
@@ -961,8 +961,7 @@ App::post('/v1/account/sessions/email')
|
||||
->inject('store')
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForToken')
|
||||
->inject('trustedIp')
|
||||
->action(function (string $email, string $password, Request $request, Response $response, User $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, string $trustedIp) {
|
||||
->action(function (string $email, string $password, Request $request, Response $response, User $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) {
|
||||
$email = \strtolower($email);
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
@@ -986,7 +985,7 @@ App::post('/v1/account/sessions/email')
|
||||
|
||||
$duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($trustedIp);
|
||||
$record = $geodb->get($request->getIP());
|
||||
$secret = $proofForToken->generate();
|
||||
$session = new Document(array_merge(
|
||||
[
|
||||
@@ -997,7 +996,7 @@ App::post('/v1/account/sessions/email')
|
||||
'providerUid' => $email,
|
||||
'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $trustedIp,
|
||||
'ip' => $request->getIP(),
|
||||
'factors' => ['password'],
|
||||
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
|
||||
'expire' => DateTime::addSeconds(new \DateTime(), $duration)
|
||||
@@ -1106,8 +1105,7 @@ App::post('/v1/account/sessions/anonymous')
|
||||
->inject('store')
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForToken')
|
||||
->inject('trustedIp')
|
||||
->action(function (Request $request, Response $response, Locale $locale, User $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, string $trustedIp) {
|
||||
->action(function (Request $request, Response $response, Locale $locale, User $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) {
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
if ('console' === $project->getId()) {
|
||||
@@ -1157,7 +1155,7 @@ App::post('/v1/account/sessions/anonymous')
|
||||
// Create session token
|
||||
$duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($trustedIp);
|
||||
$record = $geodb->get($request->getIP());
|
||||
$secret = $proofForToken->generate();
|
||||
|
||||
$session = new Document(array_merge(
|
||||
@@ -1168,7 +1166,7 @@ App::post('/v1/account/sessions/anonymous')
|
||||
'provider' => SESSION_PROVIDER_ANONYMOUS,
|
||||
'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $trustedIp,
|
||||
'ip' => $request->getIP(),
|
||||
'factors' => ['anonymous'],
|
||||
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
|
||||
'expire' => DateTime::addSeconds(new \DateTime(), $duration)
|
||||
@@ -1259,7 +1257,6 @@ App::post('/v1/account/sessions/token')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->inject('proofForCode')
|
||||
->inject('trustedIp')
|
||||
->action($createSession);
|
||||
|
||||
App::get('/v1/account/sessions/oauth2/:provider')
|
||||
@@ -1328,7 +1325,7 @@ App::get('/v1/account/sessions/oauth2/:provider')
|
||||
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
|
||||
}
|
||||
|
||||
$host = $platform['consoleDomain'] ?? '';
|
||||
$host = $platform['consoleHostname'] ?? '';
|
||||
$redirectBase = $protocol . '://' . $host;
|
||||
if ($protocol === 'https' && $port !== '443') {
|
||||
$redirectBase .= ':' . $port;
|
||||
@@ -1456,8 +1453,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
->inject('store')
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForToken')
|
||||
->inject('trustedIp')
|
||||
->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, string $trustedIp) 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();
|
||||
@@ -1820,7 +1816,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
'secret' => $proofForTokenOAuth2->hash($secret), // One way hash encryption to protect DB leak
|
||||
'expire' => $expire,
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $trustedIp,
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
@@ -1844,7 +1840,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
// If the `token` param is not set, we persist the session in a cookie
|
||||
} else {
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($trustedIp);
|
||||
$record = $geodb->get($request->getIP());
|
||||
$secret = $proofForToken->generate();
|
||||
|
||||
$session = new Document(array_merge([
|
||||
@@ -1858,7 +1854,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
'providerAccessTokenExpiry' => DateTime::addSeconds(new \DateTime(), (int)$accessTokenExpiry),
|
||||
'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $trustedIp,
|
||||
'ip' => $request->getIP(),
|
||||
'factors' => [TYPE::EMAIL, 'oauth2'], // include a special oauth2 factor to bypass MFA checks
|
||||
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
|
||||
'expire' => DateTime::addSeconds(new \DateTime(), $duration)
|
||||
@@ -1982,7 +1978,7 @@ App::get('/v1/account/tokens/oauth2/:provider')
|
||||
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
|
||||
}
|
||||
|
||||
$host = $platform['consoleDomain'] ?? '';
|
||||
$host = $platform['consoleHostname'] ?? '';
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$port = $request->getPort();
|
||||
$redirectBase = $protocol . '://' . $host;
|
||||
@@ -2051,8 +2047,7 @@ App::post('/v1/account/tokens/magic-url')
|
||||
->inject('queueForMails')
|
||||
->inject('proofForPassword')
|
||||
->inject('platform')
|
||||
->inject('trustedIp')
|
||||
->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, string $trustedIp) {
|
||||
->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');
|
||||
}
|
||||
@@ -2142,7 +2137,7 @@ App::post('/v1/account/tokens/magic-url')
|
||||
'secret' => $proofForToken->hash($tokenSecret), // One way hash encryption to protect DB leak
|
||||
'expire' => $expire,
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $trustedIp,
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
@@ -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 = $platform['consoleDomain'] ?? '';
|
||||
$host = $platform['consoleHostname'] ?? '';
|
||||
$port = $request->getPort();
|
||||
$callbackBase = $protocol . '://' . $host;
|
||||
if ($protocol === 'https' && $port !== '443') {
|
||||
@@ -2320,8 +2315,7 @@ App::post('/v1/account/tokens/email')
|
||||
->inject('queueForMails')
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForCode')
|
||||
->inject('trustedIp')
|
||||
->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, User $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword, ProofsCode $proofForCode, string $trustedIp) {
|
||||
->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, User $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword, ProofsCode $proofForCode) {
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
|
||||
}
|
||||
@@ -2427,7 +2421,7 @@ App::post('/v1/account/tokens/email')
|
||||
'secret' => $proofForCode->hash($tokenSecret), // One way hash encryption to protect DB leak
|
||||
'expire' => $expire,
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $trustedIp,
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
@@ -2613,11 +2607,10 @@ App::put('/v1/account/sessions/magic-url')
|
||||
->inject('queueForMails')
|
||||
->inject('store')
|
||||
->inject('proofForCode')
|
||||
->inject('trustedIp')
|
||||
->action(function ($userId, $secret, $request, $response, $user, $dbForProject, $project, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForCode, $trustedIp) use ($createSession) {
|
||||
->action(function ($userId, $secret, $request, $response, $user, $dbForProject, $project, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForCode) use ($createSession) {
|
||||
$proofForToken = new ProofsToken(TOKEN_LENGTH_MAGIC_URL);
|
||||
$proofForToken->setHash(new Sha());
|
||||
$createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForToken, $proofForCode, $trustedIp);
|
||||
$createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForToken, $proofForCode);
|
||||
});
|
||||
|
||||
App::put('/v1/account/sessions/phone')
|
||||
@@ -2662,7 +2655,6 @@ App::put('/v1/account/sessions/phone')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->inject('proofForCode')
|
||||
->inject('trustedIp')
|
||||
->action($createSession);
|
||||
|
||||
App::post('/v1/account/tokens/phone')
|
||||
@@ -2705,8 +2697,7 @@ App::post('/v1/account/tokens/phone')
|
||||
->inject('plan')
|
||||
->inject('store')
|
||||
->inject('proofForCode')
|
||||
->inject('trustedIp')
|
||||
->action(function (string $userId, string $phone, Request $request, Response $response, User $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Store $store, ProofsCode $proofForCode, string $trustedIp) {
|
||||
->action(function (string $userId, string $phone, Request $request, Response $response, User $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Store $store, ProofsCode $proofForCode) {
|
||||
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
}
|
||||
@@ -2801,7 +2792,7 @@ App::post('/v1/account/tokens/phone')
|
||||
'secret' => $proofForCode->hash($secret),
|
||||
'expire' => $expire,
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $trustedIp,
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
@@ -3477,8 +3468,7 @@ App::post('/v1/account/recovery')
|
||||
->inject('queueForMails')
|
||||
->inject('queueForEvents')
|
||||
->inject('proofForToken')
|
||||
->inject('trustedIp')
|
||||
->action(function (string $email, string $url, Request $request, Response $response, User $user, Database $dbForProject, Document $project, Locale $locale, Mail $queueForMails, Event $queueForEvents, ProofsToken $proofForToken, string $trustedIp) {
|
||||
->action(function (string $email, string $url, Request $request, Response $response, User $user, Database $dbForProject, Document $project, Locale $locale, Mail $queueForMails, Event $queueForEvents, ProofsToken $proofForToken) {
|
||||
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
|
||||
@@ -3511,7 +3501,7 @@ App::post('/v1/account/recovery')
|
||||
'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak
|
||||
'expire' => $expire,
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $trustedIp,
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($profile->getId())->toString());
|
||||
@@ -3774,8 +3764,7 @@ App::post('/v1/account/verifications/email')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->inject('proofForToken')
|
||||
->inject('trustedIp')
|
||||
->action(function (string $url, Request $request, Response $response, Document $project, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsToken $proofForToken, string $trustedIp) {
|
||||
->action(function (string $url, Request $request, Response $response, Document $project, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsToken $proofForToken) {
|
||||
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
|
||||
@@ -3801,7 +3790,7 @@ App::post('/v1/account/verifications/email')
|
||||
'secret' => $proofForToken->hash($verificationSecret), // One way hash encryption to protect DB leak
|
||||
'expire' => $expire,
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $trustedIp,
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
@@ -4063,8 +4052,7 @@ App::post('/v1/account/verifications/phone')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('plan')
|
||||
->inject('proofForCode')
|
||||
->inject('trustedIp')
|
||||
->action(function (Request $request, Response $response, User $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, ProofsCode $proofForCode, string $trustedIp) {
|
||||
->action(function (Request $request, Response $response, User $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, ProofsCode $proofForCode) {
|
||||
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
}
|
||||
@@ -4100,7 +4088,7 @@ App::post('/v1/account/verifications/phone')
|
||||
'secret' => $proofForCode->hash($secret),
|
||||
'expire' => $expire,
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $trustedIp,
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
|
||||
@@ -32,12 +32,11 @@ App::get('/v1/locale')
|
||||
->inject('response')
|
||||
->inject('locale')
|
||||
->inject('geodb')
|
||||
->inject('trustedIp')
|
||||
->action(function (Request $request, Response $response, Locale $locale, Reader $geodb, string $trustedIp) {
|
||||
->action(function (Request $request, Response $response, Locale $locale, Reader $geodb) {
|
||||
$eu = Config::getParam('locale-eu');
|
||||
$currencies = Config::getParam('locale-currencies');
|
||||
$output = [];
|
||||
$ip = $trustedIp;
|
||||
$ip = $request->getIP();
|
||||
|
||||
$output['ip'] = $ip;
|
||||
|
||||
|
||||
@@ -1209,8 +1209,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
->inject('queueForEvents')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->inject('trustedIp')
|
||||
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken, string $trustedIp) {
|
||||
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
$membership = $dbForProject->getDocument('memberships', $membershipId);
|
||||
@@ -1262,7 +1261,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($trustedIp);
|
||||
$record = $geodb->get($request->getIP());
|
||||
$authDuration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$expire = DateTime::addSeconds(new \DateTime(), $authDuration);
|
||||
$secret = $proofForToken->generate();
|
||||
@@ -1279,7 +1278,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
'providerUid' => $user->getAttribute('email'),
|
||||
'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $trustedIp,
|
||||
'ip' => $request->getIP(),
|
||||
'factors' => ['email'],
|
||||
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
|
||||
'expire' => DateTime::addSeconds(new \DateTime(), $authDuration)
|
||||
|
||||
@@ -2216,8 +2216,7 @@ App::post('/v1/users/:userId/sessions')
|
||||
->inject('queueForEvents')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->inject('trustedIp')
|
||||
->action(function (string $userId, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken, string $trustedIp) {
|
||||
->action(function (string $userId, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
if ($user->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
@@ -2225,7 +2224,7 @@ App::post('/v1/users/:userId/sessions')
|
||||
|
||||
$secret = $proofForToken->generate();
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($trustedIp);
|
||||
$record = $geodb->get($request->getIP());
|
||||
|
||||
$duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration));
|
||||
@@ -2239,7 +2238,7 @@ App::post('/v1/users/:userId/sessions')
|
||||
'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'factors' => ['server'],
|
||||
'ip' => $trustedIp,
|
||||
'ip' => $request->getIP(),
|
||||
'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--',
|
||||
'expire' => $expire,
|
||||
],
|
||||
@@ -2306,8 +2305,7 @@ App::post('/v1/users/:userId/tokens')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('trustedIp')
|
||||
->action(function (string $userId, int $length, int $expire, Request $request, Response $response, Database $dbForProject, Event $queueForEvents, string $trustedIp) {
|
||||
->action(function (string $userId, int $length, int $expire, Request $request, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
$user = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
@@ -2327,7 +2325,7 @@ App::post('/v1/users/:userId/tokens')
|
||||
'secret' => $proofForToken->hash($secret),
|
||||
'expire' => $expire,
|
||||
'userAgent' => $request->getUserAgent('UNKNOWN'),
|
||||
'ip' => $trustedIp
|
||||
'ip' => $request->getIP()
|
||||
]);
|
||||
|
||||
$token = $dbForProject->createDocument('tokens', $token);
|
||||
|
||||
@@ -132,7 +132,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
|
||||
$commentStatus = $isAuthorized ? 'waiting' : 'failed';
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$hostname = $platform['consoleDomain'] ?? '';
|
||||
$hostname = $platform['consoleHostname'] ?? '';
|
||||
|
||||
$authorizeUrl = $protocol . '://' . $hostname . "/console/git/authorize-contributor?projectId={$projectId}&installationId={$installationId}&repositoryId={$repositoryId}&providerPullRequestId={$providerPullRequestId}";
|
||||
|
||||
@@ -555,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 = $platform['consoleDomain'] ?? '';
|
||||
$hostname = $platform['consoleHostname'] ?? '';
|
||||
|
||||
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.');
|
||||
@@ -614,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 = $platform['consoleDomain'] ?? '';
|
||||
$hostname = $platform['consoleHostname'] ?? '';
|
||||
|
||||
$defaultState = [
|
||||
'success' => $protocol . '://' . $hostname . "/console/project-$region-$projectId/settings/git-installations",
|
||||
|
||||
+28
-27
@@ -59,7 +59,7 @@ 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, array $platform, string $previewHostname, ?Key $apiKey, array $domains)
|
||||
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)
|
||||
{
|
||||
$host = $request->getHostname() ?? '';
|
||||
if (!empty($previewHostname)) {
|
||||
@@ -80,7 +80,8 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
|
||||
$errorView = __DIR__ . '/../views/general/error.phtml';
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$url = $protocol . '://' . $platform['consoleDomain'];
|
||||
$url = $protocol . '://' . $platform['consoleHostname'];
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
|
||||
if ($rule->isEmpty()) {
|
||||
$appDomainFunctionsFallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', '');
|
||||
@@ -101,7 +102,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
if (!in_array($host, $domains)) {
|
||||
if (!in_array($host, $platformHostnames)) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -269,7 +270,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
}
|
||||
|
||||
if (!$authorized) {
|
||||
$url = $protocol . "://" . $platform['consoleDomain'];
|
||||
$url = $protocol . "://" . $platform['consoleHostname'];
|
||||
$response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
@@ -366,7 +367,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
$headers['x-appwrite-country-code'] = '';
|
||||
$headers['x-appwrite-continent-code'] = '';
|
||||
$headers['x-appwrite-continent-eu'] = 'false';
|
||||
$ip = $trustedIp;
|
||||
$ip = $request->getIP();
|
||||
$headers['x-appwrite-client-ip'] = $ip;
|
||||
|
||||
$jwtExpiry = $resource->getAttribute('timeout', 900) + 60; // 1min extra to account for possible cold-starts
|
||||
@@ -858,15 +859,15 @@ App::init()
|
||||
->inject('devKey')
|
||||
->inject('apiKey')
|
||||
->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) {
|
||||
->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) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
$hostname = $request->getHostname() ?? '';
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
// Only run Router when external domain
|
||||
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)) {
|
||||
if (!in_array($hostname, $platformHostnames) || !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);
|
||||
}
|
||||
}
|
||||
@@ -1028,10 +1029,11 @@ App::init()
|
||||
->inject('console')
|
||||
->inject('dbForPlatform')
|
||||
->inject('queueForCertificates')
|
||||
->inject('domains')
|
||||
->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $domains) {
|
||||
->inject('platform')
|
||||
->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform) {
|
||||
$hostname = $request->getHostname();
|
||||
$cache = Config::getParam('domains', []);
|
||||
$cache = Config::getParam('hostnames', []);
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
|
||||
// 1. Cache hit
|
||||
if (array_key_exists($hostname, $cache)) {
|
||||
@@ -1042,7 +1044,7 @@ App::init()
|
||||
$domain = new Domain(!empty($hostname) ? $hostname : '');
|
||||
if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) {
|
||||
$cache[$domain->get()] = false;
|
||||
Config::setParam('domains', $cache);
|
||||
Config::setParam('hostnames', $cache);
|
||||
Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.');
|
||||
return;
|
||||
}
|
||||
@@ -1053,7 +1055,7 @@ App::init()
|
||||
}
|
||||
|
||||
// 3. Check if domain is a main domain
|
||||
if (!in_array($domain->get(), $domains)) {
|
||||
if (!in_array($domain->get(), $platformHostnames)) {
|
||||
Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.');
|
||||
return;
|
||||
}
|
||||
@@ -1114,7 +1116,7 @@ App::init()
|
||||
Console::info('Certificate already exists');
|
||||
} finally {
|
||||
$cache[$domain->get()] = true;
|
||||
Config::setParam('domains', $cache);
|
||||
Config::setParam('hostnames', $cache);
|
||||
Authorization::reset();
|
||||
}
|
||||
});
|
||||
@@ -1138,14 +1140,14 @@ App::options()
|
||||
->inject('project')
|
||||
->inject('devKey')
|
||||
->inject('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) {
|
||||
->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, Cors $cors) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
// Only run Router when external domain
|
||||
if (!in_array($request->getHostname(), $domains) || !empty($previewHostname)) {
|
||||
if (!in_array($request->getHostname(), $platformHostnames) || !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);
|
||||
}
|
||||
@@ -1179,9 +1181,8 @@ App::error()
|
||||
->inject('logger')
|
||||
->inject('log')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('trustedIp')
|
||||
->inject('devKey')
|
||||
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, StatsUsage $queueForStatsUsage, string $trustedIp) {
|
||||
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, StatsUsage $queueForStatsUsage) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
$route = $utopia->getRoute();
|
||||
$class = \get_class($error);
|
||||
@@ -1292,7 +1293,7 @@ App::error()
|
||||
if (isset($user) && !$user->isEmpty()) {
|
||||
$log->setUser(new User($user->getId()));
|
||||
} else {
|
||||
$log->setUser(new User('guest-' . hash('sha256', $trustedIp)));
|
||||
$log->setUser(new User('guest-' . hash('sha256', $request->getIP())));
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -1449,9 +1450,9 @@ App::get('/robots.txt')
|
||||
->inject('platform')
|
||||
->inject('previewHostname')
|
||||
->inject('apiKey')
|
||||
->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)) {
|
||||
->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) {
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
|
||||
$template = new View(__DIR__ . '/../views/general/robots.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
@@ -1481,9 +1482,9 @@ App::get('/humans.txt')
|
||||
->inject('platform')
|
||||
->inject('previewHostname')
|
||||
->inject('apiKey')
|
||||
->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)) {
|
||||
->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) {
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
|
||||
$template = new View(__DIR__ . '/../views/general/humans.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
|
||||
@@ -509,8 +509,7 @@ App::init()
|
||||
->inject('devKey')
|
||||
->inject('telemetry')
|
||||
->inject('platform')
|
||||
->inject('trustedIp')
|
||||
->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, string $trustedIp) use ($usageDatabaseListener, $eventDatabaseListener) {
|
||||
->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();
|
||||
|
||||
@@ -538,7 +537,7 @@ App::init()
|
||||
->setParam('{projectId}', $project->getId())
|
||||
->setParam('{userId}', $user->getId())
|
||||
->setParam('{userAgent}', $request->getUserAgent(''))
|
||||
->setParam('{ip}', $trustedIp)
|
||||
->setParam('{ip}', $request->getIP())
|
||||
->setParam('{url}', $request->getHostname() . $route->getPath())
|
||||
->setParam('{method}', $request->getMethod())
|
||||
->setParam('{chunkId}', (int)($start / ($end + 1 - $start)));
|
||||
@@ -599,7 +598,7 @@ App::init()
|
||||
$queueForAudits
|
||||
->setMode($mode)
|
||||
->setUserAgent($request->getUserAgent(''))
|
||||
->setIP($trustedIp)
|
||||
->setIP($request->getIP())
|
||||
->setHostname($request->getHostname())
|
||||
->setEvent($route->getLabel('audits.event', ''))
|
||||
->setProject($project);
|
||||
|
||||
@@ -36,12 +36,11 @@ App::init()
|
||||
->inject('request')
|
||||
->inject('project')
|
||||
->inject('geodb')
|
||||
->inject('trustedIp')
|
||||
->action(function (App $utopia, Request $request, Document $project, Reader $geodb, string $trustedIp) {
|
||||
->action(function (App $utopia, Request $request, Document $project, Reader $geodb) {
|
||||
$denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', '');
|
||||
if (!empty($denylist && $project->getId() === 'console')) {
|
||||
$countries = explode(',', $denylist);
|
||||
$record = $geodb->get($trustedIp) ?? [];
|
||||
$record = $geodb->get($request->getIP()) ?? [];
|
||||
$country = $record['country']['iso_code'] ?? '';
|
||||
if (in_array($country, $countries)) {
|
||||
throw new Exception(Exception::GENERAL_REGION_ACCESS_DENIED);
|
||||
|
||||
+1
-2
@@ -2,7 +2,6 @@
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Apppwrite\Network\TrustedIp;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Swoole\Constant;
|
||||
@@ -478,7 +477,7 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
if (isset($user) && !$user->isEmpty()) {
|
||||
$log->setUser(new User($user->getId()));
|
||||
} else {
|
||||
$log->setUser(new User('guest-' . hash('sha256', TrustedIp::extract($request, System::getEnv('_APP_TRUSTED_HEADERS', 'x-forwarded-for')))));
|
||||
$log->setUser(new User('guest-' . hash('sha256', $request->getIP())));
|
||||
}
|
||||
|
||||
$log->setNamespace("http");
|
||||
|
||||
+11
-35
@@ -22,7 +22,6 @@ use Appwrite\Extend\Exception;
|
||||
use Appwrite\GraphQL\Schema;
|
||||
use Appwrite\Network\Cors;
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\Network\TrustedIp;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\Network\Validator\Redirect;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
@@ -162,14 +161,6 @@ App::setResource('queueForStatsResources', function (Publisher $publisher) {
|
||||
return new StatsResources($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@@ -184,33 +175,16 @@ App::setResource('platform', function (Request $request) {
|
||||
if ($request->getPort() === '80' && $protocol !== 'http') {
|
||||
$port = ':80';
|
||||
}
|
||||
$platform['endpoint'] = "$protocol://{$platform['domain']}{$port}/v1";
|
||||
$platform['endpoint'] = "$protocol://{$platform['apiHostname']}{$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';
|
||||
}
|
||||
|
||||
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];
|
||||
App::setResource('allowedHostnames', function (array $platform, Document $project, Document $rule, Document $devKey, Request $request) {
|
||||
$allowed = [...($platform['hostnames'] ?? [])];
|
||||
|
||||
/* Add platform configured hostnames */
|
||||
if (!$project->isEmpty() && $project->getId() !== 'console') {
|
||||
@@ -224,14 +198,20 @@ App::setResource('allowedHostnames', function (array $domains, Document $project
|
||||
$allowed[] = $request->getHostname();
|
||||
}
|
||||
|
||||
/* Allow the request origin if a dev key or rule is found */
|
||||
$originHostname = parse_url($request->getOrigin(), PHP_URL_HOST);
|
||||
|
||||
/* Add request hostname for preflight requests */
|
||||
if ($request->getMethod() === 'OPTIONS') {
|
||||
$allowed[] = $originHostname;
|
||||
}
|
||||
|
||||
/* Allow the request origin if a dev key or rule is found */
|
||||
if ((!$rule->isEmpty() || !$devKey->isEmpty()) && !empty($originHostname)) {
|
||||
$allowed[] = $originHostname;
|
||||
}
|
||||
|
||||
return array_unique($allowed);
|
||||
}, ['domains', 'project', 'rule', 'devKey', 'request']);
|
||||
}, ['platform', 'project', 'rule', 'devKey', 'request']);
|
||||
|
||||
/**
|
||||
* List of allowed request schemes for the request.
|
||||
@@ -860,10 +840,6 @@ App::setResource('servers', function () {
|
||||
return $languages;
|
||||
});
|
||||
|
||||
App::setResource('trustedIp', function (Request $request) {
|
||||
return TrustedIp::extract($request, System::getEnv('_APP_TRUSTED_HEADERS', 'x-forwarded-for'));
|
||||
}, ['request']);
|
||||
|
||||
App::setResource('promiseAdapter', function ($register) {
|
||||
return $register->get('promiseAdapter');
|
||||
}, ['register']);
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Network\TrustedIp;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\PubSub\Adapter\Pool as PubSubPool;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
use Appwrite\Utopia\Request;
|
||||
@@ -552,7 +552,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
*/
|
||||
$timelimit = $timelimit('url:{url},ip:{ip}', 128, 60);
|
||||
$timelimit
|
||||
->setParam('{ip}', TrustedIp::extract($request, System::getEnv('_APP_TRUSTED_HEADERS', 'x-forwarded-for')))
|
||||
->setParam('{ip}', $request->getIP())
|
||||
->setParam('{url}', $request->getURI());
|
||||
|
||||
$abuse = new Abuse($timelimit);
|
||||
|
||||
@@ -17,7 +17,7 @@ $buttons = [];
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$platform = Config::getParam('platform', []);
|
||||
$hostname = $platform['consoleDomain'] ?? '';
|
||||
$hostname = $platform['consoleHostname'] ?? '';
|
||||
// TODO: remove this later
|
||||
if (System::getEnv('_APP_ENV') === 'development') {
|
||||
$hostname = 'localhost';
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Network;
|
||||
|
||||
use Appwrite\Utopia\Request;
|
||||
|
||||
class TrustedIp
|
||||
{
|
||||
/**
|
||||
* Extract the trusted client IP address from a request.
|
||||
*
|
||||
* This method checks configured trusted headers (e.g., X-Forwarded-For)
|
||||
* and falls back to the remote address if no valid IP is found.
|
||||
*
|
||||
* @param Request $request The Utopia request object
|
||||
* @return string The trusted client IP address
|
||||
*
|
||||
*/
|
||||
public static function extract(Request $request, string $trustedHeaders): string
|
||||
{
|
||||
// Fallback to remote address
|
||||
$remoteAddr = $request->getServer('remote_addr') ?? '0.0.0.0';
|
||||
|
||||
$trustedHeaders = explode(',', $trustedHeaders);
|
||||
$trustedHeaders = array_map('trim', $trustedHeaders);
|
||||
$trustedHeaders = array_map('strtolower', $trustedHeaders);
|
||||
$trustedHeaders = array_filter($trustedHeaders);
|
||||
|
||||
foreach ($trustedHeaders as $header) {
|
||||
$headerValue = $request->getHeader($header);
|
||||
|
||||
if (empty($headerValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Leftmost IP address is the address of the originating client
|
||||
$ips = explode(',', $headerValue);
|
||||
$ip = trim($ips[0]);
|
||||
|
||||
// Validate IP format (supports both IPv4 and IPv6)
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP)) {
|
||||
return $ip;
|
||||
}
|
||||
}
|
||||
|
||||
return $remoteAddr;
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ class Get extends Action
|
||||
->param('type', '', new WhiteList(['rules']), 'Resource type.')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('domains')
|
||||
->inject('platform')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -68,8 +68,9 @@ class Get extends Action
|
||||
string $type,
|
||||
Response $response,
|
||||
Database $dbForPlatform,
|
||||
array $domains
|
||||
array $platform
|
||||
) {
|
||||
$domains = $platform['hostnames'] ?? [];
|
||||
if ($type === 'rules') {
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
||||
@@ -98,7 +98,6 @@ class Create extends Base
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->inject('executor')
|
||||
->inject('trustedIp')
|
||||
->inject('platform')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
@@ -124,7 +123,6 @@ class Create extends Base
|
||||
Store $store,
|
||||
Token $proofForToken,
|
||||
Executor $executor,
|
||||
string $trustedIp,
|
||||
array $platform
|
||||
) {
|
||||
$async = \strval($async) === 'true' || \strval($async) === '1';
|
||||
@@ -240,7 +238,7 @@ class Create extends Base
|
||||
$headers['x-appwrite-country-code'] = '';
|
||||
$headers['x-appwrite-continent-code'] = '';
|
||||
$headers['x-appwrite-continent-eu'] = 'false';
|
||||
$ip = $trustedIp;
|
||||
$ip = $request->getIP();
|
||||
$headers['x-appwrite-client-ip'] = $ip;
|
||||
|
||||
if (!empty($ip)) {
|
||||
|
||||
@@ -67,12 +67,13 @@ class Create extends Action
|
||||
->inject('queueForCertificates')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('domains')
|
||||
->inject('platform')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $domain, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, array $domains)
|
||||
public function action(string $domain, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, array $platform)
|
||||
{
|
||||
$domains = $platform['hostnames'] ?? [];
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
||||
|
||||
@@ -72,12 +72,13 @@ class Create extends Action
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
->inject('domains')
|
||||
->inject('platform')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $domain, string $functionId, string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject, array $domains)
|
||||
public function action(string $domain, string $functionId, string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject, array $platform)
|
||||
{
|
||||
$domains = $platform['hostnames'] ?? [];
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
||||
|
||||
@@ -75,12 +75,13 @@ class Create extends Action
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
->inject('domains')
|
||||
->inject('platform')
|
||||
->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, array $domains)
|
||||
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 $platform)
|
||||
{
|
||||
$domains = $platform['hostnames'] ?? [];
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
||||
|
||||
@@ -72,12 +72,13 @@ class Create extends Action
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('dbForProject')
|
||||
->inject('domains')
|
||||
->inject('platform')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $domain, string $siteId, string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject, array $domains)
|
||||
public function action(string $domain, string $siteId, string $branch, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject, array $platform)
|
||||
{
|
||||
$domains = $platform['hostnames'] ?? [];
|
||||
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
|
||||
$functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
// Example usage: docker compose exec appwrite screenshot --templateId="playground-for-tanstack-start"
|
||||
// Example of env vars flag: --variables="{\"VITE_FORMSPREE_FORM_ID\":\"xvgkbzll\", \"VITE_FORMSPREE_FORM_SECRET\":\"some_secret\"}"
|
||||
// Expected output: public/images/sites/templates/playground-for-tanstack-start-light.png (and dark.png)
|
||||
|
||||
use Appwrite\ID;
|
||||
@@ -10,6 +11,7 @@ use Tests\E2E\Client;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Screenshot extends Action
|
||||
@@ -24,11 +26,24 @@ class Screenshot extends Action
|
||||
$this
|
||||
->desc('Create Site template screenshot')
|
||||
->param('templateId', '', new Text(128), 'Template ID.')
|
||||
->param('variables', '', new Text(16384), 'JSON of env variables to use when setting up the site.')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $templateId): void
|
||||
public function action(string $templateId, string $variables): void
|
||||
{
|
||||
if (empty($variables)) {
|
||||
$variables = [];
|
||||
} else {
|
||||
$variables = \json_decode($variables, true);
|
||||
if (!\is_array($variables)) {
|
||||
throw new \Exception('Invalid JSON in --variables flag');
|
||||
}
|
||||
}
|
||||
if ($variables === null) {
|
||||
throw new \Exception('Invalid JSON in --variables flag');
|
||||
}
|
||||
|
||||
$templates = Config::getParam('templates-site', []);
|
||||
|
||||
$allowedTemplates = \array_filter($templates, function ($item) use ($templateId) {
|
||||
@@ -133,6 +148,11 @@ class Screenshot extends Action
|
||||
|
||||
$framework = $template['frameworks'][0];
|
||||
|
||||
// Use best specifications to prevent out-of-memory during build
|
||||
$specifications = Config::getParam('specifications', []);
|
||||
$specifications = array_keys($specifications);
|
||||
$specification = \end($specifications);
|
||||
|
||||
// Create site
|
||||
$site = $client->call(Client::METHOD_POST, '/sites', [
|
||||
'content-type' => 'application/json',
|
||||
@@ -141,6 +161,7 @@ class Screenshot extends Action
|
||||
'cookie' => $cookieConsole
|
||||
], [
|
||||
'siteId' => ID::unique(),
|
||||
'specification' => $specification,
|
||||
'name' => $template["name"],
|
||||
'framework' => $framework['key'],
|
||||
'adapter' => $framework['adapter'],
|
||||
@@ -162,21 +183,48 @@ class Screenshot extends Action
|
||||
|
||||
$siteId = $site['body']['$id'];
|
||||
|
||||
// Prepare API key, incase it's needed as variable
|
||||
$response = $client->call(Client::METHOD_POST, '/projects/' . $projectId . '/keys', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
'cookie' => $cookieConsole
|
||||
], [
|
||||
'name' => 'Screenshot API key',
|
||||
'scopes' => \array_keys(Config::getParam('scopes', []))
|
||||
]);
|
||||
|
||||
if ($response['headers']['status-code'] !== 201) {
|
||||
Console::error(\json_encode($response));
|
||||
throw new \Exception("Failed to create API key");
|
||||
}
|
||||
|
||||
$apiKey = $response['body']['secret'];
|
||||
|
||||
Console::info("API key created");
|
||||
|
||||
$variables['APPWRITE_API_KEY'] = $apiKey;
|
||||
|
||||
// Create variables
|
||||
if (!empty($template['variables'] ?? [])) {
|
||||
foreach ($template['variables'] as $variable) {
|
||||
if (empty($variable['value'] ?? '')) {
|
||||
if (($variable['required'] ?? false) === true) {
|
||||
throw new \Exception("Missing required variable: {$variable['name']}");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
$name = $variable['name'];
|
||||
|
||||
$value = $variable['value'];
|
||||
$value = \str_replace('{projectName}', $projectName, $value);
|
||||
$value = \str_replace('{projectId}', $projectId, $value);
|
||||
$value = \str_replace('{apiEndpoint}', 'http://localhost/v1', $value);
|
||||
$value = \str_replace('{apiEndpoint}', 'http://' . System::getEnv('_APP_DOMAIN', '') . '/v1', $value);
|
||||
|
||||
if (\array_key_exists($name, $variables)) {
|
||||
$value = $variables[$name];
|
||||
}
|
||||
|
||||
if (empty($value)) {
|
||||
if (($variable['required'] ?? false) === true) {
|
||||
throw new \Exception("Missing required variable: {$variable['name']}. Provide it using the --variables flag to resolve this.");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$response = $client->call(Client::METHOD_POST, '/sites/' . $siteId . '/variables', [
|
||||
'content-type' => 'application/json',
|
||||
@@ -207,7 +255,8 @@ class Screenshot extends Action
|
||||
'owner' => $template['providerOwner'],
|
||||
'repository' => $template['providerRepositoryId'],
|
||||
'rootDirectory' => $framework['providerRootDirectory'],
|
||||
'version' => $template['providerVersion'],
|
||||
'reference' => $template['providerVersion'],
|
||||
'type' => 'tag',
|
||||
'activate' => true,
|
||||
]);
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ class Comment
|
||||
$i = 0;
|
||||
foreach ($projects as $projectId => $project) {
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$hostname = $this->platform['consoleDomain'] ?? '';
|
||||
$hostname = $this->platform['consoleHostname'] ?? '';
|
||||
|
||||
$text .= "## {$project['name']}\n\n";
|
||||
$text .= "Project ID: `{$projectId}`\n\n";
|
||||
@@ -233,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 = $this->platform['consoleDomain'] ?? '';
|
||||
$hostname = $this->platform['consoleHostname'] ?? '';
|
||||
|
||||
$imageLight = $protocol . '://' . $hostname . $pathLight;
|
||||
$imageDark = $protocol . '://' . $hostname . $pathDark;
|
||||
|
||||
@@ -184,7 +184,22 @@ class HTTPTest extends Scope
|
||||
'origin' => 'http://google.com',
|
||||
]);
|
||||
$this->assertNull($response['headers']['access-control-allow-origin'] ?? null);
|
||||
}
|
||||
|
||||
public function testPreflight()
|
||||
{
|
||||
|
||||
$endpoint = '/v1/projects'; // Can be any non-404 route
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_OPTIONS, $endpoint, [
|
||||
'origin' => 'http://random.com',
|
||||
'access-control-request-headers' => 'X-Appwrite-Project',
|
||||
'access-control-request-method' => 'GET'
|
||||
]);
|
||||
$this->assertEquals('http://random.com', $response['headers']['access-control-allow-origin']);
|
||||
}
|
||||
|
||||
public function testConsoleRedirect()
|
||||
|
||||
@@ -326,38 +326,4 @@ trait AccountBase
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 204);
|
||||
}
|
||||
|
||||
public function testTrustedIp(): void
|
||||
{
|
||||
$email = uniqid() . 'user@localhost.test';
|
||||
$password = 'password';
|
||||
$name = 'User Name';
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-forwarded-for' => '203.0.113.195',
|
||||
]), [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 201);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-forwarded-for' => '203.0.113.195',
|
||||
]), [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
$this->assertEquals($response['headers']['status-code'], 201);
|
||||
$this->assertEquals('203.0.113.195', $response['body']['clientIp'] ?? $response['body']['ip'] ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user